diff --git a/.idea/light-domotique-NEW.iml b/.idea/light-domotique-NEW.iml index 9d15e58..ee88408 100644 --- a/.idea/light-domotique-NEW.iml +++ b/.idea/light-domotique-NEW.iml @@ -6,6 +6,8 @@ + + @@ -100,6 +102,7 @@ + diff --git a/.idea/php.xml b/.idea/php.xml index c273597..79705eb 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -106,12 +106,18 @@ + + + + + + diff --git a/domokits/.gitignore b/domokits/.gitignore index f6592d8..7ca027e 100644 --- a/domokits/.gitignore +++ b/domokits/.gitignore @@ -17,7 +17,6 @@ /templates/pdf/default # Thelia config -/local/config /local/setup /local/media /local/session @@ -25,9 +24,6 @@ !local/media/images/store/thelia.svg !local/media/images/store/banner.png -# Thelia modules -/local/modules/* - ### Please add your dependancies here ###> symfony/framework-bundle ### @@ -46,3 +42,4 @@ npm-debug.log yarn-error.log ###< symfony/webpack-encore-bundle ### +/log/ diff --git a/domokits/composer.json b/domokits/composer.json index d7df361..be4e185 100644 --- a/domokits/composer.json +++ b/domokits/composer.json @@ -18,7 +18,8 @@ "thelia/thelia-library-module": "^1.1.7", "thelia/product-loop-attribute-filter-module": "~2.0.0", "thelia/reset-password-module": "~1.0.1", - "thelia/re-captcha-module": "~3.0.1" + "thelia/re-captcha-module": "~3.0.1", + "stripe/stripe-php": "6.*" }, "suggest": { "vlopes/maintenance-module": "Add a way to put your site in maintenance mode", diff --git a/domokits/local/modules/BetterSeo/.github/workflows/release.yml b/domokits/local/modules/BetterSeo/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/BetterSeo/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/BetterSeo/BetterSeo.php b/domokits/local/modules/BetterSeo/BetterSeo.php new file mode 100644 index 0000000..05d0001 --- /dev/null +++ b/domokits/local/modules/BetterSeo/BetterSeo.php @@ -0,0 +1,77 @@ +insertSql(null, [__DIR__ . "/Config/thelia.sql"]); + self::setConfigValue('is_initialized', 1); + } + } + + public function update($currentVersion, $newVersion, ConnectionInterface $con = null):void + { + $sqlToExecute = []; + $finder = new Finder(); + $sort = function (\SplFileInfo $a, \SplFileInfo $b) { + $a = strtolower(substr($a->getRelativePathname(), 0, -4)); + $b = strtolower(substr($b->getRelativePathname(), 0, -4)); + return version_compare($a, $b); + }; + + $files = $finder->name('*.sql') + ->in(__DIR__ . "/Config/Update/") + ->sort($sort); + + foreach ($files as $file) { + if (version_compare($file->getFilename(), $currentVersion, ">")) { + $sqlToExecute[$file->getFilename()] = $file->getRealPath(); + } + } + + $database = new Database($con); + + foreach ($sqlToExecute as $version => $sql) { + $database->insertSql(null, [$sql]); + } + } + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.1.0.sql b/domokits/local/modules/BetterSeo/Config/Update/1.1.0.sql new file mode 100644 index 0000000..17b07a6 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.1.0.sql @@ -0,0 +1 @@ +ALTER TABLE `better_seo_i18n` ADD `h1` TEXT NOT NULL AFTER `canonical_field`; \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.2.0.sql b/domokits/local/modules/BetterSeo/Config/Update/1.2.0.sql new file mode 100644 index 0000000..2fd3c3b --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.2.0.sql @@ -0,0 +1 @@ +ALTER TABLE `better_seo_i18n` ADD `mesh_text_1` TEXT NOT NULL AFTER `h1`, ADD `mesh_url_1` TEXT NOT NULL AFTER `mesh_text_1`, ADD `mesh_text_2` TEXT NOT NULL AFTER `mesh_url_1`, ADD `mesh_url_2` TEXT NOT NULL AFTER `mesh_text_2`, ADD `mesh_text_3` TEXT NOT NULL AFTER `mesh_url_2`, ADD `mesh_url_3` TEXT NOT NULL AFTER `mesh_text_3`, ADD `mesh_text_4` TEXT NOT NULL AFTER `mesh_url_3`, ADD `mesh_url_4` TEXT NOT NULL AFTER `mesh_text_4`, ADD `mesh_text_5` TEXT NOT NULL AFTER `mesh_url_4`, ADD `mesh_url_5` TEXT NOT NULL AFTER `mesh_text_5`; \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.2.1.sql b/domokits/local/modules/BetterSeo/Config/Update/1.2.1.sql new file mode 100644 index 0000000..fa52b5f --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.2.1.sql @@ -0,0 +1,10 @@ +ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_1` `mesh_text_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_1` `mesh_url_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_2` `mesh_text_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_2` `mesh_url_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_3` `mesh_text_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_3` `mesh_url_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_4` `mesh_text_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_4` `mesh_url_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_text_5` `mesh_text_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_url_5` `mesh_url_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.3.0.sql b/domokits/local/modules/BetterSeo/Config/Update/1.3.0.sql new file mode 100644 index 0000000..e1e7297 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.3.0.sql @@ -0,0 +1,6 @@ +ALTER TABLE `better_seo_i18n` + ADD `mesh_1` TEXT NOT NULL AFTER `mesh_url_5`, + ADD `mesh_2` TEXT NOT NULL AFTER `mesh_1`, + ADD `mesh_3` TEXT NOT NULL AFTER `mesh_2`, + ADD `mesh_4` TEXT NOT NULL AFTER `mesh_3`, + ADD `mesh_5` TEXT NOT NULL AFTER `mesh_4`; \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.3.1.sql b/domokits/local/modules/BetterSeo/Config/Update/1.3.1.sql new file mode 100644 index 0000000..a9cc91b --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.3.1.sql @@ -0,0 +1,5 @@ +ALTER TABLE `better_seo_i18n` CHANGE `mesh_1` `mesh_1` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_2` `mesh_2` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_3` `mesh_3` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_4` `mesh_4` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; +ALTER TABLE `better_seo_i18n` CHANGE `mesh_5` `mesh_5` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NULL; \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/Config/Update/1.4.0.sql b/domokits/local/modules/BetterSeo/Config/Update/1.4.0.sql new file mode 100644 index 0000000..74bcd7a --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/Update/1.4.0.sql @@ -0,0 +1 @@ +ALTER TABLE `better_seo_i18n` ADD `json_data` TEXT NOT NULL AFTER `mesh_5`; diff --git a/domokits/local/modules/BetterSeo/Config/config.xml b/domokits/local/modules/BetterSeo/Config/config.xml new file mode 100644 index 0000000..88a5a63 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/config.xml @@ -0,0 +1,20 @@ + + + + +
+ + + + + + + + + + + + + diff --git a/domokits/local/modules/BetterSeo/Config/module.xml b/domokits/local/modules/BetterSeo/Config/module.xml new file mode 100644 index 0000000..370d2cb --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/module.xml @@ -0,0 +1,32 @@ + + + BetterSeo\BetterSeo + + Set noindex, nofollow, h1, tag on pages, and manage mesh links + + + Ajoute la balise noindex, nofollow, h1, sur les pages, plus gestion des liens maillés + + + en_US + fr_FR + + 2.1.2 + + + Nicolas Barbey + nabrbey@openstudio.fr + + + Gilles Bourgeat + gilles.bourgeat@gmail.com + + + classic + 2.5.0 + rc + 0 + 0 + diff --git a/domokits/local/modules/BetterSeo/Config/routing.xml b/domokits/local/modules/BetterSeo/Config/routing.xml new file mode 100644 index 0000000..cc2e8ce --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/routing.xml @@ -0,0 +1,10 @@ + + + + + + BetterSeo\Controller\BetterSeoController::saveAction + + diff --git a/domokits/local/modules/BetterSeo/Config/schema.xml b/domokits/local/modules/BetterSeo/Config/schema.xml new file mode 100644 index 0000000..bb2d822 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/schema.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
diff --git a/domokits/local/modules/BetterSeo/Config/sqldb.map b/domokits/local/modules/BetterSeo/Config/sqldb.map new file mode 100644 index 0000000..63a93ba --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +thelia.sql=thelia diff --git a/domokits/local/modules/BetterSeo/Config/thelia.sql b/domokits/local/modules/BetterSeo/Config/thelia.sql new file mode 100644 index 0000000..4f3bfb6 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Config/thelia.sql @@ -0,0 +1,58 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- better_seo +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `better_seo`; + +CREATE TABLE `better_seo` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `object_id` INTEGER NOT NULL, + `object_type` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- better_seo_i18n +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `better_seo_i18n`; + +CREATE TABLE `better_seo_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `noindex` TINYINT(4) DEFAULT 0 NOT NULL, + `nofollow` TINYINT(4) DEFAULT 0 NOT NULL, + `canonical_field` TEXT, + `h1` TEXT, + `mesh_text_1` TEXT, + `mesh_url_1` TEXT, + `mesh_text_2` TEXT, + `mesh_url_2` TEXT, + `mesh_text_3` TEXT, + `mesh_url_3` TEXT, + `mesh_text_4` TEXT, + `mesh_url_4` TEXT, + `mesh_text_5` TEXT, + `mesh_url_5` TEXT, + `mesh_1` TEXT, + `mesh_2` TEXT, + `mesh_3` TEXT, + `mesh_4` TEXT, + `mesh_5` TEXT, + `json_data` TEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `better_seo_i18n_FK_1` + FOREIGN KEY (`id`) + REFERENCES `better_seo` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/BetterSeo/Controller/BetterSeoController.php b/domokits/local/modules/BetterSeo/Controller/BetterSeoController.php new file mode 100644 index 0000000..8083744 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Controller/BetterSeoController.php @@ -0,0 +1,64 @@ +createForm(BetterSeoForm::getName()); + + $seoForm = $this->validateForm($form); + + $object_id = $request->get('object_id'); + $object_type = $request->get('object_type'); + + $lang = LangQuery::create() + ->filterById($request->get('lang_id')) + ->findOne(); + + if (null === $objectSeo = BetterSeoQuery::create() + ->filterByObjectId($object_id) + ->filterByObjectType($object_type) + ->findOne() + ) { + $objectSeo = (new BetterSeo()) + ->setObjectId($object_id) + ->setObjectType($object_type); + } + + $objectSeo + ->setLocale($lang->getLocale()) + ->setJsonData($seoForm->get('json_data')->getData()) + ->setNoindex(null === $seoForm->get('noindex_checkbox')->getData() ? 0 : 1) + ->setNofollow(null === $seoForm->get('nofollow_checkbox')->getData() ? 0 : 1) + ->setH1(null === $seoForm->get('h1')->getData() ? '' : $seoForm->get('h1')->getData()); + + for ($i = 1; $i <= 5; $i++) { + call_user_func([$objectSeo, 'setMeshUrl' . $i], $seoForm->get('mesh_url_' . $i)->getData()); + call_user_func([$objectSeo, 'setMeshText' . $i], $seoForm->get('mesh_text_' . $i)->getData()); + call_user_func([$objectSeo, 'setMesh' . $i], $seoForm->get('mesh_' . $i)->getData()); + } + + $objectSeo->save(); + + return $this->generateRedirect( + URL::getInstance()->absoluteUrl( + $request->getSession()->getReturnToUrl(), + ['current_tab' => 'seo'] + ) + ); + } +} diff --git a/domokits/local/modules/BetterSeo/EventListeners/SeoListener.php b/domokits/local/modules/BetterSeo/EventListeners/SeoListener.php new file mode 100644 index 0000000..b305cc8 --- /dev/null +++ b/domokits/local/modules/BetterSeo/EventListeners/SeoListener.php @@ -0,0 +1,75 @@ +request = $requestStack->getCurrentRequest(); + } + + public function removeHrefLang(AlternateHreflangEvent $event) + { + $objectType = $this->request->get('_view'); + $objectId = $this->request->get($objectType.'_id'); + + $betterSeoObject = $this->getBetterSeoObject($objectType, $objectId); + } + + public function checkSiteMap(SitemapEvent $event) + { + $objectId = $event->getRewritingUrl()->getViewId(); + $objectType = $event->getRewritingUrl()->getView(); + + $betterSeoObject = $this->getBetterSeoObject($objectType, $objectId); + + if (null !== $betterSeoObject){ + if ($betterSeoObject->getNoindex() === 1){ + $event->setHide(true); + } + } + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + $events = []; + if (class_exists('Sitemap\Event\SitemapEvent')){ + $events[SitemapEvent::SITEMAP_EVENT] = ['checkSiteMap',128]; + } + if (class_exists('AlternateHreflang\Event\AlternateHreflangEvent')){ + $events[AlternateHreflangEvent::BASE_EVENT_NAME] = ['removeHrefLang',128]; + } + return $events; + } + + protected function getBetterSeoObject($objectType, $objectId) + { + $lang = $this->request->getSession()->getLang()->getLocale(); + + $betterSeoObject = BetterSeoQuery::create() + ->filterByObjectType($objectType) + ->filterByObjectId($objectId) + ->findOne(); + if (null !== $betterSeoObject){ + $betterSeoObject->setLocale($lang); + } + + return $betterSeoObject; + } +} diff --git a/domokits/local/modules/BetterSeo/Form/BetterSeoForm.php b/domokits/local/modules/BetterSeo/Form/BetterSeoForm.php new file mode 100644 index 0000000..5eb79f5 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Form/BetterSeoForm.php @@ -0,0 +1,136 @@ +formBuilder; + $form + ->add( + 'noindex_checkbox', + IntegerType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'noindex', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'noindex_checkbox' + ) + ) + ) + ->add( + 'nofollow_checkbox', + IntegerType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'nofollow', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'nofollow_checkbox' + ) + ) + ) + ->add( + 'h1', + TextType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'h1', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'h1' + ) + ) + ) + ->add( + 'json_data', + TextareaType::class, + [ + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'JSON structured data', + [], + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'json_data' + ) + ] + + ); + + for ($i = 1; $i <= 5; $i++) { + $form->add( + 'mesh_text_' . $i, + TextType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'text', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'mesh_text_' . $i + ) + ) + ) + ->add( + 'mesh_url_' . $i, + UrlType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'url', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'mesh_url_' . $i + ) + ) + ) + ->add( + 'mesh_' . $i, + TextType::class, + array( + 'required' => false, + 'label' => Translator::getInstance()->trans( + 'text', + array(), + BetterSeo::DOMAIN_NAME + ), + 'label_attr' => array( + 'for' => 'mesh_' . $i + ) + ) + ); + } + } + + public static function getName() + { + return 'betterseo_form'; + } +} diff --git a/domokits/local/modules/BetterSeo/Hook/MetaHook.php b/domokits/local/modules/BetterSeo/Hook/MetaHook.php new file mode 100644 index 0000000..4bb0bd8 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Hook/MetaHook.php @@ -0,0 +1,38 @@ +request = $requestStack->getCurrentRequest(); + } + + public function onMainHeadBottom(HookRenderEvent $event) + { + $view = $this->request->get('_view'); + if ($view && preg_match('#^[a-zA-Z0-9\-_\.]+$#', $view)) { + + $id = $this->request->get($view . '_id'); + + $lang = $this->request->getSession()->getLang(); + + $event->add( + $this->render('meta_hook.html', [ + 'object_id' => $id, + 'object_type' => $view, + 'lang_id' => $lang->getId() + ]) + ); + } + } +} diff --git a/domokits/local/modules/BetterSeo/Hook/SeoFormHook.php b/domokits/local/modules/BetterSeo/Hook/SeoFormHook.php new file mode 100644 index 0000000..3751b89 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Hook/SeoFormHook.php @@ -0,0 +1,35 @@ +getArgument('id'); + $objectType = $event->getArgument('type'); + + $event->add( + $this->render( + "seo-additional-fields.html", + [ + 'object_id' => $objectId, + 'object_type' => $objectType, + ] + ) + ); + } +} \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/I18n/backOffice/default/en_US.php b/domokits/local/modules/BetterSeo/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..2a43203 --- /dev/null +++ b/domokits/local/modules/BetterSeo/I18n/backOffice/default/en_US.php @@ -0,0 +1,11 @@ + 'Save', + + 'noindex_nofollow.help' => 'Management of the meta robots noindex, nofollow. Allow to not index this page for search engines. Be careful before checking this, check that your page does not generate traffic on Google Analytics. You risk losing SEO.', + + + 'label.noindex' => 'Management of the meta robots noindex, nofollow, h1 tag and mesh links', + 'h1' => 'H1', +); diff --git a/domokits/local/modules/BetterSeo/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/BetterSeo/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..cb5aa43 --- /dev/null +++ b/domokits/local/modules/BetterSeo/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,19 @@ + 'Enregistrer', + + 'noindex_nofollow.help' => 'Gestion de la meta robots noindex, nofollow. Permet de ne pas indexer cette page pour les moteurs de recherche. Attention avant de cocher cela, bien vérifier que votre page ne génère pas de trafic sur Google Analytics. Vous risquez de perdre du référencement.', + + + 'label.noindex' => 'Gestion de la meta robots noindex, nofollow, balise H1 et liens maillés :', + 'h1' => 'H1', + 'Link' => 'Lien', + 'text' => 'texte', + 'url' => 'url', + 'Link text' => 'Texte du lien', + 'Link URL' => 'URL du lien', + 'Mesh links' => 'Liens maillés', + 'Mesh' => 'Texte de maillage', + 'Text' => 'Texte', +); diff --git a/domokits/local/modules/BetterSeo/I18n/en_US.php b/domokits/local/modules/BetterSeo/I18n/en_US.php new file mode 100644 index 0000000..afca37e --- /dev/null +++ b/domokits/local/modules/BetterSeo/I18n/en_US.php @@ -0,0 +1,5 @@ +getObjectId(); + $objectType = $this->getObjectType(); + $langId = $this->getLangId(); + + $lang = LangQuery::create() + ->filterById($langId) + ->findOne(); + + $query = BetterSeoQuery::create() + ->filterByObjectId($objectId) + ->filterByObjectType($objectType) + ->useBetterSeoI18nQuery() + ->filterByLocale($lang->getLocale()) + ->endUse() + ->withColumn(BetterSeoI18nTableMap::NOINDEX, 'noindex') + ->withColumn(BetterSeoI18nTableMap::NOFOLLOW, 'nofollow') + ->withColumn(BetterSeoI18nTableMap::H1, 'h1') + ->withColumn(BetterSeoI18nTableMap::JSON_DATA, 'json_data'); + + for ($i = 1; $i <= 5; $i++) { + $query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_TEXT_' . $i), 'mesh_text_' . $i); + $query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_URL_' . $i), 'mesh_url_' . $i); + $query->withColumn(constant(BetterSeoI18nTableMap::class . '::MESH_' . $i), 'mesh_' . $i); + } + + return $query; + } + + /** + * @param LoopResult $loopResult + * @return LoopResult + * @throws \Propel\Runtime\Exception\PropelException + */ + public function parseResults(LoopResult $loopResult) + { + /** @var BetterSeo $data */ + foreach ($loopResult->getResultDataCollection() as $data) { + $loopResultRow = new LoopResultRow($data); + + $loopResultRow->set('ID', $data->getId()); + $loopResultRow->set('OBJECT_ID', $data->getObjectId()); + $loopResultRow->set('OBJECT_TYPE', $data->getObjectType()); + $loopResultRow->set('NOINDEX', $data->getVirtualColumn('noindex')); + $loopResultRow->set('NOFOLLOW', $data->getVirtualColumn('nofollow')); + $loopResultRow->set('H1', $data->getVirtualColumn('h1')); + $loopResultRow->set('JSON_DATA', $data->getVirtualColumn('json_data')); + + for ($i = 1; $i <= 5; $i++) { + $loopResultRow->set('MESH_TEXT_' . $i, $data->getVirtualColumn('mesh_text_' . $i)); + $loopResultRow->set('MESH_URL_' . $i, $data->getVirtualColumn('mesh_url_' . $i)); + $loopResultRow->set('MESH_' . $i, $data->getVirtualColumn('mesh_' . $i)); + } + + $loopResult->addRow($loopResultRow); + } + return $loopResult; + } +} diff --git a/domokits/local/modules/BetterSeo/Model/BetterSeo.php b/domokits/local/modules/BetterSeo/Model/BetterSeo.php new file mode 100644 index 0000000..f0e9b59 --- /dev/null +++ b/domokits/local/modules/BetterSeo/Model/BetterSeo.php @@ -0,0 +1,10 @@ +/local/modules/``` directory and be sure that the name of the module is BetterSeo. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/better-seo-module:~1.4.1 +``` + +## Loop + +[better_seo_loop] + +### Input arguments + +|Argument |Description | +|--- |--- | +|**object_id** | The id of the object to display, exemple: object_id="12" | +|**object_type** | The type of the object to display (product, category, brand, folder, content) exemple object_type="brand"| +|**lang_id** | The id of the language| + + ### Output arguments + +|Variable |Description | +|--- |--- | +|$ID | the id in seo_noindex table | +|$OBJECT_ID | the id of the object | +|$OBJECT_TYPE | the type of the object | +|$NOINDEX | if the page of the object is index or not (value 0 or 1) | +|$NOFOLLOW | if the page of the object is follow or not (value 0 or 1) | +|$CANONICAL | Canonical Url | +|$H1 | H1 | +|$MESH_TEXT_1 | mesh text 1 | +|$MESH_URL_1 | mesh url 1 | +|$MESH_TEXT_2 | mesh text 2 | +|$MESH_URL_2 | mesh url 2 | +|$MESH_TEXT_3 | mesh text 3 | +|$MESH_URL_3 | mesh url 3 | +|$MESH_TEXT_4 | mesh text 4 | +|$MESH_URL_4 | mesh url 4 | +|$MESH_TEXT_5 | mesh text 5 | +|$MESH_URL_5 | mesh url 5 | +|$MESH_1 | mesh 1 | +|$MESH_2 | mesh 2 | +|$MESH_3 | mesh 3 | +|$MESH_4 | mesh 4 | +|$MESH_5 | mesh 5 | +|$JSON_DATA | JSON data for ld json | + +### Exemple + + {loop type="better_seo_loop" name="exemple.loop" object_id="42" object_type="category" lang_id="1"} + + +To use ld json you need to add this part to the head of your pages (product, category, brand, folder, content) + + {loop name="loop-name" type="better_seo_loop" object_id=$object_id object_type=$object_type lang_id=$langId} + + {/loop} + + diff --git a/domokits/local/modules/BetterSeo/Smarty/Plugins/BetterSeoMicroDataPlugin.php b/domokits/local/modules/BetterSeo/Smarty/Plugins/BetterSeoMicroDataPlugin.php new file mode 100644 index 0000000..c072ecf --- /dev/null +++ b/domokits/local/modules/BetterSeo/Smarty/Plugins/BetterSeoMicroDataPlugin.php @@ -0,0 +1,318 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace BetterSeo\Smarty\Plugins; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Core\Event\Image\ImageEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Exception\TaxEngineException; +use Thelia\Model\Category; +use Thelia\Model\CategoryQuery; +use Thelia\Model\ConfigQuery; +use Thelia\Model\Content; +use Thelia\Model\ContentQuery; +use Thelia\Model\Folder; +use Thelia\Model\FolderQuery; +use Thelia\Model\Lang; +use Thelia\Model\LangQuery; +use Thelia\Model\Product; +use Thelia\Model\ProductImageQuery; +use Thelia\Model\ProductPriceQuery; +use Thelia\Model\ProductQuery; +use Thelia\Model\ProductSaleElementsQuery; +use Thelia\TaxEngine\TaxEngine; +use TheliaSmarty\Template\AbstractSmartyPlugin; +use TheliaSmarty\Template\SmartyPluginDescriptor; + +class BetterSeoMicroDataPlugin extends AbstractSmartyPlugin +{ + protected $request; + protected $taxEngine; + protected $dispatcher; + + public function __construct(RequestStack $requestStack, TaxEngine $taxEngine, EventDispatcherInterface $dispatcher) + { + $this->request = $requestStack->getCurrentRequest(); + $this->taxEngine = $taxEngine; + $this->dispatcher = $dispatcher; + } + + public function getPluginDescriptors() + { + return [ + new SmartyPluginDescriptor('function', 'BetterSeoMicroData', $this, 'betterSeoMicroData'), + ]; + } + + /** + * @param $params + * + * @return array|int|string + * + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function betterSeoMicroData($params) + { + $type = $params['type'] ?? $this->request->get('_view'); + + $lang = $this->request->getSession()->getLang(); + + if (!$lang) { + $lang = LangQuery::create()->filterByByDefault(1)->findOne(); + } + $microdata = null; + + switch ($type) { + case 'product': + $id = $params['id'] ?? $this->request->get('product_id'); + $product = ProductQuery::create()->filterById($id)->findOne(); + $relatedProducts = null; + + if (array_key_exists('related_products', $params)){ + $relatedProducts = \is_array($params['related_products']) ? $params['related_products'] : $this->explode($params['related_products']); + } + + $microdata = $this->getProductMicroData($product, $lang, $relatedProducts); + break; + case 'category': + $id = $params['id'] ?? $this->request->get('category_id'); + if ($id) { + $category = CategoryQuery::create()->filterById($id)->findOne(); + $microdata = $this->getCategoryMicroData($category, $lang); + } + break; + case 'folder': + $id = $params['id'] ?? $this->request->get('folder_id'); + if ($id) { + $folder = FolderQuery::create()->filterById($id)->findOne(); + $microdata = $this->getFolderMicroData($folder, $lang); + } + break; + case 'content': + $id = $params['id'] ?? $this->request->get('content_id'); + if ($id) { + $microdata = $this->getContentMicroData($id, $lang); + } + break; + } + + $scriptsTag = ''; + + $scriptsTag .= ''; + if (null !== $microdata) { + $scriptsTag .= ''; + } + + return $scriptsTag; + } + + /** + * @return array + */ + protected function getStoreMicroData() + { + $microData = [ + '@context' => 'https://schema.org/', + '@type' => 'Organization', + 'name' => ConfigQuery::read('store_name'), + 'description' => ConfigQuery::read('store_description'), + 'url' => ConfigQuery::read('url_site'), + 'address' => [ + '@type' => 'PostalAddress', + 'streetAddress' => ConfigQuery::read('store_address1').' '.ConfigQuery::read('store_address2').' '.ConfigQuery::read('store_address3'), + 'addressLocality' => ConfigQuery::read('store_city'), + 'postalCode' => ConfigQuery::read('store_zipcode'), + ], + ]; + + return $microData; + } + + /** + * @param array $relatedProducts + * + * @return array + * + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + protected function getProductMicroData(Product $product, Lang $lang, $relatedProducts = []) + { + $product->setLocale($lang->getLocale()); + $image = ProductImageQuery::create()->filterByProductId($product->getId())->orderByPosition()->find()[0]; + $pse = ProductSaleElementsQuery::create()->filterByProductId($product->getId())->filterByIsDefault(1)->findOne(); + $psePrice = ProductPriceQuery::create()->filterByProductSaleElementsId($pse->getId())->findOne(); + $taxCountry = $this->taxEngine->getDeliveryCountry(); + + try { + $taxedPrice = $product->getTaxedPrice( + $taxCountry, + $psePrice->getPrice() + ); + if ($pse->getPromo()) { + $taxedPrice = $product->getTaxedPromoPrice( + $taxCountry, + $psePrice->getPromoPrice() + ); + } + } catch (TaxEngineException $e) { + $taxedPrice = null; + } + + $imagePath = null; + + if ($image) { + $baseSourceFilePath = ConfigQuery::read('images_library_path'); + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images'; + } else { + $baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath; + } + $event = new ImageEvent(); + $sourceFilePath = $baseSourceFilePath.'/product/'.$image->getFile(); + + $event->setSourceFilepath($sourceFilePath); + $event->setCacheSubdirectory('product'); + + try { + $this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS); + $imagePath = $event->getFileUrl(); + } catch (\Exception $e) { + $imagePath = $image->getFile(); + } + } + + $microData = [ + '@context' => 'https://schema.org/', + '@type' => 'Product', + 'name' => $product->getTitle(), + 'image' => $imagePath, + 'description' => $product->getDescription(), + 'sku' => $product->getRef(), + 'offers' => [ + 'url' => $product->getUrl(), + 'priceCurrency' => $this->request->getSession()->getCurrency()->getCode(), + 'price' => $taxedPrice, + 'itemCondition' => 'https://schema.org/NewCondition', + 'availability' => $pse->getQuantity() > 0 ? 'http://schema.org/InStock' : 'http://schema.org/OutOfStock', + ], + ]; + + if ($pse->getEanCode()) { + $microData['gtin13'] = $pse->getEanCode(); + } + + if ($brand = $product->getBrand()) { + $microData['brand']['@type'] = 'Brand'; + $microData['brand']['name'] = $brand->getTitle(); + } + + if ($relatedProducts) { + foreach ($relatedProducts as $relatedProductId) { + $relatedProduct = ProductQuery::create()->filterById($relatedProductId)->findOne(); + $microData['isRelatedTo'][] = $this->getProductMicroData($relatedProduct, $lang); + } + } + + return $microData; + } + + /** + * @return array + */ + protected function getCategoryMicroData(Category $category, Lang $lang) + { + $category->setLocale($lang->getLocale()); + + $products = $category->getProducts(); + + $itemListElement = []; + + $i = 1; + foreach ($products as $product) { + $itemListElement[] = [ + '@type' => 'ListItem', + 'position' => $i++, + 'url' => $product->getUrl(), + ]; + } + + $microData = [ + '@context' => 'https://schema.org/', + '@type' => 'ItemList', + 'url' => $category->getUrl(), + 'numberOfItems' => \count($products), + 'itemListElement' => $itemListElement, + ]; + + return $microData; + } + + /** + * @return array + */ + protected function getFolderMicroData(Folder $folder, Lang $lang) + { + $folder->setLocale($lang->getLocale()); + + $microData = [ + '@context' => 'https://schema.org/', + '@type' => 'Guide', + 'url' => $folder->getUrl(), + "name" => $folder->getTitle(), + "abstract" => $folder->getChapo(), + ]; + + + return $microData; + } + + /** + * @return array + */ + protected function getContentMicroData($contentId, Lang $lang) + { + $content = ContentQuery::create()->filterById($contentId)->findOne(); + + if (null === $content) { + return null; + } + + $content->setLocale($lang->getLocale()); + + $microData = [ + '@context' => 'https://schema.org/', + '@type' => 'Article', + 'url' => $content->getUrl(), + "name" => $content->getTitle(), + "abstract" => $content->getChapo(), + ]; + + $defaultFoIdlder = $content->getDefaultFolderId(); + + if (null !== $defaultFoIdlder) { + $default_folder = FolderQuery::create()->findOneById($defaultFoIdlder); + if (null !== $default_folder) { + $default_folder->setLocale($lang->getLocale()); + $microData['isPartOf'] = [ + 'name' => $default_folder->getTitle(), + 'url' => $default_folder->getUrl() + ]; + } + } + + return $microData; + } +} diff --git a/domokits/local/modules/BetterSeo/composer.json b/domokits/local/modules/BetterSeo/composer.json new file mode 100644 index 0000000..79326f6 --- /dev/null +++ b/domokits/local/modules/BetterSeo/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/better-seo-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "BetterSeo" + } +} \ No newline at end of file diff --git a/domokits/local/modules/BetterSeo/templates/backOffice/default/seo-additional-fields.html b/domokits/local/modules/BetterSeo/templates/backOffice/default/seo-additional-fields.html new file mode 100644 index 0000000..39bc1db --- /dev/null +++ b/domokits/local/modules/BetterSeo/templates/backOffice/default/seo-additional-fields.html @@ -0,0 +1,196 @@ +{$pageUrl|default:null} +{$noindex_val = null} +{$nofollow_val = null} +{$json_data = null} +{$h1 = null} +{for $i=1 to 5} + {assign var="mesh_text_$i" value=null} + {assign var="mesh_url_$i" value=null} + {assign var="mesh_$i" value=null} +{/for} + +{loop type="better_seo_loop" name="better_seo_data" object_id=$object_id object_type=$object_type lang_id=$edit_language_id} +{$noindex_val = $NOINDEX} +{$nofollow_val = $NOFOLLOW} +{$json_data = $JSON_DATA} +{$h1 = $H1} +{for $i=1 to 5} + {assign var="mesh_text_$i" value={$MESH_TEXT_{$i}}} + {assign var="mesh_url_$i" value={$MESH_URL_{$i}}} + {assign var="mesh_$i" value={$MESH_{$i}}} +{/for} +{/loop} +{form name = "betterseo_form"} + +
+
+ +
+
+ + {form_hidden_fields form=$form} + {if $form_error} +
+
+
{$form_error_message}
+
+
+ {/if} + +
+ +
+ + {form_field field="noindex_checkbox"} +
+ + + {if $error} +
+ {$message} +
+ {/if} +
+ {/form_field} + + {form_field field="nofollow_checkbox"} +
+ + + {if $error} +
+ {$message} +
+ {/if} +
+ {/form_field} +
+ {intl l="noindex_nofollow.help" d="betterseo.bo.default"} +
+ + {form_field field="h1"} +
+ + + {if $error} +
+ {$message} +
+ {/if} +
+
+
+ {/form_field} + + {form_field field="json_data"} +
+ + +
+ {/form_field} + +
+ + + + + {for $i=1 to 5} + + {/for} + + + + + {for $i=1 to 5} + {$textVar = "MESH_$i"} + + + {/for} + + +
{intl l="Mesh" d="betterseo.bo.default"}
+ {intl l="Text" d="betterseo.bo.default"} {$i} +
+ {form_field field="mesh_$i"} +
+ + {if $error} +
+ {$message} +
+ {/if} +
+ {/form_field} +
+ +
+ + + + + + + + + + + + {for $i=1 to 5} + {$urlVar = "MESH_URL_$i"} + {$textVar = "MESH_TEXT_$i"} + + + + + + + {/for} + +
{intl l="Mesh links" d="betterseo.bo.default"}
+ + + {intl l="Link text" d="betterseo.bo.default"} + + {intl l="Link URL" d="betterseo.bo.default"} +
+ {intl l="Link" d="betterseo.bo.default"} {$i} + + {form_field field="mesh_text_$i"} +
+ + + {if $error} +
+ {$message} +
+ {/if} +
+ {/form_field} +
+ {form_field field="mesh_url_$i"} +
+ + + {if $error} +
+ {$message} +
+ {/if} +
+ {/form_field} +
+ + +
+
+ + +
+
+
+ +{/form} diff --git a/domokits/local/modules/BetterSeo/templates/frontOffice/default/meta_hook.html b/domokits/local/modules/BetterSeo/templates/frontOffice/default/meta_hook.html new file mode 100644 index 0000000..54ee946 --- /dev/null +++ b/domokits/local/modules/BetterSeo/templates/frontOffice/default/meta_hook.html @@ -0,0 +1,18 @@ + +{BetterSeoMicroData} + +{loop type="better_seo_loop" name="better_seo_meta_loop" object_id=$object_id object_type=$object_type lang_id=$lang_id} + {if $NOINDEX == 1 and $NOFOLLOW == 1} + + {elseif $NOINDEX == 1} + + {elseif $NOFOLLOW == 1} + + {/if} + + {if $JSON_DATA} + + {/if} +{/loop} diff --git a/domokits/local/modules/CanonicalUrl/CHANGELOG.md b/domokits/local/modules/CanonicalUrl/CHANGELOG.md new file mode 100644 index 0000000..1261d48 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/CHANGELOG.md @@ -0,0 +1,20 @@ +# 1.2.0 + +- Add canonical override in seo form + +# 1.1.1 + +- Fix exception when Thelia was not configured + +# 1.1.0 + +- Adds the unit tests in the case of a single domain +- Adds the case there is a subfolder + +# 1.0.1 + +- Fix ```installer-name``` in the composer.json file + +# 1.0.2 + +- Fix hook scope for compatibility with the other modules \ No newline at end of file diff --git a/domokits/local/modules/CanonicalUrl/CanonicalUrl.php b/domokits/local/modules/CanonicalUrl/CanonicalUrl.php new file mode 100644 index 0000000..aa9e51d --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/CanonicalUrl.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; + +class CanonicalUrl extends BaseModule +{ + /** @var string */ + const DOMAIN_NAME = 'canonicalurl'; + + const SEO_CANONICAL_META_KEY = 'seo_canonical_meta'; + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/CanonicalUrl/Config/config.xml b/domokits/local/modules/CanonicalUrl/Config/config.xml new file mode 100644 index 0000000..585a638 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Config/config.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/domokits/local/modules/CanonicalUrl/Config/module.xml b/domokits/local/modules/CanonicalUrl/Config/module.xml new file mode 100644 index 0000000..118f20b --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Config/module.xml @@ -0,0 +1,38 @@ + + + CanonicalUrl\CanonicalUrl + + Adds the canonical Url in the metas of your site + + + Ajoute l'Url canonique dans les metas de votre site + + + en_US + fr_FR + + 2.1.6 + + + Gilles Bourgeat + gilles.bourgeat@gmail.com + https://github.com/gillesbourgeat + + + Franck Allimant + CQFDev + franck@cqfdev.fr + www.cqfdev.fr + + + Vincent Lopes-Vicente + OpenStudio + vlopes@openstudio.fr + + + classic + 2.5.0 + prod + diff --git a/domokits/local/modules/CanonicalUrl/Config/routing.xml b/domokits/local/modules/CanonicalUrl/Config/routing.xml new file mode 100644 index 0000000..fc772b7 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Config/routing.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvent.php b/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvent.php new file mode 100644 index 0000000..51c9862 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvent.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\Event; + +use Symfony\Contracts\EventDispatcher\Event; + +/** + * Class CanonicalUrlEvent. + * + * @author Gilles Bourgeat + */ +class CanonicalUrlEvent extends Event +{ + /** @var string|null */ + protected $url = null; + + /** + * @return string|null + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string|null $url + * + * @return $this + */ + public function setUrl($url) + { + if ($url !== null && $url[0] !== '/' && filter_var($url, \FILTER_VALIDATE_URL) === false) { + throw new \InvalidArgumentException('The value "'.(string) $url.'" is not a valid Url or Uri.'); + } + + $this->url = $url; + + return $this; + } +} diff --git a/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvents.php b/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvents.php new file mode 100644 index 0000000..0312382 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Event/CanonicalUrlEvents.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\Event; + +/** + * Class CanonicalUrlEvents. + * + * @author Gilles Bourgeat + */ +class CanonicalUrlEvents +{ + const GENERATE_CANONICAL = 'canonical.url.generate.canonical'; +} diff --git a/domokits/local/modules/CanonicalUrl/EventListener/CanonicalUrlListener.php b/domokits/local/modules/CanonicalUrl/EventListener/CanonicalUrlListener.php new file mode 100644 index 0000000..25a9cb7 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/EventListener/CanonicalUrlListener.php @@ -0,0 +1,249 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\EventListener; + +use BetterSeo\Model\BetterSeoQuery; +use CanonicalUrl\CanonicalUrl; +use CanonicalUrl\Event\CanonicalUrlEvent; +use CanonicalUrl\Event\CanonicalUrlEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Session\Session; +use Thelia\Log\Tlog; +use Thelia\Model\ConfigQuery; +use Thelia\Model\Lang; +use Thelia\Model\LangQuery; +use Thelia\Model\MetaDataQuery; + +/** + * Class CanonicalUrlListener. + * + * @author Gilles Bourgeat + */ +class CanonicalUrlListener implements EventSubscriberInterface +{ + /** @var RequestStack */ + protected $requestStack; + + /** @var Session */ + protected $session; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function generateUrlCanonical(CanonicalUrlEvent $event): void + { + /** @var Request $request */ + if (null === $request = $this->requestStack->getCurrentRequest()) { + return; + } + + if ($event->getUrl() !== null) { + return; + } + + if (null !== $canonicalOverride = $this->getCanonicalOverride()) { + try { + $event->setUrl($canonicalOverride); + + return; + } catch (\InvalidArgumentException $e) { + Tlog::getInstance()->addWarning($e->getMessage()); + } + } + + $parseUrlByCurrentLocale = $this->getParsedUrlByCurrentLocale(); + + if (empty($parseUrlByCurrentLocale['host'])) { + return; + } + + // Be sure to use the proper domain name + $canonicalUrl = $parseUrlByCurrentLocale['scheme'].'://'.$parseUrlByCurrentLocale['host']; + + // preserving a potential subdirectory, e.g. http://somehost.com/mydir/index.php/... + $canonicalUrl .= $request->getBaseUrl(); + + // Remove script name from path, e.g. http://somehost.com/index.php/... + $canonicalUrl = preg_replace("!/index(_dev)?\.php!", '', $canonicalUrl); + + $path = $request->getPathInfo(); + + if (!empty($path) && $path != '/') { + $canonicalUrl .= $path; + + $canonicalUrl = rtrim($canonicalUrl, '/'); + } else if (isset($parseUrlByCurrentLocale['query'])) { + $canonicalUrl .= '/?'. (array_key_exists("query", $parseUrlByCurrentLocale)) ? $parseUrlByCurrentLocale['query'] : ""; + } + + try { + $event->setUrl($canonicalUrl); + } catch (\InvalidArgumentException $e) { + Tlog::getInstance()->addWarning($e->getMessage()); + } + } + + /** + * @return array + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + CanonicalUrlEvents::GENERATE_CANONICAL => [ + 'generateUrlCanonical', 128, + ], + ]; + } + + /** + * @return array + * + * At least one element will be present within the array. + * Potential keys within this array are: + * scheme - e.g. http + * host + * port + * user + * pass + * path + * query - after the question mark ? + * fragment - after the hashmark # + */ + protected function getParsedUrlByCurrentLocale() + { + /** @var Request $request */ + $request = $this->requestStack->getCurrentRequest(); + + // for one domain by lang + if ((int) ConfigQuery::read('one_domain_foreach_lang', 0) === 1) { + // We always query the DB here, as the Lang configuration (then the related URL) may change during the + // user session lifetime, and improper URLs could be generated. This is quite odd, okay, but may happen. + $langUrl = LangQuery::create()->findPk($request->getSession()->getLang()->getId())->getUrl(); + + if (!empty($langUrl) && false !== $parse = parse_url($langUrl)) { + return $parse; + } + } + + // Configured site URL + $urlSite = ConfigQuery::read('url_site'); + if (!empty($urlSite) && false !== $parse = parse_url($urlSite)) { + return $parse; + } + + // return current URL + return parse_url($request->getUri()); + } + + /** + * @return string|null + */ + protected function getCanonicalOverride() + { + /** @var Request $request */ + $request = $this->requestStack->getCurrentRequest(); + $lang = $request->getSession()->getLang(); + + $routeParameters = $this->getRouteParameters(); + + if (null === $routeParameters) { + return null; + } + + $url = null; + + $metaCanonical = MetaDataQuery::create() + ->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY) + ->filterByElementKey($routeParameters['view']) + ->filterByElementId($routeParameters['id']) + ->findOne(); + + if (null !== $metaCanonical) { + $canonicalValues = json_decode($metaCanonical->getValue(), true); + + $url = isset($canonicalValues[$lang->getLocale()]) && ! empty($canonicalValues[$lang->getLocale()]) ? $canonicalValues[$lang->getLocale()] :null; + } + + // Try to get old field of BetterSeoModule + if (null === $url && class_exists("BetterSeo\BetterSeo")) { + try { + $betterSeoData = BetterSeoQuery::create() + ->filterByObjectType($routeParameters['view']) + ->filterByObjectId($routeParameters['id']) + ->findOne(); + + $url = $betterSeoData->setLocale($lang->getLocale()) + ->getCanonicalField(); + } catch (\Throwable $exception) { + //Catch if field doesn't exist but do nothing + } + } + + if (null === $url) { + return null; + } + + if (false === filter_var($url, \FILTER_VALIDATE_URL)) { + return rtrim($this->getSiteBaseUrlForLocale($lang), "/")."/".$url; + } + + return $url; + } + + protected function getSiteBaseUrlForLocale(Lang $lang = null) + { + if (null === $lang) { + $lang = $this->requestStack->getCurrentRequest()->getSession()->getLang(); + } + if ((int) ConfigQuery::read('one_domain_foreach_lang', 0) === 1) { + // We always query the DB here, as the Lang configuration (then the related URL) may change during the + // user session lifetime, and improper URLs could be generated. This is quite odd, okay, but may happen. + $langUrl = LangQuery::create()->findPk($lang->getId())->getUrl(); + return $langUrl; + } + + // Configured site URL + $urlSite = ConfigQuery::read('url_site'); + return $urlSite; + } + + /** + * @return array|null + */ + protected function getRouteParameters() + { + /** @var Request $request */ + $request = $this->requestStack->getCurrentRequest(); + + $view = $request->get('view'); + if (null === $view) { + $view = $request->get('_view'); + } + if (null === $view) { + return null; + } + + $id = $request->get($view.'_id'); + + if (null === $id) { + return null; + } + + return compact('view', 'id'); + } +} diff --git a/domokits/local/modules/CanonicalUrl/EventListener/SeoFormListener.php b/domokits/local/modules/CanonicalUrl/EventListener/SeoFormListener.php new file mode 100644 index 0000000..1e06cc3 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/EventListener/SeoFormListener.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\EventListener; + +use CanonicalUrl\CanonicalUrl; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Action\BaseAction; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Event\TheliaFormEvent; +use Thelia\Core\Event\UpdateSeoEvent; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Model\MetaDataQuery; + +class SeoFormListener extends BaseAction implements EventSubscriberInterface +{ + /** @var RequestStack */ + protected $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public static function getSubscribedEvents() + { + return [ + TheliaEvents::FORM_AFTER_BUILD.'.thelia_seo' => ['addCanonicalField', 128], + TheliaEvents::CATEGORY_UPDATE_SEO => ['saveCategorySeoFields', 128], + TheliaEvents::BRAND_UPDATE_SEO => ['saveBrandSeoFields', 128], + TheliaEvents::CONTENT_UPDATE_SEO => ['saveContentSeoFields', 128], + TheliaEvents::FOLDER_UPDATE_SEO => ['saveFolderSeoFields', 128], + TheliaEvents::PRODUCT_UPDATE_SEO => ['saveProductSeoFields', 128], + ]; + } + + public function saveCategorySeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + $this->saveSeoFields($event, $eventName, $dispatcher, 'category'); + } + + public function saveBrandSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + $this->saveSeoFields($event, $eventName, $dispatcher, 'brand'); + } + + public function saveContentSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + $this->saveSeoFields($event, $eventName, $dispatcher, 'content'); + } + + public function saveFolderSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + $this->saveSeoFields($event, $eventName, $dispatcher, 'folder'); + } + + public function saveProductSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + $this->saveSeoFields($event, $eventName, $dispatcher, 'product'); + } + + protected function saveSeoFields(UpdateSeoEvent $event, $eventName, EventDispatcherInterface $dispatcher, $elementKey): void + { + $form = $this->requestStack->getCurrentRequest()->get('thelia_seo'); + + if (null === $form || !\array_key_exists('id', $form) || !\array_key_exists('canonical', $form)) { + return; + } + + $canonicalValues = []; + + $canonicalMetaData = MetaDataQuery::create() + ->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY) + ->filterByElementKey($elementKey) + ->filterByElementId($form['id']) + ->findOneOrCreate(); + + if (!$canonicalMetaData->isNew()) { + $canonicalValues = json_decode($canonicalMetaData->getValue(), true); + } + + $locale = $form['locale']; + $canonicalValues[$locale] = $form['canonical']; + + $canonicalMetaData + ->setIsSerialized(0) + ->setValue(json_encode($canonicalValues)) + ->save(); + } + + public function addCanonicalField(TheliaFormEvent $event): void + { + $event->getForm()->getFormBuilder() + ->add( + 'canonical', + TextType::class + ); + } +} diff --git a/domokits/local/modules/CanonicalUrl/Hook/MetaHook.php b/domokits/local/modules/CanonicalUrl/Hook/MetaHook.php new file mode 100644 index 0000000..7f02df8 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Hook/MetaHook.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\Hook; + +use CanonicalUrl\Event\CanonicalUrlEvent; +use CanonicalUrl\Event\CanonicalUrlEvents; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class MetaHook. + * + * @author Gilles Bourgeat + */ +class MetaHook extends BaseHook +{ + /** @var EventDispatcherInterface */ + protected $eventDispatcher; + + public function __construct(EventDispatcherInterface $eventDispatcher) + { + $this->eventDispatcher = $eventDispatcher; + } + + public function onMainHeadBottom(HookRenderEvent $hookRender): void + { + $event = new CanonicalUrlEvent(); + + $this->eventDispatcher->dispatch( + $event, + CanonicalUrlEvents::GENERATE_CANONICAL, + ); + + if ($event->getUrl()) { + $hookRender->add(''); + } + } +} diff --git a/domokits/local/modules/CanonicalUrl/Hook/SeoUpdateFormHook.php b/domokits/local/modules/CanonicalUrl/Hook/SeoUpdateFormHook.php new file mode 100644 index 0000000..90d780f --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Hook/SeoUpdateFormHook.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\Hook; + +use CanonicalUrl\CanonicalUrl; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Model\MetaDataQuery; + +class SeoUpdateFormHook extends BaseHook +{ + public function addInputs(HookRenderEvent $event): void + { + $id = $event->getArgument('id'); + $type = $event->getArgument('type'); + + $canonical = null; + $canonicalMetaData = MetaDataQuery::create() + ->filterByMetaKey(CanonicalUrl::SEO_CANONICAL_META_KEY) + ->filterByElementKey($type) + ->filterByElementId($id) + ->findOneOrCreate(); + + $canonicalMetaDataValues = json_decode($canonicalMetaData->getValue(), true); + + $lang = $this->getSession()->getAdminEditionLang(); + + if (isset($canonicalMetaDataValues[$lang->getLocale()])) { + $canonical = $canonicalMetaDataValues[$lang->getLocale()]; + } + + $event->add($this->render( + 'hook-seo-update-form.html', + [ + 'form' => $event->getArgument('form'), + 'canonical' => $canonical, + ] + )); + } +} diff --git a/domokits/local/modules/CanonicalUrl/I18n/backOffice/default/en_US.php b/domokits/local/modules/CanonicalUrl/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..e9c0307 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/I18n/backOffice/default/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Surcharge de l\'url canonique' => 'Canonical url override', +]; diff --git a/domokits/local/modules/CanonicalUrl/LICENSE.txt b/domokits/local/modules/CanonicalUrl/LICENSE.txt new file mode 100644 index 0000000..cfbc66d --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/domokits/local/modules/CanonicalUrl/Readme.md b/domokits/local/modules/CanonicalUrl/Readme.md new file mode 100644 index 0000000..5c4fa63 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Readme.md @@ -0,0 +1,47 @@ +# Canonical Url + +This module generates a canonical URL for every page of your shop. Once activated, you'll find a `` tag in the header of your pages. + +## Examples + +- If the page URL is not rewritten, the canonical URL will contain all the URL parameters. Example for : For URL ```http://demo.thelia.net/?view=product&locale=en_US&product_id=18``` + ```html + + ``` + Obviously, this is far from ideal. Consider activating URL rewriting ! + +- When the page URL contains the script name (index.php), it will be removed from the canonical URL. Example, the canonical URL of ```http://demo.thelia.net/index.php?view=product&locale=en_US&product_id=18``` is : + ```html + + ``` + + When a rewritten URL contains parameters, these parameters a removed. For ```http://demo.thelia.net/index.php/en_en-your-path.html?page=44```, the canonical URL is : + ```html + + ``` + +- If the page URL contains a domain which is not the main shop domain, this domain is replaced by the main shop domain. For ```http://demo458.thelia.net/index.php/en_en-your-path.html?page=44``` the canonical URL is : + ```html + + ``` + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is CanonicalUrl. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/canonical-url-module:~2.1 +``` + +## Usage + +You just have to activate the module and check the meta tags of your shop. + +The canonical will be generated automatically but you can define a canonical url in seo form for each item if you want override the generated url. diff --git a/domokits/local/modules/CanonicalUrl/Tests/CanonicalUrlTest.php b/domokits/local/modules/CanonicalUrl/Tests/CanonicalUrlTest.php new file mode 100644 index 0000000..40bf0e7 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/Tests/CanonicalUrlTest.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace CanonicalUrl\Tests; + +use CanonicalUrl\Event\CanonicalUrlEvent; +use CanonicalUrl\EventListener\CanonicalUrlListener; +use Symfony\Component\HttpFoundation\Request; + +/** + * Class CanonicalUrlTest. + * + * @author Gilles Bourgeat + */ +class CanonicalUrlTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp(): void + { + /*$config = $this->getMock('Thelia\Model\ConfigQuery'); + + $config->expects($this->any()) + ->method('read') + ->with('allow_slash_ended_uri') + ->will($this->returnValue(true));*/ + } + + public function testRemoveFileIndex(): void + { + $this->performList('http://myhost.com/test', [ + 'http://myhost.com/index.php/test', + 'http://myhost.com/index.php/test/', + 'http://myhost.com/index.php/test?page=22&list=1', + 'http://myhost.com/index.php/test/?page=22&list=1', + ]); + } + + public function testRemoveFileIndexDev(): void + { + $this->performList('http://myhost.com/test', [ + 'http://myhost.com/index_dev.php/test', + 'http://myhost.com/index_dev.php/test/', + 'http://myhost.com/index_dev.php/test?page=22&list=1', + 'http://myhost.com/index_dev.php/test/?page=22&list=1', + ], $this->fakeServer( + '/var/www/web/index_dev.php', + '/index_dev.php' + )); + } + + public function testHTTPWithSubDomain(): void + { + $this->performList('http://mysubdomain.myhost.com/test', [ + 'http://mysubdomain.myhost.com/index.php/test/?page=22&list=1', + ]); + } + + public function testHTTPS(): void + { + $this->performList('https://myhost.com/test', [ + 'https://myhost.com/index.php/test/?page=22&list=1', + ]); + } + + public function testHTTPSWithSubDomain(): void + { + $this->performList('https://mysubdomain.myhost.com/test', [ + 'https://mysubdomain.myhost.com/index.php/test/?page=22&list=1', + ]); + } + + public function testHTTPWithSubdirectory(): void + { + $this->performList('http://myhost.com/web/test', [ + 'http://myhost.com/web/index.php/test', + 'http://myhost.com/web/index.php/test/', + 'http://myhost.com/web/index.php/test?page=22&list=1', + 'http://myhost.com/web/index.php/test?page=22&list=1/', + ], $this->fakeServer( + '/var/www/web/index.php', + '/web/index.php' + )); + + $this->performList('http://myhost.com/web/test', [ + 'http://myhost.com/web/index_dev.php/test', + 'http://myhost.com/web/index_dev.php/test/', + 'http://myhost.com/web/index_dev.php/test?page=22&list=1', + 'http://myhost.com/web/index_dev.php/test?page=22&list=1/', + ], $this->fakeServer( + '/var/www/web/index_dev.php', + '/web/index_dev.php' + )); + } + + public function testHTTPWithMultipleSubdirectory(): void + { + $this->performList('http://myhost.com/web/web2/web3/test', [ + 'http://myhost.com/web/web2/web3/index.php/test/?page=22&list=1', + ], $this->fakeServer( + '/var/www/web/web2/web3/index.php', + '/web/web2/web3/index.php' + )); + + $this->performList('http://myhost.com/web/web2/web3/test', [ + 'http://myhost.com/web/web2/web3/index_dev.php/test/?page=22&list=1', + ], $this->fakeServer( + '/var/www/web/web2/web3/index_dev.php', + '/web/web2/web3/index_dev.php' + )); + } + + public function testHTTPSWithSubdirectory(): void + { + $this->performList('https://myhost.com/web/test', [ + 'https://myhost.com/web/index.php/test/?page=22&list=1', + ], $this->fakeServer( + '/var/www/web/index.php', + '/web/index.php' + )); + } + + public function testHTTPSWithMultipleSubdirectory(): void + { + $this->performList('https://myhost.com/web/web2/web3/test', [ + 'https://myhost.com/web/web2/web3/index.php/test/?page=22&list=1', + ], $this->fakeServer( + '/var/www/web/web2/web3/index.php', + '/web/web2/web3/index.php' + )); + } + + public function testWithNoPath(): void + { + $this->performList('http://myhost.com/?list=22&page=1', [ + 'http://myhost.com?list=22&page=1', + 'http://myhost.com/?list=22&page=1', + 'http://myhost.com/index.php?list=22&page=1', + 'http://myhost.com/index.php/?list=22&page=1', + ]); + + $this->performList('http://myhost.com/?list=22&page=1', [ + 'http://myhost.com/index_dev.php?list=22&page=1', + 'http://myhost.com/index_dev.php/?list=22&page=1', + ], $this->fakeServer( + '/var/www/web/index_dev.php', + '/index_dev.php' + )); + } + + public function testWithNoPathAndMultipleSubdirectory(): void + { + $this->performList('http://myhost.com/web/?list=22&page=1', [ + 'http://myhost.com/web/index.php?list=22&page=1', + 'http://myhost.com/web/?list=22&page=1', + 'http://myhost.com/web/index.php?list=22&page=1', + 'http://myhost.com/web/index.php/?list=22&page=1', + ], $this->fakeServer( + '/var/www/web/index.php', + '/web/index.php' + )); + + $this->performList('http://myhost.com/web/?list=22&page=1', [ + 'http://myhost.com/web/index_dev.php?list=22&page=1', + 'http://myhost.com/web/index_dev.php/?list=22&page=1', + ], $this->fakeServer( + '/var/www/web/index_dev.php', + '/web/index_dev.php' + )); + } + + public function testWithNotRewrittenUrl(): void + { + $this->performList('http://myhost.com/web/?view=category&lang=fr_FR&category_id=48', [ + 'http://myhost.com/web/index.php?view=category&lang=fr_FR&category_id=48', + 'http://myhost.com/web/?lang=fr_FR&view=category&category_id=48', + 'http://myhost.com/web/index.php?&category_id=48&lang=fr_FR&view=category', + 'http://myhost.com/web/index.php/?category_id=48&view=category&lang=fr_FR', + ], $this->fakeServer( + '/var/www/web/index.php', + '/web/index.php' + )); + } + + public function testOverrideCanonicalEvent(): void + { + $canonicalUrlListener = new CanonicalUrlListener(Request::create('https://myhost.com/test')); + + $event = new CanonicalUrlEvent(); + + // override canonical + $canonical = 'http://myscanonical.com'; + $event->setUrl($canonical); + + $canonicalUrlListener->generateUrlCanonical($event); + + $this->assertEquals($canonical, $event->getUrl()); + } + + /** + * @param string $scriptFileName + * @param string $scriptName + * + * @return array + */ + protected function fakeServer( + $scriptFileName = '/var/www/web/index.php', + $scriptName = '/index.php' + ) { + return [ + 'SCRIPT_FILENAME' => $scriptFileName, + 'SCRIPT_NAME' => $scriptName, + ]; + } + + /** + * @param string $canonicalExpected canonical expected + * @param array $list array of uri + */ + protected function performList($canonicalExpected, array $list, array $server = []): void + { + if (empty($server)) { + $server = $this->fakeServer(); + } + + foreach ($list as $uri) { + $canonicalUrlListener = new CanonicalUrlListener( + Request::create($uri, 'GET', [], [], [], $server) + ); + + $event = new CanonicalUrlEvent(); + + $canonicalUrlListener->generateUrlCanonical($event); + + $this->assertEquals($canonicalExpected, $event->getUrl()); + } + } +} diff --git a/domokits/local/modules/CanonicalUrl/composer.json b/domokits/local/modules/CanonicalUrl/composer.json new file mode 100644 index 0000000..0d30553 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/canonical-url-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "CanonicalUrl" + } +} \ No newline at end of file diff --git a/domokits/local/modules/CanonicalUrl/templates/backOffice/default/hook-seo-update-form.html b/domokits/local/modules/CanonicalUrl/templates/backOffice/default/hook-seo-update-form.html new file mode 100644 index 0000000..502f863 --- /dev/null +++ b/domokits/local/modules/CanonicalUrl/templates/backOffice/default/hook-seo-update-form.html @@ -0,0 +1,6 @@ +{form_field form=$form field='canonical' } +
+ + +
+{/form_field} \ No newline at end of file diff --git a/domokits/local/modules/Carousel/CHANGELOG.md b/domokits/local/modules/Carousel/CHANGELOG.md new file mode 100644 index 0000000..b4525a3 --- /dev/null +++ b/domokits/local/modules/Carousel/CHANGELOG.md @@ -0,0 +1,6 @@ +# 2.3.0-alpha1 + +- Moved the images from the directory 'media' in the module to thelia/local/media/images/carousel. +- The current images will be automatically copied in the new directory during the update of the module +- Removed AdminIncludes directory +- All html,js and css files are now in 'templates' \ No newline at end of file diff --git a/domokits/local/modules/Carousel/Carousel.php b/domokits/local/modules/Carousel/Carousel.php new file mode 100644 index 0000000..ac2cd89 --- /dev/null +++ b/domokits/local/modules/Carousel/Carousel.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel; + +use Propel\Runtime\Connection\ConnectionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; +use Thelia\Install\Database; +use Thelia\Model\ConfigQuery; +use Thelia\Module\BaseModule; + +/** + * Class Carousel. + * + * @author Franck Allimant + */ +class Carousel extends BaseModule +{ + public const DOMAIN_NAME = 'carousel'; + + /** + * @return bool true to continue module activation, false to prevent it + */ + public function preActivation(ConnectionInterface $con = null) + { + if (!self::getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, [__DIR__.'/Config/TheliaMain.sql']); + + self::setConfigValue('is_initialized', true); + } + + return true; + } + + public function destroy(ConnectionInterface $con = null, $deleteModuleData = false): void + { + $database = new Database($con); + + $database->insertSql(null, [__DIR__.'/Config/sql/destroy.sql']); + } + + public function getUploadDir() + { + $uploadDir = ConfigQuery::read('images_library_path'); + + if ($uploadDir === null) { + $uploadDir = THELIA_LOCAL_DIR.'media'.DS.'images'; + } else { + $uploadDir = THELIA_ROOT.$uploadDir; + } + + return $uploadDir.DS.self::DOMAIN_NAME; + } + + /** + * @param string $currentVersion + * @param string $newVersion + * + * @author Thomas Arnaud + */ + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + $uploadDir = $this->getUploadDir(); + $fileSystem = new Filesystem(); + + if (!$fileSystem->exists($uploadDir) && $fileSystem->exists(__DIR__.DS.'media'.DS.'carousel')) { + $finder = new Finder(); + $finder->files()->in(__DIR__.DS.'media'.DS.'carousel'); + + $fileSystem->mkdir($uploadDir); + + /** @var SplFileInfo $file */ + foreach ($finder as $file) { + copy($file, $uploadDir.DS.$file->getRelativePathname()); + } + $fileSystem->remove(__DIR__.DS.'media'); + } + + $finder = (new Finder())->files()->name('#.*?\.sql#')->sortByName()->in(__DIR__.DS.'Config'.DS.'update'); + + if (0 === $finder->count()) { + return; + } + + $database = new Database($con); + + // apply update only if table exists + if ($database->execute("SHOW TABLES LIKE 'carousel'")->rowCount() === 0) { + return; + } + + /** @var SplFileInfo $updateSQLFile */ + foreach ($finder as $updateSQLFile) { + if (version_compare($currentVersion, str_replace('.sql', '', $updateSQLFile->getFilename()), '<')) { + $database->insertSql(null, [$updateSQLFile->getPathname()]); + } + } + } + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/Carousel/Config/TheliaMain.sql b/domokits/local/modules/Carousel/Config/TheliaMain.sql new file mode 100644 index 0000000..f39cc5a --- /dev/null +++ b/domokits/local/modules/Carousel/Config/TheliaMain.sql @@ -0,0 +1,51 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- carousel +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `carousel`; + +CREATE TABLE `carousel` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `file` VARCHAR(255), + `position` INTEGER, + `disable` INTEGER, + `group` VARCHAR(255), + `url` VARCHAR(255), + `limited` INTEGER, + `start_date` DATETIME, + `end_date` DATETIME, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- carousel_i18n +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `carousel_i18n`; + +CREATE TABLE `carousel_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `alt` VARCHAR(255), + `title` VARCHAR(255), + `description` LONGTEXT, + `chapo` TEXT, + `postscriptum` TEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `carousel_i18n_fk_2ec1b2` + FOREIGN KEY (`id`) + REFERENCES `carousel` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/Carousel/Config/config.xml b/domokits/local/modules/Carousel/Config/config.xml new file mode 100644 index 0000000..443a64e --- /dev/null +++ b/domokits/local/modules/Carousel/Config/config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/domokits/local/modules/Carousel/Config/module.xml b/domokits/local/modules/Carousel/Config/module.xml new file mode 100644 index 0000000..c9eeff7 --- /dev/null +++ b/domokits/local/modules/Carousel/Config/module.xml @@ -0,0 +1,24 @@ + + + Carousel\Carousel + + An image carousel + + + Un carrousel d'images + + + en_US + fr_FR + + 2.5.4 + + Manuel Raynaud, Franck Allimant + manu@raynaud.io, franck@cqfdev.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/Carousel/Config/routing.xml b/domokits/local/modules/Carousel/Config/routing.xml new file mode 100644 index 0000000..44e9590 --- /dev/null +++ b/domokits/local/modules/Carousel/Config/routing.xml @@ -0,0 +1,42 @@ + + + + + + + Carousel\Controller\ConfigurationController::uploadImage + + + + Carousel\Controller\ConfigurationController::updateAction + + + + Carousel\Controller\ConfigurationController::deleteAction + + + diff --git a/domokits/local/modules/Carousel/Config/schema.xml b/domokits/local/modules/Carousel/Config/schema.xml new file mode 100644 index 0000000..9764d90 --- /dev/null +++ b/domokits/local/modules/Carousel/Config/schema.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/Carousel/Config/sql/destroy.sql b/domokits/local/modules/Carousel/Config/sql/destroy.sql new file mode 100644 index 0000000..e611606 --- /dev/null +++ b/domokits/local/modules/Carousel/Config/sql/destroy.sql @@ -0,0 +1,6 @@ +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `carousel`; +DROP TABLE IF EXISTS `carousel_i18n`; + +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/domokits/local/modules/Carousel/Config/sqldb.map b/domokits/local/modules/Carousel/Config/sqldb.map new file mode 100644 index 0000000..a58fd16 --- /dev/null +++ b/domokits/local/modules/Carousel/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +TheliaMain.sql=TheliaMain diff --git a/domokits/local/modules/Carousel/Config/update/2.4.0.sql b/domokits/local/modules/Carousel/Config/update/2.4.0.sql new file mode 100644 index 0000000..8f95a9a --- /dev/null +++ b/domokits/local/modules/Carousel/Config/update/2.4.0.sql @@ -0,0 +1 @@ +ALTER TABLE `carousel` ADD (`disable` INTEGER, `group` VARCHAR(255),`limited` INTEGER, `start_date` DATETIME, `end_date` DATETIME); diff --git a/domokits/local/modules/Carousel/Controller/ConfigurationController.php b/domokits/local/modules/Carousel/Controller/ConfigurationController.php new file mode 100644 index 0000000..a79d460 --- /dev/null +++ b/domokits/local/modules/Carousel/Controller/ConfigurationController.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Controller; + +use Carousel\Form\CarouselImageForm; +use Carousel\Form\CarouselUpdateForm; +use Carousel\Model\Carousel; +use Carousel\Model\CarouselQuery; +use Symfony\Component\Form\Form; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Event\File\FileCreateOrUpdateEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Form\TheliaFormFactory; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Model\Lang; +use Thelia\Model\LangQuery; +use Thelia\Tools\URL; + +/** + * Class ConfigurationController. + * + * @author manuel raynaud + */ +class ConfigurationController extends BaseAdminController +{ + public function uploadImage( + Request $request, + TheliaFormFactory $formFactory, + EventDispatcherInterface $eventDispatcher + ) { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::CREATE)) { + return $response; + } + + $form = $formFactory->createForm(CarouselImageForm::class); + $error_message = null; + try { + $formData = $this->validateForm($form)->getData(); + + /** @var UploadedFile $fileBeingUploaded */ + $fileBeingUploaded = $formData['file']; + + $fileModel = new Carousel(); + + $fileCreateOrUpdateEvent = new FileCreateOrUpdateEvent(1); + $fileCreateOrUpdateEvent->setModel($fileModel); + $fileCreateOrUpdateEvent->setUploadedFile($fileBeingUploaded); + + $eventDispatcher->dispatch( + $fileCreateOrUpdateEvent, + TheliaEvents::IMAGE_SAVE + ); + + // Compensate issue #1005 + $langs = LangQuery::create()->find(); + + /** @var Lang $lang */ + foreach ($langs as $lang) { + $fileCreateOrUpdateEvent->getModel()->setLocale($lang->getLocale())->setTitle('')->save(); + } + + $response = $this->redirectToConfigurationPage(); + } catch (FormValidationException $e) { + $error_message = $this->createStandardFormValidationErrorMessage($e); + } + + if (null !== $error_message) { + $this->setupFormErrorContext( + 'carousel upload', + $error_message, + $form + ); + + $response = $this->render( + 'module-configure', + [ + 'module_code' => 'Carousel', + ] + ); + } + + return $response; + } + + /** + * @param Form $form + * @param string $fieldName + * @param int $id + * + * @return string + */ + protected function getFormFieldValue($form, $fieldName, $id) + { + $value = $form->get(sprintf('%s%d', $fieldName, $id))->getData(); + + return $value; + } + + public function updateAction( + TheliaFormFactory $formFactory + ) { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::UPDATE)) { + return $response; + } + + $form = $formFactory->createForm(CarouselUpdateForm::class); + + $error_message = null; + + try { + $updateForm = $this->validateForm($form); + + $carousels = CarouselQuery::create()->findAllByPosition(); + + $locale = $this->getCurrentEditionLocale(); + + /** @var Carousel $carousel */ + foreach ($carousels as $carousel) { + $id = $carousel->getId(); + + $carousel + ->setPosition($this->getFormFieldValue($updateForm, 'position', $id)) + ->setDisable($this->getFormFieldValue($updateForm, 'disable', $id)) + ->setUrl($this->getFormFieldValue($updateForm, 'url', $id)) + ->setLocale($locale) + ->setTitle($this->getFormFieldValue($updateForm, 'title', $id)) + ->setAlt($this->getFormFieldValue($updateForm, 'alt', $id)) + ->setChapo($this->getFormFieldValue($updateForm, 'chapo', $id)) + ->setDescription($this->getFormFieldValue($updateForm, 'description', $id)) + ->setPostscriptum($this->getFormFieldValue($updateForm, 'postscriptum', $id)) + ->setGroup($this->getFormFieldValue($updateForm, 'group', $id)) + ->setLimited($this->getFormFieldValue($updateForm, 'limited', $id)) + ->setStartDate($this->getFormFieldValue($updateForm, 'start_date', $id)) + ->setEndDate($this->getFormFieldValue($updateForm, 'end_date', $id)) + ->save(); + } + + $response = $this->redirectToConfigurationPage(); + } catch (FormValidationException $e) { + $error_message = $this->createStandardFormValidationErrorMessage($e); + } + + if (null !== $error_message) { + $this->setupFormErrorContext( + 'carousel upload', + $error_message, + $form + ); + + $response = $this->render('module-configure', ['module_code' => 'Carousel']); + } + + return $response; + } + + public function deleteAction( + Request $request + ) { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, ['carousel'], AccessManager::DELETE)) { + return $response; + } + + $imageId = $request->get('image_id'); + + if ($imageId != '') { + $carousel = CarouselQuery::create()->findPk($imageId); + + if (null !== $carousel) { + $carousel->delete(); + } + } + + return $this->redirectToConfigurationPage(); + } + + protected function redirectToConfigurationPage() + { + return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/Carousel')); + } +} diff --git a/domokits/local/modules/Carousel/Form/CarouselImageForm.php b/domokits/local/modules/Carousel/Form/CarouselImageForm.php new file mode 100644 index 0000000..02653ee --- /dev/null +++ b/domokits/local/modules/Carousel/Form/CarouselImageForm.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Form; + +use Carousel\Carousel; +use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Validator\Constraints\Image; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +/** + * Class CarouselImageForm. + * + * @author manuel raynaud + */ +class CarouselImageForm extends BaseForm +{ + protected function buildForm(): void + { + $translator = Translator::getInstance(); + $this->formBuilder + ->add( + 'file', + FileType::class, + [ + 'constraints' => [ + new Image(), + ], + 'label' => $translator->trans('Carousel image', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'file', + ], + ] + ); + } +} diff --git a/domokits/local/modules/Carousel/Form/CarouselUpdateForm.php b/domokits/local/modules/Carousel/Form/CarouselUpdateForm.php new file mode 100644 index 0000000..f9e0497 --- /dev/null +++ b/domokits/local/modules/Carousel/Form/CarouselUpdateForm.php @@ -0,0 +1,222 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Form; + +use Carousel\Carousel; +use Carousel\Model\CarouselQuery; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\UrlType; +use Thelia\Form\BaseForm; + +/** + * Class CarouselUpdateForm. + * + * @author manuel raynaud + */ +class CarouselUpdateForm extends BaseForm +{ + protected function buildForm(): void + { + $formBuilder = $this->formBuilder; + + $carousels = CarouselQuery::create()->orderByPosition()->find(); + + /** @var \Carousel\Model\Carousel $carousel */ + foreach ($carousels as $carousel) { + $id = $carousel->getId(); + + $formBuilder->add( + 'position'.$id, + TextType::class, + [ + 'label' => $this->translator->trans('Image position in carousel', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'position'.$id, + ], + 'required' => false, + 'attr' => [ + 'placeholder' => $this->translator->trans( + 'Image position in carousel', + [], + Carousel::DOMAIN_NAME + ), + ], + ] + )->add( + 'alt'.$id, + TextType::class, + [ + 'label' => $this->translator->trans('Alternative image text', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'alt'.$id, + ], + 'required' => false, + 'attr' => [ + 'placeholder' => $this->translator->trans( + 'Displayed when image is not visible', + [], + Carousel::DOMAIN_NAME + ), + ], + ] + )->add( + 'group'.$id, + TextType::class, + [ + 'label' => $this->translator->trans('Group image', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'group'.$id, + ], + 'required' => false, + 'attr' => [ + 'placeholder' => $this->translator->trans( + 'Group of images', + [], + Carousel::DOMAIN_NAME + ), + ], + ] + )->add( + 'url'.$id, + UrlType::class, + [ + 'label' => $this->translator->trans('Image URL', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'url'.$id, + ], + 'required' => false, + 'attr' => [ + 'placeholder' => $this->translator->trans( + 'Please enter a valid URL', + [], + Carousel::DOMAIN_NAME + ), + ], + ] + )->add( + 'title'.$id, + TextType::class, + [ + 'constraints' => [], + 'required' => false, + 'label' => $this->translator->trans('Title', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'title_field'.$id, + ], + 'attr' => [ + 'placeholder' => $this->translator->trans('A descriptive title', [], Carousel::DOMAIN_NAME), + ], + ] + )->add( + 'chapo'.$id, + TextareaType::class, + [ + 'constraints' => [], + 'required' => false, + 'label' => $this->translator->trans('Summary', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'summary_field'.$id, + 'help' => $this->translator->trans( + 'A short description, used when a summary or an introduction is required', + [], + Carousel::DOMAIN_NAME + ), + ], + 'attr' => [ + 'rows' => 3, + 'placeholder' => $this->translator->trans('Short description text', [], Carousel::DOMAIN_NAME), + ], + ] + )->add( + 'description'.$id, + TextareaType::class, + [ + 'constraints' => [], + 'required' => false, + 'label' => $this->translator->trans('Detailed description', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'detailed_description_field'.$id, + 'help' => $this->translator->trans('The detailed description.', [], Carousel::DOMAIN_NAME), + ], + 'attr' => [ + 'rows' => 5, + ], + ] + )->add( + 'disable'.$id, + CheckboxType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Disable image', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'enable'.$id, + ], + ] + )->add( + 'limited'.$id, + CheckboxType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Limited', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'limited'.$id, + ], + ] + )->add( + 'start_date'.$id, + DateTimeType::class, + [ + 'label' => $this->translator->trans('Start date', [], Carousel::DOMAIN_NAME), + 'widget' => 'single_text', + 'required' => false, + ] + )->add( + 'end_date'.$id, + DateTimeType::class, + [ + 'label' => $this->translator->trans('End date', [], Carousel::DOMAIN_NAME), + 'widget' => 'single_text', + 'required' => false, + ] + )->add( + 'postscriptum'.$id, + TextareaType::class, + [ + 'constraints' => [], + 'required' => false, + 'label' => $this->translator->trans('Conclusion', [], Carousel::DOMAIN_NAME), + 'label_attr' => [ + 'for' => 'conclusion_field'.$id, + 'help' => $this->translator->trans( + 'A short text, used when an additional or supplemental information is required.', + [], + Carousel::DOMAIN_NAME + ), + ], + 'attr' => [ + 'placeholder' => $this->translator->trans('Short additional text', [], Carousel::DOMAIN_NAME), + 'rows' => 3, + ], + ] + ); + } + } + + public static function getName() + { + return 'carousel_update'; + } +} diff --git a/domokits/local/modules/Carousel/Hook/BackHook.php b/domokits/local/modules/Carousel/Hook/BackHook.php new file mode 100644 index 0000000..25c4a35 --- /dev/null +++ b/domokits/local/modules/Carousel/Hook/BackHook.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Hook; + +use Carousel\Carousel; +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Tools\URL; + +/** + * Class BackHook. + * + * @author Emmanuel Nurit + */ +class BackHook extends BaseHook +{ + /** + * Add a new entry in the admin tools menu. + * + * should add to event a fragment with fields : id,class,url,title + */ + public function onMainTopMenuTools(HookRenderBlockEvent $event): void + { + $event->add( + [ + 'id' => 'tools_menu_carousel', + 'class' => '', + 'url' => URL::getInstance()->absoluteUrl('/admin/module/Carousel'), + 'title' => $this->trans('Edit your carousel', [], Carousel::DOMAIN_NAME), + ] + ); + } +} diff --git a/domokits/local/modules/Carousel/I18n/backOffice/default/de_DE.php b/domokits/local/modules/Carousel/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..00b6c40 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/backOffice/default/de_DE.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Add an image to the carousel' => 'Ein Bild zu Karussell hinzufügen', + 'Add this image to the carousel' => 'Dieses Bild zu Karussell hinzufügen', + 'Carousel image' => 'Karussell-Bild', + 'Carousel images' => 'Karussell-Bilder', + 'Delete a carousel image' => 'Ein Karussell-Bild löschen', + 'Do you really want to remove this image from the carousel ?' => 'Wollen Sie dieses Bild wirklich aus dem Karussell entfernen?', + 'Edit your carousel.' => 'Karussell bearbeiten.', + 'Remove this image' => 'Dieses Bild entfernen', + 'Your carousel contains no image. Please add one using the form above.' => 'Das Karussell enthält kein Bild. Bitte fügen Sie mit dem Formular oben eines hinzu.', + 'Position' => 'Position', +]; diff --git a/domokits/local/modules/Carousel/I18n/backOffice/default/en_US.php b/domokits/local/modules/Carousel/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..a55382d --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/backOffice/default/en_US.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Add an image to the carousel' => 'Add an image to the carousel', + 'Add this image to the carousel' => 'Add this image to the carousel', + 'Carousel image' => 'Carousel image', + 'Carousel images' => 'Carousel images', + 'Delete a carousel image' => 'Delete a carousel image', + 'Do you really want to remove this image from the carousel ?' => 'Do you really want to remove this image from the carousel ?', + 'Edit your carousel.' => 'Edit your carousel.', + 'Remove this image' => 'Remove this image', + 'Your carousel contains no image. Please add one using the form above.' => 'Your carousel contains no image. Please add one using the form above.', + 'Position' => 'Position', + 'YYYY-MM-DD' => 'YYYY-MM-DD', +]; diff --git a/domokits/local/modules/Carousel/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/Carousel/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..0b43a67 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Add an image to the carousel' => 'Ajouter une image au carrousel', + 'Add this image to the carousel' => 'Ajouter l\'image au carrousel', + 'Carousel image' => 'Image du carrousel', + 'Carousel images' => 'Images du carrousel', + 'Delete a carousel image' => 'Supprimer une image du carrousel', + 'Do you really want to remove this image from the carousel ?' => 'Voulez-vous vraiment retirer cette image du carrousel ?', + 'Edit your carousel.' => 'Modifier votre carrousel', + 'Remove this image' => 'Supprimer cette image', + 'Your carousel contains no image. Please add one using the form above.' => 'Votre carrousel ne contient aucune image. Ajoutez votre première image avec le formulaire ci-dessus', + 'Position' => 'Position', + 'YYYY-MM-DD' => 'AAAA-MM-JJ', +]; diff --git a/domokits/local/modules/Carousel/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/Carousel/I18n/backOffice/default/ru_RU.php new file mode 100644 index 0000000..5a162b4 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Add an image to the carousel' => 'Добавить изображение в карусель', + 'Add this image to the carousel' => 'Добавить это изображение в карусель', + 'Carousel image' => 'Изображение карусели', + 'Carousel images' => 'Изображения карусели', + 'Delete a carousel image' => 'Удалить изображение карусели', + 'Do you really want to remove this image from the carousel ?' => 'Вы действительно хотите удалить это изображение из карусели ?', + 'Edit your carousel.' => 'Редактировать вашу карусель.', + 'Remove this image' => 'Удалить это изображение', + 'Your carousel contains no image. Please add one using the form above.' => 'Ваша карусель не содержит изображений. Пожалуйста, добавьте одно используя форму ниже.', + 'Position' => 'Позиция', +]; diff --git a/domokits/local/modules/Carousel/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/Carousel/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..252bcb1 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Add an image to the carousel' => 'Slayt için bir resim ekle', + 'Add this image to the carousel' => 'slayt için bu resim ekleme', + 'Carousel image' => 'slayt görüntü', + 'Carousel images' => 'slayt görüntüleri', + 'Delete a carousel image' => 'Bir slayt resmi silme', + 'Do you really want to remove this image from the carousel ?' => 'Bu görüntüyü slayttan kaldırmak istiyor musunuz?', + 'Edit your carousel.' => 'slayt düzenleyin.', + 'Remove this image' => 'Bu resmi kaldırma', + 'Your carousel contains no image. Please add one using the form above.' => 'Senin slayt hiçbir görüntü içermiyor . Lütfen yukarıdaki formu kullanarak ekleyin.', + 'Position' => 'Pozisyon', +]; diff --git a/domokits/local/modules/Carousel/I18n/de_DE.php b/domokits/local/modules/Carousel/I18n/de_DE.php new file mode 100644 index 0000000..fa52b4b --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/de_DE.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'Beschreibungstitel', + 'A short description, used when a summary or an introduction is required' => 'Eine kurze beschreibung, benutzt wenn eine Zusammenfassung order eine Einleitung ist nötig', + 'A short text, used when an additional or supplemental information is required.' => 'Ein kurzer Text, der verwendet wird, wenn eine zusätzliche oder ergänzende Information erforderlich ist.', + 'Alternative image text' => 'Alternativer Bildtext', + 'Carousel image' => 'Karussell-Bild', + 'Conclusion' => 'Abschluss', + 'Detailed description' => 'Detaillierte Beschreibung', + 'Displayed when image is not visible' => 'Angezeigt, wenn das Bild nicht sichtbar ist', + 'Image URL' => 'Bild-URL', + 'Image position in carousel' => 'Position des Bildes im Karussell', + 'Please enter a valid URL' => 'Bitte geben Sie eine gültige URL ein', + 'Short additional text' => 'Kurzer zusätzlicher Text', + 'Short description text' => 'Kurzes Beschreibungstext', + 'Summary' => 'Zusammenfassung', + 'The detailed description.' => 'Die detaillierte Beschreibung.', + 'Title' => 'Titel', +]; diff --git a/domokits/local/modules/Carousel/I18n/en_US.php b/domokits/local/modules/Carousel/I18n/en_US.php new file mode 100644 index 0000000..f3423c2 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/en_US.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'A descriptive title', + 'A short description, used when a summary or an introduction is required' => 'A short description, used when a summary or an introduction is required', + 'A short text, used when an additional or supplemental information is required.' => 'A short text, used when an additional or supplemental information is required.', + 'Alternative image text' => 'Alternative image text', + 'Carousel image' => 'Carousel image', + 'Conclusion' => 'Conclusion', + 'Detailed description' => 'Detailed description', + 'Displayed when image is not visible' => 'Displayed when image is not visible', + 'Edit your carousel' => 'Edit your carousel', + 'Image URL' => 'Image URL', + 'Image position in carousel' => 'Image position in carousel', + 'Please enter a valid URL' => 'Please enter a valid URL', + 'Short additional text' => 'Short additional text', + 'Short description text' => 'Short description text', + 'Summary' => 'Summary', + 'The detailed description.' => 'The detailed description.', + 'Title' => 'Title', + 'YYYY-MM-DD' => 'AAAA-MM-JJ', +]; diff --git a/domokits/local/modules/Carousel/I18n/fr_FR.php b/domokits/local/modules/Carousel/I18n/fr_FR.php new file mode 100644 index 0000000..2822cff --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/fr_FR.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'Un titre descriptif', + 'A short description, used when a summary or an introduction is required' => 'Une courte description, utilisée lorsqu\'un résumé ou une introduction est requise', + 'A short text, used when an additional or supplemental information is required.' => 'Un texte court, utilisé quand une conclusion ou une information complémentaire est nécessaire.', + 'Alternative image text' => 'Texte alternatif de l\'image', + 'Carousel image' => 'Image du carrousel', + 'Conclusion' => 'Conclusion', + 'Detailed description' => 'Description détaillée', + 'Disable image' => 'Désactiver l\'image', + 'Displayed when image is not visible' => 'Affiché lorsque l\'image n\'est pas visible', + 'Edit your carousel' => 'Modifier votre carousel', + 'End date' => 'Date de fin', + 'Group image' => 'Groupe de l\'image', + 'Group of images' => 'Nom du groupe auquel l\'image appartient', + 'Image URL' => 'URL de l\'image', + 'Image position in carousel' => 'Position de l\'image dans le carrousel', + 'Limited' => 'Afficher l\'image entre les dates ci-dessous', + 'Please enter a valid URL' => 'Merci d\'indiquer une URL valide', + 'Short additional text' => 'Un court texte supplémentaire', + 'Short description text' => 'Un court texte de description', + 'Start date' => 'Date de début', + 'Summary' => 'Résumé', + 'The detailed description.' => 'La description détaillée.', + 'Title' => 'Titre', +]; diff --git a/domokits/local/modules/Carousel/I18n/it_IT.php b/domokits/local/modules/Carousel/I18n/it_IT.php new file mode 100644 index 0000000..62eb7e7 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/it_IT.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'Un titolo descrittivo', + 'A short description, used when a summary or an introduction is required' => 'Una breve descrizione, utilizzata quando è necessario un sommario o un\'introduzione', + 'Conclusion' => 'Conclusione', + 'Detailed description' => 'Descrizione dettagliata', + 'Summary' => 'Riassunto', + 'The detailed description.' => 'La descrizione dettagliata.', + 'Title' => 'Titolo', +]; diff --git a/domokits/local/modules/Carousel/I18n/ru_RU.php b/domokits/local/modules/Carousel/I18n/ru_RU.php new file mode 100644 index 0000000..bcd1443 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/ru_RU.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'Описательный заголовок', + 'A short description, used when a summary or an introduction is required' => 'Краткое описание, используется когда необходимо', + 'A short text, used when an additional or supplemental information is required.' => 'Краткий текст используемый, когда необходима дополнительной информации.', + 'Alternative image text' => 'Альтернативный текст изображения', + 'Carousel image' => 'Изображение карусели', + 'Conclusion' => 'Заключение', + 'Detailed description' => 'Детальное описание', + 'Displayed when image is not visible' => 'Отображается когда изображения не видно', + 'Edit your carousel' => 'Редактировать вашу карусель', + 'Image URL' => 'URL изображения', + 'Image position in carousel' => 'Позиция изображения в карусели', + 'Please enter a valid URL' => 'Пожалуйста введите корректный URL', + 'Short additional text' => 'Краткий дополнительный текст', + 'Short description text' => 'Текст краткого описания', + 'Summary' => 'Краткое описание', + 'The detailed description.' => 'Детальное описание', + 'Title' => 'Заголовок', +]; diff --git a/domokits/local/modules/Carousel/I18n/tr_TR.php b/domokits/local/modules/Carousel/I18n/tr_TR.php new file mode 100644 index 0000000..0d33e73 --- /dev/null +++ b/domokits/local/modules/Carousel/I18n/tr_TR.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'A descriptive title' => 'Açıklayıcı bir başlık', + 'A short description, used when a summary or an introduction is required' => 'Bir Özeti veya giriş gerekli olduğunda kullanılan kısa bir açıklama', + 'A short text, used when an additional or supplemental information is required.' => 'Bir ek ya da tamamlayıcı bilgi gerekli olduğunda kullanılan kısa bir metin.', + 'Alternative image text' => 'Alternatif resim metini', + 'Carousel image' => 'slayt görüntü', + 'Conclusion' => 'Sonuç', + 'Detailed description' => 'Detaylı açıklama', + 'Displayed when image is not visible' => 'resim görünür olmadığında görüntülenen', + 'Image URL' => 'Resim Bağlantı [Link]', + 'Image position in carousel' => 'slayt bulunduğu resim', + 'Please enter a valid URL' => 'Lütfen geçerli bir URL girin', + 'Short additional text' => 'Kısa ek metin', + 'Short description text' => 'Kısa açıklama metni', + 'Summary' => 'Özet', + 'The detailed description.' => 'Ayrıntılı açıklama.', + 'Title' => 'Başlık', +]; diff --git a/domokits/local/modules/Carousel/Loop/Carousel.php b/domokits/local/modules/Carousel/Loop/Carousel.php new file mode 100644 index 0000000..5c995ec --- /dev/null +++ b/domokits/local/modules/Carousel/Loop/Carousel.php @@ -0,0 +1,237 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Loop; + +use Carousel\Model\CarouselQuery; +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Core\Event\Image\ImageEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Core\Template\Element\LoopResultRow; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Core\Template\Loop\Image; +use Thelia\Log\Tlog; +use Thelia\Type\EnumListType; +use Thelia\Type\EnumType; +use Thelia\Type\TypeCollection; + +/** + * Class CarouselLoop. + * + * @author manuel raynaud + */ +class Carousel extends Image +{ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('width'), + Argument::createIntTypeArgument('height'), + Argument::createIntTypeArgument('rotation', 0), + Argument::createAnyTypeArgument('background_color'), + Argument::createIntTypeArgument('quality'), + new Argument( + 'resize_mode', + new TypeCollection( + new EnumType(['crop', 'borders', 'none']) + ), + 'none' + ), + new Argument( + 'order', + new TypeCollection( + new EnumListType(['alpha', 'alpha-reverse', 'manual', 'manual-reverse', 'random']) + ), + 'manual' + ), + Argument::createAnyTypeArgument('effects'), + Argument::createBooleanTypeArgument('allow_zoom', false), + Argument::createBooleanTypeArgument('filter_disable_slides', true), + Argument::createAlphaNumStringTypeArgument('group'), + Argument::createAlphaNumStringTypeArgument('format') + ); + } + + /** + * @throws \Propel\Runtime\Exception\PropelException + * + * @return LoopResult + */ + public function parseResults(LoopResult $loopResult) + { + /** @var \Carousel\Model\Carousel $carousel */ + foreach ($loopResult->getResultDataCollection() as $carousel) { + $imgSourcePath = $carousel->getUploadDir().DS.$carousel->getFile(); + if (!file_exists($imgSourcePath)) { + Tlog::getInstance()->error(sprintf('Carousel source image file %s does not exists.', $imgSourcePath)); + continue; + } + + $startDate = $carousel->getStartDate(); + $endDate = $carousel->getEndDate(); + + if ($carousel->getLimited()) { + $now = new \DateTime(); + if ($carousel->getDisable()) { + if ($now > $startDate && $now < $endDate) { + $carousel + ->setDisable(0) + ->save(); + } + } else { + if ($now < $startDate || $now > $endDate) { + $carousel + ->setDisable(1) + ->save(); + } + } + } + + if ($this->getFilterDisableSlides() && $carousel->getDisable()) { + continue; + } + + $loopResultRow = new LoopResultRow($carousel); + + $event = new ImageEvent(); + $event->setSourceFilepath($imgSourcePath) + ->setCacheSubdirectory('carousel'); + + switch ($this->getResizeMode()) { + case 'crop': + $resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_CROP; + break; + case 'borders': + $resize_mode = \Thelia\Action\Image::EXACT_RATIO_WITH_BORDERS; + break; + case 'none': + default: + $resize_mode = \Thelia\Action\Image::KEEP_IMAGE_RATIO; + } + + // Prepare tranformations + $width = $this->getWidth(); + $height = $this->getHeight(); + $rotation = $this->getRotation(); + $background_color = $this->getBackgroundColor(); + $quality = $this->getQuality(); + $effects = $this->getEffects(); + $format = $this->getFormat(); + + if (null !== $width) { + $event->setWidth($width); + } + if (null !== $height) { + $event->setHeight($height); + } + $event->setResizeMode($resize_mode); + if (null !== $rotation) { + $event->setRotation($rotation); + } + if (null !== $background_color) { + $event->setBackgroundColor($background_color); + } + if (null !== $quality) { + $event->setQuality($quality); + } + if (null !== $effects) { + $event->setEffects($effects); + } + if (null !== $format) { + $event->setFormat($format); + } + + $event->setAllowZoom($this->getAllowZoom()); + + // Dispatch image processing event + $this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS); + + if ($startDate) { + $startDate = $startDate->format('Y-m-d').'T'.$startDate->format('H:i'); + } + if ($endDate) { + $endDate = $endDate->format('Y-m-d').'T'.$endDate->format('H:i'); + } + + $loopResultRow + ->set('ID', $carousel->getId()) + ->set('LOCALE', $this->locale) + ->set('IMAGE_URL', $event->getFileUrl()) + ->set('ORIGINAL_IMAGE_URL', $event->getOriginalFileUrl()) + ->set('IMAGE_PATH', $event->getCacheFilepath()) + ->set('ORIGINAL_IMAGE_PATH', $event->getSourceFilepath()) + ->set('TITLE', $carousel->getVirtualColumn('i18n_TITLE')) + ->set('CHAPO', $carousel->getVirtualColumn('i18n_CHAPO')) + ->set('DESCRIPTION', $carousel->getVirtualColumn('i18n_DESCRIPTION')) + ->set('POSTSCRIPTUM', $carousel->getVirtualColumn('i18n_POSTSCRIPTUM')) + ->set('ALT', $carousel->getVirtualColumn('i18n_ALT')) + ->set('URL', $carousel->getUrl()) + ->set('POSITION', $carousel->getPosition()) + ->set('DISABLE', $carousel->getDisable()) + ->set('GROUP', $carousel->getGroup()) + ->set('LIMITED', $carousel->getLimited()) + ->set('START_DATE', $startDate) + ->set('END_DATE', $endDate) + ; + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } + + /** + * this method returns a Propel ModelCriteria. + * + * @return \Propel\Runtime\ActiveQuery\ModelCriteria + */ + public function buildModelCriteria() + { + $search = CarouselQuery::create(); + $group = $this->getGroup(); + + $this->configureI18nProcessing($search, ['ALT', 'TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM']); + + $orders = $this->getOrder(); + + // Results ordering + foreach ($orders as $order) { + switch ($order) { + case 'alpha': + $search->addAscendingOrderByColumn('i18n_TITLE'); + break; + case 'alpha-reverse': + $search->addDescendingOrderByColumn('i18n_TITLE'); + break; + case 'manual-reverse': + $search->orderByPosition(Criteria::DESC); + break; + case 'manual': + $search->orderByPosition(Criteria::ASC); + break; + case 'random': + $search->clearOrderByColumns(); + $search->addAscendingOrderByColumn('RAND()'); + break 2; + break; + } + } + + if ($group) { + $search->filterByGroup($group); + } + + return $search; + } +} diff --git a/domokits/local/modules/Carousel/Model/Carousel.php b/domokits/local/modules/Carousel/Model/Carousel.php new file mode 100644 index 0000000..f81a496 --- /dev/null +++ b/domokits/local/modules/Carousel/Model/Carousel.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Model; + +use Carousel\Model\Base\Carousel as BaseCarousel; +use Propel\Runtime\ActiveQuery\ModelCriteria; +use Propel\Runtime\Connection\ConnectionInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; +use Thelia\Files\FileModelInterface; +use Thelia\Files\FileModelParentInterface; +use Thelia\Form\BaseForm; + +class Carousel extends BaseCarousel implements FileModelInterface +{ + public function preDelete(ConnectionInterface $con = null) + { + $carousel = new \Carousel\Carousel(); + + $fs = new Filesystem(); + + try { + $fs->remove($carousel->getUploadDir().DS.$this->getFile()); + + return true; + } catch (IOException $e) { + return false; + } + } + + /** + * Set file parent id. + * + * @param int $parentId parent id + * + * @return $this + */ + public function setParentId($parentId) + { + return $this; + } + + /** + * Get file parent id. + * + * @return int parent id + */ + public function getParentId() + { + return $this->getId(); + } + + /** + * @return FileModelParentInterface the parent file model + */ + public function getParentFileModel() + { + return new static(); + } + + /** + * Get the ID of the form used to change this object information. + * + * @return BaseForm the form + */ + public function getUpdateFormId() + { + return 'carousel.image'; + } + + /** + * @return string the path to the upload directory where files are stored, without final slash + */ + public function getUploadDir() + { + $carousel = new \Carousel\Carousel(); + + return $carousel->getUploadDir(); + } + + /** + * @return string the URL to redirect to after update from the back-office + */ + public function getRedirectionUrl() + { + return '/admin/module/Carousel'; + } + + /** + * Get the Query instance for this object. + * + * @return ModelCriteria + */ + public function getQueryInstance() + { + return CarouselQuery::create(); + } + + /** + * @param bool $visible true if the file is visible, false otherwise + * + * @return FileModelInterface + */ + public function setVisible($visible) + { + // Not implemented + + return $this; + } +} diff --git a/domokits/local/modules/Carousel/Model/CarouselI18n.php b/domokits/local/modules/Carousel/Model/CarouselI18n.php new file mode 100644 index 0000000..dd31b3e --- /dev/null +++ b/domokits/local/modules/Carousel/Model/CarouselI18n.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Model; + +use Carousel\Model\Base\CarouselI18n as BaseCarouselI18n; + +class CarouselI18n extends BaseCarouselI18n +{ +} diff --git a/domokits/local/modules/Carousel/Model/CarouselI18nQuery.php b/domokits/local/modules/Carousel/Model/CarouselI18nQuery.php new file mode 100644 index 0000000..b54b2d4 --- /dev/null +++ b/domokits/local/modules/Carousel/Model/CarouselI18nQuery.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Model; + +use Carousel\Model\Base\CarouselI18nQuery as BaseCarouselI18nQuery; + +/** + * Skeleton subclass for performing query and update operations on the 'carousel_i18n' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class CarouselI18nQuery extends BaseCarouselI18nQuery +{ +} // CarouselI18nQuery diff --git a/domokits/local/modules/Carousel/Model/CarouselQuery.php b/domokits/local/modules/Carousel/Model/CarouselQuery.php new file mode 100644 index 0000000..6a478a8 --- /dev/null +++ b/domokits/local/modules/Carousel/Model/CarouselQuery.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Carousel\Model; + +use Carousel\Model\Base\CarouselQuery as BaseCarouselQuery; + +/** + * Skeleton subclass for performing query and update operations on the 'carousel' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class CarouselQuery extends BaseCarouselQuery +{ + public function findAllByPosition() + { + return $this->orderByPosition() + ->find(); + } +} // CarouselQuery diff --git a/domokits/local/modules/Carousel/Readme.md b/domokits/local/modules/Carousel/Readme.md new file mode 100644 index 0000000..830f32d --- /dev/null +++ b/domokits/local/modules/Carousel/Readme.md @@ -0,0 +1,69 @@ +# Carousel + +This module for Thelia add a customizable carousel on your home page. You can upload you own image and overload the default template in your template for using the carousel. + +## Installation + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is Carousel. +* Activate it in your thelia administration panel + +## Usage + +In the configuration panel of this module, you can upload as many images as you want. + +## Hook + +The carousel is installed in the "Home page - main area" (home.body) hook. + +## Loop + +Customize images with the `carousel` loop, which has the same arguments as the `image` loop. You can define a width, a height, and many other parameters + +### Input arguments + +|Argument |Description | +|--- |--- | +|**width** | A width in pixels, for resizing image. If only the width is provided, the image ratio is preserved. Example : width="200" | +|**height** | A height in pixels, for resizing image. If only the height is provided, the image ratio is preserved. example : height="200" | +|**rotation** |The rotation angle in degrees (positive or negative) applied to the image. The background color of the empty areas is the one specified by 'background_color'. example : rotation="90" | +|**background_color** |The color applied to empty image parts during processing. Use $rgb or $rrggbb color format. example : background_color="$cc8000"| +|**quality** |The generated image quality, from 0(!) to 100%. The default value is 75% (you can hange this in the Administration panel). example : quality="70"| +|**resize_mode** | If 'crop', the image will have the exact specified width and height, and will be cropped if required. If 'borders', the image will have the exact specified width and height, and some borders may be added. The border color is the one specified by 'background_color'. If 'none' or missing, the image ratio is preserved, and depending od this ratio, may not have the exact width and height required. resize_mode="crop"| +|**effects** |One or more comma separated effects definitions, that will be applied to the image in the specified order. Please see below a detailed description of available effects. Expected values :
  • gamma:value : change the image Gamma to the specified value. Example: gamma:0.7.
  • grayscale or greyscale : switch image to grayscale.
  • colorize:color : apply a color mask to the image. The color format is $rgb or $rrggbb. Example: colorize:$ff2244.
  • negative : transform the image in its negative equivalent.
  • vflip or vertical_flip : flip the image vertically.
  • hflip or horizontal_flip : flip the image horizontally.
example : effects="greyscale,gamma:0.7,vflip" | +|**group** |The name of an image group. Return only images from the specified group| +|**filter_disable_slides** |if true (the default), the disabled slides will not be displayed| + +### Ouput arguments + +|Variable |Description | +|--- |--- | +|$ID |the image ID | +|$IMAGE_URL |The absolute URL to the generated image | +|$ORIGINAL_IMAGE_URL |The absolute URL to the original image | +|$IMAGE_PATH |The absolute path to the generated image file | +|$ORIGINAL_IMAGE_PATH |The absolute path to the original image file | +|$ALT |alt text | +|$TITLE |the slide title | +|$CHAPO |the slide summary | +|$DESCRIPTION |the slide description | +|$POSTSCRIPTUM |the slide conclusion | +|$LOCALE |the textual elements locale | +|$POSITION |the slide position in the carousel | +|$URL |the related URL | +|$LIMITED| true if slide is disabled, false otherwise | +|$START_DATE| limited slide display start date | +|$END_DATE| limited slide display end date | +|$DISABLE| true if slide display is limited | +|$GROUP| name of the group the slide belong to | + +### Exemple + +``` +{loop type="carousel" name="carousel.front" width="1200" height="390" resize_mode="borders"} + {$ALT} +{/loop} +``` + +## How to override ? + +If you want your own carousel in your tempalte, create the directory ```modules/Carousel``` then create the template ```carousel.html``` in this directory. Here you can create your own carousel and the replace the default template provided in the module. diff --git a/domokits/local/modules/Carousel/composer.json b/domokits/local/modules/Carousel/composer.json new file mode 100644 index 0000000..5cfd6d3 --- /dev/null +++ b/domokits/local/modules/Carousel/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/carousel-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "Carousel" + } +} diff --git a/domokits/local/modules/Carousel/templates/backOffice/default/assets/js/module-configuration.js b/domokits/local/modules/Carousel/templates/backOffice/default/assets/js/module-configuration.js new file mode 100644 index 0000000..54b03e5 --- /dev/null +++ b/domokits/local/modules/Carousel/templates/backOffice/default/assets/js/module-configuration.js @@ -0,0 +1,6 @@ +$(function() { + // Set proper image ID in delete from + $('a.image-delete').click(function(ev) { + $('#image_delete_id').val($(this).data('id')); + }); +}); diff --git a/domokits/local/modules/Carousel/templates/backOffice/default/module_configuration.html b/domokits/local/modules/Carousel/templates/backOffice/default/module_configuration.html new file mode 100644 index 0000000..df01b41 --- /dev/null +++ b/domokits/local/modules/Carousel/templates/backOffice/default/module_configuration.html @@ -0,0 +1,179 @@ +
+
+
+ {intl l='Edit your carousel.' d='carousel.bo.default'} +
+
+ +
+
+
+ {form name=Carousel\Form\CarouselImageForm::getName()} +
+ + {form_hidden_fields} + + {form_field field='file'} +
+ +
+ + + + +
+
+ {/form_field} +
+ {/form} +
+
+
+ +
+
+ {intl l='Carousel images' d='carousel.bo.default'} +
+
+ +
+
+
+ {ifloop rel="carousel.image"} + {form name="Carousel\Form\CarouselUpdateForm"} +
+ + {include + file = "includes/inner-form-toolbar.html" + page_url = "{url path='/admin/module/Carousel'}" + close_url = "{url path='/admin/modules'}" + } + + {form_hidden_fields} + + {loop name="carousel.image" type="carousel" width="550" height="200" resize_mode="borders" backend_context="1" lang="$edit_language_id" filter_disable_slides=false} + +
+
+
+

+ + {$ALT} + +

+ + + +
+
+ +
+
+ {form_field field="position{$ID}"} + + {/form_field} +
+
+
+
+ {form_field field="disable{$ID}"} + + + {/form_field} +
+
+ {form_field field="limited{$ID}"} + + + {/form_field} +
+ +
+ {form_field field="start_date{$ID}"} + +
+ +
+ {/form_field} +
+ +
+ {form_field field="end_date{$ID}"} + +
+ +
+ {/form_field} +
+
+
+ +
+ {* Not yet implemented + {render_form_field field="chapo{$ID} value=$CHAPO"} + *} + {render_form_field field="title{$ID}" value=$TITLE} + {render_form_field field="alt{$ID}" value=$ALT} + {render_form_field field="url{$ID}" value=$URL} + {render_form_field field="description{$ID}" extra_class="wysiwyg" value=$DESCRIPTION} + {render_form_field field="group{$ID}" value=$GROUP} + {* Not yet implemented + {render_form_field field="postscriptum{$ID}" value=$POSTSCRIPTUM} + *} +
+
+
+ {/loop} + + {include + file = "includes/inner-form-toolbar.html" + page_url = "{url path='/admin/module/Carousel'}" + close_url = "{url path='/admin/modules'}" + page_bottom = true + } +
+ {/form} + {/ifloop} + + {elseloop rel="carousel.image"} +
+ {intl d='carousel.bo.default' l="Your carousel contains no image. Please add one using the form above."} +
+ {/elseloop} +
+
+
+
+ +{capture "delete_dialog"} + +{/capture} + +{include +file = "includes/generic-confirm-dialog.html" + +dialog_id = "delete_carousel_dialog" +dialog_title = {intl l="Delete a carousel image" d="carousel.bo.default"} +dialog_message = {intl l="Do you really want to remove this image from the carousel ?" d="carousel.bo.default"} + +form_action = {url path='/admin/module/carousel/delete'} +form_content = {$smarty.capture.delete_dialog nofilter} +} + + + + diff --git a/domokits/local/modules/Carousel/templates/frontOffice/default/carousel.html b/domokits/local/modules/Carousel/templates/frontOffice/default/carousel.html new file mode 100644 index 0000000..6a714a5 --- /dev/null +++ b/domokits/local/modules/Carousel/templates/frontOffice/default/carousel.html @@ -0,0 +1,24 @@ +{ifloop rel="carousel.front"} + +{/ifloop} \ No newline at end of file diff --git a/domokits/local/modules/Cheque/Cheque.php b/domokits/local/modules/Cheque/Cheque.php new file mode 100644 index 0000000..6c95115 --- /dev/null +++ b/domokits/local/modules/Cheque/Cheque.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Cheque; + +use Propel\Runtime\Connection\ConnectionInterface; +use Thelia\Install\Database; +use Thelia\Model\MessageQuery; +use Thelia\Model\Order; +use Thelia\Module\AbstractPaymentModule; + +class Cheque extends AbstractPaymentModule +{ + public const MESSAGE_DOMAIN = 'Cheque'; + + public function pay(Order $order): void + { + // Nothing special to to. + } + + /** + * This method is call on Payment loop. + * + * If you return true, the payment method will de display + * If you return false, the payment method will not be display + * + * @return bool + */ + public function isValidPayment() + { + return $this->getCurrentOrderTotalAmount() > 0; + } + + public function postActivation(ConnectionInterface $con = null): void + { + $database = new Database($con); + + // Insert email message + $database->insertSql(null, [__DIR__.'/Config/setup.sql']); + } + + public function destroy(ConnectionInterface $con = null, $deleteModuleData = false): void + { + // Delete our message + if (null !== $message = MessageQuery::create()->findOneByName('order_confirmation_cheque')) { + $message->delete($con); + } + + parent::destroy($con, $deleteModuleData); + } + + /** + * if you want, you can manage stock in your module instead of order process. + * Return false if you want to manage yourself the stock. + * + * @return bool + */ + public function manageStockOnCreation() + { + return false; + } +} diff --git a/domokits/local/modules/Cheque/Config/config.xml b/domokits/local/modules/Cheque/Config/config.xml new file mode 100644 index 0000000..b09cf49 --- /dev/null +++ b/domokits/local/modules/Cheque/Config/config.xml @@ -0,0 +1,25 @@ + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/Cheque/Config/module.xml b/domokits/local/modules/Cheque/Config/module.xml new file mode 100644 index 0000000..897c68f --- /dev/null +++ b/domokits/local/modules/Cheque/Config/module.xml @@ -0,0 +1,25 @@ + + + Cheque\Cheque + + Cheque + + + Cheque + + images + + en_US + fr_FR + + 2.5.4 + + Manuel Raynaud + manu@raynaud.io + + payment + 2.5.4 + alpha + diff --git a/domokits/local/modules/Cheque/Config/routing.xml b/domokits/local/modules/Cheque/Config/routing.xml new file mode 100644 index 0000000..8c741c0 --- /dev/null +++ b/domokits/local/modules/Cheque/Config/routing.xml @@ -0,0 +1,9 @@ + + + + + Cheque\Controller\ConfigureController::configure + + diff --git a/domokits/local/modules/Cheque/Config/setup.sql b/domokits/local/modules/Cheque/Config/setup.sql new file mode 100644 index 0000000..2f91a0e --- /dev/null +++ b/domokits/local/modules/Cheque/Config/setup.sql @@ -0,0 +1,32 @@ +-- --------------------------------------------------------------------- +-- Mail template for cheque +-- --------------------------------------------------------------------- + +-- First, delete existing entries +SET @var := 0; +SELECT @var := `id` FROM `message` WHERE name="order_confirmation_cheque"; +DELETE FROM `message` WHERE `id`=@var; + +-- Then add new entries +SELECT @max := MAX(`id`) FROM `message`; +SET @max := @max+1; + +-- insert message +INSERT INTO `message` (`id`, `name`, `secured`) VALUES + (@max, + 'order_confirmation_cheque', + '0' + ); +-- and mail templates +INSERT INTO `message_i18n` (`id`, `locale`, `title`, `subject`, `text_message`, `html_message`) VALUES + (@max, + 'en_US', + 'Confirmation of payment by cheque', + 'Payment of order {$order_ref}', 'Dear customer,\r\nThis is a confirmation of the payment by cheque of your order {$order_ref} on our shop.\r\nYour invoice is now available in your customer account at {config key="url_site"}\r\nThank you again for your purchase.\r\nThe {config key="store_name"} team.', '\r\n\r\n\r\n \r\n courriel de confirmation de commande de {config key="url_site"} \r\n \r\n\r\n\r\n
\r\n
\r\n

{config key="store_name"}

\r\n

The payment of your order is confirmed

\r\n

Reference {$order_ref}

\r\n
\r\n

\r\n Your invoice is now available in your customer account on\r\n {config key="store_name"}.\r\n

\r\n

Thank you for your order !

\r\n

The {config key="store_name"} team.

\r\n
\r\n\r\n' + ), + (@max, + 'fr_FR', + 'Confirmation de paiement par chèque', + 'Paiement de la commande : {$order_ref}', + 'Cher client,\r\nCe message confirme le paiement par chèque de votre commande numero {$order_ref} sur notre boutique.\r\nVotre facture est maintenant disponible dans votre compte client à l''adresse {config key="url_site"}\r\nMerci encore pour votre achat !\r\nL''équipe {config key="store_name"}', '\r\n\r\n\r\n \r\n Confirmation du paiement de votre commande sur {config key="url_site"} \r\n \r\n\r\n\r\n
\r\n
\r\n

{config key="store_name"}

\r\n

Confirmation du paiement de votre commande

\r\n

N° {$order_ref}

\r\n
\r\n

\r\n Le suivi de votre commande est disponible dans la rubrique mon compte sur\r\n {config key="url_site"}\r\n

\r\n

Merci pour votre achat !

\r\n

L''équipe {config key="store_name"}

\r\n
\r\n\r\n' + ); diff --git a/domokits/local/modules/Cheque/Controller/ConfigureController.php b/domokits/local/modules/Cheque/Controller/ConfigureController.php new file mode 100644 index 0000000..607ca10 --- /dev/null +++ b/domokits/local/modules/Cheque/Controller/ConfigureController.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Cheque\Controller; + +use Cheque\Cheque; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Tools\URL; + +/** + * Class SetTransferConfig. + * + * @author Thelia + */ +class ConfigureController extends BaseAdminController +{ + public function configure() + { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'Cheque', AccessManager::UPDATE)) { + return $response; + } + + // Initialize the potential exception + $ex = null; + + // Create the Form from the request + $configurationForm = $this->createForm('cheque.instructions.configure'); + + try { + // Check the form against constraints violations + $form = $this->validateForm($configurationForm, 'POST'); + + // Get the form field values + $data = $form->getData(); + + Cheque::setConfigValue('instructions', $data['instructions'], $this->getCurrentEditionLocale()); + Cheque::setConfigValue('payable_to', $data['payable_to']); + + // Log configuration modification + $this->adminLogAppend( + 'cheque.configuration.message', + AccessManager::UPDATE, + 'Cheque instructions configuration updated' + ); + + // Everything is OK. + return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/Cheque')); + } catch (FormValidationException $ex) { + // Form cannot be validated. Create the error message using + // the BaseAdminController helper method. + $error_msg = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $error_msg = $ex->getMessage(); + } + + // At this point, the form has errors, and should be redisplayed. We don not redirect, + // just redisplay the same template. + // Setup the Form error context, to make error information available in the template. + $this->setupFormErrorContext( + $this->getTranslator()->trans('Cheque instructions configuration', [], Cheque::MESSAGE_DOMAIN), + $error_msg, + $configurationForm, + $ex + ); + + // Do not redirect at this point, or the error context will be lost. + // Just redisplay the current template. + return $this->render('module-configure', ['module_code' => 'Cheque']); + } +} diff --git a/domokits/local/modules/Cheque/Form/ConfigurationForm.php b/domokits/local/modules/Cheque/Form/ConfigurationForm.php new file mode 100644 index 0000000..312d181 --- /dev/null +++ b/domokits/local/modules/Cheque/Form/ConfigurationForm.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Cheque\Form; + +use Cheque\Cheque; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +/** + * Class ConfigurationForm. + * + * @author Thelia + */ +class ConfigurationForm extends BaseForm +{ + protected function trans($str, $params = []) + { + return Translator::getInstance()->trans($str, $params, Cheque::MESSAGE_DOMAIN); + } + + protected function buildForm(): void + { + $this->formBuilder + ->add( + 'payable_to', + TextType::class, + [ + 'constraints' => [new NotBlank()], + 'label' => $this->trans('Cheque is payable to: '), + 'label_attr' => [ + 'for' => 'payable_to', + 'help' => $this->trans('The name to which the cheque shoud be payable to.'), + ], + 'attr' => [ + 'rows' => 10, + 'placeholder' => $this->trans('Pay cheque to'), + ], + ] + ) + ->add( + 'instructions', + TextareaType::class, + [ + 'constraints' => [], + 'required' => false, + 'label' => $this->trans('Cheque instructions'), + 'label_attr' => [ + 'for' => 'namefield', + 'help' => $this->trans('Please enter here the payment by cheque instructions'), + ], + 'attr' => [ + 'rows' => 10, + 'placeholder' => $this->trans('Payment instruction'), + ], + ] + ) + ; + } + + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return 'cheque_configuration_instructions'; + } +} diff --git a/domokits/local/modules/Cheque/Hook/HookManager.php b/domokits/local/modules/Cheque/Hook/HookManager.php new file mode 100644 index 0000000..8591131 --- /dev/null +++ b/domokits/local/modules/Cheque/Hook/HookManager.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Cheque\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class HookManager. + * + * @author Franck Allimant + */ +class HookManager extends BaseHook +{ + public function onAdditionalPaymentInfo(HookRenderEvent $event): void + { + $content = $this->render('order-placed.additional-payment-info.html', [ + 'placed_order_id' => $event->getArgument('placed_order_id'), + ]); + + $event->add($content); + } +} diff --git a/domokits/local/modules/Cheque/I18n/backOffice/default/de_DE.php b/domokits/local/modules/Cheque/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..ebed375 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/backOffice/default/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions configuration' => 'Scheck-Anleitungen-Konfiguration', +]; diff --git a/domokits/local/modules/Cheque/I18n/backOffice/default/en_US.php b/domokits/local/modules/Cheque/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..d6dc1de --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/backOffice/default/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions configuration' => 'Cheque instructions configuration', +]; diff --git a/domokits/local/modules/Cheque/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/Cheque/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..3dd9d8e --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions configuration' => 'Instructions de paiement par chèque', +]; diff --git a/domokits/local/modules/Cheque/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/Cheque/I18n/backOffice/default/ru_RU.php new file mode 100644 index 0000000..a40b84d --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions configuration' => 'Конфигурация инструкций для чека', +]; diff --git a/domokits/local/modules/Cheque/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/Cheque/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..d7dbdb2 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions configuration' => 'Çek yönergeleri yapılandırma', +]; diff --git a/domokits/local/modules/Cheque/I18n/de_DE.php b/domokits/local/modules/Cheque/I18n/de_DE.php new file mode 100644 index 0000000..2f37846 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/de_DE.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions' => 'Scheck-Anweisungen', + 'Cheque instructions configuration' => 'Scheck-Anleitungen-Konfiguration', + 'Cheque is payable to: ' => 'Scheck ist zahlbar an: ', + 'Pay cheque to' => 'Scheck bezahlen an', + 'Payment instruction' => 'Zahlungsanweisungen', + 'Please enter here the payment by cheque instructions' => 'Bitte geben Sie hier die Zahlung durch Scheck Anweisungen ein', + 'The name to which the cheque shoud be payable to.' => 'Der Name, an den der Scheck bezahlbar sein soll.', +]; diff --git a/domokits/local/modules/Cheque/I18n/en_US.php b/domokits/local/modules/Cheque/I18n/en_US.php new file mode 100644 index 0000000..2ef9451 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/en_US.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions' => 'Cheque instructions', + 'Cheque instructions configuration' => 'Cheque instructions configuration', + 'Cheque is payable to: ' => 'Cheque is payable to: ', + 'Pay cheque to' => 'Pay cheque to', + 'Payment instruction' => 'Payment instruction', + 'Please enter here the payment by cheque instructions' => 'Please enter here the payment by cheque instructions', + 'The name to which the cheque shoud be payable to.' => 'The name to which the cheque shoud be payable to.', +]; diff --git a/domokits/local/modules/Cheque/I18n/fr_FR.php b/domokits/local/modules/Cheque/I18n/fr_FR.php new file mode 100644 index 0000000..0c1ab59 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/fr_FR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions' => 'Instructions de paiement', + 'Cheque instructions configuration' => 'Instructions de paiement par chèque', + 'Cheque is payable to: ' => 'Ordre du chèque', + 'Pay cheque to' => 'Ordre du chèque', + 'Payment instruction' => 'Instructions de paiement', + 'Please enter here the payment by cheque instructions' => 'Indiquez ici les instructions particulières de paiement par chèque', + 'The name to which the cheque shoud be payable to.' => 'Le nom à fare figurer sur le chèque', +]; diff --git a/domokits/local/modules/Cheque/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/Cheque/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..46100d2 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Be sure to sign your cheque !' => 'Vergessen Sie nicht, Ihren Scheck zu unterschreiben !', + 'Please make your cheque payable to %name, and send it to the following address :' => 'Bitte stellen Sie den Scheck auf %name, und senden Sie es an die folgende Adresse : ', +]; diff --git a/domokits/local/modules/Cheque/I18n/frontOffice/default/en_US.php b/domokits/local/modules/Cheque/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..8499d61 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/frontOffice/default/en_US.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Be sure to sign your cheque !' => 'Be sure to sign your cheque !', + 'Please make your cheque payable to %name, and send it to the following address :' => 'Please make your cheque payable to %name, and send it to the following address :', +]; diff --git a/domokits/local/modules/Cheque/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/Cheque/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..5f2d4b2 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Be sure to sign your cheque !' => 'N\'oubliez pas de signer votre chèque !', + 'Please make your cheque payable to %name, and send it to the following address :' => 'Merci de libeller votre chèque à l\'ordre de %name, et de l\'expédier à l\'adresse suivante :', +]; diff --git a/domokits/local/modules/Cheque/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/Cheque/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..25c109d --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Be sure to sign your cheque !' => 'Убедитесь в том, что подписали чек !', + 'Please make your cheque payable to %name, and send it to the following address :' => 'Пожалуйста убедитесь что ваш чек предназначен %name, и вышлите его по следующему адресу :', +]; diff --git a/domokits/local/modules/Cheque/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/Cheque/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..01546fc --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Be sure to sign your cheque !' => 'Çekini imzalamak emin olun!', + 'Please make your cheque payable to %name, and send it to the following address :' => 'Lütfen, Çek %name için ödenecek olun ve aşağıdaki adrese gönderin:', +]; diff --git a/domokits/local/modules/Cheque/I18n/ru_RU.php b/domokits/local/modules/Cheque/I18n/ru_RU.php new file mode 100644 index 0000000..8c0e366 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/ru_RU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions' => 'Инструкция для чека', + 'Cheque instructions configuration' => 'Конфигурация инструкций для чека', + 'Cheque is payable to: ' => 'Чек предназначен:', + 'Pay cheque to' => 'Оплата чеком для', + 'Payment instruction' => 'Инструкции для оплаты', + 'Please enter here the payment by cheque instructions' => 'Введите здесь инструкции для оплаты чеком', + 'The name to which the cheque shoud be payable to.' => 'Имя получателя чека.', +]; diff --git a/domokits/local/modules/Cheque/I18n/tr_TR.php b/domokits/local/modules/Cheque/I18n/tr_TR.php new file mode 100644 index 0000000..20a0076 --- /dev/null +++ b/domokits/local/modules/Cheque/I18n/tr_TR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cheque instructions' => 'Çek yönergeleri', + 'Cheque instructions configuration' => 'Çek yönergeleri yapılandırma', + 'Cheque is payable to: ' => 'Çek için ödenir: ', + 'Pay cheque to' => 'Çek için ödeme', + 'Payment instruction' => 'Ödeme talimatı', + 'Please enter here the payment by cheque instructions' => 'Lütfen burada ödeme çek yönergeleri tarafından girin', + 'The name to which the cheque shoud be payable to.' => 'Adı için çek shoud için ödenecek.', +]; diff --git a/domokits/local/modules/Cheque/LICENSE.txt b/domokits/local/modules/Cheque/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/Cheque/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/Cheque/Listener/SendPaymentConfirmationEmail.php b/domokits/local/modules/Cheque/Listener/SendPaymentConfirmationEmail.php new file mode 100644 index 0000000..b0dcc8c --- /dev/null +++ b/domokits/local/modules/Cheque/Listener/SendPaymentConfirmationEmail.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Cheque\Listener; + +use Cheque\Cheque; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Action\BaseAction; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Mailer\MailerFactory; + +/** + * Class SendEMail. + * + * @author Thelia + */ +class SendPaymentConfirmationEmail extends BaseAction implements EventSubscriberInterface +{ + /** + * @var MailerFactory + */ + protected $mailer; + + public function __construct(MailerFactory $mailer) + { + $this->mailer = $mailer; + } + + /** + * @param orderEvent $event + * + * Check if we're the payment module, and send the payment confirmation email to the customer if it's the case + */ + public function sendConfirmationEmail(OrderEvent $event): void + { + if ($event->getOrder()->getPaymentModuleId() === Cheque::getModuleId()) { + if ($event->getOrder()->isPaid()) { + $order = $event->getOrder(); + + $this->mailer->sendEmailToCustomer( + 'order_confirmation_cheque', + $order->getCustomer(), + [ + 'order_id' => $order->getId(), + 'order_ref' => $order->getRef(), + ] + ); + } + } + } + + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_UPDATE_STATUS => ['sendConfirmationEmail', 128], + ]; + } +} diff --git a/domokits/local/modules/Cheque/composer.json b/domokits/local/modules/Cheque/composer.json new file mode 100644 index 0000000..8dca232 --- /dev/null +++ b/domokits/local/modules/Cheque/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/cheque-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "Cheque" + } +} diff --git a/domokits/local/modules/Cheque/images/cheque.png b/domokits/local/modules/Cheque/images/cheque.png new file mode 100644 index 0000000..0aad099 Binary files /dev/null and b/domokits/local/modules/Cheque/images/cheque.png differ diff --git a/domokits/local/modules/Cheque/templates/backOffice/default/module_configuration.html b/domokits/local/modules/Cheque/templates/backOffice/default/module_configuration.html new file mode 100644 index 0000000..5cdeb14 --- /dev/null +++ b/domokits/local/modules/Cheque/templates/backOffice/default/module_configuration.html @@ -0,0 +1,55 @@ +{if isset($smarty.get.errmes) && !empty($smarty.get.errmes)} +
+ {$smarty.get.errmes} +
+{/if} + +
+
+ +
+
+ {intl d='cheque.bo.default' l="Cheque instructions configuration"} +
+
+ +
+
+
+ + {form name="cheque.instructions.configure"} + + + + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + + page_url = {url path="/admin/module/Cheque"} + close_url = {url path="/admin/modules"} + } + + {form_hidden_fields} + + {if $form_error} +
+
+
{$form_error_message}
+
+
+ {/if} + + {loop type="module-config" name="get-payable-to" module="Cheque" variable="payable_to"} + {render_form_field field="payable_to" value=$VALUE} + {/loop} + + {loop type="module-config" name="get-instruction" module="Cheque" variable="instructions" locale=$edit_language_locale} + {render_form_field field="instructions" extra_class="wysiwyg" value=$VALUE} + {/loop} + + {/form} +
+
+
+
+
\ No newline at end of file diff --git a/domokits/local/modules/Cheque/templates/frontOffice/default/order-placed.additional-payment-info.html b/domokits/local/modules/Cheque/templates/frontOffice/default/order-placed.additional-payment-info.html new file mode 100644 index 0000000..dba86bf --- /dev/null +++ b/domokits/local/modules/Cheque/templates/frontOffice/default/order-placed.additional-payment-info.html @@ -0,0 +1,21 @@ +{loop type="module-config" name="cheque-instructions" module="cheque" variable="payable_to"} +

{intl d='cheque.fo.default' l="Please make your cheque payable to %name, and send it to the following address :" name={$VALUE}}

+{/loop} + +
+ {config key="store_name"}
+ {config key="store_address1"}
+ {if ! empty({config key="store_address2"})}{config key="store_address2"}
{/if} + {if ! empty({config key="store_address3"})}{config key="store_address3"}
{/if} + {config key="store_zipcode"}, {config key="store_city"}
+ {if {config key="store_country"}} + {loop type="country" name="store_country" id={config key="store_country"}} + {$TITLE}
+ {/loop} + {/if} +
+

{intl d='cheque.fo.default' l="Be sure to sign your cheque !"}

+ +{loop type="module-config" name="cheque-instructions" module="cheque" variable="instructions"} +

{$VALUE nofilter}

+{/loop} \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/ChoiceFilter.php b/domokits/local/modules/ChoiceFilter/ChoiceFilter.php new file mode 100644 index 0000000..630aa5f --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/ChoiceFilter.php @@ -0,0 +1,50 @@ + + */ +class ChoiceFilter extends BaseModule +{ + /** @var string */ + public const DOMAIN_NAME = 'choicefilter'; + + public function preActivation(ConnectionInterface $con = null) + { + if (!$this->getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, [__DIR__.'/Config/thelia.sql', __DIR__.'/Config/insert.sql']); + + $this->setConfigValue('is_initialized', true); + } + + return true; + } + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/ChoiceFilter/Config/config.xml b/domokits/local/modules/ChoiceFilter/Config/config.xml new file mode 100644 index 0000000..f2a1ef9 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/ChoiceFilter/Config/insert.sql b/domokits/local/modules/ChoiceFilter/Config/insert.sql new file mode 100644 index 0000000..e7dd3bd --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/insert.sql @@ -0,0 +1,17 @@ +-- +-- Contenu de la table `choice_filter_other` +-- + +INSERT INTO `choice_filter_other` (`id`, `type`, `visible`) VALUES +(2, 'brand', 1), +(3, 'category', 1); + +-- +-- Contenu de la table `choice_filter_other_i18n` +-- + +INSERT INTO `choice_filter_other_i18n` (`id`, `locale`, `title`, `description`) VALUES +(2, 'en_US', 'Brand', NULL), +(2, 'fr_FR', 'Marque', NULL), +(3, 'en_US', 'Category', NULL), +(3, 'fr_FR', 'Catégorie', NULL); diff --git a/domokits/local/modules/ChoiceFilter/Config/module.xml b/domokits/local/modules/ChoiceFilter/Config/module.xml new file mode 100644 index 0000000..9d8b71f --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/module.xml @@ -0,0 +1,28 @@ + + + ChoiceFilter\ChoiceFilter + + This module allows the management of filters in front by template and category + + + Ce module permet la gestion des filtres en front par gabarit et catégorie + + + en_US + fr_FR + + 2.1.1 + + + Gilles Bourgeat + gbourgeat@openstudio.fr + + + classic + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/ChoiceFilter/Config/routing.xml b/domokits/local/modules/ChoiceFilter/Config/routing.xml new file mode 100644 index 0000000..7aba3b9 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/routing.xml @@ -0,0 +1,15 @@ + + + + + + ChoiceFilter\Controller\ChoiceFilterController::saveAction + + + + ChoiceFilter\Controller\ChoiceFilterController::clearAction + + + diff --git a/domokits/local/modules/ChoiceFilter/Config/schema.xml b/domokits/local/modules/ChoiceFilter/Config/schema.xml new file mode 100644 index 0000000..b1a3bb9 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/schema.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/ChoiceFilter/Config/thelia.sql b/domokits/local/modules/ChoiceFilter/Config/thelia.sql new file mode 100644 index 0000000..381e0e7 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Config/thelia.sql @@ -0,0 +1,91 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- choice_filter_other +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `choice_filter_other`; + +CREATE TABLE `choice_filter_other` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `type` VARCHAR(55), + `visible` TINYINT(1), + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- choice_filter +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `choice_filter`; + +CREATE TABLE `choice_filter` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `feature_id` INTEGER, + `attribute_id` INTEGER, + `other_id` INTEGER, + `category_id` INTEGER, + `template_id` INTEGER, + `position` INTEGER DEFAULT 0 NOT NULL, + `visible` TINYINT(1), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `choice_filter_FI_1` (`attribute_id`), + INDEX `choice_filter_FI_2` (`feature_id`), + INDEX `choice_filter_FI_3` (`other_id`), + INDEX `choice_filter_FI_4` (`category_id`), + INDEX `choice_filter_FI_5` (`template_id`), + CONSTRAINT `choice_filter_FK_1` + FOREIGN KEY (`attribute_id`) + REFERENCES `attribute` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `choice_filter_FK_2` + FOREIGN KEY (`feature_id`) + REFERENCES `feature` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `choice_filter_FK_3` + FOREIGN KEY (`other_id`) + REFERENCES `choice_filter_other` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `choice_filter_FK_4` + FOREIGN KEY (`category_id`) + REFERENCES `category` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `choice_filter_FK_5` + FOREIGN KEY (`template_id`) + REFERENCES `template` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- choice_filter_other_i18n +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `choice_filter_other_i18n`; + +CREATE TABLE `choice_filter_other_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `title` VARCHAR(255), + `description` LONGTEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `choice_filter_other_i18n_FK_1` + FOREIGN KEY (`id`) + REFERENCES `choice_filter_other` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/ChoiceFilter/Controller/ChoiceFilterController.php b/domokits/local/modules/ChoiceFilter/Controller/ChoiceFilterController.php new file mode 100644 index 0000000..870f71b --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Controller/ChoiceFilterController.php @@ -0,0 +1,130 @@ + + */ +class ChoiceFilterController extends BaseAdminController +{ + public function saveAction(Request $request) + { + $data = $request->get('ChoiceFilter'); + + if (!empty($data['template_id'])) { + ChoiceFilterQuery::create() + ->filterByTemplateId((int) $data['template_id']) + ->delete(); + + $choiceFilterBase = (new ChoiceFilter()) + ->setTemplateId((int) $data['template_id']); + + $template = 'choice-filter/template-edit'; + $parameters = [ + 'template_id' => (int) $data['template_id'] + ]; + $redirectUrl = '/admin/configuration/templates/update'; + + } elseif (!empty($data['category_id'])) { + ChoiceFilterQuery::create() + ->filterByCategoryId((int) $data['category_id']) + ->delete(); + + $choiceFilterBase = (new ChoiceFilter()) + ->setCategoryId((int) $data['category_id']); + + $template = 'choice-filter/category-edit'; + $parameters = [ + 'category_id' => (int) $data['category_id'] + ]; + $redirectUrl = '/admin/categories/update'; + + } else { + throw new \Exception("Missing parameter"); + } + + foreach ($data['filter'] as $filter) { + $choiceFilter = clone $choiceFilterBase; + + $choiceFilter + ->setVisible((int) $filter['visible']) + ->setPosition((int) $filter['position']); + + if ($filter['type'] === 'attribute') { + $choiceFilter + ->setAttributeId($filter['id']); + } elseif ($filter['type'] === 'feature') { + $choiceFilter + ->setFeatureId((int) $filter['id']); + } else { + $choiceFilter + ->setOtherId((int) $filter['id']); + } + + $choiceFilter->save(); + } + + $this->getSession()->getFlashBag()->add('choice-filter-success', 'configuration sauvegardée avec succès'); + + if ($request->isXmlHttpRequest()) { + return $this->render( + $template, + $parameters + ); + } else { + return $this->generateRedirect( + URL::getInstance()->absoluteUrl($redirectUrl, $parameters) + ); + } + } + + public function clearAction(Request $request) + { + $data = $request->get('ChoiceFilter'); + + if (!empty($data['template_id'])) { + ChoiceFilterQuery::create() + ->filterByTemplateId((int) $data['template_id']) + ->delete(); + + $template = 'choice-filter/template-edit'; + $parameters = [ + 'template_id' => (int) $data['template_id'] + ]; + $redirectUrl = '/admin/configuration/templates/update'; + + } elseif (!empty($data['category_id'])) { + ChoiceFilterQuery::create() + ->filterByCategoryId((int) $data['category_id']) + ->delete(); + + $template = 'choice-filter/category-edit'; + $parameters = [ + 'category_id' => (int) $data['category_id'] + ]; + $redirectUrl = '/admin/categories/update'; + + } else { + throw new \Exception("Missing parameter"); + } + + $this->getSession()->getFlashBag()->add('choice-filter-success', 'configuration sauvegardée avec succès'); + + if ($request->isXmlHttpRequest()) { + return $this->render( + $template, + $parameters + ); + } else { + return $this->generateRedirect( + URL::getInstance()->absoluteUrl($redirectUrl, $parameters) + ); + } + } +} diff --git a/domokits/local/modules/ChoiceFilter/Controller/Front/ChoiceFilterFrontController.php b/domokits/local/modules/ChoiceFilter/Controller/Front/ChoiceFilterFrontController.php new file mode 100644 index 0000000..973beeb --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Controller/Front/ChoiceFilterFrontController.php @@ -0,0 +1,202 @@ +get('locale', $request->getSession()->getLang()->getLocale()); + + $categoryId = $request->get('category_id'); + $visible = $request->get('visible', true); + + $features = new ObjectCollection(); + $attributes = new ObjectCollection(); + $others = new ObjectCollection(); + + $category = CategoryQuery::create()->findPk($categoryId); + + $categoryChoiceFilters = ChoiceFilterQuery::findChoiceFilterByCategory($category, $templateIdFind); + + if (null !== $templateIdFind) { + $features = ChoiceFilterQuery::findFeaturesByTemplateId( + $templateIdFind, + [$locale] + ); + $attributes = ChoiceFilterQuery::findAttributesByTemplateId( + $templateIdFind, + [$locale] + ); + $others = ChoiceFilterOtherQuery::findOther([$locale]); + } + + $filters = Util::merge($categoryChoiceFilters, $features, $attributes, $others); + + if (Type\BooleanOrBothType::ANY !== $visible) { + $visible = $visible ? 1 : 0; + $filters = array_filter($filters, function ($filter) use ($visible) {return $filter['Visible'] == $visible;}); + } + + $results = []; + + $attributeResults = array_map( + fn ($filter) => $modelFactory->buildModel('ChoiceFilter', $filter, $locale), + array_filter($filters, function ($filter) {return $filter['Type'] === "attribute";}) + ); + + if (!empty($attributeResults)) { + $results['attributes'] = $attributeResults; + } + + $featureResults = array_map( + fn ($filter) => $modelFactory->buildModel('ChoiceFilter', $filter, $locale), + array_filter($filters, function ($filter) {return $filter['Type'] === "feature";}) + ); + + if (!empty($attributeResults)) { + $results['features'] = $featureResults; + } + + $categoryIds = [$categoryId]; + $needCategories = !empty(array_filter($filters, function ($filter) {return $filter['Type'] === "category";})); + if ($needCategories) { + $con = Propel::getConnection(); + $stmt = $con->prepare(" + SELECT category.*, ci18n.title as title FROM category + LEFT JOIN category c_parent ON category.parent = c_parent.id + LEFT JOIN category c_parent_2 ON c_parent.parent = c_parent_2.id + LEFT JOIN category c_parent_3 ON c_parent_2.parent = c_parent_3.id + LEFT JOIN category c_parent_4 ON c_parent_3.parent = c_parent_4.id + LEFT JOIN category_i18n ci18n on category.id = ci18n.id AND ci18n.locale = :locale + WHERE category.id = :categoryId OR c_parent.id = :categoryId OR c_parent_2.id = :categoryId OR c_parent_3.id = :categoryId OR c_parent_4.id = :categoryId + "); + $stmt->bindValue(':locale', $locale, \PDO::PARAM_STR); + $stmt->bindValue(':categoryId', $categoryId, \PDO::PARAM_INT); + $stmt->execute(); + + $categoryResults = array_map( + fn ($category) => $modelFactory->buildModel('CategoryChoiceFilter', $category, $locale), + $stmt->fetchAll(\PDO::FETCH_ASSOC) + ); + + if (!empty($categoryResults)) { + $results['categories'] = $categoryResults; + } + + $categoryIds = array_map( + fn (CategoryChoiceFilter $categoryChoiceFilter) => $categoryChoiceFilter->getId(), + $categoryResults + ); + } + + $needBrands = !empty(array_filter($filters, function ($filter) {return $filter['Type'] === "brand";})); + if ($needBrands) { + $con = Propel::getConnection(); + $stmt = $con->prepare(" + SELECT DISTINCT brand.id as id, brand.*, bi18n.* FROM brand + INNER JOIN product p on brand.id = p.brand_id + LEFT JOIN brand_i18n bi18n on brand.id = bi18n.id AND bi18n.locale = :locale + INNER JOIN product_category ON p.id = product_category.product_id AND product_category.category_id IN (:categoryIds) + "); + $stmt->bindValue(':locale', $locale, \PDO::PARAM_STR); + $stmt->bindValue(':categoryIds', implode(",", $categoryIds), \PDO::PARAM_STR); + $stmt->execute(); + + $brandResults = array_map( + fn ($brand) => $modelFactory->buildModel('BrandChoiceFilter', $brand, $locale), + $stmt->fetchAll(\PDO::FETCH_ASSOC) + ); + + if (!empty($brandResults)) { + $results['brands'] = $brandResults; + } + } + + return OpenApiService::jsonResponse($results); + } +} diff --git a/domokits/local/modules/ChoiceFilter/Hook/ChoiceFilterHook.php b/domokits/local/modules/ChoiceFilter/Hook/ChoiceFilterHook.php new file mode 100644 index 0000000..0b98d34 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Hook/ChoiceFilterHook.php @@ -0,0 +1,142 @@ + + */ +class ChoiceFilterHook extends BaseHook +{ + protected $requestStack; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + /** + * @param HookRenderEvent $event + */ + public function onTemplateEditBottom(HookRenderEvent $event) + { + $templateId = $event->getArgument('template_id'); + + $locales = $this->getEditLocales(); + + $features = ChoiceFilterQuery::findFeaturesByTemplateId($templateId, $locales); + $attributes = ChoiceFilterQuery::findAttributesByTemplateId($templateId, $locales); + $others = ChoiceFilterOtherQuery::findOther($locales); + + /** @var ChoiceFilter[] $choiceFilters */ + $choiceFilters = ChoiceFilterQuery::create() + ->filterByTemplateId($templateId) + ->orderByPosition() + ->find(); + + if (count($choiceFilters)) { + $enabled = true; + } else { + $enabled = false; + } + + $filters = Util::merge($choiceFilters, $features, $attributes, $others); + + $event->add($this->render( + 'choice-filter/hook/template-edit.bottom.html', + $event->getArguments() + ['filters' => $filters, 'enabled' => $enabled] + )); + } + + public function onCategoryTabContent(HookRenderEvent $event) + { + if ($event->getArgument('view') !== 'category') { + return; + } + + $locales = $this->getEditLocales(); + + $category = CategoryQuery::create()->filterById($event->getArgument('id'))->findOne(); + + $templateId = null; + $categoryId = null; + $choiceFilters = ChoiceFilterQuery::findChoiceFilterByCategory($category, $templateId, $categoryId); + + $messageInfo = []; + $enabled = false; + + if ($templateId === null) { + $features = new ObjectCollection(); + $attributes = new ObjectCollection(); + $others = new ObjectCollection(); + $choiceFilters = new ObjectCollection(); + + $messageInfo[] = "Cette catégorie utilise aucune configuration des filtres"; + } else { + $features = ChoiceFilterQuery::findFeaturesByTemplateId( + $templateId, + $locales + ); + $attributes = ChoiceFilterQuery::findAttributesByTemplateId( + $templateId, + $locales + ); + $others = ChoiceFilterOtherQuery::findOther(); + + if (null === $categoryId) { + $messageInfo[] = "Cette catégorie utilise la configuration du gabarit " . $templateId; + } elseif ($categoryId == $category->getId()) { + $enabled = true; + $messageInfo[] = "Cette catégorie utilise sa propre configuration des filtres"; + } else { + $messageInfo[] = "Cette catégorie utilise la configuration des filtres de la catégorie " . $categoryId; + } + } + + $filters = Util::merge($choiceFilters, $features, $attributes, $others); + + $event->add($this->render( + 'choice-filter/hook/category.tab-content.html', + $event->getArguments() + ['category_id' => $event->getArgument('id'), 'filters' => $filters, 'enabled' => $enabled, 'messageInfo' => $messageInfo] + )); + } + + public function onCategoryEditJs(HookRenderEvent $event) + { + $event->add($this->render( + 'choice-filter/hook/category.edit-js.html', + $event->getArguments() + )); + } + + public function onTemplateEditJs(HookRenderEvent $event) + { + $event->add($this->render( + 'choice-filter/hook/template.edit-js.html', + $event->getArguments() + )); + } + + /** + * @return string[] list of locale + */ + protected function getEditLocales() + { + /** @var Session $session */ + $session = $this->requestStack->getCurrentRequest()->getSession(); + + $locale = $session->getAdminEditionLang()->getLocale(); + + return [$locale]; + } +} diff --git a/domokits/local/modules/ChoiceFilter/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/ChoiceFilter/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..c3c7b21 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,17 @@ + 'Personnaliser la configuration des filtres pour cette catégorie', + 'ID' => 'ID', + 'No' => 'Non', + 'Position' => 'Position', + 'Position of the filters in front for this category' => 'Position des filtres en front pour cette category', + 'Position of the filters in front for this template' => 'Position des filtres en front pour ce gabarit', + 'Reset default values' => 'Réinitialiser les valeurs par défaut', + 'Save' => 'Sauvegarder', + 'The position of the filters is for now the default one.' => 'La position des filtres est pour le moment celle par défaut.', + 'Title' => 'Titre', + 'Type' => 'Type', + 'Visible' => 'Visible', + 'Yes' => 'Oui', +); diff --git a/domokits/local/modules/ChoiceFilter/I18n/en_US.php b/domokits/local/modules/ChoiceFilter/I18n/en_US.php new file mode 100644 index 0000000..0b4fa14 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/domokits/local/modules/ChoiceFilter/I18n/fr_FR.php b/domokits/local/modules/ChoiceFilter/I18n/fr_FR.php new file mode 100644 index 0000000..3708624 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/I18n/fr_FR.php @@ -0,0 +1,4 @@ + 'La traduction française de la chaine', +); diff --git a/domokits/local/modules/ChoiceFilter/LICENCE b/domokits/local/modules/ChoiceFilter/LICENCE new file mode 100644 index 0000000..3312f1f --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +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. \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/Loop/ChoiceFilterLoop.php b/domokits/local/modules/ChoiceFilter/Loop/ChoiceFilterLoop.php new file mode 100644 index 0000000..1e65416 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Loop/ChoiceFilterLoop.php @@ -0,0 +1,152 @@ + + */ +class ChoiceFilterLoop extends BaseLoop implements ArraySearchLoopInterface +{ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('template_id'), + Argument::createIntTypeArgument('category_id'), + Argument::createBooleanOrBothTypeArgument('visible', true), + new Argument( + 'order', + new Type\TypeCollection( + new Type\EnumListType( + [ + 'position', + 'position_reverse' + ] + ) + ), + 'position' + ) + ); + } + + public function buildArray() + { + $templateId = $this->getTemplateId(); + $categoryId = $this->getCategoryId(); + + if (null === $templateId && null === $categoryId) { + throw new LoopException('The argument template_id or category_id is required'); + } + + if (null !== $templateId && null !== $categoryId) { + throw new LoopException('The argument template_id or category_id can not be set together'); + } + + if (null !== $categoryId) { + $category = CategoryQuery::create()->findPk($categoryId); + + $templateIdFind = null; + $categoryIdFind = null; + $choiceFilters = ChoiceFilterQuery::findChoiceFilterByCategory($category, $templateIdFind, $categoryId); + + if ($templateIdFind === null) { + $features = new ObjectCollection(); + $attributes = new ObjectCollection(); + $others = new ObjectCollection(); + $choiceFilters = new ObjectCollection(); + } else { + $features = ChoiceFilterQuery::findFeaturesByTemplateId( + $templateIdFind + ); + $attributes = ChoiceFilterQuery::findAttributesByTemplateId( + $templateIdFind + ); + $others = ChoiceFilterOtherQuery::findOther(); + } + + $filters = Util::merge($choiceFilters, $features, $attributes, $others); + } elseif (null !== $templateId) { + $features = ChoiceFilterQuery::findFeaturesByTemplateId($templateId); + $attributes = ChoiceFilterQuery::findAttributesByTemplateId($templateId); + $others = ChoiceFilterOtherQuery::findOther(); + + /** @var ChoiceFilter[] $choiceFilters */ + $choiceFilters = ChoiceFilterQuery::create() + ->filterByTemplateId($templateId) + ->orderByPosition() + ->find(); + + $filters = Util::merge($choiceFilters, $features, $attributes, $others); + } + + if (null !== $orders = $this->getOrder()) { + foreach ($orders as $order) { + switch ($order) { + case "position_reverse": + return array_reverse($filters); + break; + } + } + } + + if ($this->getVisible() !== Type\BooleanOrBothType::ANY) { + $visible = $this->getVisible() ? 1 : 0; + foreach ($filters as $key => $filter) { + if ($filter['Visible'] != $visible) { + unset($filters[$key]); + } + } + } + + return $filters; + } + + /** + * @param LoopResult $loopResult + * + * @return LoopResult + */ + public function parseResults(LoopResult $loopResult) + { + /** @var ChoiceFilter $choiceFilter */ + foreach ($loopResult->getResultDataCollection() as $choiceFilter) { + $loopResultRow = new LoopResultRow($choiceFilter); + + $loopResultRow + ->set('TYPE', $choiceFilter['Type']) + ->set('ID', $choiceFilter['Id']) + ->set('VISIBLE', $choiceFilter['Visible']) + ->set('POSITION', $choiceFilter['Position']); + + $this->addOutputFields($loopResultRow, $choiceFilter); + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/Api/BrandChoiceFilter.php b/domokits/local/modules/ChoiceFilter/Model/Api/BrandChoiceFilter.php new file mode 100644 index 0000000..d8d04e5 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/Api/BrandChoiceFilter.php @@ -0,0 +1,122 @@ +id; + } + + public function setId(int $id): BrandChoiceFilter + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(?string $title): BrandChoiceFilter + { + $this->title = $title; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + /** + * @param bool $visible + */ + public function setVisible(?bool $visible = true): BrandChoiceFilter + { + $this->visible = $visible; + + return $this; + } + + /** + * @return int + */ + public function getPosition(): ?int + { + return $this->position; + } + + /** + * @param int $position + */ + public function setPosition(?int $position): BrandChoiceFilter + { + $this->position = $position; + + return $this; + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/Api/CategoryChoiceFilter.php b/domokits/local/modules/ChoiceFilter/Model/Api/CategoryChoiceFilter.php new file mode 100644 index 0000000..821c5a8 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/Api/CategoryChoiceFilter.php @@ -0,0 +1,149 @@ +id; + } + + public function setId(int $id): CategoryChoiceFilter + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(?string $title): CategoryChoiceFilter + { + $this->title = $title; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + /** + * @param bool $visible + */ + public function setVisible(?bool $visible = true): CategoryChoiceFilter + { + $this->visible = $visible; + + return $this; + } + + /** + * @return int + */ + public function getPosition(): ?int + { + return $this->position; + } + + /** + * @param int $position + */ + public function setPosition(?int $position): CategoryChoiceFilter + { + $this->position = $position; + + return $this; + } + + /** + * @return int + */ + public function getParent(): ?int + { + return $this->parent; + } + + /** + * @param int|null $parent + * @return CategoryChoiceFilter + */ + public function setParent(?int $parent): CategoryChoiceFilter + { + $this->parent = $parent; + + return $this; + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilter.php b/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilter.php new file mode 100644 index 0000000..81e89f7 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilter.php @@ -0,0 +1,226 @@ +id; + } + + public function setId(int $id): ChoiceFilter + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(?string $title): ChoiceFilter + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getType(): ?string + { + return $this->type; + } + + /** + * @param string $title + */ + public function setType(?string $type): ChoiceFilter + { + $this->type = $type; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + /** + * @param bool $visible + */ + public function setVisible(?bool $visible = true): ChoiceFilter + { + $this->visible = $visible; + + return $this; + } + + /** + * @return int + */ + public function getPosition(): ?int + { + return $this->position; + } + + /** + * @param int $position + */ + public function setPosition(?int $position): ChoiceFilter + { + $this->position = $position; + + return $this; + } + + public function getValues(): array + { + return $this->values; + } + + /** + * @param int $values + */ + public function setValues(?array $values): ChoiceFilter + { + $this->values = $values; + + return $this; + } + + protected function getTheliaModel($propelModelName = null) + { + return parent::getTheliaModel(\ChoiceFilter\Model\ChoiceFilter::class); + } + + public function createOrUpdateFromData($data, $locale = null): void + { + parent::createOrUpdateFromData($data, $locale); + + $values = []; + + $modelFactory = $this->modelFactory; + if ($this->type === "feature") { + $values = array_map( + function (FeatureAv $featureAv) use ($modelFactory) { + return $modelFactory->buildModel('ChoiceFilterValue', + [ + 'id' => $featureAv->getId(), + 'title' => $featureAv->getTitle(), + 'position' => $featureAv->getPosition() + ] + ); + }, + iterator_to_array( + FeatureAvQuery::create() + ->filterByFeatureId($this->getId()) + ->useFeatureAvI18nQuery() + ->filterByLocale($locale) + ->endUse() + ->find() + ) + ); + } + if ($this->type === "attribute") { + $values = array_map( + function (AttributeAv $attributeAv) use ($modelFactory) { + return $modelFactory->buildModel('ChoiceFilterValue', + [ + 'id' => $attributeAv->getId(), + 'title' => $attributeAv->getTitle(), + 'position' => $attributeAv->getPosition() + ] + ); + }, + iterator_to_array( + AttributeAvQuery::create() + ->filterByAttributeId($this->getId()) + ->useAttributeAvI18nQuery() + ->filterByLocale($locale) + ->endUse() + ->find() + ) + ); + } + + $this->setValues($values); + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilterValue.php b/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilterValue.php new file mode 100644 index 0000000..acd4330 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/Api/ChoiceFilterValue.php @@ -0,0 +1,88 @@ +id; + } + + public function setId(int $id): ChoiceFilterValue + { + $this->id = $id; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): ChoiceFilterValue + { + $this->title = $title; + + return $this; + } + + /** + * @return int + */ + public function getPosition(): ?int + { + return $this->position; + } + + /** + * @param int $position + */ + public function setPosition(?int $position): ChoiceFilterValue + { + $this->position = $position; + + return $this; + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/ChoiceFilter.php b/domokits/local/modules/ChoiceFilter/Model/ChoiceFilter.php new file mode 100644 index 0000000..28ce769 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/ChoiceFilter.php @@ -0,0 +1,10 @@ +useChoiceFilterOtherI18nQuery(null, Criteria::LEFT_JOIN) + ->endUse(); + + $locales = array_map(function ($value) { + return '"' . $value . '"'; + }, $locales); + + $otherQuery->addJoinCondition('ChoiceFilterOtherI18n', 'ChoiceFilterOtherI18n.locale IN (' . implode(',', $locales) . ')'); + + $otherQuery->withColumn('ChoiceFilterOtherI18n.title', 'Title'); + + $otherQuery->groupBy('id'); + + return $otherQuery->find(); + } +} diff --git a/domokits/local/modules/ChoiceFilter/Model/ChoiceFilterQuery.php b/domokits/local/modules/ChoiceFilter/Model/ChoiceFilterQuery.php new file mode 100644 index 0000000..2f88ee4 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Model/ChoiceFilterQuery.php @@ -0,0 +1,192 @@ +useAttributeTemplateQuery() + ->filterByTemplateId($templateId) + ->endUse(); + + $attributeQuery->useAttributeI18nQuery(null, Criteria::LEFT_JOIN) + ->endUse(); + + $locales = array_map(function ($value) { + return '"' . $value . '"'; + }, $locales); + + $attributeQuery->addJoinCondition('AttributeI18n', 'AttributeI18n.locale IN (' . implode(',', $locales) . ')'); + + $attributeQuery->withColumn('AttributeI18n.title', 'Title'); + $attributeQuery->withColumn('AttributeTemplate.position', 'Position'); + + $attributeQuery->groupBy('id'); + + $attributeQuery->orderBy('position'); + + return $attributeQuery->find(); + } + + /** + * @param int $templateId + * @param string[] list of locale + * @return Feature[]|ObjectCollection + */ + public static function findFeaturesByTemplateId($templateId, $locales = ['en_US']) + { + $featureQuery = FeatureQuery::create(); + + $featureQuery->useFeatureTemplateQuery() + ->filterByTemplateId($templateId) + ->endUse(); + + $featureQuery->useFeatureI18nQuery(null, Criteria::LEFT_JOIN) + ->endUse(); + + $locales = array_map(function ($value) { + return '"' . $value . '"'; + }, $locales); + + $featureQuery->addJoinCondition('FeatureI18n', 'FeatureI18n.locale IN (' . implode(',', $locales) . ')'); + + $featureQuery->withColumn('FeatureI18n.title', 'Title'); + $featureQuery->withColumn('FeatureTemplate.position', 'Position'); + + $featureQuery->groupBy('id'); + + $featureQuery->orderBy('position'); + + return $featureQuery->find(); + } + + /** + * @param Category $category + * @return Category[] + */ + protected static function getParentCategoriesHasTemplate(Category $category) + { + $categories = []; + if (0 !== (int) $category->getParent()) { + $category = CategoryQuery::create()->filterById($category->getParent())->findOne(); + + if (null !== $category->getDefaultTemplateId()) { + $categories[] = $category; + } + + $categories = $categories + static::getParentCategoriesHasTemplate($category); + } + + return $categories; + } + + public static function findChoiceFilterByCategory( + Category $category, + &$templateId = null, + &$categoryId = null + ) { + $choiceFilters = ChoiceFilterQuery::create() + ->filterByCategoryId($category->getId()) + ->orderByPosition() + ->find(); + + $parents = static::getParentCategoriesHasTemplate($category); + + if (count($choiceFilters)) { + if (null !== $category->getDefaultTemplateId()) { + $templateId = $category->getDefaultTemplateId(); + $categoryId = $category->getId(); + return $choiceFilters; + } else { + foreach ($parents as $parent) { + if (null !== $parent->getDefaultTemplateId()) { + $templateId = $parent->getDefaultTemplateId(); + $categoryId = $category->getId(); + return $choiceFilters; + } + } + } + } + + if (null !== $category->getDefaultTemplateId()) { + $choiceFilters = ChoiceFilterQuery::create() + ->filterByCategoryId($category->getId()) + ->orderByPosition() + ->find(); + + if (count($choiceFilters)) { + $templateId = $category->getDefaultTemplateId(); + $categoryId = $category->getId(); + return $choiceFilters; + } + } + + foreach ($parents as $parent) { + $choiceFilters = ChoiceFilterQuery::create() + ->filterByCategoryId($parent->getId()) + ->orderByPosition() + ->find(); + + if (count($choiceFilters)) { + $templateId = $parent->getDefaultTemplateId(); + $categoryId = $parent->getId(); + return $choiceFilters; + } + } + + if (null !== $category->getDefaultTemplateId()) { + $choiceFilters = ChoiceFilterQuery::create() + ->filterByTemplateId($category->getDefaultTemplateId()) + ->orderByPosition() + ->find(); + + $templateId = $category->getDefaultTemplateId(); + $categoryId = null; + return $choiceFilters; + } + + foreach ($parents as $parent) { + $choiceFilters = ChoiceFilterQuery::create() + ->filterByTemplateId($parent->getDefaultTemplateId()) + ->orderByPosition() + ->find(); + + if (count($choiceFilters)) { + $templateId = $parent->getDefaultTemplateId(); + $categoryId = null; + return $choiceFilters; + } + } + + return new ObjectCollection(); + } +} diff --git a/domokits/local/modules/ChoiceFilter/Readme.md b/domokits/local/modules/ChoiceFilter/Readme.md new file mode 100644 index 0000000..a71e1ca --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Readme.md @@ -0,0 +1,108 @@ +# Choice Filter + +This module allows the management of filters in front by template and category + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is ChoiceFilter. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/choice-filter-module:~2.0.0 +``` + +## Usage + +You can choose the position of the filters +- When you edit a template +- When you edit a category + +## Loop + +[choice_filter] + +### Input arguments + +|Argument |Description | +|--- |--- | +|**template_id** | id of template | +|**category_id** | id of category | +|**order** | `position` or `position_reverse` | + +### Output arguments + +|Variable |Description | +|--- |--- | +|**$TYPE** | `feature` or `attribute` or other | +|**$ID** | id of filter | +|**$POSITION** | position of filter | +|**$VISIBLE** | visible of filter | + +### Exemple + +#### For a template +```smarty +{loop name="choice_filter" type="choice_filter" template_id=$template_id} + {if $TYPE == "feature" and $VISIBLE} + {loop type="feature" name="feature-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "attribute" and $VISIBLE} + {loop type="attribute" name="attribute-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "brand" and $VISIBLE} + {* your code *} + {elseif $TYPE == "price" and $VISIBLE} + {* your code *} + {/if} +{/loop} +``` + +#### For a category +```smarty +{loop name="choice_filter" type="choice_filter" category_id=$category_id} + {if $TYPE == "feature" and $VISIBLE} + {loop type="feature" name="feature-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "attribute" and $VISIBLE} + {loop type="attribute" name="attribute-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "brand" and $VISIBLE} + {* your code *} + {elseif $TYPE == "price" and $VISIBLE} + {* your code *} + {/if} +{/loop} +``` + +for performance, it is best to use a cache block +http://doc.thelia.net/en/documentation/templates/smarty/cache.html + +```smarty +{cache key="choice-filter" ttl=600 category_id==$category_id} + {loop name="choice_filter" type="choice_filter" category_id=$category_id} + {if $TYPE == "feature" and $VISIBLE} + {loop type="feature" name="feature-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "attribute" and $VISIBLE} + {loop type="attribute" name="attribute-$ID" id=$ID} + {* your code *} + {/loop} + {elseif $TYPE == "brand" and $VISIBLE} + {* your code *} + {elseif $TYPE == "price" and $VISIBLE} + {* your code *} + {/if} + {/loop} +{/cache} +``` diff --git a/domokits/local/modules/ChoiceFilter/Util.php b/domokits/local/modules/ChoiceFilter/Util.php new file mode 100644 index 0000000..78bbef6 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/Util.php @@ -0,0 +1,79 @@ + 'feature', 'Visible' => 1]); + }, $features->toArray()); + + $attributesArray = array_map(function ($attribute) { + return array_merge($attribute, ['Type' => 'attribute', 'Visible' => 1]); + }, $attributes->toArray()); + + $othersArray = array_map(function ($other) { + return $other; + }, $others->toArray()); + + if (count($choiceFilters)) { + $merge = []; + foreach ($choiceFilters as $choiceFilter) { + if (null !== $attributeId = $choiceFilter->getAttributeId()) { + foreach ($attributesArray as $key => $attributeArray) { + if ($attributeId == $attributeArray['Id']) { + $attributeArray['Visible'] = $choiceFilter->getVisible() ? 1 : 0; + $merge[] = $attributeArray; + unset($attributesArray[$key]); + } + } + } elseif (null !== $featureId = $choiceFilter->getFeatureId()) { + foreach ($featuresArray as $key => $featureArray) { + if ($featureId == $featureArray['Id']) { + $featureArray['Visible'] = $choiceFilter->getVisible() ? 1 : 0; + $merge[] = $featureArray; + unset($featuresArray[$key]); + } + } + } elseif (null !== $type = $choiceFilter->getChoiceFilterOther()->getType()) { // todo ajouter une jointure pour le type + foreach ($othersArray as $key => $otherArray) { + if ($type == $otherArray['Type']) { + $otherArray['Visible'] = $choiceFilter->getVisible() ? 1 : 0; + $merge[] = $otherArray; + unset($othersArray[$key]); + } + } + } + } + + $merge = array_merge($merge, $attributesArray); + $merge = array_merge($merge, $featuresArray); + $merge = array_merge($merge, $othersArray); + } else { + $merge = array_merge($attributesArray, $featuresArray); + $merge = array_merge($merge, $othersArray); + } + + $p = 1; + $merge = array_map(function ($array) use (&$p) { + return array_merge($array, ['Position' => $p++]); + }, $merge); + + return $merge; + } +} diff --git a/domokits/local/modules/ChoiceFilter/composer.json b/domokits/local/modules/ChoiceFilter/composer.json new file mode 100644 index 0000000..d711527 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/choice-filter-module", + "description": "Allows the management of filters in front by template and category", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "ChoiceFilter" + } +} diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/category-edit.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/category-edit.html new file mode 100644 index 0000000..3e2dbe6 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/category-edit.html @@ -0,0 +1 @@ +{hook name="category.tab-content" category_id=$category_id id=$category_id view="category"} \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.edit-js.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.edit-js.html new file mode 100644 index 0000000..af65a05 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.edit-js.html @@ -0,0 +1,27 @@ + \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.tab-content.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.tab-content.html new file mode 100644 index 0000000..a4908c7 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/category.tab-content.html @@ -0,0 +1 @@ +{include file="choice-filter/hook/template-edit.bottom.html" category_id=$category_id enabled=$enabled messageInfo=$messageInfo} \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template-edit.bottom.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template-edit.bottom.html new file mode 100644 index 0000000..5f1657d --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template-edit.bottom.html @@ -0,0 +1,117 @@ +
+
+
+

+ {if isset($template_id)} + {intl l="Position of the filters in front for this template" d="choicefilter.bo.default"} + {/if} + {if isset($category_id)} + {intl l="Position of the filters in front for this category" d="choicefilter.bo.default"} + {/if} +

+ + {flash type="choice-filter-success"} +
+ {$MESSAGE} +
+ {/flash} + {if $messageInfo|default:[]|count} +
+ {foreach from=$messageInfo item=message} + {$message}
+ {/foreach} +
+ {/if} + + {if !$enabled} +
+ {intl l="The position of the filters is for now the default one." d="choicefilter.bo.default"} +
+ {/if} + +
+ + {if isset($template_id)} + + {/if} + + {if isset($category_id)} + + {/if} + + {if !$enabled} + + {/if} + + + + + + + + + + + + + {foreach from=$filters key=key item=filter} + + + + + + + + {/foreach} + + + + + + +
{intl l="ID" d="choicefilter.bo.default"}{intl l="Type" d="choicefilter.bo.default"}{intl l="Title" d="choicefilter.bo.default"}{intl l="Visible" d="choicefilter.bo.default"}{intl l="Position" d="choicefilter.bo.default"}
+ + {$filter.Id} + + + {$filter.Type} + + {$filter.Title} + + + + +
+ +
+
+ + {if $enabled} +
+ {if isset($template_id)} + + {/if} + + {if isset($category_id)} + + {/if} + + +
+
+
+ {/if} +
+
+
diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template.edit-js.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template.edit-js.html new file mode 100644 index 0000000..4bdc391 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/hook/template.edit-js.html @@ -0,0 +1 @@ +{include file="choice-filter/hook/category.edit-js.html"} \ No newline at end of file diff --git a/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/template-edit.html b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/template-edit.html new file mode 100644 index 0000000..b46fd82 --- /dev/null +++ b/domokits/local/modules/ChoiceFilter/templates/backOffice/default/choice-filter/template-edit.html @@ -0,0 +1 @@ +{hook name="template-edit.bottom" template_id=$template_id|default:null} diff --git a/domokits/local/modules/CustomDelivery/.github/workflows/release.yml b/domokits/local/modules/CustomDelivery/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/CustomDelivery/CHANGELOG.md b/domokits/local/modules/CustomDelivery/CHANGELOG.md new file mode 100644 index 0000000..c9970d4 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/CHANGELOG.md @@ -0,0 +1,17 @@ +# 1.0.7 + +- The dispatcher is now passed to Session::getSessionCart() + +# 1.0.3 + +- Fixed stability tag in module.xml + +# 1.0.2 + +- Removed bootbox dependency +- Added alert when no shipping zones defined +- Better float validation + +# 1.0.1 + +- Resolve #4 Add js dependency bootbox diff --git a/domokits/local/modules/CustomDelivery/Config/config.xml b/domokits/local/modules/CustomDelivery/Config/config.xml new file mode 100644 index 0000000..64b71cb --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/CustomDelivery/Config/module.xml b/domokits/local/modules/CustomDelivery/Config/module.xml new file mode 100644 index 0000000..6aaff34 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/module.xml @@ -0,0 +1,24 @@ + + + CustomDelivery\CustomDelivery + + Custom Delivery + + + Livraison Personnalisée + + + en_US + fr_FR + + 3.1.3 + + Julien Chanséaume + julien@thelia.net + + delivery + 2.5.0 + prod + diff --git a/domokits/local/modules/CustomDelivery/Config/routing.xml b/domokits/local/modules/CustomDelivery/Config/routing.xml new file mode 100644 index 0000000..413e2b8 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/routing.xml @@ -0,0 +1,19 @@ + + + + + + CustomDelivery\Controller\BackController::saveAction + + + + CustomDelivery\Controller\BackController::deleteAction + + + + CustomDelivery\Controller\BackController::saveConfigurationAction + + + diff --git a/domokits/local/modules/CustomDelivery/Config/schema.xml b/domokits/local/modules/CustomDelivery/Config/schema.xml new file mode 100644 index 0000000..2dd4af5 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/schema.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/CustomDelivery/Config/sqldb.map b/domokits/local/modules/CustomDelivery/Config/sqldb.map new file mode 100644 index 0000000..63a93ba --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +thelia.sql=thelia diff --git a/domokits/local/modules/CustomDelivery/Config/thelia.sql b/domokits/local/modules/CustomDelivery/Config/thelia.sql new file mode 100644 index 0000000..298b293 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Config/thelia.sql @@ -0,0 +1,29 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- custom_delivery_slice +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `custom_delivery_slice`; + +CREATE TABLE `custom_delivery_slice` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `area_id` INTEGER NOT NULL, + `price_max` FLOAT DEFAULT 0, + `weight_max` FLOAT DEFAULT 0, + `price` FLOAT DEFAULT 0, + PRIMARY KEY (`id`), + INDEX `FI_area_id` (`area_id`), + CONSTRAINT `fk_area_id` + FOREIGN KEY (`area_id`) + REFERENCES `area` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/CustomDelivery/Controller/BackController.php b/domokits/local/modules/CustomDelivery/Controller/BackController.php new file mode 100644 index 0000000..fa790ae --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Controller/BackController.php @@ -0,0 +1,245 @@ + + */ +class BackController extends BaseAdminController +{ + protected $currentRouter = 'router.customdelivery'; + + protected $useFallbackTemplate = true; + + /** + * Save slice + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function saveAction(Request $request) + { + $response = $this->checkAuth([], ['customdelivery'], AccessManager::UPDATE); + + if (null !== $response) { + return $response; + } + + $this->checkXmlHttpRequest(); + + $responseData = [ + "success" => false, + "message" => '', + "slice" => null + ]; + + $messages = []; + $response = null; + $config = CustomDelivery::getConfig(); + + try { + if (0 !== $id = (int)$request->get('id', 0)) { + $slice = CustomDeliverySliceQuery::create()->findPk($id); + } else { + $slice = new CustomDeliverySlice(); + } + + if (0 !== $areaId = (int)$request->get('area', 0)) { + $slice->setAreaId($areaId); + } else { + $messages[] = Translator::getInstance()->trans( + 'The area is not valid', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + } + + if ($config['method'] !== CustomDelivery::METHOD_WEIGHT) { + $priceMax = $this->getFloatVal($request->get('priceMax', 0)); + if (0 < $priceMax) { + $slice->setPriceMax($priceMax); + } else { + $messages[] = Translator::getInstance()->trans( + 'The price max value is not valid', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + } + } + + if ($config['method'] !== CustomDelivery::METHOD_PRICE) { + $weightMax = $this->getFloatVal($request->get('weightMax', 0)); + if (0 < $weightMax) { + $slice->setWeightMax($weightMax); + } else { + $messages[] = Translator::getInstance()->trans( + 'The weight max value is not valid', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + } + } + + $price = $this->getFloatVal($request->get('price', 0)); + if (0 <= $price) { + $slice->setPrice($price); + } else { + $messages[] = Translator::getInstance()->trans( + 'The price value is not valid', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + } + + if (0 === count($messages)) { + $slice->save(); + $messages[] = Translator::getInstance()->trans( + 'Your slice has been saved', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + + $responseData['success'] = true; + $responseData['slice'] = $slice->toArray(TableMap::TYPE_STUDLYPHPNAME); + } + } catch (\Exception $e) { + $message[] = $e->getMessage(); + } + + $responseData['message'] = $messages; + + return $this->jsonResponse(json_encode($responseData)); + } + + protected function getFloatVal($val, $default=-1) + { + if (preg_match("#^([\d.,]+)$#", $val, $match)) { + return (float) str_replace(array('.', ','), array('', '.'), $match[0]); + } + + return $default; + } + + /** + * Save slice + * + * @return Response + */ + public function deleteAction(Request $request) + { + $response = $this->checkAuth([], ['customdelivery'], AccessManager::DELETE); + + if (null !== $response) { + return $response; + } + + $this->checkXmlHttpRequest(); + + $responseData = [ + "success" => false, + "message" => '', + "slice" => null + ]; + + $response = null; + + try { + if (0 !== $id = (int)$request->get('id', 0)) { + $slice = CustomDeliverySliceQuery::create()->findPk($id); + $slice->delete(); + $responseData['success'] = true; + } else { + $responseData['message'] = Translator::getInstance()->trans( + 'The slice has not been deleted', + [], + CustomDelivery::MESSAGE_DOMAIN + ); + } + } catch (\Exception $e) { + $responseData['message'] = $e->getMessage(); + } + + return $this->jsonResponse(json_encode($responseData)); + } + + /** + * Save module configuration + * + * @param ParserContext $parserContext + * @return \Symfony\Component\HttpFoundation\Response + */ + public function saveConfigurationAction() + { + $response = $this->checkAuth([AdminResources::MODULE], ['customdelivery'], AccessManager::UPDATE); + + if (null !== $response) { + return $response; + } + + $form = $this->createForm('customdelivery.configuration.form'); + $message = ""; + + $response = null; + + try { + $vform = $this->validateForm($form); + $data = $vform->getData(); + + ConfigQuery::write( + CustomDelivery::CONFIG_TRACKING_URL, + $data['url'] + ); + ConfigQuery::write( + CustomDelivery::CONFIG_PICKING_METHOD, + $data['method'] + ); + ConfigQuery::write( + CustomDelivery::CONFIG_TAX_RULE_ID, + $data['tax'] + ); + } catch (\Exception $e) { + $message = $e->getMessage(); + } + if ($message) { + $form->setErrorMessage($message); + $this->getParserContext() + ->addForm($form) + ->setGeneralError($message); + + return $this->render( + "module-configure", + ["module_code" => CustomDelivery::getModuleCode()] + ); + } + + return $this->generateRedirect( + URL::getInstance()->absoluteUrl("/admin/module/" . CustomDelivery::getModuleCode()) + ); + } +} diff --git a/domokits/local/modules/CustomDelivery/CustomDelivery.php b/domokits/local/modules/CustomDelivery/CustomDelivery.php new file mode 100755 index 0000000..5f6ca1e --- /dev/null +++ b/domokits/local/modules/CustomDelivery/CustomDelivery.php @@ -0,0 +1,288 @@ + ( + ConfigQuery::read(self::CONFIG_TRACKING_URL, self::DEFAULT_TRACKING_URL) + ), + 'method' => ( + intval(ConfigQuery::read(self::CONFIG_PICKING_METHOD, self::DEFAULT_PICKING_METHOD)) + ), + 'tax' => ( + intval(ConfigQuery::read(self::CONFIG_TAX_RULE_ID)) + ) + ]; + + return $config; + } + + public function postActivation(ConnectionInterface $con = null): void + { + if (!$this->getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, array(__DIR__ . '/Config/thelia.sql')); + + $this->setConfigValue('is_initialized', true); + } + + // register config variables + if (null === ConfigQuery::read(self::CONFIG_TRACKING_URL, null)) { + ConfigQuery::write(self::CONFIG_TRACKING_URL, self::DEFAULT_TRACKING_URL); + } + + if (null === ConfigQuery::read(self::CONFIG_PICKING_METHOD, null)) { + ConfigQuery::write(self::CONFIG_PICKING_METHOD, self::DEFAULT_PICKING_METHOD); + } + + // create new message + if (null === MessageQuery::create()->findOneByName('mail_custom_delivery')) { + + $message = new Message(); + $message + ->setName('mail_custom_delivery') + ->setHtmlTemplateFileName('custom-delivery-shipping.html') + ->setHtmlLayoutFileName('') + ->setTextTemplateFileName('custom-delivery-shipping.txt') + ->setTextLayoutFileName('') + ->setSecured(0); + + $languages = LangQuery::create()->find(); + + foreach ($languages as $language) { + $locale = $language->getLocale(); + + $message->setLocale($locale); + + $message->setTitle( + $this->trans('Custom delivery shipping message', [], $locale) + ); + $message->setSubject( + $this->trans('Your order {$order_ref} has been shipped', [], $locale) + ); + } + + $message->save(); + } + } + + /** + * This method is called by the Delivery loop, to check if the current module has to be displayed to the customer. + * Override it to implements your delivery rules/ + * + * If you return true, the delivery method will de displayed to the customer + * If you return false, the delivery method will not be displayed + * + * @param Country $country the country to deliver to. + * @param State $state the state to deliver to. + * + * @return boolean + */ + public function isValidDelivery(Country $country, State $state = null) + { + // Retrieve the cart + $cart = $this->getRequest()->getSession()->getSessionCart($this->getDispatcher()); + + /** @var CustomDeliverySlice $slice */ + $slice = $this->getSlicePostage($cart, $country, $state); + + return null !== $slice; + } + + /** + * Calculate and return delivery price in the shop's default currency + * + * @param Country $country the country to deliver to. + * @param State $state the state to deliver to. + * + * @return OrderPostage the delivery price + * @throws DeliveryException if the postage price cannot be calculated. + */ + public function getPostage(Country $country, State $state = null) + { + $cart = $this->getRequest()->getSession()->getSessionCart($this->getDispatcher()); + + /** @var CustomDeliverySlice $slice */ + $postage = $this->getSlicePostage($cart, $country, $state); + + if (null === $postage) { + throw new DeliveryException(); + } + + return $postage; + } + + /** + * + * This method return true if your delivery manages virtual product delivery. + * + * @return bool + */ + public function handleVirtualProductDelivery() + { + return false; + } + + protected function trans($id, array $parameters = [], $locale = null) + { + if (null === $this->translator) { + $this->translator = Translator::getInstance(); + } + + return $this->translator->trans($id, $parameters, CustomDelivery::MESSAGE_DOMAIN, $locale); + } + + public function getDeliveryMode() + { + return 'delivery'; + } + + /** + * If a state is given and has slices, use them. + * If state is given but has no slices, check if the country has slices. + * If the country has slices, use them. + * If the country has no slices, the module is not valid for delivery + * + * @param Cart $cart + * @param Country $country + * @param State $state + * @return OrderPostage|null + */ + protected function getSlicePostage(Cart $cart, Country $country, State $state = null) + { + $config = self::getConfig(); + $currency = $cart->getCurrency(); + /** @var CustomDeliverySlice $slice */ + $slice = null; + + if (null !== $state && null !== $areas = CountryAreaQuery::create() + ->filterByStateId($state->getId()) + ->select([CountryAreaTableMap::AREA_ID]) + ->find() + ) { + $slice = $this->getAreaSlice($areas, $cart, $currency, $config); + } + + if (null === $slice && null !== $areas = CountryAreaQuery::create() + ->filterByCountryId($country->getId()) + ->filterByStateId(null) + ->select([CountryAreaTableMap::AREA_ID]) + ->find() + ) { + $slice = $this->getAreaSlice($areas, $cart, $currency, $config); + } + + if ($slice === null) { + return null; + } + + return $this->getAreaPostage($slice, $currency, $country, $config); + } + + /** + * @param $areas + * @param Cart $cart + * @param Currency $currency + * @param $config + * @return CustomDeliverySlice + */ + protected function getAreaSlice($areas, Cart $cart, Currency $currency, $config) + { + $query = CustomDeliverySliceQuery::create()->filterByAreaId($areas, Criteria::IN); + + if ($config['method'] != CustomDelivery::METHOD_PRICE) { + $query->filterByWeightMax($cart->getWeight(), Criteria::GREATER_THAN); + $query->orderByWeightMax(Criteria::ASC); + } + + if ($config['method'] != CustomDelivery::METHOD_WEIGHT) { + $total = $cart->getTotalAmount(); + // convert amount to the default currency + if (0 == $currency->getByDefault()) { + $total = $total / $currency->getRate(); + } + + $query->filterByPriceMax($total, Criteria::GREATER_THAN); + $query->orderByPriceMax(Criteria::ASC); + } + + return $query->findOne(); + } + + /** + * @param CustomDeliverySlice $slice + * @param Currency $currency + * @param Country $country + * @param $config + * @return OrderPostage + */ + protected function getAreaPostage(CustomDeliverySlice $slice, Currency $currency, Country $country, $config) + { + if (0 == $currency->getByDefault()) { + $untaxedPostage = $slice->getPrice() * $currency->getRate(); + } else { + $untaxedPostage = $slice->getPrice(); + } + $untaxedPostage = round($untaxedPostage, 2); + + $locale = $this->getRequest()->getSession()->getLang()->getLocale(); + + return $this->buildOrderPostage($untaxedPostage, $country, $locale, $config['tax']); + } +} diff --git a/domokits/local/modules/CustomDelivery/EventListeners/ApiListener.php b/domokits/local/modules/CustomDelivery/EventListeners/ApiListener.php new file mode 100644 index 0000000..52b7a84 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/EventListeners/ApiListener.php @@ -0,0 +1,104 @@ +container = $container; + $this->request = $requestStack->getCurrentRequest(); + } + + public function getDeliveryModuleOptions(DeliveryModuleOptionEvent $deliveryModuleOptionEvent) + { + if ($deliveryModuleOptionEvent->getModule()->getId() !== CustomDelivery::getModuleId()) { + return ; + } + $isValid = true; + $postage = null; + $postageTax = null; + + $locale = $this->request->getSession()->getLang()->getLocale(); + + $propelModule = ModuleQuery::create() + ->filterById(CustomDelivery::getModuleId()) + ->findOne() + ->setLocale($locale); + + try { + $module = $propelModule->getModuleInstance($this->container); + $country = $deliveryModuleOptionEvent->getCountry(); + $state = $deliveryModuleOptionEvent->getState(); + + if (empty($module->isValidDelivery($country, $state))) { + throw new DeliveryException(Translator::getInstance()->trans("Custom delivery is not available")); + } + + /** @var OrderPostage $orderPostage */ + $orderPostage = $module->getPostage($country, $state); + $postage = $orderPostage->getAmount(); + $postageTax = $orderPostage->getAmountTax(); + } catch (\Exception $exception) { + $isValid = false; + } + + $minimumDeliveryDate = ''; // TODO (calculate delivery date from day of order) + $maximumDeliveryDate = ''; // TODO (calculate delivery date from day of order + + /** @var DeliveryModuleOption $deliveryModuleOption */ + $deliveryModuleOption = ($this->container->get('open_api.model.factory'))->buildModel('DeliveryModuleOption'); + $deliveryModuleOption + ->setCode(CustomDelivery::getModuleCode()) + ->setValid($isValid) + ->setTitle($propelModule->getTitle()) + ->setImage('') + ->setMinimumDeliveryDate($minimumDeliveryDate) + ->setMaximumDeliveryDate($maximumDeliveryDate) + ->setPostage($postage) + ->setPostageTax($postageTax) + ->setPostageUntaxed($postage - $postageTax) + ; + + $deliveryModuleOptionEvent->appendDeliveryModuleOptions($deliveryModuleOption); + } + + public static function getSubscribedEvents() + { + $listenedEvents = []; + + /** Check for old versions of Thelia where the events used by the API didn't exists */ + if (class_exists(DeliveryModuleOptionEvent::class)) { + $listenedEvents[OpenApiEvents::MODULE_DELIVERY_GET_OPTIONS] = array("getDeliveryModuleOptions", 129); + } + + return $listenedEvents; + } +} diff --git a/domokits/local/modules/CustomDelivery/EventListeners/CustomDeliveryEvents.php b/domokits/local/modules/CustomDelivery/EventListeners/CustomDeliveryEvents.php new file mode 100644 index 0000000..5f3c28d --- /dev/null +++ b/domokits/local/modules/CustomDelivery/EventListeners/CustomDeliveryEvents.php @@ -0,0 +1,127 @@ + + */ +class CustomDeliveryEvents implements EventSubscriberInterface +{ + protected $parser; + + protected $mailer; + + public function __construct(ParserInterface $parser, MailerFactory $mailer) + { + $this->parser = $parser; + $this->mailer = $mailer; + } + + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_UPDATE_STATUS => ["updateStatus", 128] + ]; + } + + public function updateStatus(OrderEvent $event) + { + $order = $event->getOrder(); + $customDelivery = new CustomDelivery(); + + if ($order->isSent() && $order->getDeliveryModuleId() == $customDelivery->getModuleModel()->getId()) { + $contactEmail = ConfigQuery::getStoreEmail(); + + if ($contactEmail) { + + $message = MessageQuery::create() + ->filterByName('mail_custom_delivery') + ->findOne(); + + if (false === $message) { + throw new \Exception("Failed to load message 'mail_custom_delivery'."); + } + + $order = $event->getOrder(); + $customer = $order->getCustomer(); + $package = $order->getDeliveryRef(); + $trackingUrl = null; + + if (!empty($package)) { + $config = CustomDelivery::getConfig(); + $trackingUrl = $config['url']; + if (!empty($trackingUrl)) { + $trackingUrl = str_replace('%ID%', $package, $trackingUrl); + } + } + + $this->mailer->sendEmailMessage( + 'mail_custom_delivery', + [$contactEmail => ConfigQuery::getStoreName()], + [$customer->getEmail() => $customer->getFirstname() . " " . $customer->getLastname()], + [ + 'customer_id' => $customer->getId(), + 'order_id' => $order->getId(), + 'order_ref' => $order->getRef(), + 'order_date' => $order->getCreatedAt(), + 'update_date' => $order->getUpdatedAt(), + 'package' => $package, + 'tracking_url' => $trackingUrl + ] + ); + + Tlog::getInstance()->debug( + "Custom Delivery shipping message sent to customer " . $customer->getEmail() + ); + } else { + $customer = $order->getCustomer(); + Tlog::getInstance()->debug( + "Custom Delivery shipping message no contact email customer_id ".$customer->getId() + ); + } + } + } +} diff --git a/domokits/local/modules/CustomDelivery/Form/ConfigurationForm.php b/domokits/local/modules/CustomDelivery/Form/ConfigurationForm.php new file mode 100644 index 0000000..1a8ec86 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Form/ConfigurationForm.php @@ -0,0 +1,130 @@ + + */ +class ConfigurationForm extends BaseForm +{ + public function checkTaxRuleId($value, ExecutionContextInterface $context) + { + if (0 !== intval($value)) { + if (null === TaxRuleQuery::create()->findPk($value)) { + $context->addViolation( + $this->trans( + "The Tax Rule id '%id' doesn't exist", + [ + "%id" => $value, + ] + ) + ); + } + } + } + + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return "customdelivery-configuration-form"; + } + + protected function buildForm() + { + $form = $this->formBuilder; + + $config = CustomDelivery::getConfig(); + + $form + ->add( + "url", + TextType::class, + [ + 'constraints' => [ + new NotBlank() + ], + 'data' => $config['url'], + 'label' => $this->trans("Tracking URL"), + 'label_attr' => [ + 'for' => "url", + 'help' => $this->trans( + "The tracking URL. %ID% will be replaced by the tracking number entered in the order" + ) + ], + ] + ) + ->add( + "method", + ChoiceType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(['value' => 0]) + ], + "choices" => [ + $this->trans("Price and weight") => CustomDelivery::METHOD_PRICE_WEIGHT, + $this->trans("Price") => CustomDelivery::METHOD_PRICE, + $this->trans("Weight") =>CustomDelivery::METHOD_WEIGHT + ], + 'data' => $config['method'], + 'label' => $this->trans("Method"), + 'label_attr' => [ + 'for' => "method", + 'help' => $this->trans( + "The method used to select the right slice." + ) + ], + ] + ) + ->add( + "tax", + TaxRuleIdType::class, + [ + "constraints" => [ + new Callback( + [$this, 'checkTaxRuleId'] + ), + ], + 'required' => false, + 'data' => $config['tax'], + 'label' => $this->trans("Tax rule"), + 'label_attr' => [ + 'for' => "method", + 'help' => $this->trans( + "The tax rule used to calculate postage taxes." + ) + ], + ] + ); + } + + protected function trans($id, array $parameters = []) + { + return $this->translator->trans($id, $parameters, CustomDelivery::MESSAGE_DOMAIN); + } +} diff --git a/domokits/local/modules/CustomDelivery/Form/SliceForm.php b/domokits/local/modules/CustomDelivery/Form/SliceForm.php new file mode 100644 index 0000000..06c40c5 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Form/SliceForm.php @@ -0,0 +1,92 @@ + + */ +class SliceForm extends BaseForm +{ + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return "customdelivery-configuration-form"; + } + + protected function buildForm() + { + $form = $this->formBuilder; + + $form + ->add( + "id", + NumberType::class, + [ + 'constraints' => [ + new NotBlank() + ], + 'label' => $this->trans("Id") + ] + ) + ->add( + "area", + AreaIdType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(['value' => 0]) + ], + 'label' => $this->trans("Area"), + ] + ) + ->add( + "priceMax", + FloatType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(['value' => 0]) + ], + 'label' => $this->trans("Area"), + ] + ) + ->add( + "weightMax", + FloatType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(['value' => 0]) + ], + 'label' => $this->trans("Area"), + ] + ); + } + + protected function trans($id, array $parameters = []) + { + return $this->translator->trans($id, $parameters, CustomDelivery::MESSAGE_DOMAIN); + } +} diff --git a/domokits/local/modules/CustomDelivery/Hook/HookManager.php b/domokits/local/modules/CustomDelivery/Hook/HookManager.php new file mode 100644 index 0000000..e82655a --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Hook/HookManager.php @@ -0,0 +1,67 @@ + + */ +class HookManager extends BaseHook +{ + + public function onAccountOrderAfterProducts(HookRenderEvent $event) + { + $orderId = $event->getArgument('order'); + + if (null !== $orderId) { + $render = $this->render( + 'account-order-after-products.html', + [ + "order_id" => $orderId + ] + ); + $event->add($render); + } + + $event->stopPropagation(); + } + + public function onModuleConfiguration(HookRenderEvent $event) + { + $moduleId = $this->getModule()->getModuleId(); + $config = CustomDelivery::getConfig(); + + $event->add( + $this->render( + "module-configuration.html", + [ + 'module_id' => $moduleId, + 'method' => $config['method'] + ] + ) + ); + } + + public function onModuleConfigJs(HookRenderEvent $event) + { + $event->add( + $this->render("module-config-js.html") + ); + } +} diff --git a/domokits/local/modules/CustomDelivery/I18n/backOffice/default/en_US.php b/domokits/local/modules/CustomDelivery/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..4c34c00 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/backOffice/default/en_US.php @@ -0,0 +1,19 @@ + 'Actions', + 'Add this price slice' => 'Add this price slice', + 'Area : ' => 'Area : ', + 'Configuration.' => 'Configuration.', + 'Delete this price slice' => 'Delete this price slice', + 'Message' => 'Message', + 'No taxes' => 'No taxes', + 'Price (%symbol)' => 'Price (%symbol)', + 'Save' => 'Save', + 'Save this price slice' => 'Save this price slice', + 'Slices.' => 'Slices.', + 'Untaxed Price up to ... %symbol' => 'Untaxed Price up to ... %symbol', + 'Weight up to ... kg' => 'Weight up to ... kg', + 'You should first attribute shipping zones to the modules: ' => 'You should first attribute shipping zones to the module: ', + 'manage shipping zones' => 'manage shipping zones', +); diff --git a/domokits/local/modules/CustomDelivery/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/CustomDelivery/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..6b6e2ef --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,19 @@ + 'Actions', + 'Add this price slice' => 'Ajouter cette tranche', + 'Area : ' => 'Zone :', + 'Configuration.' => 'Configuration.', + 'Delete this price slice' => 'Supprimer cette tranche', + 'Message' => 'Message', + 'No taxes' => 'Pas de taxe', + 'Price (%symbol)' => 'Prix (%symbol)', + 'Save' => 'Enregistrer', + 'Save this price slice' => 'Enregistrer cette tranche', + 'Slices.' => 'Tranches.', + 'Untaxed Price up to ... %symbol' => 'Prix HT jusqu\'à ... %symbol', + 'Weight up to ... kg' => 'Poids jusqu\'à ... kg', + 'You should first attribute shipping zones to the modules: ' => 'Vous devez d\'abord attribuer des zones de livraisons au module :', + 'manage shipping zones' => 'gèrer les zones de livraisons', +); diff --git a/domokits/local/modules/CustomDelivery/I18n/email/default/en_US.php b/domokits/local/modules/CustomDelivery/I18n/email/default/en_US.php new file mode 100644 index 0000000..865088d --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/email/default/en_US.php @@ -0,0 +1,11 @@ + 'Best Regards.', + 'Dear' => 'Dear', + 'Feel free to contact us for any further information' => 'Feel free to contact us for any further information', + 'Please check this URL to track your parcel : %tracking_url' => 'Please check this URL to track your parcel : %tracking_url', + 'Thank you for your order on our online store %store_name' => 'Thank you for your order on our online store %store_name', + 'The tracking number for this delivery is: %package' => 'The tracking number for this delivery is: %package', + 'Your order %order_ref dated %order_date has been shipped on %update_date' => 'Your order %order_ref dated %order_date has been shipped on %update_date', +); diff --git a/domokits/local/modules/CustomDelivery/I18n/email/default/fr_FR.php b/domokits/local/modules/CustomDelivery/I18n/email/default/fr_FR.php new file mode 100644 index 0000000..f86e3cd --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/email/default/fr_FR.php @@ -0,0 +1,11 @@ + 'Cordialement.', + 'Dear' => 'Chère', + 'Feel free to contact us for any further information' => 'Vous pouvez nous contacter pour toutes informations complémentaires', + 'Please check this URL to track your parcel : %tracking_url' => 'Vous pouvez suivre votre colis en ligne à cette adresse : %tracking_url', + 'Thank you for your order on our online store %store_name' => 'Merci pour votre commande sur notre boutique en ligne %store_name', + 'The tracking number for this delivery is: %package' => 'Le numéro de suivi pour cette commande est: %package', + 'Your order %order_ref dated %order_date has been shipped on %update_date' => 'Votre commande %order_ref datée du %order_date a été expédié le %update_date', +); diff --git a/domokits/local/modules/CustomDelivery/I18n/en_US.php b/domokits/local/modules/CustomDelivery/I18n/en_US.php new file mode 100644 index 0000000..79bfd28 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/en_US.php @@ -0,0 +1,24 @@ + 'Area', + 'Custom delivery shipping message' => 'Custom delivery shipping message', + 'Id' => 'Id', + 'Method' => 'Method', + 'Price' => 'Price', + 'Price and weight' => 'Price and weight', + 'Tax rule' => 'Tax rule', + 'The Tax Rule id \'%id\' doesn\'t exist' => 'The Tax Rule id \'%id\' doesn\'t exist', + 'The area is not valid' => 'The area is not valid', + 'The method used to select the right slice.' => 'The method used to select the right slice.', + 'The price max value is not valid' => 'The price max value is not valid', + 'The price value is not valid' => 'The price value is not valid', + 'The slice has not been deleted' => 'The slice has not been deleted', + 'The tax rule used to calculate postage taxes.' => 'The tax rule used to calculate postage taxes.', + 'The tracking URL. %ID% will be replaced by the tracking number entered in the order' => 'The tracking URL. %ID% will be replaced by the tracking number entered in the order', + 'The weight max value is not valid' => 'The weight max value is not valid', + 'Tracking URL' => 'Tracking URL', + 'Weight' => 'Weight', + 'Your order {$order_ref} has been shipped' => 'Your order {$order_ref} has been shipped', + 'Your slice has been saved' => 'Your slice has been saved', +); diff --git a/domokits/local/modules/CustomDelivery/I18n/fr_FR.php b/domokits/local/modules/CustomDelivery/I18n/fr_FR.php new file mode 100644 index 0000000..5273164 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/I18n/fr_FR.php @@ -0,0 +1,24 @@ + 'Zone', + 'Custom delivery shipping message' => 'Message d\'envoi pour la livraison personnalisée', + 'Id' => 'Id', + 'Method' => 'Méthode', + 'Price' => 'Prix', + 'Price and weight' => 'Prix et poids', + 'Tax rule' => 'Règle de taxe', + 'The Tax Rule id \'%id\' doesn\'t exist' => 'La règle de taxe id \'%id\' n\'existe pas', + 'The area is not valid' => 'La zone n\'est pas valide', + 'The method used to select the right slice.' => 'La méthode utilisée pour sélectionner la bonne tranche.', + 'The price max value is not valid' => 'La valeur du prix max n\'est pas valide', + 'The price value is not valid' => 'La valeur du prix n\'est pas valide', + 'The slice has not been deleted' => 'Votre tranche n\'a pas été supprimé', + 'The tax rule used to calculate postage taxes.' => 'La règle de taxe utilisée pour calculer les taxes associés à la livraison.', + 'The tracking URL. %ID% will be replaced by the tracking number entered in the order' => 'L\'URL de suivi. %ID% sera remplacé par le numéro de suivi saisi dans la commande', + 'The weight max value is not valid' => 'La valeur du poids max n\'est pas valide', + 'Tracking URL' => 'URL de suivi', + 'Weight' => 'Poids', + 'Your order {$order_ref} has been shipped' => 'Votre commande {$order_ref} vient d\'être expédiée', + 'Your slice has been saved' => 'Votre tranche a été sauvegardé', +); diff --git a/domokits/local/modules/CustomDelivery/LICENSE b/domokits/local/modules/CustomDelivery/LICENSE new file mode 100644 index 0000000..fb6d90b --- /dev/null +++ b/domokits/local/modules/CustomDelivery/LICENSE @@ -0,0 +1,166 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/domokits/local/modules/CustomDelivery/Loop/CustomDeliverySliceLoop.php b/domokits/local/modules/CustomDelivery/Loop/CustomDeliverySliceLoop.php new file mode 100644 index 0000000..ff26d64 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Loop/CustomDeliverySliceLoop.php @@ -0,0 +1,168 @@ + + */ +class CustomDeliverySliceLoop extends BaseLoop implements PropelSearchLoopInterface +{ + protected $timestampable = false; + + protected $versionable = false; + + /** + * @param LoopResult $loopResult + * + * @return LoopResult + */ + public function parseResults(LoopResult $loopResult) + { + /** @var CustomDeliverySlice $slice */ + foreach ($loopResult->getResultDataCollection() as $slice) { + + $loopResultRow = new LoopResultRow($slice); + + $loopResultRow + ->set("ID", $slice->getId()) + ->set("AREA_ID", $slice->getAreaId()) + ->set("PRICE_MAX", $slice->getPriceMax()) + ->set("WEIGHT_MAX", $slice->getWeightMax()) + ->set("PRICE", $slice->getPrice()); + + $this->addOutputFields($loopResultRow, $slice); + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } + + /** + * this method returns a Propel ModelCriteria + * + * @return \Propel\Runtime\ActiveQuery\ModelCriteria + */ + public function buildModelCriteria() + { + $query = CustomDeliverySliceQuery::create(); + + $id = $this->getArgValue('id'); + if (null !== $id) { + $query->filterById($id, Criteria::IN); + } + + $id = $this->getArgValue('area_id'); + if (null !== $id) { + $query->filterByAreaId($id, Criteria::IN); + } + + $orders = $this->getArgValue('order'); + + foreach ($orders as $order) { + switch ($order) { + case "id": + $query->orderById(Criteria::ASC); + break; + case "id_reverse": + $query->orderById(Criteria::DESC); + break; + case "weight_max": + $query->orderByWeightMax(Criteria::ASC); + break; + case "weight_max_reverse": + $query->orderByWeightMax(Criteria::DESC); + break; + case "price_max": + $query->orderByPriceMax(Criteria::ASC); + break; + case "price_max_reverse": + $query->orderByPriceMax(Criteria::DESC); + break; + case "price": + $query->orderByPrice(Criteria::ASC); + break; + case "price_reverse": + $query->orderByPrice(Criteria::DESC); + break; + } + } + + return $query; + } + + /** + * Definition of loop arguments + * + * example : + * + * public function getArgDefinitions() + * { + * return new ArgumentCollection( + * + * Argument::createIntListTypeArgument('id'), + * new Argument( + * 'ref', + * new TypeCollection( + * new Type\AlphaNumStringListType() + * ) + * ), + * Argument::createIntListTypeArgument('category'), + * Argument::createBooleanTypeArgument('new'), + * ... + * ); + * } + * + * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntListTypeArgument('id'), + Argument::createIntListTypeArgument('area_id'), + new Argument( + 'order', + new TypeCollection( + new EnumListType( + [ + 'id', + 'id_reverse', + 'weight_max', + 'weight_max_reverse', + 'price_max', + 'price_max_reverse', + 'price', + 'price_reverse', + ] + ) + ), + 'id' + ) + ); + } +} diff --git a/domokits/local/modules/CustomDelivery/Model/CustomDeliverySlice.php b/domokits/local/modules/CustomDelivery/Model/CustomDeliverySlice.php new file mode 100644 index 0000000..aa4d36f --- /dev/null +++ b/domokits/local/modules/CustomDelivery/Model/CustomDeliverySlice.php @@ -0,0 +1,10 @@ +/local/modules/``` directory and be sure that the name of the module is CustomDelivery. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/custom-delivery-module:~1.0 +``` + +## Usage + +Just enter the price you want for each area in the configuration page of the module. +You can create as many slices you want. These slices are based on the amount price and/or the weight of an order. You +can associate an optional taxe rule to the module to include taxes for the shipment. + + +# customization + +You can customize the mails sent by the module in the **Mailing templates** configuration page in the back-office. The + template used is called `mail_custom_delivery`. diff --git a/domokits/local/modules/CustomDelivery/composer.json b/domokits/local/modules/CustomDelivery/composer.json new file mode 100644 index 0000000..277e415 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/custom-delivery-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "CustomDelivery" + } +} \ No newline at end of file diff --git a/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-config-js.html b/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-config-js.html new file mode 100644 index 0000000..f064982 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-config-js.html @@ -0,0 +1,127 @@ + +{javascripts file='assets/js/libs/underscore-min.js'} + +{/javascripts} + + \ No newline at end of file diff --git a/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-configuration.html b/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-configuration.html new file mode 100644 index 0000000..1909641 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/templates/backOffice/default/module-configuration.html @@ -0,0 +1,195 @@ + +
+
+ +
+ {intl d='customdelivery.bo.default' l='Configuration.'} +
+ +
+
+ + {form name="customdelivery.configuration.form"} + + + {if $form_error_message}
{$form_error_message}
{/if} + + {form_hidden_fields form=$form} + + {render_form_field form=$form field="url"} + {render_form_field form=$form field="method"} + + {form_field form=$form field="tax"} +
+ + + +
+ {/form_field} + + + + + {/form} + +
+ +
+ +
+
+ + +{* default currency *} +{loop type="currency" name="default_currency" default_only="1"} + {$currencySymbol=$SYMBOL} +{/loop} + + +
+
+ +
+ {intl d='customdelivery.bo.default' l='Slices.'} +
+ +
+ + {loop type="area" name="area" module_id=$module_id backend_context=true} + {$area_id=$ID} +
+
+ + + + + {if $method != 2}{/if} + {if $method != 1}{/if} + + + + + + {loop type="custom-delivery-slice" name="custom-delivery-slice" area_id=$area_id order="weight_max,price_max" } + + {if $method != 2} + + {/if} + {if $method != 1} + + {/if} + + + + {/loop} + + {* New slice *} + {loop type="auth" name="can_change" role="ADMIN" module="customdelivery" access="CREATE"} + + {if $method != 2} + + {/if} + {if $method != 1} + + {/if} + + + + {/loop} + +
+ {intl d='customdelivery.bo.default' l="Area : "} {$NAME} +
{intl d='customdelivery.bo.default' l="Untaxed Price up to ... %symbol" symbol=$currencySymbol}{intl d='customdelivery.bo.default' l="Weight up to ... kg"}{intl d='customdelivery.bo.default' l="Price (%symbol)" symbol=$currencySymbol}{intl d='customdelivery.bo.default' l="Actions"}
+ + + + + + +
+ {loop type="auth" name="can_change" role="ADMIN" module="customdelivery" access="UPDATE"} + + + + {/loop} + {loop type="auth" name="can_change" role="ADMIN" module="customdelivery" access="DELETE"} + + + + {/loop} +
+
+ + + + + + + + + +
+
+
+ + {/loop} + {elseloop rel="area"} +
+
+ {intl d='customdelivery.bo.default' l="You should first attribute shipping zones to the modules: "} + + {intl d='customdelivery.bo.default' l="manage shipping zones"} + +
+
+ {/elseloop} +
+ +
+
+ +{include + file = "includes/generic-warning-dialog.html" + + dialog_id = "custom_delivery_dialog" + dialog_title = {intl d='customdelivery.bo.default' l="Message"} + dialog_body = "" +} + +{* JS Templates *} + diff --git a/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.html b/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.html new file mode 100644 index 0000000..0b1137e --- /dev/null +++ b/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.html @@ -0,0 +1,17 @@ +{default_translation_domain domain="customdelivery.email.default"} + +{loop type="customer" name="customer.order" current="false" id="$customer_id" backend_context="1"} +

{intl l="Dear" } {$LASTNAME} {$FIRSTNAME},

+{/loop} + +

{intl l="Thank you for your order on our online store %store_name" store_name={config key="store_name"}}

+

{intl l="Your order %order_ref dated %order_date has been shipped on %update_date" order_ref={$order_ref} order_date={format_date date=$order_date} update_date={format_date date=$update_date}}

+{if $package} +

{intl l="The tracking number for this delivery is: %package" package={$package}}

+{if $tracking_url} +

{intl l="Please check this URL to track your parcel : %tracking_url" tracking_url={$tracking_url}}

+{/if} +{/if} +

{intl l="Feel free to contact us for any further information"}

+ +

{intl l="Best Regards."}

\ No newline at end of file diff --git a/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.txt b/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.txt new file mode 100644 index 0000000..04f5026 --- /dev/null +++ b/domokits/local/modules/CustomDelivery/templates/email/default/custom-delivery-shipping.txt @@ -0,0 +1,18 @@ +{default_translation_domain domain="customdelivery.email.default"} + +{loop type="customer" name="customer.order" current="false" id="$customer_id" backend_context="1"} + {intl l="Dear" } {$LASTNAME} {$FIRSTNAME}, +{/loop} + +{intl l="Thank you for your order on our online store %store_name" store_name={config key="store_name"}} + +{intl l="Your order %order_ref dated %order_date has been shipped on %update_date" order_ref={$order_ref} order_date={format_date date=$order_date} update_date={format_date date=$update_date}} +{if $package} +{intl l="The tracking number for this delivery is: %package" package={$package}} +{if $tracking_url} +{intl l="Please check this URL to track your parcel : %tracking_url" tracking_url={$tracking_url}} +{/if} +{/if} +{intl l="Feel free to contact us for any further information"} + +{intl l="Best Regards."} \ No newline at end of file diff --git a/domokits/local/modules/FreeOrder/Config/config.xml b/domokits/local/modules/FreeOrder/Config/config.xml new file mode 100644 index 0000000..42d3733 --- /dev/null +++ b/domokits/local/modules/FreeOrder/Config/config.xml @@ -0,0 +1,6 @@ + + + + diff --git a/domokits/local/modules/FreeOrder/Config/module.xml b/domokits/local/modules/FreeOrder/Config/module.xml new file mode 100644 index 0000000..955d422 --- /dev/null +++ b/domokits/local/modules/FreeOrder/Config/module.xml @@ -0,0 +1,27 @@ + + + FreeOrder\FreeOrder + + There's nothing to pay for this order + This is a pseudo-payment module for free orders. + + + Vous n'avez rien à payer pour cette commande + Un pseudo-module de paiement pour les commandes de montant nul + + + en_US + fr_FR + + 2.5.4 + + Franck Allimant + CQFDev + franck@cqfdev.fr + + payment + 2.5.4 + alpha + diff --git a/domokits/local/modules/FreeOrder/FreeOrder.php b/domokits/local/modules/FreeOrder/FreeOrder.php new file mode 100644 index 0000000..099ccb3 --- /dev/null +++ b/domokits/local/modules/FreeOrder/FreeOrder.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FreeOrder; + +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Model\Order; +use Thelia\Model\OrderStatusQuery; +use Thelia\Module\AbstractPaymentModule; + +class FreeOrder extends AbstractPaymentModule +{ + /** + * @return bool + */ + public function isValidPayment() + { + return round($this->getCurrentOrderTotalAmount(), 4) == 0; + } + + public function pay(Order $order): void + { + $event = new OrderEvent($order); + $event->setStatus(OrderStatusQuery::getPaidStatus()->getId()); + $this->getDispatcher()->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + } + + /** + * @return bool + */ + public function manageStockOnCreation() + { + return false; + } +} diff --git a/domokits/local/modules/FreeOrder/LICENSE.txt b/domokits/local/modules/FreeOrder/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/FreeOrder/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/FreeOrder/Readme.md b/domokits/local/modules/FreeOrder/Readme.md new file mode 100644 index 0000000..6333346 --- /dev/null +++ b/domokits/local/modules/FreeOrder/Readme.md @@ -0,0 +1,25 @@ +# Free Order + +This module is used to terminate the order process when the order amount is 0,00. In this case, none of the traditional +payment modules applies. + +## Installation + +This module is bundled with Thelia standard distribution. + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is FreeOrder. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/free-order-module:~1.0 +``` + +## Usage + +The module is displayed as needed in the payment modules list of the order-invoice page. \ No newline at end of file diff --git a/domokits/local/modules/FreeOrder/composer.json b/domokits/local/modules/FreeOrder/composer.json new file mode 100644 index 0000000..f32437e --- /dev/null +++ b/domokits/local/modules/FreeOrder/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/free-order-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "FreeOrder" + } +} \ No newline at end of file diff --git a/domokits/local/modules/Front/Config/config.xml b/domokits/local/modules/Front/Config/config.xml new file mode 100644 index 0000000..4e04106 --- /dev/null +++ b/domokits/local/modules/Front/Config/config.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + Front/Config/front.xml + + %kernel.cache_dir% + %kernel.debug% + + + + + + diff --git a/domokits/local/modules/Front/Config/front.xml b/domokits/local/modules/Front/Config/front.xml new file mode 100644 index 0000000..20bea5d --- /dev/null +++ b/domokits/local/modules/Front/Config/front.xml @@ -0,0 +1,276 @@ + + + + + + Thelia\Controller\Front\DefaultController::noAction + includes/mini-cart + + + + Thelia\Controller\Front\DefaultController::noAction + includes/addedToCart + + + + + Front\Controller\CustomerController::viewRegisterAction + + + + Front\Controller\CustomerController::createAction + register + + + + + Front\Controller\CustomerController::viewLoginAction + + + + Front\Controller\CustomerController::loginAction + login + + + + + Front\Controller\CustomerController::newPasswordAction + password + + + + Front\Controller\CustomerController::newPasswordSentAction + password + + + + + Front\Controller\CustomerController::logoutAction + + + + + Front\Controller\CustomerController::confirmCustomerAction + + + + + + Thelia\Controller\Front\DefaultController::noAction + account + + + + Front\Controller\CustomerController::viewAction + account-update + + + + Front\Controller\CustomerController::updateAction + account-update + + + + + Front\Controller\CustomerController::updatePasswordAction + account-password + + + + Thelia\Controller\Front\DefaultController::noAction + account-password + + + + Front\Controller\OrderController::viewAction + \d+ + + + + Front\Controller\OrderController::generateDeliveryPdf + \d+ + + + + Front\Controller\OrderController::generateInvoicePdf + \d+ + + + + Front\Controller\OrderController::downloadVirtualProduct + \d+ + + + + + + Thelia\Controller\Front\DefaultController::noAction + address + + + + Front\Controller\AddressController::createAction + address + + + + Front\Controller\AddressController::updateViewAction + address-update + + + + Front\Controller\AddressController::processUpdateAction + address-update + + + + Front\Controller\AddressController::deleteAction + account + + + + Front\Controller\AddressController::generateModalAction + modal-address + \d+ + + + + Front:Address:makeAddressDefault + \d+ + + + + + + + Thelia\Controller\Front\DefaultController::noAction + cart + + + + Front\Controller\CartController::addItem + + + + Front\Controller\CartController::deleteItem + cart + + + + Front\Controller\CartController::changeItem + cart + + + + Front\Controller\CartController::changeCountry + cart + + + + + + Front\Controller\OrderController::deliver + order-delivery + + + + Front\Controller\OrderController::deliverView + order-delivery + + + + Front\Controller\OrderController::getDeliveryModuleListAjaxAction + + + + Front\Controller\OrderController::invoice + order-invoice + + + + Thelia\Controller\Front\DefaultController::noAction + order-invoice + + + + Front\Controller\CouponController::consumeAction + order-invoice + + + + Front\Controller\CouponController::clearAllCouponsAction + order-invoice + + + + Front\Controller\OrderController::pay + + + + Front\Controller\OrderController::orderPlaced + order-placed + + + + Front\Controller\OrderController::orderFailed + order-failed + + + + + + Front\Controller\ContactController::sendAction + contact + + + + Thelia\Controller\Front\DefaultController::noAction + contact-success + + + + + + Front\Controller\NewsletterController::subscribeAction + newsletter + + + + Front\Controller\NewsletterController::unsubscribeAction + newsletter-unsubscribe + + + + + + + Front\Controller\SitemapController::generateAction + + + Front\Controller\SitemapController::generateAction + + + + + + Front\Controller\FeedController::generateAction + catalog + + + + + + + + Thelia\Controller\Front\DefaultController::emptyRoute + + + + + Thelia\Controller\Front\DefaultController::noAction + index + ^(?!admin|api)[^/]+ + + diff --git a/domokits/local/modules/Front/Config/module.xml b/domokits/local/modules/Front/Config/module.xml new file mode 100644 index 0000000..1d4876f --- /dev/null +++ b/domokits/local/modules/Front/Config/module.xml @@ -0,0 +1,29 @@ + + + Front\Front + + Front integration + + + + Front office module + + + + en_US + fr_FR + + 2.5.4 + + + Thelia team + info@thelia.net + + + classic + 2.5.4 + alpha + 1 + diff --git a/domokits/local/modules/Front/Controller/AddressController.php b/domokits/local/modules/Front/Controller/AddressController.php new file mode 100644 index 0000000..81426a0 --- /dev/null +++ b/domokits/local/modules/Front/Controller/AddressController.php @@ -0,0 +1,257 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Symfony\Component\Form\Form; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Address\AddressCreateOrUpdateEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\AddressQuery; +use Thelia\Model\Customer; +use Thelia\Model\Event\AddressEvent; + +/** + * Class AddressController. + * + * @author Manuel Raynaud + */ +class AddressController extends BaseFrontController +{ + /** + * Controller for generate modal containing update form + * Check if request is a XmlHttpRequest and address owner is the current customer. + */ + public function generateModalAction($address_id): void + { + $this->checkAuth(); + $this->checkXmlHttpRequest(); + } + + /** + * Create controller. + * Check if customer is logged in. + * + * Dispatch TheliaEvents::ADDRESS_CREATE event + */ + public function createAction(EventDispatcherInterface $eventDispatcher) + { + $this->checkAuth(); + + $addressCreate = $this->createForm(FrontForm::ADDRESS_CREATE); + + try { + /** @var Customer $customer */ + $customer = $this->getSecurityContext()->getCustomerUser(); + + $form = $this->validateForm($addressCreate, 'post'); + $event = $this->createAddressEvent($form); + $event->setCustomer($customer); + + $eventDispatcher->dispatch($event, TheliaEvents::ADDRESS_CREATE); + + return $this->generateSuccessRedirect($addressCreate); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans('Please check your input: %s', ['%s' => $e->getMessage()], Front::MESSAGE_DOMAIN); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans('Sorry, an error occured: %s', ['%s' => $e->getMessage()], Front::MESSAGE_DOMAIN); + } + + Tlog::getInstance()->error(sprintf('Error during address creation process : %s', $message)); + + $addressCreate->setErrorMessage($message); + + $this->getParserContext() + ->addForm($addressCreate) + ->setGeneralError($message) + ; + + // Redirect to error URL if defined + if ($addressCreate->hasErrorUrl()) { + return $this->generateErrorRedirect($addressCreate); + } + } + + protected function createAddressEvent(Form $form) + { + return new AddressCreateOrUpdateEvent( + $form->get('label')->getData(), + $form->get('title')->getData(), + $form->get('firstname')->getData(), + $form->get('lastname')->getData(), + $form->get('address1')->getData(), + $form->get('address2')->getData(), + $form->get('address3')->getData(), + $form->get('zipcode')->getData(), + $form->get('city')->getData(), + $form->get('country')->getData(), + $form->get('cellphone')->getData(), + $form->get('phone')->getData(), + $form->get('company')->getData(), + $form->get('is_default')->getData(), + $form->get('state')->getData() + ); + } + + public function updateViewAction($address_id) + { + $this->checkAuth(); + + $customer = $this->getSecurityContext()->getCustomerUser(); + $address = AddressQuery::create()->findPk($address_id); + + if (!$address || $customer->getId() != $address->getCustomerId()) { + return $this->generateRedirectFromRoute('default'); + } + + $this->getParserContext()->set('address_id', $address_id); + } + + public function processUpdateAction($address_id, EventDispatcherInterface $eventDispatcher) + { + $this->checkAuth(); + + $addressUpdate = $this->createForm(FrontForm::ADDRESS_UPDATE); + + try { + $customer = $this->getSecurityContext()->getCustomerUser(); + + $form = $this->validateForm($addressUpdate); + + $address = AddressQuery::create()->findPk($address_id); + + if (null === $address) { + return $this->generateRedirectFromRoute('default'); + } + + if ($address->getCustomer()->getId() != $customer->getId()) { + return $this->generateRedirectFromRoute('default'); + } + + $event = $this->createAddressEvent($form); + $event->setAddress($address); + + $eventDispatcher->dispatch($event, TheliaEvents::ADDRESS_UPDATE); + + return $this->generateSuccessRedirect($addressUpdate); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans('Please check your input: %s', ['%s' => $e->getMessage()], Front::MESSAGE_DOMAIN); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans('Sorry, an error occured: %s', ['%s' => $e->getMessage()], Front::MESSAGE_DOMAIN); + } + + $this->getParserContext()->set('address_id', $address_id); + + Tlog::getInstance()->error(sprintf('Error during address creation process : %s', $message)); + + $addressUpdate->setErrorMessage($message); + + $this->getParserContext() + ->addForm($addressUpdate) + ->setGeneralError($message) + ; + + if ($addressUpdate->hasErrorUrl()) { + return $this->generateErrorRedirect($addressUpdate); + } + } + + public function deleteAction(EventDispatcherInterface $eventDispatcher, $address_id) + { + $this->checkAuth(); + $error_message = false; + + $customer = $this->getSecurityContext()->getCustomerUser(); + $address = AddressQuery::create()->findPk($address_id); + + if (!$address || $customer->getId() != $address->getCustomerId()) { + // If Ajax Request + if ($this->getRequest()->isXmlHttpRequest()) { + return $this->jsonResponse( + json_encode( + [ + 'success' => false, + 'message' => $this->getTranslator()->trans( + 'Error during address deletion process', + [], + Front::MESSAGE_DOMAIN + ), + ] + ) + ); + } + + return $this->generateRedirectFromRoute('default'); + } + + try { + $eventDispatcher->dispatch(new AddressEvent($address), TheliaEvents::ADDRESS_DELETE); + } catch (\Exception $e) { + $error_message = $e->getMessage(); + } + + Tlog::getInstance()->error(sprintf('Error during address deletion : %s', $error_message)); + + // If Ajax Request + if ($this->getRequest()->isXmlHttpRequest()) { + if ($error_message) { + $response = $this->jsonResponse(json_encode([ + 'success' => false, + 'message' => $error_message, + ])); + } else { + $response = $this->jsonResponse( + json_encode([ + 'success' => true, + 'message' => '', + ]) + ); + } + + return $response; + } + + return $this->generateRedirectFromRoute('default', ['view' => 'account']); + } + + public function makeAddressDefaultAction(EventDispatcherInterface $eventDispatcher, $addressId) + { + $this->checkAuth(); + + $address = AddressQuery::create() + ->filterByCustomerId($this->getSecurityContext()->getCustomerUser()->getId()) + ->findPk($addressId) + ; + + if (null === $address) { + $this->pageNotFound(); + } + + try { + $event = new AddressEvent($address); + $eventDispatcher->dispatch($event, TheliaEvents::ADDRESS_DEFAULT); + } catch (\Exception $e) { + $this->getParserContext() + ->setGeneralError($e->getMessage()) + ; + + return $this->render('account'); + } + + return $this->generateRedirectFromRoute('default', ['view' => 'account']); + } +} diff --git a/domokits/local/modules/Front/Controller/CartController.php b/domokits/local/modules/Front/Controller/CartController.php new file mode 100644 index 0000000..edc022b --- /dev/null +++ b/domokits/local/modules/Front/Controller/CartController.php @@ -0,0 +1,241 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Cart\CartEvent; +use Thelia\Core\Event\Delivery\DeliveryPostageEvent; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Form\CartAdd; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\AddressQuery; +use Thelia\Model\ConfigQuery; +use Thelia\Tools\URL; + +class CartController extends BaseFrontController +{ + public function addItem(EventDispatcherInterface $eventDispatcher) + { + $request = $this->getRequest(); + + $cartAdd = $this->getAddCartForm($request); + $message = null; + + try { + $form = $this->validateForm($cartAdd); + + $cartEvent = $this->getCartEvent($eventDispatcher); + + $cartEvent->bindForm($form); + + $eventDispatcher->dispatch($cartEvent, TheliaEvents::CART_ADDITEM); + + $this->afterModifyCart($eventDispatcher); + + if (!$this->changeViewForAjax()) { + if (null !== $response = $this->generateSuccessRedirect($cartAdd)) { + return $response; + } + } + } catch (PropelException $e) { + Tlog::getInstance()->error(sprintf('Failed to add item to cart with message : %s', $e->getMessage())); + $message = $this->getTranslator()->trans( + 'Failed to add this article to your cart, please try again', + [], + Front::MESSAGE_DOMAIN + ); + } catch (FormValidationException $e) { + $message = $e->getMessage(); + } + + if ($message) { + $cartAdd->setErrorMessage($message); + $this->getParserContext()->addForm($cartAdd); + } + } + + public function changeItem(EventDispatcherInterface $eventDispatcher) + { + $cartEvent = $this->getCartEvent($eventDispatcher); + $cartEvent->setCartItemId($this->getRequest()->get('cart_item')); + $cartEvent->setQuantity($this->getRequest()->get('quantity')); + + try { + $this->getTokenProvider()->checkToken( + $this->getRequest()->query->get('_token') + ); + + $eventDispatcher->dispatch($cartEvent, TheliaEvents::CART_UPDATEITEM); + + $this->afterModifyCart($eventDispatcher); + + if (!$this->changeViewForAjax()) { + if (null !== $successUrl = $this->getRequest()->get('success_url')) { + return $this->generateRedirect(URL::getInstance()->absoluteUrl($successUrl)); + } + } + } catch (\Exception $e) { + Tlog::getInstance()->error(sprintf('Failed to change cart item quantity: %s', $e->getMessage())); + + $this->getParserContext()->setGeneralError($e->getMessage()); + } + } + + public function deleteItem(EventDispatcherInterface $eventDispatcher) + { + $cartEvent = $this->getCartEvent($eventDispatcher); + $cartEvent->setCartItemId($this->getRequest()->get('cart_item')); + + try { + $this->getTokenProvider()->checkToken( + $this->getRequest()->query->get('_token') + ); + + $eventDispatcher->dispatch($cartEvent, TheliaEvents::CART_DELETEITEM); + + $this->afterModifyCart($eventDispatcher); + } catch (\Exception $e) { + Tlog::getInstance()->error(sprintf('error during deleting cartItem with message : %s', $e->getMessage())); + $this->getParserContext()->setGeneralError($e->getMessage()); + } + + if (!$this->changeViewForAjax()) { + if (null !== $successUrl = $this->getRequest()->get('success_url')) { + return $this->generateRedirect(URL::getInstance()->absoluteUrl($successUrl)); + } + } + } + + protected function changeViewForAjax() + { + // If this is an ajax request, and if the template allow us to return an ajax result + if ($this->getRequest()->isXmlHttpRequest() && (0 === (int) $this->getRequest()->get('no_ajax_check', 0))) { + $request = $this->getRequest(); + + $view = $request->get('ajax-view', 'includes/mini-cart'); + + $request->attributes->set('_view', $view); + + return true; + } + + return false; + } + + public function changeCountry() + { + $redirectUrl = URL::getInstance()->absoluteUrl('/cart'); + $deliveryId = $this->getRequest()->get('country'); + $cookieName = ConfigQuery::read('front_cart_country_cookie_name', 'fcccn'); + $cookieExpires = ConfigQuery::read('front_cart_country_cookie_expires', 2592000); + $cookieExpires = (int) $cookieExpires ?: 2592000; + + $cookie = new Cookie($cookieName, $deliveryId, time() + $cookieExpires, '/'); + + $response = $this->generateRedirect($redirectUrl); + $response->headers->setCookie($cookie); + + return $response; + } + + /** + * @return \Thelia\Core\Event\Cart\CartEvent + */ + protected function getCartEvent(EventDispatcherInterface $eventDispatcher) + { + $cart = $this->getSession()->getSessionCart($eventDispatcher); + + return new CartEvent($cart); + } + + /** + * Find the good way to construct the cart form. + * + * @return CartAdd + */ + private function getAddCartForm(Request $request) + { + /* @var CartAdd $cartAdd */ + if ($request->isMethod('post')) { + $cartAdd = $this->createForm(FrontForm::CART_ADD); + } else { + $cartAdd = $this->createForm( + FrontForm::CART_ADD, + FormType::class, + [], + [ + 'csrf_protection' => false, + ] + ); + } + + return $cartAdd; + } + + /** + * @throws PropelException + */ + protected function afterModifyCart(EventDispatcherInterface $eventDispatcher): void + { + /* recalculate postage amount */ + $order = $this->getSession()->getOrder(); + if (null !== $order) { + $deliveryModule = $order->getModuleRelatedByDeliveryModuleId(); + $deliveryAddress = AddressQuery::create()->findPk($order->getChoosenDeliveryAddress()); + + if (null !== $deliveryModule && null !== $deliveryAddress) { + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + + $orderEvent = new OrderEvent($order); + + try { + $deliveryPostageEvent = new DeliveryPostageEvent( + $moduleInstance, + $this->getSession()->getSessionCart($eventDispatcher), + $deliveryAddress + ); + + $eventDispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE, + ); + + $postage = $deliveryPostageEvent->getPostage(); + + if (null !== $postage) { + $orderEvent->setPostage($postage->getAmount()); + $orderEvent->setPostageTax($postage->getAmountTax()); + $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle()); + } + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + } catch (\Exception $ex) { + // The postage has been chosen, but changes in the cart causes an exception. + // Reset the postage data in the order + $orderEvent->setDeliveryModule(0); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + } + } + } + } +} diff --git a/domokits/local/modules/Front/Controller/ContactController.php b/domokits/local/modules/Front/Controller/ContactController.php new file mode 100644 index 0000000..1717f21 --- /dev/null +++ b/domokits/local/modules/Front/Controller/ContactController.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Contact\ContactEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Template\ParserContext; +use Thelia\Core\Translation\Translator; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Mailer\MailerFactory; +use Thelia\Model\ConfigQuery; + +/** + * Class ContactController. + * + * @author Manuel Raynaud + * @author Loïc Mo + */ +class ContactController extends BaseFrontController +{ + /** + * Send contact message. + */ + public function sendAction(EventDispatcherInterface $eventDispatcher, MailerFactory $mailer, ParserContext $parserContext) + { + $translator = Translator::getInstance(); + $contactForm = $this->createForm(FrontForm::CONTACT); + + try { + $form = $this->validateForm($contactForm); + $event = new ContactEvent($form); + $eventDispatcher->dispatch($event, TheliaEvents::CONTACT_SUBMIT); + + $name = $translator?->trans('Sender name: %name%', ['%name%' => $event->getName()]); + $email = $translator?->trans('Sender\'s e-mail address: %email%', ['%email%' => $event->getEmail()]); + $message = $translator?->trans('Message content: %message%', ['%message%' => $event->getMessage()]); + + $messageContent = + "

$name

\n". + "

$email

\n". + "

$message

"; + + $mailer->sendSimpleEmailMessage( + [ConfigQuery::getStoreEmail() => $event->getName()], + [ConfigQuery::getStoreEmail() => ConfigQuery::getStoreName()], + $event->getSubject(), + $messageContent, + strip_tags($messageContent), + [], + [], + [$event->getEmail() => $event->getName()] + ); + + if ($contactForm->hasSuccessUrl()) { + return $this->generateSuccessRedirect($contactForm); + } + + return $this->generateRedirectFromRoute('contact.success'); + } catch (FormValidationException $e) { + $error_message = $e->getMessage(); + } + + Tlog::getInstance()->error(sprintf('Error during sending contact mail : %s', $error_message)); + + $contactForm->setErrorMessage($error_message); + + $parserContext + ->addForm($contactForm) + ->setGeneralError($error_message) + ; + + // Redirect to error URL if defined + if ($contactForm->hasErrorUrl()) { + return $this->generateErrorRedirect($contactForm); + } + } +} diff --git a/domokits/local/modules/Front/Controller/CouponController.php b/domokits/local/modules/Front/Controller/CouponController.php new file mode 100644 index 0000000..3ddb749 --- /dev/null +++ b/domokits/local/modules/Front/Controller/CouponController.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Propel\Runtime\Exception\PropelException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Coupon\CouponConsumeEvent; +use Thelia\Core\Event\DefaultActionEvent; +use Thelia\Core\Event\Delivery\DeliveryPostageEvent; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Exception\UnmatchableConditionException; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\AddressQuery; + +/** + * Class CouponController. + * + * @author Guillaume MOREL + */ +class CouponController extends BaseFrontController +{ + /** + * Clear all coupons. + */ + public function clearAllCouponsAction(EventDispatcherInterface $eventDispatcher): void + { + // Dispatch Event to the Action + $eventDispatcher->dispatch(new DefaultActionEvent(), TheliaEvents::COUPON_CLEAR_ALL); + } + + /** + * Coupon consuming. + */ + public function consumeAction(EventDispatcherInterface $eventDispatcher) + { + $this->checkCartNotEmpty($eventDispatcher); + + $message = false; + $couponCodeForm = $this->createForm(FrontForm::COUPON_CONSUME); + + try { + $form = $this->validateForm($couponCodeForm, 'post'); + + $couponCode = $form->get('coupon-code')->getData(); + + if (null === $couponCode || empty($couponCode)) { + $message = true; + throw new \Exception( + $this->getTranslator()->trans( + 'Coupon code can\'t be empty', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + + $couponConsumeEvent = new CouponConsumeEvent($couponCode); + + // Dispatch Event to the Action + $eventDispatcher->dispatch($couponConsumeEvent, TheliaEvents::COUPON_CONSUME); + + /* recalculate postage amount */ + $order = $this->getSession()->getOrder(); + + if (null !== $order) { + $deliveryModule = $order->getModuleRelatedByDeliveryModuleId(); + $deliveryAddress = AddressQuery::create()->findPk($order->getChoosenDeliveryAddress()); + + if (null !== $deliveryModule && null !== $deliveryAddress) { + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + + $orderEvent = new OrderEvent($order); + + try { + $deliveryPostageEvent = new DeliveryPostageEvent( + $moduleInstance, + $this->getSession()->getSessionCart($eventDispatcher), + $deliveryAddress + ); + + $eventDispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + $postage = $deliveryPostageEvent->getPostage(); + + $orderEvent->setPostage($postage->getAmount()); + $orderEvent->setPostageTax($postage->getAmountTax()); + $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle()); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + } catch (\Exception $ex) { + // The postage has been chosen, but changes dues to coupon causes an exception. + // Reset the postage data in the order + $orderEvent->setDeliveryModule(0); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + } + } + } + + return $this->generateSuccessRedirect($couponCodeForm); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your coupon code: %message', + ['%message' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } catch (UnmatchableConditionException $e) { + $message = $this->getTranslator()->trans( + 'You should sign in or register to use this coupon', + [ + '%sign' => $this->retrieveUrlFromRouteId('customer.login.view'), + '%register' => $this->retrieveUrlFromRouteId('customer.create.view'), + ], + Front::MESSAGE_DOMAIN + ); + } catch (PropelException $e) { + $this->getParserContext()->setGeneralError($e->getMessage()); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occurred: %message', + ['%message' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } + + if ($message !== false) { + Tlog::getInstance()->error( + sprintf('Error during order delivery process : %s. Exception was %s', $message, $e->getMessage()) + ); + + $couponCodeForm->setErrorMessage($message); + + $this->getParserContext() + ->addForm($couponCodeForm) + ->setGeneralError($message); + } + + return $this->generateErrorRedirect($couponCodeForm); + } +} diff --git a/domokits/local/modules/Front/Controller/CustomerController.php b/domokits/local/modules/Front/Controller/CustomerController.php new file mode 100644 index 0000000..e64a8f5 --- /dev/null +++ b/domokits/local/modules/Front/Controller/CustomerController.php @@ -0,0 +1,595 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Customer\CustomerCreateOrUpdateEvent; +use Thelia\Core\Event\Customer\CustomerLoginEvent; +use Thelia\Core\Event\DefaultActionEvent; +use Thelia\Core\Event\LostPasswordEvent; +use Thelia\Core\Event\Newsletter\NewsletterEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Security\Authentication\CustomerUsernamePasswordFormAuthenticator; +use Thelia\Core\Security\Exception\AuthenticationException; +use Thelia\Core\Security\Exception\CustomerNotConfirmedException; +use Thelia\Core\Security\Exception\UsernameNotFoundException; +use Thelia\Core\Security\Exception\WrongPasswordException; +use Thelia\Form\CustomerLogin; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\ConfigQuery; +use Thelia\Model\Customer; +use Thelia\Model\CustomerQuery; +use Thelia\Model\Event\CustomerEvent; +use Thelia\Model\Newsletter; +use Thelia\Model\NewsletterQuery; +use Thelia\Tools\RememberMeTrait; +use Thelia\Tools\URL; + +/** + * Class CustomerController. + * + * @author Manuel Raynaud + */ +class CustomerController extends BaseFrontController +{ + use RememberMeTrait; + + /** + * Display the register template if no customer logged. + */ + public function viewLoginAction() + { + if ($this->getSecurityContext()->hasCustomerUser()) { + // Redirect to home page + return $this->generateRedirect(URL::getInstance()->getIndexPage()); + } + + return $this->render('login'); + } + + /** + * Display the register template if no customer logged. + */ + public function viewRegisterAction() + { + if ($this->getSecurityContext()->hasCustomerUser()) { + // Redirect to home page + return $this->generateRedirect(URL::getInstance()->getIndexPage()); + } + + return $this->render('register'); + } + + public function newPasswordAction(EventDispatcherInterface $eventDispatcher) + { + $passwordLost = $this->createForm(FrontForm::CUSTOMER_LOST_PASSWORD); + + if (!$this->getSecurityContext()->hasCustomerUser()) { + try { + $form = $this->validateForm($passwordLost); + + $event = new LostPasswordEvent($form->get('email')->getData()); + + $eventDispatcher->dispatch($event, TheliaEvents::LOST_PASSWORD); + + return $this->generateSuccessRedirect($passwordLost); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occurred: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } + + if ($message !== false) { + Tlog::getInstance()->error( + sprintf( + 'Error during customer creation process : %s. Exception was %s', + $message, + $e->getMessage() + ) + ); + } + } else { + $message = $this->getTranslator()->trans( + "You're currently logged in. Please log out before requesting a new password.", + [], + Front::MESSAGE_DOMAIN + ); + } + + $passwordLost->setErrorMessage($message); + + $this->getParserContext() + ->addForm($passwordLost) + ->setGeneralError($message) + ; + + // Redirect to error URL if defined + if ($passwordLost->hasErrorUrl()) { + return $this->generateErrorRedirect($passwordLost); + } + } + + public function newPasswordSentAction(): void + { + $this->getParser()->assign('password_sent', true); + } + + /** + * Create a new customer. + * On success, redirect to success_url if exists, otherwise, display the same view again. + */ + public function createAction(EventDispatcherInterface $eventDispatcher) + { + if (!$this->getSecurityContext()->hasCustomerUser()) { + $customerCreation = $this->createForm(FrontForm::CUSTOMER_CREATE); + + try { + $form = $this->validateForm($customerCreation, 'post'); + + $customerCreateEvent = $this->createEventInstance($form->getData()); + + $eventDispatcher->dispatch($customerCreateEvent, TheliaEvents::CUSTOMER_CREATEACCOUNT); + + $newCustomer = $customerCreateEvent->getCustomer(); + + // Newsletter + if (true === $form->get('newsletter')->getData()) { + $newsletterEmail = $newCustomer->getEmail(); + $nlEvent = new NewsletterEvent( + $newsletterEmail, + $this->getRequest()->getSession()->getLang()->getLocale() + ); + $nlEvent->setFirstname($newCustomer->getFirstname()); + $nlEvent->setLastname($newCustomer->getLastname()); + + // Security : Check if this new Email address already exist + if (null !== $newsletter = NewsletterQuery::create()->findOneByEmail($newsletterEmail)) { + $nlEvent->setId($newsletter->getId()); + $eventDispatcher->dispatch($nlEvent, TheliaEvents::NEWSLETTER_UPDATE); + } else { + $eventDispatcher->dispatch($nlEvent, TheliaEvents::NEWSLETTER_SUBSCRIBE); + } + } + + if (ConfigQuery::isCustomerEmailConfirmationEnable() && !$newCustomer->getEnable()) { + $response = $this->generateRedirectFromRoute('customer.login.view'); + } else { + $this->processLogin($eventDispatcher, $customerCreateEvent->getCustomer()); + + $cart = $this->getSession()->getSessionCart($eventDispatcher); + if ($cart->getCartItems()->count() > 0) { + $response = $this->generateRedirectFromRoute('cart.view'); + } else { + $response = $this->generateSuccessRedirect($customerCreation); + } + } + + return $response; + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } + + Tlog::getInstance()->error( + sprintf( + 'Error during customer creation process : %s. Exception was %s', + $message, + $e->getMessage() + ) + ); + + $customerCreation->setErrorMessage($message); + + $this->getParserContext() + ->addForm($customerCreation) + ->setGeneralError($message) + ; + + // Redirect to error URL if defined + if ($customerCreation->hasErrorUrl()) { + return $this->generateErrorRedirect($customerCreation); + } + } + } + + /** + * Prepare customer data update. + */ + public function viewAction(): void + { + $this->checkAuth(); + + /** @var Customer $customer */ + $customer = $this->getSecurityContext()->getCustomerUser(); + $newsletter = NewsletterQuery::create()->findOneByEmail($customer->getEmail()); + $data = [ + 'id' => $customer->getId(), + 'title' => $customer->getTitleId(), + 'firstname' => $customer->getFirstName(), + 'lastname' => $customer->getLastName(), + 'email' => $customer->getEmail(), + 'email_confirm' => $customer->getEmail(), + 'lang_id' => $customer->getLangId(), + 'newsletter' => $newsletter instanceof Newsletter ? !$newsletter->getUnsubscribed() : false, + ]; + + $customerProfileUpdateForm = $this->createForm(FrontForm::CUSTOMER_PROFILE_UPDATE, FormType::class, $data); + + // Pass it to the parser + $this->getParserContext()->addForm($customerProfileUpdateForm); + } + + public function updatePasswordAction(EventDispatcherInterface $eventDispatcher) + { + if ($this->getSecurityContext()->hasCustomerUser()) { + $customerPasswordUpdateForm = $this->createForm(FrontForm::CUSTOMER_PASSWORD_UPDATE); + + try { + /** @var Customer $customer */ + $customer = $this->getSecurityContext()->getCustomerUser(); + + $form = $this->validateForm($customerPasswordUpdateForm, 'post'); + + $customerChangeEvent = $this->createEventInstance($form->getData()); + $customerChangeEvent->setCustomer($customer); + $eventDispatcher->dispatch($customerChangeEvent, TheliaEvents::CUSTOMER_UPDATEPROFILE); + + return $this->generateSuccessRedirect($customerPasswordUpdateForm); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } + + Tlog::getInstance()->error( + sprintf( + 'Error during customer password modification process : %s.', + $message + ) + ); + + $customerPasswordUpdateForm->setErrorMessage($message); + + $this->getParserContext() + ->addForm($customerPasswordUpdateForm) + ->setGeneralError($message) + ; + + // Redirect to error URL if defined + if ($customerPasswordUpdateForm->hasErrorUrl()) { + return $this->generateErrorRedirect($customerPasswordUpdateForm); + } + } + } + + public function updateAction(EventDispatcherInterface $eventDispatcher) + { + if ($this->getSecurityContext()->hasCustomerUser()) { + $customerProfileUpdateForm = $this->createForm(FrontForm::CUSTOMER_PROFILE_UPDATE); + + try { + /** @var Customer $customer */ + $customer = $this->getSecurityContext()->getCustomerUser(); + $newsletterOldEmail = $customer->getEmail(); + + $form = $this->validateForm($customerProfileUpdateForm, 'post'); + + $customerChangeEvent = $this->createEventInstance($form->getData()); + $customerChangeEvent->setCustomer($customer); + + $customerChangeEvent->setEmailUpdateAllowed( + ((int) ConfigQuery::read('customer_change_email', 0)) ? true : false + ); + + $eventDispatcher->dispatch($customerChangeEvent, TheliaEvents::CUSTOMER_UPDATEPROFILE); + + $updatedCustomer = $customerChangeEvent->getCustomer(); + + // Newsletter + if (true === $form->get('newsletter')->getData()) { + $nlEvent = new NewsletterEvent( + $updatedCustomer->getEmail(), + $this->getRequest()->getSession()->getLang()->getLocale() + ); + $nlEvent->setFirstname($updatedCustomer->getFirstname()); + $nlEvent->setLastname($updatedCustomer->getLastname()); + + if (null !== $newsletter = NewsletterQuery::create()->findOneByEmail($newsletterOldEmail)) { + $nlEvent->setId($newsletter->getId()); + $eventDispatcher->dispatch($nlEvent, TheliaEvents::NEWSLETTER_UPDATE); + } else { + $eventDispatcher->dispatch($nlEvent, TheliaEvents::NEWSLETTER_SUBSCRIBE); + } + } else { + if (null !== $newsletter = NewsletterQuery::create()->findOneByEmail($newsletterOldEmail)) { + $nlEvent = new NewsletterEvent( + $updatedCustomer->getEmail(), + $this->getRequest()->getSession()->getLang()->getLocale() + ); + $nlEvent->setId($newsletter->getId()); + $eventDispatcher->dispatch($nlEvent, TheliaEvents::NEWSLETTER_UNSUBSCRIBE); + } + } + + $this->processLogin($eventDispatcher, $updatedCustomer); + + return $this->generateSuccessRedirect($customerProfileUpdateForm); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + [ + '%s' => $e->getMessage(), + ], + Front::MESSAGE_DOMAIN + ); + } + + Tlog::getInstance()->error(sprintf('Error during customer modification process : %s.', $message)); + + $customerProfileUpdateForm->setErrorMessage($message); + + $this->getParserContext() + ->addForm($customerProfileUpdateForm) + ->setGeneralError($message) + ; + + // Redirect to error URL if defined + if ($customerProfileUpdateForm->hasErrorUrl()) { + return $this->generateErrorRedirect($customerProfileUpdateForm); + } + } + } + + /** + * Perform user login. On a successful login, the user is redirected to the URL + * found in the success_url form parameter, or / if none was found. + * + * If login is not successfull, the same view is displayed again. + */ + public function loginAction(EventDispatcherInterface $eventDispatcher) + { + if (!$this->getSecurityContext()->hasCustomerUser()) { + $request = $this->getRequest(); + $customerLoginForm = $this->createForm(CustomerLogin::class); + + try { + $form = $this->validateForm($customerLoginForm, 'post'); + + // If User is a new customer + if ($form->get('account')->getData() == 0 && $form->get('email')->getErrors()->count() == 0) { + return $this->generateRedirectFromRoute( + 'customer.create.process', + ['email' => $form->get('email')->getData()] + ); + } + try { + $authenticator = new CustomerUsernamePasswordFormAuthenticator($request, $customerLoginForm); + + /** @var Customer $customer */ + $customer = $authenticator->getAuthentifiedUser(); + + $this->processLogin($eventDispatcher, $customer); + + if ((int) $form->get('remember_me')->getData() > 0) { + // If a remember me field if present and set in the form, create + // the cookie thant store "remember me" information + $this->createRememberMeCookie( + $customer, + $this->getRememberMeCookieName(), + $this->getRememberMeCookieExpiration() + ); + } + + return $this->generateSuccessRedirect($customerLoginForm); + } catch (UsernameNotFoundException $e) { + $message = $this->getTranslator()->trans( + 'Wrong email or password. Please try again', + [], + Front::MESSAGE_DOMAIN + ); + } catch (WrongPasswordException $e) { + $message = $this->getTranslator()->trans( + 'Wrong email or password. Please try again', + [], + Front::MESSAGE_DOMAIN + ); + } catch (CustomerNotConfirmedException $e) { + if ($e->getUser() !== null) { + // Send the confirmation email again + $eventDispatcher->dispatch( + new CustomerEvent($e->getUser()), + TheliaEvents::SEND_ACCOUNT_CONFIRMATION_EMAIL + ); + } + $message = $this->getTranslator()->trans( + 'Your account is not yet confirmed. A confirmation email has been sent to your email address, please check your mailbox', + [], + Front::MESSAGE_DOMAIN + ); + } catch (AuthenticationException $e) { + $message = $this->getTranslator()->trans( + 'Wrong email or password. Please try again', + [], + Front::MESSAGE_DOMAIN + ); + } + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } + + Tlog::getInstance()->error( + sprintf( + 'Error during customer login process : %s. Exception was %s', + $message, + $e->getMessage() + ) + ); + + $customerLoginForm->setErrorMessage($message); + + $this->getParserContext()->addForm($customerLoginForm); + + if ($customerLoginForm->hasErrorUrl()) { + return $this->generateErrorRedirect($customerLoginForm); + } + } + } + + /** + * Perform customer logout. + */ + public function logoutAction(EventDispatcherInterface $eventDispatcher) + { + if ($this->getSecurityContext()->hasCustomerUser()) { + $eventDispatcher->dispatch(new DefaultActionEvent(), TheliaEvents::CUSTOMER_LOGOUT); + } + + $this->clearRememberMeCookie($this->getRememberMeCookieName()); + + // Redirect to home page + return $this->generateRedirect(URL::getInstance()->getIndexPage()); + } + + /** + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function confirmCustomerAction($token) + { + /** @var Customer $customer */ + if (null === $customer = CustomerQuery::create()->findOneByConfirmationToken($token)) { + throw new NotFoundHttpException(); + } + + $customer + ->setEnable(true) + ->save() + ; + + // Clear form error context + + return $this->generateRedirectFromRoute('customer.login.view', ['validation_done' => 1]); + } + + /** + * Dispatch event for customer login action. + */ + protected function processLogin(EventDispatcherInterface $eventDispatcher, Customer $customer): void + { + $eventDispatcher->dispatch(new CustomerLoginEvent($customer), TheliaEvents::CUSTOMER_LOGIN); + } + + /** + * @return \Thelia\Core\Event\Customer\CustomerCreateOrUpdateEvent + */ + private function createEventInstance($data) + { + $customerCreateEvent = new CustomerCreateOrUpdateEvent( + $data['title'] ?? null, + $data['firstname'] ?? null, + $data['lastname'] ?? null, + $data['address1'] ?? null, + $data['address2'] ?? null, + $data['address3'] ?? null, + $data['phone'] ?? null, + $data['cellphone'] ?? null, + $data['zipcode'] ?? null, + $data['city'] ?? null, + $data['country'] ?? null, + $data['email'] ?? null, + $data['password'] ?? null, + $data['lang_id'] ?? $this->getSession()->getLang()->getId(), + $data['reseller'] ?? null, + $data['sponsor'] ?? null, + $data['discount'] ?? null, + $data['company'] ?? null, + null, + $data['state'] ?? null + ); + + return $customerCreateEvent; + } + + protected function getRememberMeCookieName() + { + return ConfigQuery::read('customer_remember_me_cookie_name', 'crmcn'); + } + + protected function getRememberMeCookieExpiration() + { + return ConfigQuery::read('customer_remember_me_cookie_expiration', 2592000 /* 1 month */); + } +} diff --git a/domokits/local/modules/Front/Controller/FeedController.php b/domokits/local/modules/Front/Controller/FeedController.php new file mode 100644 index 0000000..86e48b2 --- /dev/null +++ b/domokits/local/modules/Front/Controller/FeedController.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Response; +use Thelia\Model\BrandQuery; +use Thelia\Model\CategoryQuery; +use Thelia\Model\ConfigQuery; +use Thelia\Model\FolderQuery; +use Thelia\Model\Lang; +use Thelia\Model\LangQuery; + +/** + * Controller uses to generate RSS Feeds. + * + * A default cache of 2 hours is used to avoid attack. You can flush cache if you have `ADMIN` role and pass flush=1 in + * query string parameter. + * + * @author Julien Chanséaume + */ +class FeedController extends BaseFrontController +{ + /** + * Folder name for feeds cache. + */ + public const FEED_CACHE_DIR = 'feeds'; + + /** + * Key prefix for feed cache. + */ + public const FEED_CACHE_KEY = 'feed'; + + /** + * render the RSS feed. + * + * @param $context string The context of the feed : catalog, content. default: catalog + * @param $lang string The lang of the feed : fr_FR, en_US, ... default: default language of the site + * @param $id string The id of the parent element. The id of the main parent category for catalog context. + * The id of the content folder for content context + * + * @throws \RuntimeException + * + * @return Response + */ + public function generateAction($context, $lang, $id) + { + /** @var Request $request */ + $request = $this->getRequest(); + + // context + if ('' === $context) { + $context = 'catalog'; + } elseif (!\in_array($context, ['catalog', 'content', 'brand'])) { + $this->pageNotFound(); + } + + // the locale : fr_FR, en_US, + if ('' !== $lang) { + if (!$this->checkLang($lang)) { + $this->pageNotFound(); + } + } else { + try { + $lang = Lang::getDefaultLanguage(); + $lang = $lang->getLocale(); + } catch (\RuntimeException $ex) { + // @todo generate error page + throw new \RuntimeException('No default language is defined. Please define one.'); + } + } + if (null === $lang = LangQuery::create()->findOneByLocale($lang)) { + $this->pageNotFound(); + } + $lang = $lang->getId(); + + // check if element exists and is visible + if ('' !== $id) { + if (false === $this->checkId($context, $id)) { + $this->pageNotFound(); + } + } + + $flush = $request->query->get('flush', ''); + + /** @var AdapterInterface $cacheAdapter */ + $cacheAdapter = $this->container->get('thelia.cache'); + + $cacheKey = self::FEED_CACHE_KEY.$lang.$context; + + $cacheItem = $cacheAdapter->getItem($cacheKey); + + if (!$cacheItem->isHit() || $flush) { + $cacheExpire = (int) (ConfigQuery::read('feed_ttl', '7200')) ?: 7200; + + // render the view + $cacheContent = $this->renderRaw( + 'feed', + [ + '_context_' => $context, + '_lang_' => $lang, + '_id_' => $id, + ] + ); + + $cacheItem->expiresAfter($cacheExpire); + $cacheItem->set($cacheContent); + $cacheAdapter->save($cacheItem); + } + + $response = new Response(); + $response->setContent($cacheItem->get()); + $response->headers->set('Content-Type', 'application/rss+xml'); + + return $response; + } + + /** + * get the cache directory for feeds. + * + * @return mixed|string + */ + protected function getCacheDir() + { + $cacheDir = $this->container->getParameter('kernel.cache_dir'); + $cacheDir = rtrim($cacheDir, '/'); + $cacheDir .= '/'.self::FEED_CACHE_DIR.'/'; + + return $cacheDir; + } + + /** + * Check if current user has ADMIN role. + * + * @return bool + */ + protected function checkAdmin() + { + return $this->getSecurityContext()->hasAdminUser(); + } + + /** + * Check if a lang is used. + * + * @param $lang string The lang code. e.g.: fr + * + * @return bool true if the language is used, otherwise false + */ + private function checkLang($lang) + { + // load locals + $lang = LangQuery::create() + ->findOneByLocale($lang); + + return null !== $lang; + } + + /** + * Check if the element exists and is visible. + * + * @param $context string catalog or content + * @param $id string id of the element + * + * @return bool + */ + private function checkId($context, $id) + { + $ret = false; + if (is_numeric($id)) { + if ('catalog' === $context) { + $cat = CategoryQuery::create()->findPk($id); + $ret = (null !== $cat && $cat->getVisible()); + } elseif ('brand' === $context) { + $brand = BrandQuery::create()->findPk($id); + $ret = (null !== $brand && $brand->getVisible()); + } else { + $folder = FolderQuery::create()->findPk($id); + $ret = (null !== $folder && $folder->getVisible()); + } + } + + return $ret; + } +} diff --git a/domokits/local/modules/Front/Controller/NewsletterController.php b/domokits/local/modules/Front/Controller/NewsletterController.php new file mode 100644 index 0000000..1d998e8 --- /dev/null +++ b/domokits/local/modules/Front/Controller/NewsletterController.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Newsletter\NewsletterEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Form\Definition\FrontForm; +use Thelia\Log\Tlog; +use Thelia\Model\Customer; +use Thelia\Model\NewsletterQuery; + +/** + * Class NewsletterController. + * + * @author Manuel Raynaud , Franck Allimant + */ +class NewsletterController extends BaseFrontController +{ + /** + * @since 2.3.0-alpha2 + */ + public function unsubscribeAction(EventDispatcherInterface $eventDispatcher) + { + $errorMessage = false; + + $newsletterForm = $this->createForm(FrontForm::NEWSLETTER_UNSUBSCRIBE); + + try { + $form = $this->validateForm($newsletterForm); + + $email = $form->get('email')->getData(); + + if (null !== $newsletter = NewsletterQuery::create()->findOneByEmail($email)) { + $event = new NewsletterEvent( + $email, + $this->getRequest()->getSession()->getLang()->getLocale() + ); + + $event->setId($newsletter->getId()); + + $eventDispatcher->dispatch($event, TheliaEvents::NEWSLETTER_UNSUBSCRIBE); + + // If a success URL is defined in the form, redirect to it, otherwise use the defaut view + if ($newsletterForm->hasSuccessUrl() && !$this->getRequest()->isXmlHttpRequest()) { + return $this->generateSuccessRedirect($newsletterForm); + } + } + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + + Tlog::getInstance()->error(sprintf('Error during newsletter unsubscription : %s', $errorMessage)); + + $newsletterForm->setErrorMessage($errorMessage); + } + + // If Ajax Request + if ($this->getRequest()->isXmlHttpRequest()) { + return new JsonResponse([ + 'success' => ($errorMessage) ? false : true, + 'message' => ($errorMessage) ? $errorMessage : $this->getTranslator()->trans( + 'Your subscription to our newsletter has been canceled.', + [], + Front::MESSAGE_DOMAIN + ), + ], ($errorMessage) ? 500 : 200); + } + + $this->getParserContext() + ->setGeneralError($errorMessage) + ->addForm($newsletterForm); + + // If an error URL is defined in the form, redirect to it, otherwise use the defaut view + if ($errorMessage && $newsletterForm->hasErrorUrl()) { + return $this->generateErrorRedirect($newsletterForm); + } + } + + public function subscribeAction(EventDispatcherInterface $eventDispatcher) + { + $errorMessage = false; + + $newsletterForm = $this->createForm(FrontForm::NEWSLETTER); + + try { + $form = $this->validateForm($newsletterForm); + + $event = new NewsletterEvent( + $form->get('email')->getData(), + $this->getRequest()->getSession()->getLang()->getLocale() + ); + + /** @var Customer $customer */ + if (null !== $customer = $this->getSecurityContext()->getCustomerUser()) { + $event + ->setFirstname($customer->getFirstname()) + ->setLastname($customer->getLastname()) + ; + } else { + $event + ->setFirstname($form->get('firstname')->getData()) + ->setLastname($form->get('lastname')->getData()) + ; + } + + $eventDispatcher->dispatch($event, TheliaEvents::NEWSLETTER_SUBSCRIBE); + + // If a success URL is defined in the form, redirect to it, otherwise use the defaut view + if ($newsletterForm->hasSuccessUrl() && !$this->getRequest()->isXmlHttpRequest()) { + return $this->generateSuccessRedirect($newsletterForm); + } + } catch (\Exception $e) { + $errorMessage = $e->getMessage(); + + Tlog::getInstance()->error(sprintf('Error during newsletter subscription : %s', $errorMessage)); + + $newsletterForm->setErrorMessage($errorMessage); + } + + // If Ajax Request + if ($this->getRequest()->isXmlHttpRequest()) { + return new JsonResponse([ + 'success' => ($errorMessage) ? false : true, + 'message' => ($errorMessage) ? $errorMessage : $this->getTranslator()->trans( + "Thanks for signing up! We'll keep you posted whenever we have any new updates.", + [], + Front::MESSAGE_DOMAIN + ), + ], ($errorMessage) ? 500 : 200); + } + + $this->getParserContext() + ->setGeneralError($errorMessage) + ->addForm($newsletterForm); + + // If an error URL is defined in the form, redirect to it, otherwise use the defaut view + if ($errorMessage && $newsletterForm->hasErrorUrl()) { + return $this->generateErrorRedirect($newsletterForm); + } + } +} diff --git a/domokits/local/modules/Front/Controller/OrderController.php b/domokits/local/modules/Front/Controller/OrderController.php new file mode 100644 index 0000000..35967bd --- /dev/null +++ b/domokits/local/modules/Front/Controller/OrderController.php @@ -0,0 +1,591 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Front\Front; +use Propel\Runtime\ActiveQuery\Criteria; +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\HttpFoundation\Response as BaseResponse; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Delivery\DeliveryPostageEvent; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\Product\VirtualProductOrderDownloadResponseEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Exception\TheliaProcessException; +use Thelia\Form\Definition\FrontForm; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Log\Tlog; +use Thelia\Model\Address; +use Thelia\Model\AddressQuery; +use Thelia\Model\AreaDeliveryModuleQuery; +use Thelia\Model\ConfigQuery; +use Thelia\Model\ModuleQuery; +use Thelia\Model\Order; +use Thelia\Model\OrderProductQuery; +use Thelia\Model\OrderQuery; +use Thelia\Module\AbstractDeliveryModule; +use Thelia\Module\Exception\DeliveryException; + +/** + * Class OrderController. + * + * @author Etienne Roudeix + */ +class OrderController extends BaseFrontController +{ + /** + * Check if the cart contains only virtual products. + */ + public function deliverView(EventDispatcherInterface $eventDispatcher) + { + $this->checkAuth(); + $this->checkCartNotEmpty($eventDispatcher); + + // check if the cart contains only virtual products + $cart = $this->getSession()->getSessionCart($eventDispatcher); + + $deliveryAddress = $this->getCustomerAddress(); + + if ($cart->isVirtual()) { + if (null !== $deliveryAddress) { + $deliveryModule = ModuleQuery::create()->retrieveVirtualProductDelivery($this->container); + + if (false === $deliveryModule) { + Tlog::getInstance()->error( + $this->getTranslator()->trans( + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated', + [], + Front::MESSAGE_DOMAIN + ) + ); + } elseif (\count($deliveryModule) == 1) { + return $this->registerVirtualProductDelivery($eventDispatcher, $deliveryModule[0], $deliveryAddress); + } + } + } + + return $this->render( + 'order-delivery', + [ + 'delivery_address_id' => (null !== $deliveryAddress) ? $deliveryAddress->getId() : null, + ] + ); + } + + /** + * @param AbstractDeliveryModule $moduleInstance + * @param Address $deliveryAddress + * + * @return \Symfony\Component\HttpFoundation\Response + */ + private function registerVirtualProductDelivery(EventDispatcherInterface $eventDispatcher, $moduleInstance, $deliveryAddress) + { + /* get postage amount */ + $deliveryModule = $moduleInstance->getModuleModel(); + $cart = $this->getSession()->getSessionCart($eventDispatcher); + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, $deliveryAddress); + + $eventDispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + $postage = $deliveryPostageEvent->getPostage(); + + $orderEvent = $this->getOrderEvent(); + $orderEvent->setDeliveryAddress($deliveryAddress->getId()); + $orderEvent->setDeliveryModule($deliveryModule->getId()); + $orderEvent->setPostage($postage->getAmount()); + $orderEvent->setPostageTax($postage->getAmountTax()); + $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle()); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_ADDRESS); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + + return $this->generateRedirectFromRoute('order.invoice'); + } + + /** + * set delivery address + * set delivery module. + */ + public function deliver(EventDispatcherInterface $eventDispatcher) + { + $this->checkAuth(); + $this->checkCartNotEmpty($eventDispatcher); + + $message = false; + + $orderDelivery = $this->createForm(FrontForm::ORDER_DELIVER); + + try { + $form = $this->validateForm($orderDelivery, 'post'); + + $deliveryAddressId = $form->get('delivery-address')->getData(); + $deliveryModuleId = $form->get('delivery-module')->getData(); + $deliveryAddress = AddressQuery::create()->findPk($deliveryAddressId); + $deliveryModule = ModuleQuery::create()->findPk($deliveryModuleId); + + /* check that the delivery address belongs to the current customer */ + if ($deliveryAddress->getCustomerId() !== $this->getSecurityContext()->getCustomerUser()->getId()) { + throw new \Exception( + $this->getTranslator()->trans( + 'Delivery address does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + + /* check that the delivery module fetches the delivery address area */ + if (null === AreaDeliveryModuleQuery::create()->findByCountryAndModule( + $deliveryAddress->getCountry(), + $deliveryModule, + $deliveryAddress->getState() + )) { + throw new \Exception( + $this->getTranslator()->trans( + 'Delivery module cannot be use with selected delivery address', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + + /* get postage amount */ + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + + $cart = $this->getSession()->getSessionCart($eventDispatcher); + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, $deliveryAddress); + + $eventDispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + if (!$deliveryPostageEvent->isValidModule() || null === $deliveryPostageEvent->getPostage()) { + throw new DeliveryException( + $this->getTranslator()->trans('The delivery module is not valid.', [], Front::MESSAGE_DOMAIN) + ); + } + + $postage = $deliveryPostageEvent->getPostage(); + + $orderEvent = $this->getOrderEvent(); + $orderEvent->setDeliveryAddress($deliveryAddressId); + $orderEvent->setDeliveryModule($deliveryModuleId); + $orderEvent->setPostage($postage->getAmount()); + $orderEvent->setPostageTax($postage->getAmountTax()); + $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle()); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_ADDRESS); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + + return $this->generateRedirectFromRoute('order.invoice'); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } catch (PropelException $e) { + $this->getParserContext()->setGeneralError($e->getMessage()); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } + + if ($message !== false) { + Tlog::getInstance()->error( + sprintf('Error during order delivery process : %s. Exception was %s', $message, $e->getMessage()) + ); + + $orderDelivery->setErrorMessage($message); + + $this->getParserContext() + ->addForm($orderDelivery) + ->setGeneralError($message) + ; + } + } + + /** + * set invoice address + * set payment module. + */ + public function invoice(EventDispatcherInterface $eventDispatcher) + { + $this->checkAuth(); + $this->checkCartNotEmpty($eventDispatcher); + $this->checkValidDelivery(); + + $message = false; + + $orderPayment = $this->createForm(FrontForm::ORDER_PAYMENT); + + try { + $form = $this->validateForm($orderPayment, 'post'); + + $invoiceAddressId = $form->get('invoice-address')->getData(); + $paymentModuleId = $form->get('payment-module')->getData(); + + /* check that the invoice address belongs to the current customer */ + $invoiceAddress = AddressQuery::create()->findPk($invoiceAddressId); + if ($invoiceAddress->getCustomerId() !== $this->getSecurityContext()->getCustomerUser()->getId()) { + throw new \Exception( + $this->getTranslator()->trans( + 'Invoice address does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + + $orderEvent = $this->getOrderEvent(); + $orderEvent->setInvoiceAddress($invoiceAddressId); + $orderEvent->setPaymentModule($paymentModuleId); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_INVOICE_ADDRESS); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_PAYMENT_MODULE); + + return $this->generateRedirectFromRoute('order.payment.process'); + } catch (FormValidationException $e) { + $message = $this->getTranslator()->trans( + 'Please check your input: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } catch (PropelException $e) { + $this->getParserContext()->setGeneralError($e->getMessage()); + } catch (\Exception $e) { + $message = $this->getTranslator()->trans( + 'Sorry, an error occured: %s', + ['%s' => $e->getMessage()], + Front::MESSAGE_DOMAIN + ); + } + + if ($message !== false) { + Tlog::getInstance()->error( + sprintf('Error during order payment process : %s. Exception was %s', $message, $e->getMessage()) + ); + + $orderPayment->setErrorMessage($message); + + $this->getParserContext() + ->addForm($orderPayment) + ->setGeneralError($message) + ; + } + + return $this->generateErrorRedirect($orderPayment); + } + + public function pay(EventDispatcherInterface $eventDispatcher) + { + /* check customer */ + $this->checkAuth(); + + /* check cart count */ + $this->checkCartNotEmpty($eventDispatcher); + + /* check stock not empty */ + if (true === ConfigQuery::checkAvailableStock()) { + if (null !== $response = $this->checkStockNotEmpty($eventDispatcher)) { + return $response; + } + } + + /* check delivery address and module */ + $this->checkValidDelivery(); + + /* check invoice address and payment module */ + $this->checkValidInvoice(); + + $orderEvent = $this->getOrderEvent(); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_PAY); + + $placedOrder = $orderEvent->getPlacedOrder(); + + if (null !== $placedOrder && null !== $placedOrder->getId()) { + /* order has been placed */ + if ($orderEvent->hasResponse()) { + return $orderEvent->getResponse(); + } + + return $this->generateRedirectFromRoute( + 'order.placed', + [], + ['order_id' => $orderEvent->getPlacedOrder()->getId()] + ); + } + + /* order has not been placed */ + return $this->generateRedirectFromRoute('cart.view'); + } + + public function orderPlaced(EventDispatcherInterface $eventDispatcher, $order_id): void + { + /* check if the placed order matched the customer */ + $placedOrder = OrderQuery::create()->findPk( + $this->getRequest()->attributes->get('order_id') + ); + + if (null === $placedOrder) { + throw new TheliaProcessException( + $this->getTranslator()->trans( + 'No placed order', + [], + Front::MESSAGE_DOMAIN + ), + TheliaProcessException::NO_PLACED_ORDER, + $placedOrder + ); + } + + $customer = $this->getSecurityContext()->getCustomerUser(); + + if (null === $customer || $placedOrder->getCustomerId() !== $customer->getId()) { + throw new TheliaProcessException( + $this->getTranslator()->trans( + 'Received placed order id does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ), + TheliaProcessException::PLACED_ORDER_ID_BAD_CURRENT_CUSTOMER, + $placedOrder + ); + } + + $eventDispatcher->dispatch($this->getOrderEvent(), TheliaEvents::ORDER_CART_CLEAR); + + $this->getParserContext()->set('placed_order_id', $placedOrder->getId()); + } + + public function orderFailed($order_id, $message): void + { + if (empty($order_id)) { + // Fallback to request parameter if the method parameter is empty. + $order_id = $this->getRequest()->get('order_id'); + } + + $failedOrder = OrderQuery::create()->findPk($order_id); + + if (null !== $failedOrder) { + $customer = $this->getSecurityContext()->getCustomerUser(); + + if (null === $customer || $failedOrder->getCustomerId() !== $customer->getId()) { + throw new TheliaProcessException( + $this->getTranslator()->trans( + 'Received failed order id does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ), + TheliaProcessException::PLACED_ORDER_ID_BAD_CURRENT_CUSTOMER, + $failedOrder + ); + } + } else { + Tlog::getInstance()->warning("Failed order ID '$order_id' not found."); + } + + $this->getParserContext() + ->set('failed_order_id', $order_id) + ->set('failed_order_message', $message) + ; + } + + protected function getOrderEvent() + { + $order = $this->getOrder($this->getRequest()); + + return new OrderEvent($order); + } + + public function getOrder(Request $request) + { + $session = $request->getSession(); + + if (null !== $order = $session->getOrder()) { + return $order; + } + + $order = new Order(); + + $session->setOrder($order); + + return $order; + } + + public function viewAction($order_id) + { + $this->checkOrderCustomer($order_id); + + return $this->render('account-order', ['order_id' => $order_id]); + } + + public function generateInvoicePdf(EventDispatcherInterface $eventDispatcher, $order_id) + { + $this->checkOrderCustomer($order_id); + + return $this->generateOrderPdf($eventDispatcher, $order_id, ConfigQuery::read('pdf_invoice_file', 'invoice')); + } + + public function generateDeliveryPdf(EventDispatcherInterface $eventDispatcher, $order_id) + { + $this->checkOrderCustomer($order_id); + + return $this->generateOrderPdf($eventDispatcher, $order_id, ConfigQuery::read('pdf_delivery_file', 'delivery')); + } + + public function downloadVirtualProduct(EventDispatcherInterface $eventDispatcher, $order_product_id) + { + if (null !== $orderProduct = OrderProductQuery::create()->findPk($order_product_id)) { + $order = $orderProduct->getOrder(); + + if ($order->isPaid(false)) { + // check customer + $this->checkOrderCustomer($order->getId()); + + $virtualProductEvent = new VirtualProductOrderDownloadResponseEvent($orderProduct); + $eventDispatcher->dispatch( + $virtualProductEvent, + TheliaEvents::VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE + ); + + $response = $virtualProductEvent->getResponse(); + + if (!$response instanceof BaseResponse) { + throw new \RuntimeException('A Response must be added in the event TheliaEvents::VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE'); + } + + return $response; + } + } + + throw new AccessDeniedHttpException(); + } + + private function checkOrderCustomer($order_id): void + { + $this->checkAuth(); + + $order = OrderQuery::create()->findPk($order_id); + $valid = true; + if ($order) { + $customerOrder = $order->getCustomer(); + $customer = $this->getSecurityContext()->getCustomerUser(); + + if ($customerOrder->getId() != $customer->getId()) { + $valid = false; + } + } else { + $valid = false; + } + + if (false === $valid) { + throw new AccessDeniedHttpException(); + } + } + + public function getDeliveryModuleListAjaxAction() + { + $this->checkXmlHttpRequest(); + + // Change the delivery address if customer has changed it + $address = null; + $session = $this->getSession(); + $addressId = $this->getRequest()->get('address_id', null); + if (null !== $addressId && $addressId !== $session->getOrder()->getChoosenDeliveryAddress()) { + $address = AddressQuery::create()->findPk($addressId); + if (null !== $address && $address->getCustomerId() === $session->getCustomerUser()->getId()) { + $session->getOrder()->setChoosenDeliveryAddress($addressId); + } + } + + $address = AddressQuery::create()->findPk($session->getOrder()->getChoosenDeliveryAddress()); + + $countryId = $address->getCountryId(); + $stateId = $address->getStateId(); + + $args = [ + 'country' => $countryId, + 'state' => $stateId, + 'address' => $session->getOrder()->getChoosenDeliveryAddress(), + ]; + + return $this->render('ajax/order-delivery-module-list', $args); + } + + /** + * Redirect to cart view if at least one non product is out of stock. + * + * @return BaseResponse|null + */ + private function checkStockNotEmpty(EventDispatcherInterface $eventDispatcher) + { + $cart = $this->getSession()->getSessionCart($eventDispatcher); + + $cartItems = $cart->getCartItems(); + + foreach ($cartItems as $cartItem) { + $pse = $cartItem->getProductSaleElements(); + + $product = $cartItem->getProduct(); + + if ($pse->getQuantity() <= 0 && $product->getVirtual() !== 1) { + return $this->generateRedirectFromRoute('cart.view'); + } + } + + return null; + } + + /** + * Retrieve the chosen delivery address for a cart or the default customer address if not exists. + * + * @return Address|null + */ + protected function getCustomerAddress() + { + $deliveryAddress = null; + $addressId = $this->getSession()->getOrder()->getChoosenDeliveryAddress(); + if (null === $addressId) { + $customer = $this->getSecurityContext()->getCustomerUser(); + + $deliveryAddress = AddressQuery::create() + ->filterByCustomerId($customer->getId()) + ->orderByIsDefault(Criteria::DESC) + ->findOne(); + + if (null !== $deliveryAddress) { + $this->getSession()->getOrder()->setChoosenDeliveryAddress( + $deliveryAddress->getId() + ); + } + } else { + $deliveryAddress = AddressQuery::create()->findPk($addressId); + } + + return $deliveryAddress; + } +} diff --git a/domokits/local/modules/Front/Controller/SitemapController.php b/domokits/local/modules/Front/Controller/SitemapController.php new file mode 100644 index 0000000..5c18554 --- /dev/null +++ b/domokits/local/modules/Front/Controller/SitemapController.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front\Controller; + +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Response; +use Thelia\Model\ConfigQuery; +use Thelia\Model\LangQuery; + +/** + * Controller uses to generate sitemap.xml. + * + * A default cache of 2 hours is used to avoid attack. You can flush cache if you have `ADMIN` role and pass flush=1 in + * query string parameter. + * + * You can generate sitemap according to specific language and/or specific context (catalog/content). You have to + * use ```lang``` and ```context``` query string parameters to do so. If a language is not used in website or if the + * context is not supported the page not found is displayed. + * + * {url}/sitemap?lang=fr&context=catalog will generate a sitemap for catalog (categories and products) + * for french language. + * + * @author Julien Chanséaume + */ +class SitemapController extends BaseFrontController +{ + /** + * Folder name for sitemap cache. + */ + public const SITEMAP_CACHE_DIR = 'sitemap'; + + /** + * Key prefix for sitemap cache. + */ + public const SITEMAP_CACHE_KEY = 'sitemap'; + + /** + * @return Response + */ + public function generateAction() + { + /** @var Request $request */ + $request = $this->getRequest(); + + // the locale : fr, en, + $lang = $request->query->get('lang', ''); + if ('' !== $lang) { + if (!$this->checkLang($lang)) { + $this->pageNotFound(); + } + } + + // specific content : product, category, cms + $context = $request->query->get('context', ''); + if (!\in_array($context, ['', 'catalog', 'content'])) { + $this->pageNotFound(); + } + + $flush = $request->query->get('flush', ''); + + /** @var AdapterInterface $cacheAdapter */ + $cacheAdapter = $this->container->get('thelia.cache'); + + $cacheKey = self::SITEMAP_CACHE_KEY.$lang.$context; + + $cacheItem = $cacheAdapter->getItem($cacheKey); + + if (!$cacheItem->isHit() || $flush) { + $cacheExpire = (int) (ConfigQuery::read('sitemap_ttl', '7200')) ?: 7200; + + // Render the view. Compression causes problems and is deactivated. + $cacheContent = $this->getParser(null)->render( + 'sitemap.html', + [ + '_lang_' => $lang, + '_context_' => $context, + ], + false + ); + + $cacheItem->expiresAfter($cacheExpire); + $cacheItem->set($cacheContent); + $cacheAdapter->save($cacheItem); + } + + $response = new Response(); + $response->setContent($cacheItem->get()); + $response->headers->set('Content-Type', 'application/xml'); + + return $response; + } + + /** + * get the cache directory for sitemap. + * + * @return mixed|string + */ + protected function getCacheDir() + { + $cacheDir = $this->container->getParameter('kernel.cache_dir'); + $cacheDir = rtrim($cacheDir, '/'); + $cacheDir .= '/'.self::SITEMAP_CACHE_DIR.'/'; + + return $cacheDir; + } + + /** + * Check if current user has ADMIN role. + * + * @return bool + */ + protected function checkAdmin() + { + return $this->getSecurityContext()->hasAdminUser(); + } + + /** + * Check if a lang is used. + * + * @param $lang The lang code. e.g.: fr + * + * @return bool true if the language is used, otherwise false + */ + private function checkLang($lang) + { + // load locals + $lang = LangQuery::create() + ->findOneByCode($lang); + + return null !== $lang; + } +} diff --git a/domokits/local/modules/Front/Front.php b/domokits/local/modules/Front/Front.php new file mode 100644 index 0000000..f58c4f7 --- /dev/null +++ b/domokits/local/modules/Front/Front.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Front; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; + +class Front extends BaseModule +{ + public const MESSAGE_DOMAIN = 'front'; + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/Front/I18n/de_DE.php b/domokits/local/modules/Front/I18n/de_DE.php new file mode 100644 index 0000000..d32e2b4 --- /dev/null +++ b/domokits/local/modules/Front/I18n/de_DE.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Gutschein-Code darf nicht leer sein', + 'Delivery address does not belong to the current customer' => 'Lieferadresse gehört nicht zum aktuellen Kunden', + 'Delivery module cannot be use with selected delivery address' => 'Lieferung-Modul kann nicht mit ausgewählten Lieferadresse verwendet werden', + 'Error during address deletion process' => 'Fehler beim Löschen der Adresse', + 'Failed to add this article to your cart, please try again' => 'Der Artikel konnte nicht zum Warenkorb hinzugefügt werden, bitte versuchen Sie es erneut', + 'Invoice address does not belong to the current customer' => 'Rechnungsadresse gehört nicht zum aktuellen Kunden', + 'No placed order' => 'Keine Bestellungen', + 'Please check your coupon code: %message' => 'Bitte überprüfen Sie Ihren Gutschein-Code: %message', + 'Please check your input: %s' => 'Bitte überprüfen Sie Ihre Eingabe: %s', + 'Received failed order id does not belong to the current customer' => 'Empfangene Id einer fehlgeschlagenen Bestellung gehört nicht zum aktuellen Kunden', + 'Received placed order id does not belong to the current customer' => 'Empfangene Bestellungs-Id gehört nicht zum aktuellen Kunden', + 'Sorry, an error occured: %s' => 'Leider ist ein Fehler aufgetreten: %s', + 'Sorry, an error occurred: %message' => 'Leider ist ein Fehler aufgetreten: %message', + 'Sorry, an error occurred: %s' => 'Es tut uns Leid, aber ein Fehler ist aufgetreten: %s', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Vielen Dank für Ihre Anmeldung! Wir halten Ihnen auf dem Laufenden über neuen Updates.', + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated' => 'Um das virtuelle Produkt-Feature zu aktivieren, sollte das VirtualProductDelivery-Modul aktiviert werden', + 'Wrong email or password. Please try again' => 'E-Mail oder Passwort falsch. Bitte erneut versuchen', + 'You\'re currently logged in. Please log out before requesting a new password.' => 'Sie sind derzeit angemeldet. Bitte melden Sie sich ab, bevor Sie ein neues Passwort anfordern.', +]; diff --git a/domokits/local/modules/Front/I18n/en_US.php b/domokits/local/modules/Front/I18n/en_US.php new file mode 100644 index 0000000..086864d --- /dev/null +++ b/domokits/local/modules/Front/I18n/en_US.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Coupon code can\'t be empty', + 'Delivery address does not belong to the current customer' => 'Delivery address does not belong to the current customer', + 'Delivery module cannot be use with selected delivery address' => 'Delivery module cannot be use with selected delivery address', + 'Error during address deletion process' => 'Error during address deletion process', + 'Failed to add this article to your cart, please try again' => 'Failed to add this article to your cart, please try again', + 'Invoice address does not belong to the current customer' => 'Invoice address does not belong to the current customer', + 'No placed order' => 'No placed order', + 'Please check your coupon code: %message' => 'Please check your coupon code: %message', + 'Please check your input: %s' => 'Please check your input: %s', + 'Received failed order id does not belong to the current customer' => 'Received failed order id does not belong to the current customer', + 'Received placed order id does not belong to the current customer' => 'Received placed order id does not belong to the current customer', + 'Sorry, an error occured: %s' => 'Sorry, an error occured: %s', + 'Sorry, an error occurred: %message' => 'Sorry, an error occurred: %message', + 'Sorry, an error occurred: %s' => 'Sorry, an error occurred: %s', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.', + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated' => 'To enable the virtual product feature, the VirtualProductDelivery module should be activated', + 'Wrong email or password. Please try again' => 'Wrong email or password. Please try again', + 'You should sign in or register to use this coupon' => 'You should sign in or register to use this coupon', + 'You\'re currently logged in. Please log out before requesting a new password.' => 'You\'re currently logged in. Please log out before requesting a new password.', + 'Your account is not yet confirmed check out your mailbox' => 'Your account is not yet confirmed check out your mailbox', +]; diff --git a/domokits/local/modules/Front/I18n/fr_FR.php b/domokits/local/modules/Front/I18n/fr_FR.php new file mode 100644 index 0000000..45e3faa --- /dev/null +++ b/domokits/local/modules/Front/I18n/fr_FR.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Le code promo ne peut être vide', + 'Delivery address does not belong to the current customer' => 'L\'adresse de livraison n\'appartient pas au client en cours', + 'Delivery module cannot be use with selected delivery address' => 'Le module de livraison ne peut pas être utilisé avec cette adresse de livraison', + 'Error during address deletion process' => 'Désolé. Une erreur s\'est produite lors de la suppression de l\'adresse', + 'Failed to add this article to your cart, please try again' => 'Impossible d\'ajouter cet article à votre panier. Merci de ré-essayer.', + 'Invoice address does not belong to the current customer' => 'L\'adresse de facturation n\'appartient pas au client en cours', + 'No placed order' => 'Aucune commande passée', + 'Please check your coupon code: %message' => 'Merci de vérifier votre code promo : %message', + 'Please check your input: %s' => 'Merci de vérifier les informations indiquées : %s', + 'Received failed order id does not belong to the current customer' => 'L\'id de commande refusée n\'appartient pas au client en cours', + 'Received placed order id does not belong to the current customer' => 'L\'id de commande passée n\'appartient pas au client en cours', + 'Sorry, an error occured: %s' => 'Désolé. Une erreur s\'est produite : %s', + 'Sorry, an error occurred: %message' => 'Désolé. Une erreur s\'est produite : %message', + 'Sorry, an error occurred: %s' => 'Désolé, une erreur est survenue : %s', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Merci de votre inscription ! Nous vous tiendrons informé dès qu\'il y aura des nouveautés.', + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated' => 'Pour activer les produits virtuels, le module VirtualProductDelivery doit être activé', + 'Wrong email or password. Please try again' => 'Adresse email ou mot de passe incorrect. Merci de ré-essayer.', + 'You should sign in or register to use this coupon' => 'Vous devez vous connecter ou vous inscrire pour utiliser ce coupon', + 'You\'re currently logged in. Please log out before requesting a new password.' => 'Vous être actuellement connecté au site. Vous devez vous déconnecter pour demander un nouveau mot de passe.', +]; diff --git a/domokits/local/modules/Front/I18n/it_IT.php b/domokits/local/modules/Front/I18n/it_IT.php new file mode 100644 index 0000000..56afb70 --- /dev/null +++ b/domokits/local/modules/Front/I18n/it_IT.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Il codice di sconto non può essere vuoto', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Grazie per l\'inscrizione! Ti terremo aggiornato ogni volta che abbiamo eventuali nuovi aggiornamenti.', + 'You should sign in or register to use this coupon' => 'Dovresti accedere o registrarti per utilizzare questo coupon', +]; diff --git a/domokits/local/modules/Front/I18n/ru_RU.php b/domokits/local/modules/Front/I18n/ru_RU.php new file mode 100644 index 0000000..3427a9d --- /dev/null +++ b/domokits/local/modules/Front/I18n/ru_RU.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Код купона не может быть пустым', + 'Delivery address does not belong to the current customer' => 'Адрес доставки не пренадлежит текущему клиенту', + 'Delivery module cannot be use with selected delivery address' => 'Модуль доставки не может быть использован с текущим адресом доставки', + 'Error during address deletion process' => 'Ошибка удаления адреса', + 'Failed to add this article to your cart, please try again' => 'Ошибка при добавлении статьи в вашу корзину, попробуйте еще раз', + 'Invoice address does not belong to the current customer' => 'Адрес выставления счета не пренадлежит текущему клиенту', + 'No placed order' => 'Нет размещенного заказа', + 'Please check your coupon code: %message' => 'Пожалуйста, проверьте код купона: %message', + 'Please check your input: %s' => 'Пожалуйста, проверьте ваш ввод: %s', + 'Received failed order id does not belong to the current customer' => 'Полученный ошибочный заказ не пренадлежит текущему клиенту', + 'Received placed order id does not belong to the current customer' => 'Полученный размещенный заказ не пренадлежит текущему клиенту', + 'Sorry, an error occured: %s' => 'К сожалению произшла ошибка: %s', + 'Sorry, an error occurred: %message' => 'К сожалению произошла ошибка: %message ', + 'Sorry, an error occurred: %s' => 'К сожалению произошла ошибка: %s ', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Спасибо за подписку! Мы будем держать вас в курсе обновлений.', + 'The delivery module is not valid.' => 'Некоректный модуль доставки', + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated' => 'Для включения функции виртуальных продуктов, модуль VirtualProductDelivery должен быть включен', + 'Wrong email or password. Please try again' => 'Некоректный email или пароль. Пожалуйста, попробуйте еще раз', + 'You should sign in or register to use this coupon' => 'Вы должны войти или зарегистрироваться что использовать этот купон', + 'You\'re currently logged in. Please log out before requesting a new password.' => 'Вы сейчас авторизированы. Пожалуйста выйдите перед запросом нового пароля.', + 'Your subscription to our newsletter has been canceled.' => 'Ваша подписка на рассылку была отменена', +]; diff --git a/domokits/local/modules/Front/I18n/tr_TR.php b/domokits/local/modules/Front/I18n/tr_TR.php new file mode 100644 index 0000000..a709243 --- /dev/null +++ b/domokits/local/modules/Front/I18n/tr_TR.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Coupon code can\'t be empty' => 'Kupon şifre boş olamaz', + 'Delivery address does not belong to the current customer' => 'Teslimat adresi geçerli müşteriye ait değil', + 'Delivery module cannot be use with selected delivery address' => 'Teslim modülü seçili teslimat adresi kullanılamaz', + 'Error during address deletion process' => 'Adres silme işlemi sırasında bir hata oluştu', + 'Failed to add this article to your cart, please try again' => 'Sepetinize Bu ürün eklenemedi, lütfen tekrar deneyin', + 'Invoice address does not belong to the current customer' => 'Fatura adresi geçerli müşteriye ait değil', + 'No placed order' => 'Yerleştirilen hiçbir sipariş', + 'Please check your coupon code: %message' => 'Kupon kodunuzu gözden geçirin: %message', + 'Please check your input: %s' => 'Lütfen girişinizi denetleyin: %s', + 'Received failed order id does not belong to the current customer' => 'Alınan başarısız sipariş kimliği geçerli müşteriye ait değil', + 'Received placed order id does not belong to the current customer' => 'Alınmış yerleştirilmiş sipariş kimliği geçerli müşteriye ait değil', + 'Sorry, an error occured: %s' => 'Üzgünüz, bir hata oluştu: %s', + 'Sorry, an error occurred: %message' => 'Üzgünüz, bir hata oluştu: %message', + 'Sorry, an error occurred: %s' => 'Üzgünüz, bir hata oluştu: %s', + 'Thanks for signing up! We\'ll keep you posted whenever we have any new updates.' => 'Teşekkürler. Yeni güncelleştirmeler olduğunda sizi haberdar edeceğiz.', + 'To enable the virtual product feature, the VirtualProductDelivery module should be activated' => 'Sanal ürün özelliği etkinleştirmek için VirtualProductDelivery modülü etkinleştirilmesi', + 'Wrong email or password. Please try again' => 'Email adresi veya şifre hatalı. Lütfen tekrar deneyiniz', + 'You\'re currently logged in. Please log out before requesting a new password.' => 'Şu anda logged içinde. Lütfen yeni bir parola istemeden önce çıkış.', +]; diff --git a/domokits/local/modules/Front/LICENSE.txt b/domokits/local/modules/Front/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/Front/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/Front/composer.json b/domokits/local/modules/Front/composer.json new file mode 100644 index 0000000..b916b7b --- /dev/null +++ b/domokits/local/modules/Front/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/front-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "Front" + } +} diff --git a/domokits/local/modules/HookAdminHome/Config/config.xml b/domokits/local/modules/HookAdminHome/Config/config.xml new file mode 100644 index 0000000..895247b --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Config/config.xml @@ -0,0 +1,44 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/HookAdminHome/Config/module.xml b/domokits/local/modules/HookAdminHome/Config/module.xml new file mode 100644 index 0000000..a19b9af --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Config/module.xml @@ -0,0 +1,32 @@ + + + HookAdminHome\HookAdminHome + + Displays the default blocks on the homepage of the administration + + + Affiche les blocs par défaut sur la page d'accueil de l'administration + + + en_US + fr_FR + + 2.5.4 + + + Gilles Bourgeat + gilles@thelia.net + + + Franck Allimant + CQFDev + franck@cqfdev.fr + www.cqfdev.fr + + + classic + 2.5.4 + prod + diff --git a/domokits/local/modules/HookAdminHome/Config/routing.xml b/domokits/local/modules/HookAdminHome/Config/routing.xml new file mode 100644 index 0000000..a1dd0d6 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Config/routing.xml @@ -0,0 +1,12 @@ + + + + + + HookAdminHome\Controller\HomeController::processTemplateAction + ajax/thelia_news_feed + 1 + + diff --git a/domokits/local/modules/HookAdminHome/Controller/ConfigurationController.php b/domokits/local/modules/HookAdminHome/Controller/ConfigurationController.php new file mode 100644 index 0000000..174a22d --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Controller/ConfigurationController.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome\Controller; + +use HookAdminHome\HookAdminHome; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Tools\URL; + +class ConfigurationController extends BaseAdminController +{ + /** + * @Route("/admin/module/HookAdminHome/configure", name="admin.home.config", methods={"POST"}) + */ + public function editConfiguration() + { + if (null !== $response = $this->checkAuth( + AdminResources::MODULE, + [HookAdminHome::DOMAIN_NAME], + AccessManager::UPDATE + )) { + return $response; + } + + $form = $this->createForm('hookadminhome.config.form'); + $error_message = null; + + try { + $validateForm = $this->validateForm($form); + $data = $validateForm->getData(); + + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_NEWS, 0); + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_SALES, 0); + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_INFO, 0); + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_STATS, 0); + + if ($data['enabled-news']) { + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_NEWS, 1); + } + + if ($data['enabled-sales']) { + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_SALES, 1); + } + + if ($data['enabled-info']) { + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_INFO, 1); + } + + if ($data['enabled-stats']) { + HookAdminHome::setConfigValue(HookAdminHome::ACTIVATE_STATS, 1); + } + + return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/HookAdminHome')); + } catch (FormValidationException $e) { + $error_message = $this->createStandardFormValidationErrorMessage($e); + } + + if (null !== $error_message) { + $this->setupFormErrorContext( + 'configuration', + $error_message, + $form + ); + $response = $this->render('module-configure', ['module_code' => 'HookAdminHome']); + } + + return $response; + } +} diff --git a/domokits/local/modules/HookAdminHome/Controller/HomeController.php b/domokits/local/modules/HookAdminHome/Controller/HomeController.php new file mode 100644 index 0000000..abadc37 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Controller/HomeController.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome\Controller; + +use HookAdminHome\HookAdminHome; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Model\ConfigQuery; +use Thelia\Model\Currency; +use Thelia\Model\CustomerQuery; +use Thelia\Model\OrderQuery; + +/** + * Class HomeController. + * + * @author Gilles Bourgeat + */ +class HomeController extends BaseAdminController +{ + /** + * Key prefix for stats cache. + */ + public const STATS_CACHE_KEY = 'stats'; + + public const RESOURCE_CODE = 'admin.home'; + + /** + * @Route("/admin/home/stats", name="admin.home.stats") + */ + public function loadStatsAjaxAction(AdapterInterface $cacheAdapter) + { + if (null !== $response = $this->checkAuth(self::RESOURCE_CODE, [], AccessManager::VIEW)) { + return $response; + } + + $cacheExpire = ConfigQuery::getAdminCacheHomeStatsTTL(); + + $month = (int) $this->getRequest()->query->get('month', date('m')); + $year = (int) $this->getRequest()->query->get('year', date('Y')); + + $cacheKey = self::STATS_CACHE_KEY.'_'.$month.'_'.$year; + + $cacheItem = $cacheAdapter->getItem($cacheKey); + + // force flush + if ($this->getRequest()->query->get('flush', '0')) { + $cacheAdapter->deleteItem($cacheKey); + } + + if (!$cacheItem->isHit()) { + $data = $this->getStatus($month, $year); + + $cacheItem->set(json_encode($data)); + $cacheItem->expiresAfter($cacheExpire); + + if ($cacheExpire) { + $cacheAdapter->save($cacheItem); + } + } + + return $this->jsonResponse($cacheItem->get()); + } + + /** + * @Route( + * "/admin/home/month-sales-block/{month}/{year}", + * name="admin.home.month.sales.block", + * requirements={"month"="\d+", "year"="\d+"} + * ) + */ + public function blockMonthSalesStatistics($month, $year) + { + $baseDate = sprintf('%04d-%02d', $year, $month); + + $startDate = "$baseDate-01"; + $endDate = date('Y-m-t', strtotime($startDate)); + + $prevMonthStartDate = date('Y-m-01', strtotime("$baseDate -1 month")); + $prevMonthEndDate = date('Y-m-t', strtotime($prevMonthStartDate)); + + return $this->render('block-month-sales-statistics', [ + 'startDate' => $startDate, + 'endDate' => $endDate, + 'prevMonthStartDate' => $prevMonthStartDate, + 'prevMonthEndDate' => $prevMonthEndDate, + ]); + } + + /** + * @param int $month + * @param int $year + * + * @return \stdClass + */ + protected function getStatus($month, $year) + { + $data = new \stdClass(); + + $data->title = $this->getTranslator()->trans( + 'Stats on %month/%year', + ['%month' => $month, '%year' => $year], + HookAdminHome::DOMAIN_NAME + ); + + $data->series = []; + + /* sales */ + $data->series[] = $saleSeries = new \stdClass(); + $saleSeries->color = self::testHexColor('sales_color', '#adadad'); + $saleSeries->data = OrderQuery::getMonthlySaleStats($month, $year); + $saleSeries->valueFormat = '%1.2f '.Currency::getDefaultCurrency()->getSymbol(); + + /* new customers */ + $data->series[] = $newCustomerSeries = new \stdClass(); + $newCustomerSeries->color = self::testHexColor('customers_color', '#f39922'); + $newCustomerSeries->data = CustomerQuery::getMonthlyNewCustomersStats($month, $year); + $newCustomerSeries->valueFormat = '%d'; + + /* orders */ + $data->series[] = $orderSeries = new \stdClass(); + $orderSeries->color = self::testHexColor('orders_color', '#5cb85c'); + $orderSeries->data = OrderQuery::getMonthlyOrdersStats($month, $year); + $orderSeries->valueFormat = '%d'; + + /* first order */ + $data->series[] = $firstOrderSeries = new \stdClass(); + $firstOrderSeries->color = self::testHexColor('first_orders_color', '#5bc0de'); + $firstOrderSeries->data = OrderQuery::getFirstOrdersStats($month, $year); + $firstOrderSeries->valueFormat = '%d'; + + /* cancelled orders */ + $data->series[] = $cancelledOrderSeries = new \stdClass(); + $cancelledOrderSeries->color = self::testHexColor('cancelled_orders_color', '#d9534f'); + $cancelledOrderSeries->data = OrderQuery::getMonthlyOrdersStats($month, $year, [5]); + $cancelledOrderSeries->valueFormat = '%d'; + + return $data; + } + + /** + * @param string $key + * @param string $default + * + * @return string hexadecimal color or default argument + */ + protected function testHexColor($key, $default) + { + $hexColor = $this->getRequest()->query->get($key, $default); + + return preg_match('/^#[a-f0-9]{6}$/i', $hexColor) ? $hexColor : $default; + } +} diff --git a/domokits/local/modules/HookAdminHome/Form/Configuration.php b/domokits/local/modules/HookAdminHome/Form/Configuration.php new file mode 100644 index 0000000..87591ca --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Form/Configuration.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome\Form; + +use HookAdminHome\HookAdminHome; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +class Configuration extends BaseForm +{ + protected function buildForm(): void + { + $this->formBuilder->add( + 'enabled-news', + CheckboxType::class, + [ + 'label' => 'Enabled News', + 'label_attr' => [ + 'for' => 'enabled-news', + 'help' => Translator::getInstance()->trans( + 'Check if you want show news', + [], + HookAdminHome::DOMAIN_NAME + ), + ], + 'required' => false, + 'value' => HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_NEWS, 0), + ] + ); + + $this->formBuilder->add( + 'enabled-info', + CheckboxType::class, + [ + 'label' => 'Enabled Info', + 'label_attr' => [ + 'for' => 'enabled-info', + 'help' => Translator::getInstance()->trans( + 'Check if you want show info', + [], + HookAdminHome::DOMAIN_NAME + ), + ], + 'required' => false, + 'value' => HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_INFO, 0), + ] + ); + + $this->formBuilder->add( + 'enabled-stats', + CheckboxType::class, + [ + 'label' => 'Enabled default Home Stats', + 'label_attr' => [ + 'for' => 'enabled-stats', + 'help' => Translator::getInstance()->trans( + 'Check if you want show default Home Stats', + [], + HookAdminHome::DOMAIN_NAME + ), + ], + 'required' => false, + 'value' => HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_STATS, 0), + ] + ); + + $this->formBuilder->add( + 'enabled-sales', + CheckboxType::class, + [ + 'label' => 'Enabled Sales Statistics', + 'label_attr' => [ + 'for' => 'enabled-sales', + 'help' => Translator::getInstance()->trans( + 'Check if you want show sales stats', + [], + HookAdminHome::DOMAIN_NAME + ), + ], + 'required' => false, + 'value' => HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_SALES, 0), + ] + ); + } + + public static function getName() + { + return 'hookadminhomeconfigform'; + } +} diff --git a/domokits/local/modules/HookAdminHome/Hook/AdminHook.php b/domokits/local/modules/HookAdminHome/Hook/AdminHook.php new file mode 100644 index 0000000..c451460 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Hook/AdminHook.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome\Hook; + +use HookAdminHome\HookAdminHome; +use Symfony\Component\Cache\Adapter\AdapterInterface; +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class AdminHook. + * + * @author Gilles Bourgeat + */ +class AdminHook extends BaseHook +{ + protected $theliaCache; + + public function __construct(AdapterInterface $theliaCache = null) + { + $this->theliaCache = $theliaCache; + } + + public function blockStatistics(HookRenderEvent $event): void + { + if (1 == HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_STATS, 1)) { + $event->add($this->render('block-statistics.html')); + } + + $event->add($this->render('hook-admin-home-config.html')); + } + + public function blockStatisticsJs(HookRenderEvent $event): void + { + if (1 == HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_STATS, 1)) { + $event->add($this->render('block-statistics-js.html')); + } + } + + public function blockSalesStatistics(HookRenderBlockEvent $event): void + { + if (1 == HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_SALES, 1)) { + $content = trim($this->render('block-sales-statistics.html')); + if (!empty($content)) { + $event->add([ + 'id' => 'block-sales-statistics', + 'title' => $this->trans('Sales statistics', [], HookAdminHome::DOMAIN_NAME), + 'content' => $content, + ]); + } + } + } + + public function blockNews(HookRenderBlockEvent $event): void + { + if (1 == HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_NEWS, 1)) { + $content = trim($this->render('block-news.html')); + if (!empty($content)) { + $event->add([ + 'id' => 'block-news', + 'title' => $this->trans('Thelia Github activity', [], HookAdminHome::DOMAIN_NAME), + 'content' => $content, + ]); + } + } + } + + public function blockTheliaInformation(HookRenderBlockEvent $event): void + { + $releases = $this->getGithubReleases(); + if (1 == HookAdminHome::getConfigValue(HookAdminHome::ACTIVATE_INFO, 1)) { + $content = trim( + $this->render( + 'block-thelia-information.html', + [ + 'latestStableRelease' => $releases['latestStableRelease'], + 'latestPreRelease' => $releases['latestPreRelease'], + ] + ) + ); + if (!empty($content)) { + $event->add([ + 'id' => 'block-thelia-information', + 'title' => $this->trans('Thelia news', [], HookAdminHome::DOMAIN_NAME), + 'content' => $content, + ]); + } + } + } + + private function getGithubReleases(): array + { + $cachedReleases = $this->theliaCache->getItem('thelia_github_releases'); + if (!$cachedReleases->isHit()) { + try { + $resource = curl_init(); + + curl_setopt($resource, \CURLOPT_URL, 'https://api.github.com/repos/thelia/thelia/releases'); + curl_setopt($resource, \CURLOPT_RETURNTRANSFER, 1); + curl_setopt($resource, \CURLOPT_HTTPHEADER, ['accept: application/vnd.github.v3+json']); + curl_setopt($resource, \CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13'); + + $results = curl_exec($resource); + + curl_close($resource); + + $theliaReleases = json_decode($results, true); + + $publishedAtSort = function ($a, $b) {return (new \DateTime($a['published_at'])) < (new \DateTime($b['published_at'])); }; + + $stableReleases = array_filter($theliaReleases, function ($theliaRelease) { return !$theliaRelease['prerelease']; }); + usort($stableReleases, $publishedAtSort); + $latestStableRelease = $stableReleases[0] ?? null; + + $preReleases = array_filter($theliaReleases, function ($theliaRelease) { return $theliaRelease['prerelease']; }); + usort($preReleases, $publishedAtSort); + $latestPreRelease = $preReleases[0] ?? null; + + // Don't display pre-release if they are < than stable release + if (version_compare($latestPreRelease['tag_name'], $latestStableRelease['tag_name'], '<')) { + $latestPreRelease = null; + } + } catch (\Exception $exception) { + $latestPreRelease = null; + $latestStableRelease = null; + } + + $cachedReleases->expiresAfter(3600); + $cachedReleases->set( + [ + 'latestStableRelease' => $latestStableRelease, + 'latestPreRelease' => $latestPreRelease, + ] + ); + $this->theliaCache->save($cachedReleases); + } + + return $cachedReleases->get(); + } +} diff --git a/domokits/local/modules/HookAdminHome/Hook/HookAdminManager.php b/domokits/local/modules/HookAdminHome/Hook/HookAdminManager.php new file mode 100644 index 0000000..64dbfcf --- /dev/null +++ b/domokits/local/modules/HookAdminHome/Hook/HookAdminManager.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +class HookAdminManager extends BaseHook +{ + public function onModuleConfiguration(HookRenderEvent $event): void + { + $event->add( + $this->render('admin-home-config.html') + ); + } +} diff --git a/domokits/local/modules/HookAdminHome/HookAdminHome.php b/domokits/local/modules/HookAdminHome/HookAdminHome.php new file mode 100644 index 0000000..409b218 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/HookAdminHome.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAdminHome; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Core\Template\TemplateDefinition; +use Thelia\Module\BaseModule; + +class HookAdminHome extends BaseModule +{ + /** @var string */ + public const DOMAIN_NAME = 'hookadminhome'; + + /** @var string */ + public const ACTIVATE_NEWS = 'activate_home_news'; + + /** @var string */ + public const ACTIVATE_SALES = 'activate_home_sales'; + + /** @var string */ + public const ACTIVATE_INFO = 'activate_home_info'; + + /** @var string */ + public const ACTIVATE_STATS = 'activate_stats'; + + /** + * @return array + */ + public function getHooks() + { + return [ + [ + 'type' => TemplateDefinition::BACK_OFFICE, + 'code' => 'hook_home_stats', + 'title' => 'Hook Home Stats', + 'description' => 'Hook to change default stats', + ], + ]; + } + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/HookAdminHome/I18n/ar_SA.php b/domokits/local/modules/HookAdminHome/I18n/ar_SA.php new file mode 100644 index 0000000..537ee04 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/ar_SA.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Stats on %month/%year' => 'إحصائيات عن الشهر و السنة %month/%year', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ar_SA.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ar_SA.php new file mode 100644 index 0000000..a91b16c --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ar_SA.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'تم إحباط الطلبات', + 'Average cart' => 'متوسط العربة', + 'Categories' => 'الفئات', + 'Click here' => 'انقر هنا', + 'Current version' => 'النسخة الحالية', + 'Customers' => 'العملاء', + 'Dashboard' => 'لوحة المعلومات', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/cs_CZ.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/cs_CZ.php new file mode 100644 index 0000000..12aae3c --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/cs_CZ.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Aborted orders', + 'Average cart' => 'Average cart', + 'Categories' => 'Categories', + 'Click here' => 'Click here', + 'Current version' => 'Current version', + 'Customers' => 'Customers', + 'Dashboard' => 'Dashboard', + 'First orders' => 'First orders', + 'Latest version available' => 'Latest version available', + 'Lire la suite' => 'Lire la suite', + 'Loading Thelia lastest news...' => 'Loading Thelia lastest news...', + 'Loading...' => 'Loading...', + 'New customers' => 'New customers', + 'News' => 'News', + 'Offline products' => 'Offline products', + 'Online products' => 'Online products', + 'Orders' => 'Orders', + 'Overall sales' => 'Overall sales', + 'Previous month sales' => 'Previous month sales', + 'Previous year sales' => 'Previous year sales', + 'Products' => 'Products', + 'Sales' => 'Sales', + 'Sales excluding shipping' => 'Sales excluding shipping', + 'This month' => 'This month', + 'This year' => 'This year', + 'Today' => 'Today', + 'Yesterday sales' => 'Yesterday sales', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/de_DE.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..4b3fac2 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/de_DE.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Abgebrochene Bestellungen', + 'Average cart' => 'Durchschnittlichen Warenkorb', + 'Categories' => 'Kategorien', + 'Click here' => 'Hier Klicken', + 'Current version' => 'Aktuelle Version', + 'Customers' => 'Kunden', + 'Dashboard' => 'Dashboard', + 'First orders' => 'Erste Bestellungen', + 'Latest version available' => 'Neueste Version verfügbar', + 'Lire la suite' => 'Weiterlesen', + 'Loading Thelia lastest news...' => 'THELIAs neuesten Nachrichten Laden ...', + 'Loading...' => 'Laden...', + 'New customers' => 'Neue Kunde', + 'Offline products' => 'Offline Produkte', + 'Online products' => 'Online Produkte', + 'Orders' => 'Bestellungen', + 'Overall sales' => 'Gesamtverkäufe', + 'Previous month sales' => 'Vorheriger Monat Verkäufe', + 'Previous year sales' => 'Vorheriges Jahr Verkäufe', + 'Products' => 'Produkte', + 'Sales' => 'Verkäufe', + 'Sales excluding shipping' => 'Verkäufe ohne Lieferung', + 'This month' => 'Diesen Monat', + 'This year' => 'Dieses Jahr', + 'Today' => 'Heute', + 'Yesterday sales' => 'Verkäufe von Gestern', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/en_US.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..b212ca5 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/en_US.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Aborted orders', + 'An error occurred while reading from JSON file' => 'An error occurred while reading from JSON file', + 'Average cart' => 'Average cart', + 'Categories' => 'Categories', + 'Click here' => 'Click here', + 'Current version' => 'Current version', + 'Customers' => 'Customers', + 'Dashboard' => 'Dashboard', + 'First orders' => 'First orders', + 'Latest version available' => 'Latest version available', + 'Loading Thelia lastest news...' => 'Loading Thelia lastest news...', + 'Loading...' => 'Loading...', + 'New customers' => 'New customers', + 'News' => 'News', + 'Offline products' => 'Offline products', + 'Online products' => 'Online products', + 'Orders' => 'Orders', + 'Overall sales' => 'Overall sales', + 'Previous month sales' => 'Previous month sales', + 'Previous year sales' => 'Previous year sales', + 'Products' => 'Products', + 'Read more' => 'Read more', + 'Sales' => 'Sales', + 'Sales excluding shipping' => 'Sales excluding shipping', + 'This month' => 'This month', + 'This year' => 'This year', + 'Today' => 'Today', + 'YYYY-MM' => 'YYYY-MM', + 'Yesterday sales' => 'Yesterday sales', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/es_ES.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/es_ES.php new file mode 100644 index 0000000..3454efe --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/es_ES.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Pedidos abandonados', + 'Average cart' => 'Carrito medio', + 'Categories' => 'Categorías', + 'Click here' => 'Haz clic aquí', + 'Current version' => 'Versión actual', + 'Customers' => 'Clientes', + 'Dashboard' => 'Panel de Control', + 'First orders' => 'Primeros pedidos', + 'Latest version available' => 'Última versión disponible', + 'Lire la suite' => 'Leer más', + 'Loading Thelia lastest news...' => 'Carregant Thelia últimes notícies ...', + 'Loading...' => 'Carregant ...', + 'New customers' => 'Nuevos clientes', + 'News' => 'Noticias', + 'Offline products' => 'Productos fuera de línea', + 'Online products' => 'Productos en línea', + 'Orders' => 'Pedidos', + 'Overall sales' => 'Ventas totales', + 'Previous month sales' => 'Ventas del mes anterior', + 'Previous year sales' => 'Ventas del año anterior', + 'Products' => 'Productos', + 'Sales' => 'Ventas', + 'Sales excluding shipping' => 'Ventas sin el envio', + 'This month' => 'Este mes', + 'This year' => 'Este año', + 'Today' => 'Hoy', + 'Yesterday sales' => 'Ventas de ayer', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..b0693b6 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Paniers abandonnés', + 'An error occurred while reading from JSON file' => 'Désolé, une erreur s\'est produite pendant la récupération des données.', + 'Average cart' => 'Panier moyen', + 'Categories' => 'Rubriques', + 'Customers' => 'Clients', + 'Dashboard' => 'Tableau de bord', + 'First orders' => 'Premières commandes', + 'Latest pre-release' => 'Derniére pré-version', + 'Latest stable version' => 'Derniére version stable', + 'Loading Thelia lastest news...' => 'Chargement des dernières information Thelia...', + 'Loading Tweeter feed...' => 'Chargement du fil Tweeter...', + 'New customers' => 'Nouveaux clients', + 'Offline products' => 'Produits hors ligne', + 'Online products' => 'Produits en ligne', + 'Orders' => 'Commandes', + 'Overall sales' => 'Total des ventes', + 'Previous month sales' => 'Ventes du mois précédent', + 'Previous year sales' => 'Ventes de l\'année précédente', + 'Products' => 'Produits', + 'Read more' => 'Lire la suite', + 'Sales' => 'Ventes', + 'Sales excluding shipping' => 'Ventes hors frais de port', + 'This month' => 'Ce mois', + 'This year' => 'Cette année', + 'Today' => 'Aujourd\'hui', + 'YYYY-MM' => 'MM/YYYY', + 'Yesterday sales' => 'Ventes de la veille', + 'Your version' => 'Votre version', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/it_IT.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/it_IT.php new file mode 100644 index 0000000..a8d73b8 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/it_IT.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Ordini annullati', + 'Average cart' => 'Carrello medio', + 'Categories' => 'Categorie', + 'Click here' => 'Clicca qui', + 'Current version' => 'Versione attuale', + 'Customers' => 'Clienti', + 'Dashboard' => 'Dashboard', + 'First orders' => 'Primi ordini', + 'Latest version available' => 'Ultima versione disponibile', + 'Lire la suite' => 'Per saperne di più', + 'Loading Thelia lastest news...' => 'Caricamento delle ultime notizie su Thelia...', + 'Loading...' => 'Caricamento...', + 'New customers' => 'Nuovi clienti', + 'News' => 'Notizie', + 'Offline products' => 'Prodotti non in linea', + 'Online products' => 'Prodotti online', + 'Orders' => 'Ordini', + 'Overall sales' => 'Vendite complessive', + 'Previous month sales' => 'Vendite del mese precedente', + 'Previous year sales' => 'Vendite dell\'anno precedente', + 'Products' => 'Prodotti', + 'Read more' => 'Per saperne di più', + 'Sales' => 'Vendite', + 'Sales excluding shipping' => 'Vendite escluse spese di spedizione', + 'This month' => 'Questo mese', + 'This year' => 'Quest\'anno', + 'Today' => 'Oggi', + 'Yesterday sales' => 'Vendite di ieri', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/pt_BR.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/pt_BR.php new file mode 100644 index 0000000..7bd02a1 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/pt_BR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Ordens abortadas', + 'Click here' => 'Clique aqui', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ru_RU.php new file mode 100644 index 0000000..6e05f0f --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'Отмененные заказы', + 'Average cart' => 'Средняя корзина', + 'Categories' => 'Категории', + 'Click here' => 'Нажмите здесь', + 'Current version' => 'Текущая версия', + 'Customers' => 'Клиенты', + 'Dashboard' => 'Приборная панель', + 'First orders' => 'Первые заказы', + 'Latest version available' => 'Последняя доступная версия', + 'Loading Thelia lastest news...' => 'Загрузка последних новостей Thelia...', + 'Loading...' => 'Загрузка...', + 'New customers' => 'Новые клиенты', + 'News' => 'Новости', + 'Offline products' => 'Товары выкл.', + 'Online products' => 'Товары вкл.', + 'Orders' => 'Заказы', + 'Overall sales' => 'Всего продаж', + 'Previous month sales' => 'Продажи в предыдущем месяце', + 'Previous year sales' => 'Продажи в предыдущем году', + 'Products' => 'Товары', + 'Read more' => 'Читать далее', + 'Sales' => 'Продажи', + 'Sales excluding shipping' => 'Продаж без доставки', + 'This month' => 'В этом месяце', + 'This year' => 'В этом году', + 'Today' => 'Сегодня', + 'Yesterday sales' => 'Вчерашние продажи', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..9de1b15 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Aborted orders' => 'İptal edilen siparişler', + 'Average cart' => 'Sepet Ortalaması', + 'Categories' => 'Katogoriler', + 'Click here' => 'Buraya tıklayın', + 'Current version' => 'Güncel Sürüm', + 'Customers' => 'müşteriler', + 'Dashboard' => 'Kontrol paneli', + 'First orders' => 'İlk emir', + 'Latest version available' => 'En son yorum elde edilebilir', + 'Lire la suite' => 'Devamını okuyun', + 'Loading Thelia lastest news...' => 'Thelia yükleme son haberler...', + 'Loading...' => 'Yükleneniyor…...', + 'New customers' => 'Yeni Müşteriler', + 'News' => 'Yeni Haberler', + 'Offline products' => 'Çevrimdışı ürünler', + 'Online products' => 'Online Ürünler', + 'Orders' => 'siparişler', + 'Overall sales' => 'Genel satış', + 'Previous month sales' => 'Önceki ay satış', + 'Previous year sales' => 'Önceki yılın satış', + 'Products' => 'ürün', + 'Sales' => 'Satış', + 'Sales excluding shipping' => 'Nakliye hariç satış', + 'This month' => 'Bu Ay', + 'This year' => 'Bu yıl', + 'Today' => 'bugün', + 'Yesterday sales' => 'Dün satış', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/cs_CZ.php b/domokits/local/modules/HookAdminHome/I18n/cs_CZ.php new file mode 100644 index 0000000..ada6cbb --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/cs_CZ.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Sales statistics', + 'Stats on %month/%year' => 'Stats on %month/%year', + 'Thelia informations' => 'Thelia information', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/de_DE.php b/domokits/local/modules/HookAdminHome/I18n/de_DE.php new file mode 100644 index 0000000..3f14186 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/de_DE.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Verkaufsstatistiken', + 'Stats on %month/%year' => 'Statistiken für %month/%year', + 'Thelia informations' => 'Thelias Informationen', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/en_US.php b/domokits/local/modules/HookAdminHome/I18n/en_US.php new file mode 100644 index 0000000..aa42b6d --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/en_US.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Stats on %month/%year' => 'Stats on %month/%year', + 'Thelia informations' => 'Thelia information', + 'Sales statistics' => 'Sales statistics', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/es_ES.php b/domokits/local/modules/HookAdminHome/I18n/es_ES.php new file mode 100644 index 0000000..588ad89 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/es_ES.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Estadísticas de ventas', + 'Thelia informations' => 'información sobre Thelia', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/fr_FR.php b/domokits/local/modules/HookAdminHome/I18n/fr_FR.php new file mode 100644 index 0000000..880bd48 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Statistiques de vente', + 'Stats on %month/%year' => 'Statistiques pour %month/%year', + 'Thelia Github activity' => 'Thelia sur Github', + 'Thelia news' => 'Actualité Thelia', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/id_ID.php b/domokits/local/modules/HookAdminHome/I18n/id_ID.php new file mode 100644 index 0000000..daa0f18 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/id_ID.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Stats on %month/%year' => 'Stats on %month/%year', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/it_IT.php b/domokits/local/modules/HookAdminHome/I18n/it_IT.php new file mode 100644 index 0000000..c02246c --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Statistiche di vendita', + 'Thelia informations' => 'Thelia informazioni', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/ru_RU.php b/domokits/local/modules/HookAdminHome/I18n/ru_RU.php new file mode 100644 index 0000000..5d5de3f --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/ru_RU.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Статистика продаж', + 'Stats on %month/%year' => 'Статистика за %month/%year', + 'Thelia informations' => 'Информация о Thelia', +]; diff --git a/domokits/local/modules/HookAdminHome/I18n/tr_TR.php b/domokits/local/modules/HookAdminHome/I18n/tr_TR.php new file mode 100644 index 0000000..0c5b6b7 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/I18n/tr_TR.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Sales statistics' => 'Satış istatistikleri', + 'Stats on %month/%year' => '%month/%year istatistikleri', + 'Thelia informations' => 'Thelia bilgi', +]; diff --git a/domokits/local/modules/HookAdminHome/LICENSE.txt b/domokits/local/modules/HookAdminHome/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookAdminHome/composer.json b/domokits/local/modules/HookAdminHome/composer.json new file mode 100644 index 0000000..d5921a2 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-admin-home-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookAdminHome" + } +} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/admin-home-config.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/admin-home-config.html new file mode 100644 index 0000000..7517623 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/admin-home-config.html @@ -0,0 +1,87 @@ +
+
+
+ {intl l="Hook Admin Home config"} +
+
+
+
+
+ + {form name="hookadminhome.config.form"} + + + {form_hidden_fields} + + {form_field form=$form field="enabled-stats"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="enabled-news"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="enabled-sales"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="enabled-info"} +
+ + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + + + {/form} +
+
+
+ + diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/ajax/thelia_news_feed.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/ajax/thelia_news_feed.html new file mode 100644 index 0000000..f5c1aa7 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/ajax/thelia_news_feed.html @@ -0,0 +1,29 @@ +{* this template is loaded via Ajax in the login page, to prevent login page slowdown *} + +{* Set the default translation domain, that will be used by intl when the 'd' parameter is not set *} +{default_translation_domain domain='hookadminhome.bo.default'} + +
+ {loop type="feed" name="thelia_feeds" url="https://github.com/thelia/thelia/commits/main.atom" limit="6" timeout=30} + +
+ +
+
+ {* we use unescape:"htmlall" to unescape var before truncate, to prevent a cut in the middel of an HTML entity, eg &ea... *} +

{$DESCRIPTION|strip_tags|unescape:"htmlall"|truncate:250:"...":true nofilter}

+
+ +
+
+ + {/loop} +
diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/css/home.css b/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/css/home.css new file mode 100644 index 0000000..611f622 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/css/home.css @@ -0,0 +1 @@ +#block-information a{color:#8A8A8A}.stats{border-right:1px solid #f0f0f0;text-align:center}.stats:last-child{border-right:none}.stats h2{margin-top:0;margin-bottom:5px;font-size:30px}.stats p{margin-top:0;text-transform:uppercase;font-size:12px}@media (max-width:991px){.stats{margin-bottom:10px}.stats:nth-child(3){border-right:none}}.homepage #date-picker{text-align:center;} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/less/home.less b/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/less/home.less new file mode 100644 index 0000000..a87eaab --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/assets/less/home.less @@ -0,0 +1,45 @@ +@import "../../../../../../../../templates/backOffice/default/assets/less/bootstrap/variables.less"; +@import "../../../../../../../../templates/backOffice/default/assets/less/thelia/variables.less"; + +#block-information { + a { + color: #8A8A8A; + } +} + +.stats { + border-right: 1px solid @table-border-color; + text-align: center; + + &:last-child { + border-right: none; + } + + h2 { + margin-top: 0; + margin-bottom: 5px; + font-size: 30px; + } + + p { + margin-top: 0; + text-transform: uppercase; + font-size: @font-size-base - 1; // 12px + } +} + +@media (max-width: @screen-sm-max) { + .stats { + margin-bottom: 10px; + + &:nth-child(3) { + border-right: none; + } + } +} + +.homepage { + #date-picker { + text-align: center; + } +} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-information.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-information.html new file mode 100644 index 0000000..25f0926 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-information.html @@ -0,0 +1,59 @@ +{* Do not display shop information block if user none of the required authorizations *} + +{capture name="shop_information_block_content"} + {loop type="auth" name="can_view" role="ADMIN" resource="admin.customer" access="VIEW"} + + {/loop} + + {loop type="auth" name="can_view" role="ADMIN" resource="admin.category" access="VIEW"} + + {/loop} + + {loop type="auth" name="can_view" role="ADMIN" resource="admin.product" access="VIEW"} + + + + {/loop} + + {loop type="auth" name="can_view" role="ADMIN" resource="admin.order" access="VIEW"} + + {/loop} +{/capture} + +{if trim($smarty.capture.shop_information_block_content) ne ""} +
+
+ {$smarty.capture.shop_information_block_content nofilter} +
+
+{/if} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-month-sales-statistics.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-month-sales-statistics.html new file mode 100644 index 0000000..b4ae789 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-month-sales-statistics.html @@ -0,0 +1,48 @@ +{loop type="currency" name="default-currency" default_only="1"} +{$defaultCurrency = $SYMBOL} +{/loop} + +{if empty($startDate)}{$startDate = 'this_month'}{/if} +{if empty($startDate)}{$startDate = 'this_month'}{/if} + +{if empty($prevMonthStartDate)}{$prevMonthStartDate = 'last_month'}{/if} +{if empty($prevMonthEndDate)}{$prevMonthEndDate = 'last_month'}{/if} + +
+ + + + + + + + + + + + + + + + + + + + + + + +
{intl l="Overall sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate=$startDate endDate=$endDate|default:null} symbol=$defaultCurrency|default:'€'}
{intl l="Sales excluding shipping" d='hookadminhome.bo.default'} + {$salesNoShipping = {stats key="sales" startDate=$startDate endDate=$endDate|default:null includeShipping="false"}} + {format_money number=$salesNoShipping symbol=$defaultCurrency|default:'€'} +
{intl l="Previous month sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate=$prevMonthStartDate endDate=$prevMonthEndDate|default:null} symbol=$defaultCurrency|default:'€'}
{intl l="Orders" d='hookadminhome.bo.default'} + {$orderCount = {stats key="orders" startDate=$startDate endDate=$endDate|default:null}} + {$orderCount} +
{intl l="Average cart" d='hookadminhome.bo.default'} + {if $orderCount == 0} + {format_money number=0 symbol=$defaultCurrency|default:'€'} + {else} + {format_money number={($salesNoShipping/$orderCount)|round:"2"} symbol=$defaultCurrency|default:'€'} + {/if} +
+
diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news-js.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news-js.html new file mode 100644 index 0000000..d18e1f9 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news-js.html @@ -0,0 +1,7 @@ +{loop type="auth" name="can_view" role="ADMIN" module="HookAdminHome" access="VIEW"} + +{/loop} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news.html new file mode 100644 index 0000000..97bd6f9 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-news.html @@ -0,0 +1,5 @@ +{loop type="auth" name="can_view" role="ADMIN" module="HookAdminHome" access="VIEW"} +
+
{intl l="Loading Thelia lastest news..." d='hookadminhome.bo.default'}
+
+{/loop} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-sales-statistics.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-sales-statistics.html new file mode 100644 index 0000000..5ee04b6 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-sales-statistics.html @@ -0,0 +1,100 @@ +{loop type="auth" name="can_view" role="ADMIN" resource="admin.order" access="VIEW"} + +{loop type="currency" name="default-currency" default_only="1"} +{$defaultCurrency = $SYMBOL} +{/loop} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{intl l="Overall sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate="today" endDate="today"} symbol=$defaultCurrency|default:'€'}
{intl l="Sales excluding shipping" d='hookadminhome.bo.default'} + {$salesNoShipping = {stats key="sales" startDate="today" endDate="today" includeShipping="false"}} + {format_money number=$salesNoShipping symbol=$defaultCurrency|default:'€'} +
{intl l="Yesterday sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate="yesterday" endDate="yesterday"} symbol=$defaultCurrency|default:'€'}
{intl l="Orders" d='hookadminhome.bo.default'} + {$orderCount = {stats key="orders" startDate="today" endDate="today"}} + {$orderCount} +
{intl l="Average cart" d='hookadminhome.bo.default'} + {if $orderCount == 0} + {format_money number=0 symbol=$defaultCurrency|default:'€'} + {else} + {format_money number={($salesNoShipping/$orderCount)|round:"2"} symbol=$defaultCurrency|default:'€'} + {/if} +
+
+
+ +
+ {include file="block-month-sales-statistics.html"} +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
{intl l="Overall sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate="this_year" endDate="this_year"} symbol=$defaultCurrency|default:'€'}
{intl l="Sales excluding shipping" d='hookadminhome.bo.default'} + {$salesNoShipping = {stats key="sales" startDate="this_year" endDate="this_year" includeShipping="false"}} + {format_money number=$salesNoShipping symbol=$defaultCurrency|default:'€'} +
{intl l="Previous year sales" d='hookadminhome.bo.default'}{format_money number={stats key="sales" startDate="last_year" endDate="last_year"} symbol=$defaultCurrency|default:'€'}
{intl l="Orders" d='hookadminhome.bo.default'} + {$orderCount = {stats key="orders" startDate="this_year" endDate="this_year"}} + {$orderCount} +
{intl l="Average cart" d='hookadminhome.bo.default'} + {if $orderCount == 0} + {format_money number=0 symbol=$defaultCurrency|default:'€'} + {else} + {format_money number={($salesNoShipping/$orderCount)|round:"2"} symbol=$defaultCurrency|default:'€'} + {/if} +
+
+
+
+{/loop} diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics-js.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics-js.html new file mode 100644 index 0000000..b346c47 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics-js.html @@ -0,0 +1,207 @@ + + + + + + + \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics.html new file mode 100644 index 0000000..e29e713 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-statistics.html @@ -0,0 +1,34 @@ +{loop type="auth" name="can_view" role="ADMIN" resource="admin.order" access="VIEW"} +
+ +
+ {intl l='Dashboard' d='hookadminhome.bo.default'} +
+ + + + + + + +
+
+ +
+
+ + + + + +
+
+ +
+ +
+
+
+ +
+{/loop} \ No newline at end of file diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-thelia-information.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-thelia-information.html new file mode 100644 index 0000000..f4823d2 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/block-thelia-information.html @@ -0,0 +1,24 @@ +{loop type="auth" name="can_view" role="ADMIN" module="HookAdminHome" access="VIEW"} +
+ + + + + + + {if $latestStableRelease} + + + + + {/if} + {if $latestPreRelease} + + + + + {/if} + +
{intl l="Your version" d='hookadminhome.bo.default'}{$THELIA_VERSION}
{intl l="Latest stable version" d='hookadminhome.bo.default'}{$latestStableRelease['tag_name']}
{intl l="Latest pre-release" d='hookadminhome.bo.default'}{$latestPreRelease['tag_name']}
+
+{/loop} diff --git a/domokits/local/modules/HookAdminHome/templates/backOffice/default/hook-admin-home-config.html b/domokits/local/modules/HookAdminHome/templates/backOffice/default/hook-admin-home-config.html new file mode 100644 index 0000000..2ce7e24 --- /dev/null +++ b/domokits/local/modules/HookAdminHome/templates/backOffice/default/hook-admin-home-config.html @@ -0,0 +1 @@ +{hook name="hook_home_stats" location="hook_home_stats"} \ No newline at end of file diff --git a/domokits/local/modules/HookAnalytics/Config/config.xml b/domokits/local/modules/HookAnalytics/Config/config.xml new file mode 100644 index 0000000..57a6cb0 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Config/config.xml @@ -0,0 +1,21 @@ + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/domokits/local/modules/HookAnalytics/Config/module.xml b/domokits/local/modules/HookAnalytics/Config/module.xml new file mode 100644 index 0000000..99d80c1 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Config/module.xml @@ -0,0 +1,24 @@ + + + HookAnalytics\HookAnalytics + + Analytics (Google) + + + Statistiques (Google) + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookAnalytics/Config/routing.xml b/domokits/local/modules/HookAnalytics/Config/routing.xml new file mode 100644 index 0000000..269697d --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Config/routing.xml @@ -0,0 +1,10 @@ + + + + + HookAnalytics\Controller\Configuration::saveAction + + + diff --git a/domokits/local/modules/HookAnalytics/Controller/Configuration.php b/domokits/local/modules/HookAnalytics/Controller/Configuration.php new file mode 100644 index 0000000..ef62762 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Controller/Configuration.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAnalytics\Controller; + +use HookAnalytics\HookAnalytics; +use Symfony\Component\HttpFoundation\JsonResponse; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; + +/** + * Class Configuration. + * + * @author Julien Chanséaume + */ +class Configuration extends BaseAdminController +{ + public function saveAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ['hookanalytics'], AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm(\HookAnalytics\Form\Configuration::class); + $resp = [ + 'error' => 0, + 'message' => '', + ]; + $response = null; + + $lang = $this->getSession()->get('thelia.admin.edition.lang'); + try { + $vform = $this->validateForm($form); + $data = $vform->getData(); + + HookAnalytics::setConfigValue('hookanalytics_trackingcode', $data['trackingcode'], $lang->getLocale(), true); + } catch (\Exception $e) { + $resp['error'] = 1; + $resp['message'] = $e->getMessage(); + } + + return new JsonResponse($resp); + } +} diff --git a/domokits/local/modules/HookAnalytics/Form/Configuration.php b/domokits/local/modules/HookAnalytics/Form/Configuration.php new file mode 100644 index 0000000..595f409 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Form/Configuration.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAnalytics\Form; + +use HookAnalytics\HookAnalytics; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; +use Thelia\Model\LangQuery; + +/** + * Class Configuration. + * + * @author Julien Chanséaume + */ +class Configuration extends BaseForm +{ + protected function buildForm(): void + { + $form = $this->formBuilder; + + $lang = $this->getRequest()->getSession()->get('thelia.admin.edition.lang'); + if (!$lang) { + $lang = LangQuery::create()->filterByByDefault(1)->findOne(); + } + + $value = HookAnalytics::getConfigValue('hookanalytics_trackingcode', '', $lang->getLocale()); + $form->add( + 'trackingcode', + TextType::class, + [ + 'data' => $value, + 'label' => Translator::getInstance()->trans('Tracking Code'), + 'label_attr' => [ + 'for' => 'trackingcode', + ], + ] + ); + } + + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return 'hookanalytics'; + } +} diff --git a/domokits/local/modules/HookAnalytics/Hook/FrontHook.php b/domokits/local/modules/HookAnalytics/Hook/FrontHook.php new file mode 100644 index 0000000..9d20dd7 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/Hook/FrontHook.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAnalytics\Hook; + +use HookAnalytics\HookAnalytics; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume + */ +class FrontHook extends BaseHook +{ + public function onMainHeadBottom(HookRenderEvent $event): void + { + $lang = $this->getRequest()->getSession()->get('thelia.current.lang'); + if (null !== $lang) { + $value = trim(HookAnalytics::getConfigValue('hookanalytics_trackingcode', '', $lang->getLocale())); + if ('' != $value) { + $event->add($value); + } + } + } +} diff --git a/domokits/local/modules/HookAnalytics/HookAnalytics.php b/domokits/local/modules/HookAnalytics/HookAnalytics.php new file mode 100644 index 0000000..276a3cf --- /dev/null +++ b/domokits/local/modules/HookAnalytics/HookAnalytics.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookAnalytics; + +use Propel\Runtime\Connection\ConnectionInterface; +use Thelia\Model\ConfigQuery; +use Thelia\Model\LangQuery; +use Thelia\Module\BaseModule; + +class HookAnalytics extends BaseModule +{ + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + if (($config = ConfigQuery::read('hookanalytics_trackingcode', '')) + && version_compare($newVersion, '2.4.4', '>=') + && version_compare($currentVersion, '2.4.4', '<')) { + $langs = LangQuery::create()->filterByActive()->find(); + if ($config) { + foreach ($langs as $lang) { + self::setConfigValue('hookanalytics_trackingcode', $config, $lang->getLocale()); + } + } + } + } +} diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/de_DE.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..f9954c0 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/de_DE.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Ein Fehler ist aufgetreten', + 'Edit your analytics configuration.' => 'Analytics-Konfiguration bearbeiten.', + 'Save' => 'Speichern', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/en_US.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/en_US.php new file mode 100755 index 0000000..ff7e107 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/en_US.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'An error occured', + 'Edit your analytics configuration.' => 'Edit your analytics configuration.', + 'Save' => 'Save', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..0dec381 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Une erreur est survenue', + 'Edit your analytics configuration.' => 'Modifier la configuration des statistiques', + 'Save' => ' Enregistrer', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/it_IT.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/it_IT.php new file mode 100644 index 0000000..672ee79 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Save' => 'Salvare', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/ru_RU.php new file mode 100755 index 0000000..34e6d98 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Произошла ошибка', + 'Edit your analytics configuration.' => 'Редактировать вашу конфигурацию аналитики', + 'Save' => 'Сохранить', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..e370995 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Bir hata meydana geldi', + 'Edit your analytics configuration.' => 'Analytics yapılandırmanızı düzenleyin.', + 'Save' => 'kaydet', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/de_DE.php b/domokits/local/modules/HookAnalytics/I18n/de_DE.php new file mode 100644 index 0000000..81886cd --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'Tracking-Code', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/en_US.php b/domokits/local/modules/HookAnalytics/I18n/en_US.php new file mode 100755 index 0000000..c4078df --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'Tracking Code', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/fr_FR.php b/domokits/local/modules/HookAnalytics/I18n/fr_FR.php new file mode 100755 index 0000000..77c7d34 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'Code de suivi', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..12b828e --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS-Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'YouTube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..7ca6666 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/en_US.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..74465e0 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'Flux RSS', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..b089829 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google +', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..b3b977a --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS-канал', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..351bff5 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google +', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Beslemesi', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/it_IT.php b/domokits/local/modules/HookAnalytics/I18n/it_IT.php new file mode 100644 index 0000000..0a31bcd --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'Codice di monitoraggio', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/ru_RU.php b/domokits/local/modules/HookAnalytics/I18n/ru_RU.php new file mode 100755 index 0000000..ece2499 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'Код отслеживания', +]; diff --git a/domokits/local/modules/HookAnalytics/I18n/tr_TR.php b/domokits/local/modules/HookAnalytics/I18n/tr_TR.php new file mode 100644 index 0000000..4111611 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/I18n/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Tracking Code' => 'İzleme Kodu', +]; diff --git a/domokits/local/modules/HookAnalytics/LICENSE.txt b/domokits/local/modules/HookAnalytics/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookAnalytics/composer.json b/domokits/local/modules/HookAnalytics/composer.json new file mode 100644 index 0000000..efe5231 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-analytics-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookAnalytics" + } +} diff --git a/domokits/local/modules/HookAnalytics/templates/backOffice/default/assets/js/module-configuration.js b/domokits/local/modules/HookAnalytics/templates/backOffice/default/assets/js/module-configuration.js new file mode 100644 index 0000000..fe180bb --- /dev/null +++ b/domokits/local/modules/HookAnalytics/templates/backOffice/default/assets/js/module-configuration.js @@ -0,0 +1,29 @@ +$(document).ready(function() { + $("#hookanalytics-form").on("submit", function(e, data){ + e.preventDefault(); + var form = $(this); + + $('body').append(''); + + $.ajax({ + url: form.attr('action'), + type: form.attr('method'), + data: form.serialize() + }).done(function(){ + $("#loading-event").remove(); + }) + .success(function(data) { + if (data.error != 0) { + $("#loading-event").remove(); + $('#hookanalytics-failed-body').html(data.message); + $("#hookanalytics-failed").modal("show"); + } + }) + .fail(function(jqXHR, textStatus, errorThrown){ + $("#loading-event").remove(); + $('#hookanalytics-failed-body').html(jqXHR.responseJSON.message); + $("#hookanalytics-failed").modal("show"); + }); + + }); +}); \ No newline at end of file diff --git a/domokits/local/modules/HookAnalytics/templates/backOffice/default/module_configuration.html b/domokits/local/modules/HookAnalytics/templates/backOffice/default/module_configuration.html new file mode 100755 index 0000000..758fb54 --- /dev/null +++ b/domokits/local/modules/HookAnalytics/templates/backOffice/default/module_configuration.html @@ -0,0 +1,62 @@ + + + +
+
+ +
+ {intl l='Edit your analytics configuration.'} +
+ +
+
+ + {form name="hookanalytics.configuration.form"} + + + {form_hidden_fields} + +
+
+ {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = true + page_url = {url path=$smarty.server.REQUEST_URI} + } +
+
+ + {form_field field='trackingcode'} +
+ + +
+ {/form_field} + + + + {/form} + +
+ +
+ +
+
+ + + + diff --git a/domokits/local/modules/HookCart/Config/config.xml b/domokits/local/modules/HookCart/Config/config.xml new file mode 100644 index 0000000..f8c3dce --- /dev/null +++ b/domokits/local/modules/HookCart/Config/config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/domokits/local/modules/HookCart/Config/module.xml b/domokits/local/modules/HookCart/Config/module.xml new file mode 100644 index 0000000..9f67fe6 --- /dev/null +++ b/domokits/local/modules/HookCart/Config/module.xml @@ -0,0 +1,24 @@ + + + HookCart\HookCart + + Block Cart + + + Bloc Panier + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookCart/HookCart.php b/domokits/local/modules/HookCart/HookCart.php new file mode 100644 index 0000000..5eb0daa --- /dev/null +++ b/domokits/local/modules/HookCart/HookCart.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookCart; + +use Thelia\Module\BaseModule; + +class HookCart extends BaseModule +{ +} diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..d59fd8b --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Warenkorb', + 'Checkout' => 'Zur Kasse', + 'Remove' => 'Entfernen', + 'View Cart' => 'Warenkorb anzeigen', + 'You have no items in your shopping cart.' => 'Sie haben keine Produkte im Warenkorb', +]; diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..c3fea29 --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/en_US.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Cart', + 'Checkout' => 'Checkout', + 'Remove' => 'Remove', + 'View Cart' => 'View Cart', + 'You have no items in your shopping cart.' => 'You have no items in your shopping cart.', +]; diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..e1b0971 --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Panier', + 'Checkout' => 'Commande', + 'Remove' => 'Supprimer', + 'View Cart' => 'Voir le panier', + 'You have no items in your shopping cart.' => 'Vous n\'avez pas de produit dans votre panier.', +]; diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..492fd63 --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Carrello', + 'Checkout' => 'Procedi all\'acquisto', + 'Remove' => 'Rimuovi', + 'View Cart' => 'Visualizza il carrello', + 'You have no items in your shopping cart.' => 'Non hai nessun prodotto nel tuo carrello.', +]; diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..312788a --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Корзина', + 'Checkout' => 'Оформить', + 'Remove' => 'Удалить', + 'View Cart' => 'Просмотр', + 'You have no items in your shopping cart.' => 'Ваша корзина пуста', +]; diff --git a/domokits/local/modules/HookCart/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookCart/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..8d40125 --- /dev/null +++ b/domokits/local/modules/HookCart/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Sepet', + 'Checkout' => 'Ödeme yap', + 'Remove' => 'Kaldır', + 'View Cart' => 'Sepeti Görüntüle', + 'You have no items in your shopping cart.' => 'Sepetinizde hiç ürün yok.', +]; diff --git a/domokits/local/modules/HookCart/LICENSE.txt b/domokits/local/modules/HookCart/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookCart/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookCart/composer.json b/domokits/local/modules/HookCart/composer.json new file mode 100644 index 0000000..f56a5cb --- /dev/null +++ b/domokits/local/modules/HookCart/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-cart-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookCart" + } +} diff --git a/domokits/local/modules/HookCart/templates/frontOffice/default/assets/css/styles.css b/domokits/local/modules/HookCart/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/domokits/local/modules/HookCart/templates/frontOffice/default/main-navbar-secondary.html b/domokits/local/modules/HookCart/templates/frontOffice/default/main-navbar-secondary.html new file mode 100644 index 0000000..a5df381 --- /dev/null +++ b/domokits/local/modules/HookCart/templates/frontOffice/default/main-navbar-secondary.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookCart/templates/frontOffice/default/mini-cart.html b/domokits/local/modules/HookCart/templates/frontOffice/default/mini-cart.html new file mode 100644 index 0000000..72be18e --- /dev/null +++ b/domokits/local/modules/HookCart/templates/frontOffice/default/mini-cart.html @@ -0,0 +1,73 @@ +{ifloop rel="cartloop"} + +{/ifloop} +{elseloop rel="cartloop"} + +{/elseloop} diff --git a/domokits/local/modules/HookContact/Config/config.xml b/domokits/local/modules/HookContact/Config/config.xml new file mode 100644 index 0000000..5c54bc9 --- /dev/null +++ b/domokits/local/modules/HookContact/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookContact/Config/module.xml b/domokits/local/modules/HookContact/Config/module.xml new file mode 100644 index 0000000..f1bd006 --- /dev/null +++ b/domokits/local/modules/HookContact/Config/module.xml @@ -0,0 +1,24 @@ + + + HookContact\HookContact + + Block Contact + + + Bloc Contact + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookContact/Hook/FrontHook.php b/domokits/local/modules/HookContact/Hook/FrontHook.php new file mode 100644 index 0000000..2bde0bf --- /dev/null +++ b/domokits/local/modules/HookContact/Hook/FrontHook.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookContact\Hook; + +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume + */ +class FrontHook extends BaseHook +{ + public function onMainFooterBody(HookRenderBlockEvent $event): void + { + $content = trim($this->render('main-footer-body.html')); + if ('' != $content) { + $event->add( + [ + 'id' => 'contact-footer-body', + 'class' => 'contact', + 'title' => $this->trans('Contact', [], 'hookcontact'), + 'content' => $content, + ] + ); + } + } +} diff --git a/domokits/local/modules/HookContact/HookContact.php b/domokits/local/modules/HookContact/HookContact.php new file mode 100644 index 0000000..1f5a4d2 --- /dev/null +++ b/domokits/local/modules/HookContact/HookContact.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookContact; + +use Thelia\Module\BaseModule; + +class HookContact extends BaseModule +{ +} diff --git a/domokits/local/modules/HookContact/I18n/de_DE.php b/domokits/local/modules/HookContact/I18n/de_DE.php new file mode 100644 index 0000000..8134ca2 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'Kontakt', +]; diff --git a/domokits/local/modules/HookContact/I18n/en_US.php b/domokits/local/modules/HookContact/I18n/en_US.php new file mode 100644 index 0000000..6890337 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'Contact', +]; diff --git a/domokits/local/modules/HookContact/I18n/fr_FR.php b/domokits/local/modules/HookContact/I18n/fr_FR.php new file mode 100644 index 0000000..6890337 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'Contact', +]; diff --git a/domokits/local/modules/HookContact/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookContact/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..ec233a8 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Find us, Contact us' => 'Nous trouver, Nous contacter', +]; diff --git a/domokits/local/modules/HookContact/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookContact/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..3b01cd9 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Find us, Contact us' => 'Найти нас, Связаться с нами', +]; diff --git a/domokits/local/modules/HookContact/I18n/it_IT.php b/domokits/local/modules/HookContact/I18n/it_IT.php new file mode 100644 index 0000000..71e5dd4 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'Contatta', +]; diff --git a/domokits/local/modules/HookContact/I18n/ru_RU.php b/domokits/local/modules/HookContact/I18n/ru_RU.php new file mode 100644 index 0000000..fdfc258 --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'Контакты', +]; diff --git a/domokits/local/modules/HookContact/I18n/tr_TR.php b/domokits/local/modules/HookContact/I18n/tr_TR.php new file mode 100644 index 0000000..c60f40d --- /dev/null +++ b/domokits/local/modules/HookContact/I18n/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Contact' => 'İletişim', +]; diff --git a/domokits/local/modules/HookContact/LICENSE.txt b/domokits/local/modules/HookContact/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookContact/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookContact/composer.json b/domokits/local/modules/HookContact/composer.json new file mode 100644 index 0000000..2ecdb22 --- /dev/null +++ b/domokits/local/modules/HookContact/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-contact-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookContact" + } +} diff --git a/domokits/local/modules/HookContact/templates/frontOffice/default/main-footer-body.html b/domokits/local/modules/HookContact/templates/frontOffice/default/main-footer-body.html new file mode 100644 index 0000000..9eea38b --- /dev/null +++ b/domokits/local/modules/HookContact/templates/frontOffice/default/main-footer-body.html @@ -0,0 +1,25 @@ +
+ +
    +
  • +
    + {config key="store_address1"} {config key="store_address2"} {config key="store_address3"}
    + {config key="store_zipcode"} + + {config key="store_city"} + {if {config key="store_country"} } + {loop type="country" name="address.country.title" id={config key="store_country"}}, {$TITLE}{/loop} + {/if} + +
    +
  • + {if {config key="store_phone"} } +
  • + {config key="store_phone"} +
  • + {/if} +
  • + {intl l="Find us, Contact us" d="hookcontact.fo.default"} +
  • +
+
diff --git a/domokits/local/modules/HookCurrency/Config/config.xml b/domokits/local/modules/HookCurrency/Config/config.xml new file mode 100644 index 0000000..1b5deb7 --- /dev/null +++ b/domokits/local/modules/HookCurrency/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookCurrency/Config/module.xml b/domokits/local/modules/HookCurrency/Config/module.xml new file mode 100644 index 0000000..e15a292 --- /dev/null +++ b/domokits/local/modules/HookCurrency/Config/module.xml @@ -0,0 +1,24 @@ + + + HookCurrency\HookCurrency + + Block Currency + + + Bloc Devise + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookCurrency/HookCurrency.php b/domokits/local/modules/HookCurrency/HookCurrency.php new file mode 100644 index 0000000..d321ca3 --- /dev/null +++ b/domokits/local/modules/HookCurrency/HookCurrency.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookCurrency; + +use Thelia\Module\BaseModule; + +class HookCurrency extends BaseModule +{ +} diff --git a/domokits/local/modules/HookCurrency/LICENSE.txt b/domokits/local/modules/HookCurrency/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookCurrency/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookCurrency/composer.json b/domokits/local/modules/HookCurrency/composer.json new file mode 100644 index 0000000..5876794 --- /dev/null +++ b/domokits/local/modules/HookCurrency/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-currency-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookCurrency" + } +} diff --git a/domokits/local/modules/HookCurrency/templates/frontOffice/default/main-navbar-secondary.html b/domokits/local/modules/HookCurrency/templates/frontOffice/default/main-navbar-secondary.html new file mode 100644 index 0000000..0a8faad --- /dev/null +++ b/domokits/local/modules/HookCurrency/templates/frontOffice/default/main-navbar-secondary.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookCustomer/Config/config.xml b/domokits/local/modules/HookCustomer/Config/config.xml new file mode 100644 index 0000000..63a29ce --- /dev/null +++ b/domokits/local/modules/HookCustomer/Config/config.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/domokits/local/modules/HookCustomer/Config/module.xml b/domokits/local/modules/HookCustomer/Config/module.xml new file mode 100644 index 0000000..9d442cd --- /dev/null +++ b/domokits/local/modules/HookCustomer/Config/module.xml @@ -0,0 +1,24 @@ + + + HookCustomer\HookCustomer + + Block Customer + + + Bloc Client + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookCustomer/HookCustomer.php b/domokits/local/modules/HookCustomer/HookCustomer.php new file mode 100644 index 0000000..0f7f8c4 --- /dev/null +++ b/domokits/local/modules/HookCustomer/HookCustomer.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookCustomer; + +use Thelia\Module\BaseModule; + +class HookCustomer extends BaseModule +{ +} diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..91ab712 --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'E-mail Adresse', + 'Forgot your Password?' => 'Haben sir Ihr Passwort vergessen ?', + 'Log In!' => 'Log In!', + 'Log out!' => 'Log out!', + 'My Account' => 'Mein Kundenkonto', + 'Password' => 'Passwort', + 'Register' => 'Registrieren', + 'Register!' => 'Registrieren!', + 'Sign In' => 'Registrieren', +]; diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..bf1b235 --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/en_US.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Email address', + 'Forgot your Password?' => 'Forgot your Password?', + 'Log In!' => 'Log In!', + 'Log out!' => 'Log out!', + 'My Account' => 'My Account', + 'Password' => 'Password', + 'Register' => 'Register', + 'Register!' => 'Register!', + 'Sign In' => 'Sign In', +]; diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..ae76cbe --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Adresse e-mail', + 'Forgot your Password?' => "Mot de passe oublié\u{a0}?", + 'Log In!' => 'Se connecter', + 'Log out!' => 'Se déconnecter', + 'My Account' => 'Mon compte', + 'Password' => 'Mot de passe', + 'Register' => 'S\'inscrire', + 'Register!' => 'Enregistrez-vous !', + 'Sign In' => 'Se connecter', +]; diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..05f1353 --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Indirizzo email', + 'Forgot your Password?' => 'Hai dimenticato la password?', + 'Log In!' => 'Accedi!', + 'Log out!' => 'Esci!', + 'My Account' => 'Mio account', + 'Password' => 'Password', + 'Register' => 'Registrati', + 'Register!' => 'Registrati!', + 'Sign In' => 'Accedi', +]; diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..741d6ea --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Адрес email', + 'Forgot your Password?' => 'Забыли пароль?', + 'Log In!' => 'Вход', + 'Log out!' => 'Выход', + 'My Account' => 'Мой аккаунт', + 'Password' => 'Пароль', + 'Register' => 'Регистрация', + 'Register!' => 'Регистрация', + 'Sign In' => 'Войти', +]; diff --git a/domokits/local/modules/HookCustomer/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..791ffc1 --- /dev/null +++ b/domokits/local/modules/HookCustomer/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Eposta adresi', + 'Forgot your Password?' => 'Parolanızı mı unuttunuz?', + 'Log In!' => 'Oturum aç!', + 'Log out!' => 'Çıkış Yap!', + 'My Account' => 'Hesabım', + 'Password' => 'Parola', + 'Register' => 'Kaydol', + 'Register!' => 'Kayıt ol!', + 'Sign In' => 'Oturum Aç', +]; diff --git a/domokits/local/modules/HookCustomer/LICENSE.txt b/domokits/local/modules/HookCustomer/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookCustomer/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookCustomer/composer.json b/domokits/local/modules/HookCustomer/composer.json new file mode 100644 index 0000000..d081b40 --- /dev/null +++ b/domokits/local/modules/HookCustomer/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-customer-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookCustomer" + } +} diff --git a/domokits/local/modules/HookCustomer/templates/frontOffice/default/assets/css/styles.css b/domokits/local/modules/HookCustomer/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/domokits/local/modules/HookCustomer/templates/frontOffice/default/main-navbar-secondary.html b/domokits/local/modules/HookCustomer/templates/frontOffice/default/main-navbar-secondary.html new file mode 100644 index 0000000..23884cb --- /dev/null +++ b/domokits/local/modules/HookCustomer/templates/frontOffice/default/main-navbar-secondary.html @@ -0,0 +1,43 @@ + diff --git a/domokits/local/modules/HookLang/Config/config.xml b/domokits/local/modules/HookLang/Config/config.xml new file mode 100644 index 0000000..d30cb54 --- /dev/null +++ b/domokits/local/modules/HookLang/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookLang/Config/module.xml b/domokits/local/modules/HookLang/Config/module.xml new file mode 100644 index 0000000..dec1ec4 --- /dev/null +++ b/domokits/local/modules/HookLang/Config/module.xml @@ -0,0 +1,24 @@ + + + HookLang\HookLang + + Block Languages + + + Bloc langages + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookLang/HookLang.php b/domokits/local/modules/HookLang/HookLang.php new file mode 100644 index 0000000..942251a --- /dev/null +++ b/domokits/local/modules/HookLang/HookLang.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookLang; + +use Thelia\Module\BaseModule; + +class HookLang extends BaseModule +{ +} diff --git a/domokits/local/modules/HookLang/LICENSE.txt b/domokits/local/modules/HookLang/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookLang/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookLang/composer.json b/domokits/local/modules/HookLang/composer.json new file mode 100644 index 0000000..c533864 --- /dev/null +++ b/domokits/local/modules/HookLang/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-lang-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookLang" + } +} diff --git a/domokits/local/modules/HookLang/templates/frontOffice/default/main-navbar-secondary.html b/domokits/local/modules/HookLang/templates/frontOffice/default/main-navbar-secondary.html new file mode 100644 index 0000000..a61a7a1 --- /dev/null +++ b/domokits/local/modules/HookLang/templates/frontOffice/default/main-navbar-secondary.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookLinks/Config/config.xml b/domokits/local/modules/HookLinks/Config/config.xml new file mode 100644 index 0000000..6036b5d --- /dev/null +++ b/domokits/local/modules/HookLinks/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookLinks/Config/module.xml b/domokits/local/modules/HookLinks/Config/module.xml new file mode 100644 index 0000000..1432106 --- /dev/null +++ b/domokits/local/modules/HookLinks/Config/module.xml @@ -0,0 +1,24 @@ + + + HookLinks\HookLinks + + Block Useful links + + + Bloc Liens utiles + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookLinks/Hook/FrontHook.php b/domokits/local/modules/HookLinks/Hook/FrontHook.php new file mode 100644 index 0000000..ae69771 --- /dev/null +++ b/domokits/local/modules/HookLinks/Hook/FrontHook.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookLinks\Hook; + +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume + */ +class FrontHook extends BaseHook +{ + public function onMainFooterBody(HookRenderBlockEvent $event): void + { + $content = trim($this->render('main-footer-body.html')); + if ('' != $content) { + $event->add([ + 'id' => 'links-footer-body', + 'class' => 'default', + 'title' => $this->trans('Useful links', [], 'hooklinks'), + 'content' => $content, + ]); + } + } +} diff --git a/domokits/local/modules/HookLinks/HookLinks.php b/domokits/local/modules/HookLinks/HookLinks.php new file mode 100644 index 0000000..31677b5 --- /dev/null +++ b/domokits/local/modules/HookLinks/HookLinks.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookLinks; + +use Thelia\Module\BaseModule; + +class HookLinks extends BaseModule +{ +} diff --git a/domokits/local/modules/HookLinks/I18n/de_DE.php b/domokits/local/modules/HookLinks/I18n/de_DE.php new file mode 100644 index 0000000..5f92cfe --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Nützliche Links', +]; diff --git a/domokits/local/modules/HookLinks/I18n/en_US.php b/domokits/local/modules/HookLinks/I18n/en_US.php new file mode 100644 index 0000000..cbbd670 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Useful links', +]; diff --git a/domokits/local/modules/HookLinks/I18n/fr_FR.php b/domokits/local/modules/HookLinks/I18n/fr_FR.php new file mode 100644 index 0000000..2495350 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Liens utiles', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..6291da6 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Warenkorb', + 'Checkout' => 'Zur Kasse', + 'Log out!' => 'Log out!', + 'Login' => 'Anmeldung', + 'My Account' => 'Mein Kundenkonto', + 'Register' => 'Registrieren', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..08ef935 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/en_US.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Cart', + 'Checkout' => 'Checkout', + 'Log out!' => 'Log out!', + 'Login' => 'Login', + 'My Account' => 'My Account', + 'Register' => 'Register', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..b0a9707 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Panier', + 'Checkout' => 'Commander', + 'Log out!' => 'Se déconnecter', + 'Login' => 'Connexion', + 'My Account' => 'Mon compte', + 'Register' => 'S\'inscrire', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..0a53f32 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Carrello', + 'Checkout' => 'Procedi all\'acquisto', + 'Log out!' => 'Esci!', + 'Login' => 'Login', + 'My Account' => 'Mio account', + 'Register' => 'Registrati', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..e0a8ca7 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Корзина', + 'Checkout' => 'Оформить заказ', + 'Log out!' => 'Выход', + 'Login' => 'Вход', + 'My Account' => 'Мой аккаунт', + 'Register' => 'Регистрация', +]; diff --git a/domokits/local/modules/HookLinks/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookLinks/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..47986c4 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Cart' => 'Sepet', + 'Checkout' => 'Ödeme yap', + 'Log out!' => 'Çıkış Yap!', + 'Login' => 'Giriş yap', + 'My Account' => 'Hesabım', + 'Register' => 'Kaydol', +]; diff --git a/domokits/local/modules/HookLinks/I18n/it_IT.php b/domokits/local/modules/HookLinks/I18n/it_IT.php new file mode 100644 index 0000000..085177a --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Link utili', +]; diff --git a/domokits/local/modules/HookLinks/I18n/ru_RU.php b/domokits/local/modules/HookLinks/I18n/ru_RU.php new file mode 100644 index 0000000..ca008ad --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Полезные ссылки', +]; diff --git a/domokits/local/modules/HookLinks/I18n/tr_TR.php b/domokits/local/modules/HookLinks/I18n/tr_TR.php new file mode 100644 index 0000000..4895238 --- /dev/null +++ b/domokits/local/modules/HookLinks/I18n/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Useful links' => 'Faydalı Linkler', +]; diff --git a/domokits/local/modules/HookLinks/LICENSE.txt b/domokits/local/modules/HookLinks/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookLinks/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookLinks/composer.json b/domokits/local/modules/HookLinks/composer.json new file mode 100644 index 0000000..23abd78 --- /dev/null +++ b/domokits/local/modules/HookLinks/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-links-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookLinks" + } +} diff --git a/domokits/local/modules/HookLinks/templates/frontOffice/default/main-footer-body.html b/domokits/local/modules/HookLinks/templates/frontOffice/default/main-footer-body.html new file mode 100644 index 0000000..08607f0 --- /dev/null +++ b/domokits/local/modules/HookLinks/templates/frontOffice/default/main-footer-body.html @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookNavigation/Config/config.xml b/domokits/local/modules/HookNavigation/Config/config.xml new file mode 100644 index 0000000..1da4b2b --- /dev/null +++ b/domokits/local/modules/HookNavigation/Config/config.xml @@ -0,0 +1,17 @@ + + + + + +
+ + + + + + + + + + + diff --git a/domokits/local/modules/HookNavigation/Config/module.xml b/domokits/local/modules/HookNavigation/Config/module.xml new file mode 100644 index 0000000..a96cbc3 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Config/module.xml @@ -0,0 +1,24 @@ + + + HookNavigation\HookNavigation + + Block Navigation + + + Bloc Menu + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookNavigation/Config/routing.xml b/domokits/local/modules/HookNavigation/Config/routing.xml new file mode 100644 index 0000000..b8c3028 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Config/routing.xml @@ -0,0 +1,9 @@ + + + + HookNavigation\Controller\HookNavigationConfigController::defaultAction + + + HookNavigation\Controller\HookNavigationConfigController::saveAction + + diff --git a/domokits/local/modules/HookNavigation/Controller/HookNavigationConfigController.php b/domokits/local/modules/HookNavigation/Controller/HookNavigationConfigController.php new file mode 100644 index 0000000..a173791 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Controller/HookNavigationConfigController.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation\Controller; + +use HookNavigation\HookNavigation; +use HookNavigation\Model\Config\HookNavigationConfigValue; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Session\Session; +use Thelia\Core\Template\ParserContext; +use Thelia\Core\Translation\Translator; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Tools\URL; + +/** + * Class HookNavigationConfigController. + * + * @author Etienne PERRIERE - OpenStudio + */ +class HookNavigationConfigController extends BaseAdminController +{ + public function defaultAction(Session $session) + { + $bodyConfig = HookNavigation::getConfigValue(HookNavigationConfigValue::FOOTER_BODY_FOLDER_ID); + $bottomConfig = HookNavigation::getConfigValue(HookNavigationConfigValue::FOOTER_BOTTOM_FOLDER_ID); + + $session->getFlashBag()->set('bodyConfig', $bodyConfig ?? ''); + $session->getFlashBag()->set('bottomConfig', $bottomConfig ?? ''); + + return $this->render('hooknavigation-configuration'); + } + + public function saveAction(Request $request, Translator $translator, ParserContext $parserContext) + { + $baseForm = $this->createForm('hooknavigation.configuration'); + + $errorMessage = null; + + $parserContext->set('success', true); + + try { + $form = $this->validateForm($baseForm); + $data = $form->getData(); + + HookNavigation::setConfigValue(HookNavigationConfigValue::FOOTER_BODY_FOLDER_ID, \is_bool($data['footer_body_folder_id']) ? (int) ($data['footer_body_folder_id']) : $data['footer_body_folder_id']); + HookNavigation::setConfigValue(HookNavigationConfigValue::FOOTER_BOTTOM_FOLDER_ID, \is_bool($data['footer_bottom_folder_id']) ? (int) ($data['footer_bottom_folder_id']) : $data['footer_bottom_folder_id']); + + if ($request->get('save_mode') !== 'stay') { + // Redirect to module list + return $this->generateRedirect(URL::getInstance()->absoluteUrl('/admin/modules')); + } + } catch (FormValidationException $ex) { + // Invalid data entered + $errorMessage = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $errorMessage = $translator->trans( + 'Sorry, an error occurred: %err', + ['%err' => $ex->getMessage()], + HookNavigation::MESSAGE_DOMAIN + ); + } + + if (null !== $errorMessage) { + // Mark the form as with error + $baseForm->setErrorMessage($errorMessage); + + // Send the form and the error to the parser + $parserContext + ->addForm($baseForm) + ->setGeneralError($errorMessage) + ; + } + + return $this->defaultAction($request->getSession()); + } +} diff --git a/domokits/local/modules/HookNavigation/Form/HookNavigationConfigForm.php b/domokits/local/modules/HookNavigation/Form/HookNavigationConfigForm.php new file mode 100644 index 0000000..c955791 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Form/HookNavigationConfigForm.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation\Form; + +use HookNavigation\HookNavigation; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Thelia\Form\BaseForm; + +/** + * Class HookNavigationConfigForm. + * + * @author Etienne PERRIERE - OpenStudio + */ +class HookNavigationConfigForm extends BaseForm +{ + public static function getName() + { + return 'hooknavigation_configuration'; + } + + protected function buildForm(): void + { + $this->formBuilder + ->add( + 'footer_body_folder_id', + NumberType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Folder in footer body', [], HookNavigation::MESSAGE_DOMAIN), + ] + ) + ->add( + 'footer_bottom_folder_id', + NumberType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Folder in footer bottom', [], HookNavigation::MESSAGE_DOMAIN), + ] + ); + } +} diff --git a/domokits/local/modules/HookNavigation/Hook/FrontHook.php b/domokits/local/modules/HookNavigation/Hook/FrontHook.php new file mode 100644 index 0000000..72977d5 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Hook/FrontHook.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation\Hook; + +use HookNavigation\HookNavigation; +use HookNavigation\Model\Config\HookNavigationConfigValue; +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume , Etienne PERRIERE - OpenStudio + */ +class FrontHook extends BaseHook +{ + public function onMainFooterBody(HookRenderBlockEvent $event): void + { + $bodyConfig = HookNavigation::getConfigValue(HookNavigationConfigValue::FOOTER_BODY_FOLDER_ID); + + $content = trim($this->render('main-footer-body.html', ['bodyFolderId' => $bodyConfig])); + if ('' !== $content) { + $event->add([ + 'id' => 'navigation-footer-body', + 'class' => 'links', + 'title' => $this->trans('Latest articles', [], HookNavigation::MESSAGE_DOMAIN), + 'content' => $content, + ]); + } + } + + public function onMainFooterBottom(HookRenderEvent $event): void + { + $bottomConfig = HookNavigation::getConfigValue(HookNavigationConfigValue::FOOTER_BOTTOM_FOLDER_ID); + + $content = $this->render('main-footer-bottom.html', ['bottomFolderId' => $bottomConfig]); + $event->add($content); + } +} diff --git a/domokits/local/modules/HookNavigation/HookNavigation.php b/domokits/local/modules/HookNavigation/HookNavigation.php new file mode 100644 index 0000000..8abc3d7 --- /dev/null +++ b/domokits/local/modules/HookNavigation/HookNavigation.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; + +/** + * Class HookNavigation. + */ +class HookNavigation extends BaseModule +{ + public const MESSAGE_DOMAIN = 'hooknavigation'; + public const ROUTER = 'router.hooknavigation'; + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/de_DE.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..3c7270e --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/de_DE.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Kategorien', + 'Folder in footer body' => 'Ordner in Fußzeile', + 'Folder in footer bottom' => 'Ordner in Fußzeile', + 'Home' => 'Startseite', + 'HookNavigation configuration' => 'HookNavigation Konfiguration', + 'No articles currently' => 'Zur Zeit keine Artikel', + 'Toggle navigation' => 'Navigation umschalten', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/en_US.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..1322a48 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/en_US.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Configuration correctly saved' => 'Configuration correctly saved', + 'Configure hooknavigation' => 'Configure hooknavigation', + 'Home' => 'Home', + 'HookNavigation configuration' => 'HookNavigation configuration', + 'Modules' => 'Modules', + 'Select a folder' => 'Select a folder', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..97ecd9a --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Configuration correctly saved' => 'Configuration sauvegardée correctement', + 'Configure hooknavigation' => 'Configurer Bloc Menu', + 'Home' => 'Accueil', + 'HookNavigation configuration' => 'Configuration de Block Menu', + 'Modules' => 'Modules', + 'Select a folder' => 'Sélectionner un dossier', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/it_IT.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/it_IT.php new file mode 100644 index 0000000..477c029 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/it_IT.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Home' => 'Home', + 'Modules' => 'Moduli', + 'Select a folder' => 'Seleziona una cartella', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/ru_RU.php new file mode 100644 index 0000000..b04226e --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Configuration correctly saved' => 'Конфигурация успешно сохранена', + 'Configure hooknavigation' => 'Настройка HookNavigation', + 'Home' => 'Главная', + 'HookNavigation configuration' => 'Конфигурация HookNavigation', + 'Modules' => 'Модули', + 'Select a folder' => 'Выберите папку', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/HookNavigation/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..e39ec6c --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Katogoriler', + 'Folder in footer body' => 'Altbilgi vücut klasöründe', + 'Folder in footer bottom' => 'Altbilgi alt klasöründe', + 'Home' => 'Ana sayfa', + 'HookNavigation configuration' => 'HookNavigation yapılandırma', + 'No articles currently' => 'Hiç makale yok', + 'Toggle navigation' => 'Navigasyonu değiştir', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/en_US.php b/domokits/local/modules/HookNavigation/I18n/en_US.php new file mode 100644 index 0000000..d7ffbc2 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/en_US.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Folder in footer body' => 'Folder in footer body', + 'Folder in footer bottom' => 'Folder in footer bottom', + 'Latest articles' => 'Latest articles', + 'Sorry, an error occurred: %err' => 'Sorry, an error occurred: %err', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/fr_FR.php b/domokits/local/modules/HookNavigation/I18n/fr_FR.php new file mode 100644 index 0000000..8a1693d --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Folder in footer body' => 'Dossier du pied de page', + 'Folder in footer bottom' => 'Dossier sous le pied de page', + 'Latest articles' => 'Derniers articles', + 'Sorry, an error occurred: %err' => 'Désolé, une erreur est survenue: %err', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..199e3d2 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Latest articles' => 'Neueste Artikel', + 'No articles currently' => 'Zur Zeit keine Artikel', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..dfbcf23 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/en_US.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Categories', + 'Home' => 'Home', + 'No articles currently' => 'No articles currently', + 'Toggle navigation' => 'Toggle navigation', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..16ea044 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Rubriques', + 'Home' => 'Accueil', + 'No articles currently' => 'Aucun article pour le moment', + 'Toggle navigation' => 'Basculer la navigation', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..237c7f6 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Categorie', + 'Home' => 'Home', + 'No articles currently' => 'Attualmente non sono presenti articoli', + 'Toggle navigation' => 'Toggle navigation', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..e97a034 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Categories' => 'Категории', + 'Home' => 'Главная', + 'No articles currently' => 'Пока статей нет', + 'Toggle navigation' => 'Переключить навигацию', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..99cb89a --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Latest articles' => 'Son Makaleler', + 'No articles currently' => 'Hiç makale yok', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/it_IT.php b/domokits/local/modules/HookNavigation/I18n/it_IT.php new file mode 100644 index 0000000..dfe4d71 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Latest articles' => 'Ultimi articoli', +]; diff --git a/domokits/local/modules/HookNavigation/I18n/ru_RU.php b/domokits/local/modules/HookNavigation/I18n/ru_RU.php new file mode 100644 index 0000000..875a445 --- /dev/null +++ b/domokits/local/modules/HookNavigation/I18n/ru_RU.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Folder in footer body' => 'Папка внизу страницы', + 'Folder in footer bottom' => 'Папка в подвале страницы', + 'Latest articles' => 'Последние статьи', + 'Sorry, an error occurred: %err' => 'К сожалению произошла ошибка: %err', +]; diff --git a/domokits/local/modules/HookNavigation/LICENSE.txt b/domokits/local/modules/HookNavigation/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookNavigation/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookNavigation/Model/Config/Base/HookNavigationConfigValue.php b/domokits/local/modules/HookNavigation/Model/Config/Base/HookNavigationConfigValue.php new file mode 100644 index 0000000..bf2a659 --- /dev/null +++ b/domokits/local/modules/HookNavigation/Model/Config/Base/HookNavigationConfigValue.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation\Model\Config\Base; + +/** + * Class HookNavigationConfigValue. + */ +class HookNavigationConfigValue +{ + public const FOOTER_BODY_FOLDER_ID = 'footer_body_folder_id'; + public const FOOTER_BOTTOM_FOLDER_ID = 'footer_bottom_folder_id'; +} diff --git a/domokits/local/modules/HookNavigation/Model/Config/HookNavigationConfigValue.php b/domokits/local/modules/HookNavigation/Model/Config/HookNavigationConfigValue.php new file mode 100644 index 0000000..74db14a --- /dev/null +++ b/domokits/local/modules/HookNavigation/Model/Config/HookNavigationConfigValue.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNavigation\Model\Config; + +use HookNavigation\Model\Config\Base\HookNavigationConfigValue as BaseHookNavigationConfigValue; + +/** + * Class HookNavigationConfigValue. + */ +class HookNavigationConfigValue extends BaseHookNavigationConfigValue +{ +} diff --git a/domokits/local/modules/HookNavigation/composer.json b/domokits/local/modules/HookNavigation/composer.json new file mode 100644 index 0000000..0ccb0ca --- /dev/null +++ b/domokits/local/modules/HookNavigation/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-navigation-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookNavigation" + } +} diff --git a/domokits/local/modules/HookNavigation/templates/backOffice/default/hooknavigation-configuration.html b/domokits/local/modules/HookNavigation/templates/backOffice/default/hooknavigation-configuration.html new file mode 100644 index 0000000..871c43d --- /dev/null +++ b/domokits/local/modules/HookNavigation/templates/backOffice/default/hooknavigation-configuration.html @@ -0,0 +1,116 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} + {$admin_current_location = 'modules'} +{/block} + +{block name="page-title"}{intl d="hooknavigation.bo.default" l='HookNavigation configuration'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}HookNavigation{/block} + +{block name="main-content"} +
+ + +
+
+ {intl l="Configure hooknavigation" d="hooknavigation.bo.default"} +
+ +
+
+ {if $success|default:false} +
+ {intl l="Configuration correctly saved" d="hooknavigation.bo.default"} +
+ {/if} + + {form name="hooknavigation.configuration"} + + {include "includes/inner-form-toolbar.html" hide_flags=1 close_url={url path='/admin/modules'}} +
+ + {form_field form=$form field="success_url"} + + {/form_field} + + {form_hidden_fields form=$form} + + {flash type="bodyConfig"} + {assign var='bodyConfig' value=$MESSAGE} + {/flash} + {flash type="bottomConfig"} + {assign var='bottomConfig' value=$MESSAGE} + {/flash} + + {form_field form=$form field="footer_body_folder_id"} +
+ + + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="footer_bottom_folder_id"} +
+ + + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {include "includes/inner-form-toolbar.html" hide_flags=1 close_url={url path='/admin/modules'} page_bottom=1} + + {/form} +
+
+
+
+{/block} + +{block name="javascript-initialization"} +{/block} diff --git a/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-body.html b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-body.html new file mode 100644 index 0000000..4834c61 --- /dev/null +++ b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-body.html @@ -0,0 +1,17 @@ +{ifloop rel="blog.articles"} + +{/ifloop} +{elseloop rel="blog.articles"} +
    +
  • {intl l="No articles currently" d="hooknavigation.fo.default"}
  • +
+{/elseloop} \ No newline at end of file diff --git a/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-bottom.html b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-bottom.html new file mode 100644 index 0000000..05c8047 --- /dev/null +++ b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-footer-bottom.html @@ -0,0 +1,7 @@ + diff --git a/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-navbar-primary.html b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-navbar-primary.html new file mode 100644 index 0000000..6251461 --- /dev/null +++ b/domokits/local/modules/HookNavigation/templates/frontOffice/default/main-navbar-primary.html @@ -0,0 +1,56 @@ + + +{* classic navbar without dropdown + +*} \ No newline at end of file diff --git a/domokits/local/modules/HookNewsletter/Config/config.xml b/domokits/local/modules/HookNewsletter/Config/config.xml new file mode 100644 index 0000000..9d66a2f --- /dev/null +++ b/domokits/local/modules/HookNewsletter/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookNewsletter/Config/module.xml b/domokits/local/modules/HookNewsletter/Config/module.xml new file mode 100644 index 0000000..2bb1f44 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/Config/module.xml @@ -0,0 +1,24 @@ + + + HookNewsletter\HookNewsletter + + Block Newsletter + + + Bloc Newsletter + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookNewsletter/Hook/FrontHook.php b/domokits/local/modules/HookNewsletter/Hook/FrontHook.php new file mode 100644 index 0000000..47a41e6 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/Hook/FrontHook.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNewsletter\Hook; + +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume + */ +class FrontHook extends BaseHook +{ + public function onMainFooterBody(HookRenderBlockEvent $event): void + { + $content = trim($this->render('main-footer-body.html')); + if ('' != $content) { + $event->add([ + 'id' => 'newsletter-footer-body', + 'class' => 'newsletter', + 'title' => $this->trans('Newsletter', [], 'hooknewsletter'), + 'content' => $content, + ]); + } + } +} diff --git a/domokits/local/modules/HookNewsletter/HookNewsletter.php b/domokits/local/modules/HookNewsletter/HookNewsletter.php new file mode 100644 index 0000000..3bf0444 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/HookNewsletter.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookNewsletter; + +use Thelia\Module\BaseModule; + +class HookNewsletter extends BaseModule +{ +} diff --git a/domokits/local/modules/HookNewsletter/I18n/de_DE.php b/domokits/local/modules/HookNewsletter/I18n/de_DE.php new file mode 100644 index 0000000..4fbbce0 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'Newsletter', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/en_US.php b/domokits/local/modules/HookNewsletter/I18n/en_US.php new file mode 100755 index 0000000..4fbbce0 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'Newsletter', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/fr_FR.php b/domokits/local/modules/HookNewsletter/I18n/fr_FR.php new file mode 100755 index 0000000..2895c2d --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'Lettre d\'information', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..6d0e6a5 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'E-mail Adresse', + 'Sign up to receive our latest news.' => 'Abonnieren Sie unseren Newsletter.', + 'Subscribe' => 'Abonnieren', + 'Your email address' => 'Ihre E-Mail-Adresse', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..261b546 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/en_US.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Email address', + 'Sign up to receive our latest news.' => 'Sign up to receive our latest news.', + 'Subscribe' => 'Subscribe', + 'Your email address' => 'Your email address', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..cbd671c --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Adresse e-mail', + 'Sign up to receive our latest news.' => 'Enregistrez vous pour recevoir nos dernières nouvelles.', + 'Subscribe' => 'Inscription', + 'Your email address' => 'Votre adresse email', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..27f1570 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Indirizzo email', + 'Subscribe' => 'Abbonati', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..0878ec9 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'E-mail адрес', + 'Sign up to receive our latest news.' => 'Подпишитесь для получения новостей от нас', + 'Subscribe' => 'Подписаться', + 'Your email address' => 'Ваш email адрес', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..46f52f9 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Email address' => 'Eposta adresi', + 'Sign up to receive our latest news.' => 'En yeni haberleri almak için kaydolun.', + 'Subscribe' => 'Abone Ol', + 'Your email address' => 'E-posta adresiniz', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/it_IT.php b/domokits/local/modules/HookNewsletter/I18n/it_IT.php new file mode 100644 index 0000000..4fbbce0 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'Newsletter', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/ru_RU.php b/domokits/local/modules/HookNewsletter/I18n/ru_RU.php new file mode 100755 index 0000000..127244d --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'Подписка', +]; diff --git a/domokits/local/modules/HookNewsletter/I18n/tr_TR.php b/domokits/local/modules/HookNewsletter/I18n/tr_TR.php new file mode 100644 index 0000000..cd4dfce --- /dev/null +++ b/domokits/local/modules/HookNewsletter/I18n/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Newsletter' => 'E-Bülten', +]; diff --git a/domokits/local/modules/HookNewsletter/LICENSE.txt b/domokits/local/modules/HookNewsletter/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookNewsletter/composer.json b/domokits/local/modules/HookNewsletter/composer.json new file mode 100644 index 0000000..230b69d --- /dev/null +++ b/domokits/local/modules/HookNewsletter/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-newsletter-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookNewsletter" + } +} diff --git a/domokits/local/modules/HookNewsletter/templates/frontOffice/default/main-footer-body.html b/domokits/local/modules/HookNewsletter/templates/frontOffice/default/main-footer-body.html new file mode 100644 index 0000000..1249959 --- /dev/null +++ b/domokits/local/modules/HookNewsletter/templates/frontOffice/default/main-footer-body.html @@ -0,0 +1,13 @@ +

{intl l="Sign up to receive our latest news." d="hooknewsletter.fo.default"}

+{form name="thelia.front.newsletter"} +
+{form_hidden_fields} +{form_field field="email"} +
+ + +
+{/form_field} + +
+{/form} diff --git a/domokits/local/modules/HookProductsNew/Config/config.xml b/domokits/local/modules/HookProductsNew/Config/config.xml new file mode 100644 index 0000000..42df653 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookProductsNew/Config/module.xml b/domokits/local/modules/HookProductsNew/Config/module.xml new file mode 100644 index 0000000..abe77b3 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/Config/module.xml @@ -0,0 +1,24 @@ + + + HookProductsNew\HookProductsNew + + Block New Products + + + Bloc Nouveaux Produits + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookProductsNew/HookProductsNew.php b/domokits/local/modules/HookProductsNew/HookProductsNew.php new file mode 100644 index 0000000..e764f49 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/HookProductsNew.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookProductsNew; + +use Thelia\Module\BaseModule; + +class HookProductsNew extends BaseModule +{ +} diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..93af348 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Alle anzeigen', + 'Latest' => 'Neuigkeiten', +]; diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..2630a45 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/en_US.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ View All', + 'Latest' => 'Latest', +]; diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..c78e30b --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Tout voir', + 'Latest' => 'Nouveautés', +]; diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..030c627 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Mostra tutto', + 'Latest' => 'Ultimi', +]; diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..7c1bfb4 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Смотреть все', + 'Latest' => 'Новые', +]; diff --git a/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..f6e4bb6 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Hepsini gör', + 'Latest' => 'En son', +]; diff --git a/domokits/local/modules/HookProductsNew/LICENSE.txt b/domokits/local/modules/HookProductsNew/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookProductsNew/composer.json b/domokits/local/modules/HookProductsNew/composer.json new file mode 100644 index 0000000..3757392 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-products-new-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookProductsNew" + } +} diff --git a/domokits/local/modules/HookProductsNew/templates/frontOffice/default/home-body.html b/domokits/local/modules/HookProductsNew/templates/frontOffice/default/home-body.html new file mode 100644 index 0000000..11f6004 --- /dev/null +++ b/domokits/local/modules/HookProductsNew/templates/frontOffice/default/home-body.html @@ -0,0 +1,59 @@ +{ifloop rel="product_new"} +
+
+

{intl l="Latest" d="hookproductsnew.fo.default"} {intl l="+ View All" d="hookproductsnew.fo.default"}

+
+
+ +
+
+{/ifloop} diff --git a/domokits/local/modules/HookProductsOffer/Config/config.xml b/domokits/local/modules/HookProductsOffer/Config/config.xml new file mode 100644 index 0000000..00e4ebb --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/HookProductsOffer/Config/module.xml b/domokits/local/modules/HookProductsOffer/Config/module.xml new file mode 100644 index 0000000..6ee97b0 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/Config/module.xml @@ -0,0 +1,24 @@ + + + HookProductsOffer\HookProductsOffer + + Block Promo Products + + + Bloc Produits en promo + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookProductsOffer/HookProductsOffer.php b/domokits/local/modules/HookProductsOffer/HookProductsOffer.php new file mode 100644 index 0000000..85ade35 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/HookProductsOffer.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookProductsOffer; + +use Thelia\Module\BaseModule; + +class HookProductsOffer extends BaseModule +{ +} diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..5b64193 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Alle sehen', + 'Offers' => 'Angebote', +]; diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..2bea270 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/en_US.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ View All', + 'Offers' => 'Offers', +]; diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..b6ac60e --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Tout voir', + 'Offers' => 'Promotions', +]; diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..dee3d68 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Mostra tutto', + 'Offers' => 'Offerte', +]; diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..5052d1a --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Смотреть все', + 'Offers' => 'Предложения', +]; diff --git a/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..8ddc8ad --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + '+ View All' => '+ Hepsini gör', + 'Offers' => 'Teklifler', +]; diff --git a/domokits/local/modules/HookProductsOffer/LICENSE.txt b/domokits/local/modules/HookProductsOffer/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookProductsOffer/composer.json b/domokits/local/modules/HookProductsOffer/composer.json new file mode 100644 index 0000000..8329abd --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-products-offer-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookProductsOffer" + } +} diff --git a/domokits/local/modules/HookProductsOffer/templates/frontOffice/default/home-body.html b/domokits/local/modules/HookProductsOffer/templates/frontOffice/default/home-body.html new file mode 100644 index 0000000..462e775 --- /dev/null +++ b/domokits/local/modules/HookProductsOffer/templates/frontOffice/default/home-body.html @@ -0,0 +1,39 @@ +{ifloop rel="current-sales"} +
+ {loop name="current-sales" type="sale" limit="2"} +
+ + +
+
    + {loop name="products_in_sale" type="product" limit="4" sale=$ID} + {include file="includes/single-product.html" colClass="col-md-3 col-sm-4" product_id=$ID hasBtn=false hasDescription=false width="218" height="146"} + {/loop} +
+
+
+ {/loop} +
+{/ifloop} +{* Display "regular" promos, if any, only if we don't have active sales *} + +{elseloop rel="current-sales"} +{ifloop rel="product_promo"} +
+
+

{intl l="Offers" d="hookproductsoffer.fo.default"} {intl l="+ View All" d="hookproductsoffer.fo.default"}

+
+ +
+
    + {loop name="product_promo" type="product" limit="4" promo="yes"} + {include file="includes/single-product.html" colClass="col-md-3 col-sm-4" product_id=$ID hasBtn=false hasDescription=false width="218" height="146"} + {/loop} +
+
+
+{/ifloop} +{/elseloop} diff --git a/domokits/local/modules/HookSearch/Config/config.xml b/domokits/local/modules/HookSearch/Config/config.xml new file mode 100644 index 0000000..b5d60c6 --- /dev/null +++ b/domokits/local/modules/HookSearch/Config/config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/domokits/local/modules/HookSearch/Config/module.xml b/domokits/local/modules/HookSearch/Config/module.xml new file mode 100644 index 0000000..3a915ce --- /dev/null +++ b/domokits/local/modules/HookSearch/Config/module.xml @@ -0,0 +1,24 @@ + + + HookSearch\HookSearch + + Block Search + + + Bloc Recherche + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookSearch/HookSearch.php b/domokits/local/modules/HookSearch/HookSearch.php new file mode 100644 index 0000000..657cb1e --- /dev/null +++ b/domokits/local/modules/HookSearch/HookSearch.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookSearch; + +use Thelia\Module\BaseModule; + +class HookSearch extends BaseModule +{ +} diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..4a04d27 --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => 'Mindestens 2 Zeichen.', + 'Search' => 'Suchen', + 'Search a product' => 'Ein Produkt suchen', + 'Search...' => 'Suchen ...', +]; diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..cbf6af0 --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/en_US.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => 'Minimum 2 characters.', + 'Search' => 'Search', + 'Search a product' => 'Search a product', + 'Search...' => 'Search...', +]; diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..8a3c91c --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => '2 caractères minimum.', + 'Search' => 'Recherche', + 'Search a product' => 'Rechercher un produit', + 'Search...' => 'Rechercher...', +]; diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..a99d65d --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => 'Minimo 2 caratteri.', + 'Search' => 'Ricerca', + 'Search a product' => 'Ricerca un prodotto', + 'Search...' => 'Ricerca...', +]; diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/ru_RU.php new file mode 100644 index 0000000..b5d1754 --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => 'Минимум 2 символа.', + 'Search' => 'Поиск', + 'Search a product' => 'Поиск товара', + 'Search...' => 'Поиск...', +]; diff --git a/domokits/local/modules/HookSearch/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookSearch/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..ab844fd --- /dev/null +++ b/domokits/local/modules/HookSearch/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Minimum 2 characters.' => 'En az 2 karakter.', + 'Search' => 'Arama', + 'Search a product' => 'Ürün ara', + 'Search...' => 'Arama...', +]; diff --git a/domokits/local/modules/HookSearch/LICENSE.txt b/domokits/local/modules/HookSearch/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookSearch/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookSearch/composer.json b/domokits/local/modules/HookSearch/composer.json new file mode 100644 index 0000000..9dfdcaf --- /dev/null +++ b/domokits/local/modules/HookSearch/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-search-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookSearch" + } +} diff --git a/domokits/local/modules/HookSearch/templates/frontOffice/default/assets/css/styles.css b/domokits/local/modules/HookSearch/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 0000000..74b1839 --- /dev/null +++ b/domokits/local/modules/HookSearch/templates/frontOffice/default/assets/css/styles.css @@ -0,0 +1,11 @@ +.header-container .search-container label, +.header-container .search-container .btn-search>span { + position:absolute; + width:1px; + height:1px; + margin:-1px; + padding:0; + overflow:hidden; + clip:rect(0 0 0 0); + border:0 +} \ No newline at end of file diff --git a/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-primary.html b/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-primary.html new file mode 100644 index 0000000..31c6afd --- /dev/null +++ b/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-primary.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-secondary.html b/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-secondary.html new file mode 100644 index 0000000..40ba03a --- /dev/null +++ b/domokits/local/modules/HookSearch/templates/frontOffice/default/main-navbar-secondary.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/domokits/local/modules/HookSocial/Config/config.xml b/domokits/local/modules/HookSocial/Config/config.xml new file mode 100644 index 0000000..f151f07 --- /dev/null +++ b/domokits/local/modules/HookSocial/Config/config.xml @@ -0,0 +1,21 @@ + + + + + +
+ + + + + + + + + + + + + diff --git a/domokits/local/modules/HookSocial/Config/module.xml b/domokits/local/modules/HookSocial/Config/module.xml new file mode 100644 index 0000000..867247c --- /dev/null +++ b/domokits/local/modules/HookSocial/Config/module.xml @@ -0,0 +1,24 @@ + + + HookSocial\HookSocial + + Block Social + + + Bloc Social + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/HookSocial/Config/routing.xml b/domokits/local/modules/HookSocial/Config/routing.xml new file mode 100644 index 0000000..769a2c4 --- /dev/null +++ b/domokits/local/modules/HookSocial/Config/routing.xml @@ -0,0 +1,10 @@ + + + + + HookSocial\Controller\Configuration::saveAction + + + diff --git a/domokits/local/modules/HookSocial/Controller/Configuration.php b/domokits/local/modules/HookSocial/Controller/Configuration.php new file mode 100644 index 0000000..7966457 --- /dev/null +++ b/domokits/local/modules/HookSocial/Controller/Configuration.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookSocial\Controller; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Log\Tlog; +use Thelia\Model\ConfigQuery; + +/** + * Class Configuration. + * + * @author Julien Chanséaume + */ +class Configuration extends BaseAdminController +{ + public function saveAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ['hooksocial'], AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm(\HookSocial\Form\Configuration::class); + $resp = [ + 'error' => 0, + 'message' => '', + ]; + $response = null; + + try { + $vform = $this->validateForm($form); + $data = $vform->getData(); + + foreach ($data as $name => $value) { + if (!$form->isTemplateDefinedHiddenFieldName($name)) { + ConfigQuery::write('hooksocial_'.$name, $value, false, true); + } + + Tlog::getInstance()->debug(sprintf('%s => %s', $name, $value)); + } + } catch (\Exception $e) { + $resp['error'] = 1; + $resp['message'] = $e->getMessage(); + } + + return new JsonResponse($resp); + } +} diff --git a/domokits/local/modules/HookSocial/Form/Configuration.php b/domokits/local/modules/HookSocial/Form/Configuration.php new file mode 100644 index 0000000..65c48e5 --- /dev/null +++ b/domokits/local/modules/HookSocial/Form/Configuration.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookSocial\Form; + +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; +use Thelia\Model\ConfigQuery; + +/** + * Class Configuration. + * + * @author Julien Chanséaume + */ +class Configuration extends BaseForm +{ + protected function buildForm(): void + { + $form = $this->formBuilder; + + $definitions = [ + [ + 'id' => 'twitter', + 'label' => Translator::getInstance()->trans('Twitter username', [], 'hooksocial'), + ], + [ + 'id' => 'facebook', + 'label' => Translator::getInstance()->trans('Facebook username', [], 'hooksocial'), + ], + [ + 'id' => 'google', + 'label' => Translator::getInstance()->trans('Google + username', [], 'hooksocial'), + ], + [ + 'id' => 'instagram', + 'label' => Translator::getInstance()->trans('Instagram username', [], 'hooksocial'), + ], + [ + 'id' => 'pinterest', + 'label' => Translator::getInstance()->trans('Pinterest username', [], 'hooksocial'), + ], + [ + 'id' => 'youtube', + 'label' => Translator::getInstance()->trans('Youtube URL', [], 'hooksocial'), + ], + [ + 'id' => 'rss', + 'label' => Translator::getInstance()->trans('RSS URL', [], 'hooksocial'), + ], + ]; + + foreach ($definitions as $field) { + $value = ConfigQuery::read('hooksocial_'.$field['id'], ''); + $form->add( + $field['id'], + TextType::class, + [ + 'data' => $value, + 'label' => $field['label'], + 'label_attr' => [ + 'for' => $field['id'], + ], + ] + ); + } + } + + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return 'hooksocial'; + } +} diff --git a/domokits/local/modules/HookSocial/Hook/FrontHook.php b/domokits/local/modules/HookSocial/Hook/FrontHook.php new file mode 100644 index 0000000..c284f2c --- /dev/null +++ b/domokits/local/modules/HookSocial/Hook/FrontHook.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookSocial\Hook; + +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class FrontHook. + * + * @author Julien Chanséaume + */ +class FrontHook extends BaseHook +{ + public function onMainFooterBody(HookRenderBlockEvent $event): void + { + $content = trim($this->render('main-footer-body.html')); + if ('' != $content) { + $event->add([ + 'id' => 'social-footer-body', + 'class' => 'social', + 'title' => $this->trans('Follow us', [], 'hooksocial'), + 'content' => $content, + ]); + } + } +} diff --git a/domokits/local/modules/HookSocial/HookSocial.php b/domokits/local/modules/HookSocial/HookSocial.php new file mode 100644 index 0000000..e615459 --- /dev/null +++ b/domokits/local/modules/HookSocial/HookSocial.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace HookSocial; + +use Thelia\Module\BaseModule; + +class HookSocial extends BaseModule +{ +} diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/de_DE.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..94582e3 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/de_DE.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Ein Fehler ist aufgetreten', + 'Save' => 'Speichern', +]; diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/en_US.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/en_US.php new file mode 100755 index 0000000..0f1fc7d --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/en_US.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'An error occured', + 'Edit your social accounts.' => 'Edit your social accounts.', + 'Save' => 'Save', +]; diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..7fd0d30 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Une erreur est survenue', + 'Edit your social accounts.' => 'Modifier vos paramètres de réseaux sociaux.', + 'Save' => ' Enregistrer', +]; diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/it_IT.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/it_IT.php new file mode 100644 index 0000000..c4c1edc --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/it_IT.php @@ -0,0 +1,16 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Edit your social accounts.' => 'Modifica i tuoi account social.', + 'Save' => 'Salvare', +]; diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/ru_RU.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/ru_RU.php new file mode 100755 index 0000000..f2def80 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/ru_RU.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Произошла ошибка', + 'Edit your social accounts.' => 'Редактировать ваши социальные аккаунты.', + 'Save' => 'Сохранить', +]; diff --git a/domokits/local/modules/HookSocial/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/HookSocial/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..98a4654 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'An error occured' => 'Bir hata meydana geldi', + 'Edit your social accounts.' => 'Sosyal hesaplarınızı düzenleyin.', + 'Save' => 'kaydet', +]; diff --git a/domokits/local/modules/HookSocial/I18n/de_DE.php b/domokits/local/modules/HookSocial/I18n/de_DE.php new file mode 100644 index 0000000..53be417 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/de_DE.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Facebook Benutzername', + 'Follow us' => 'Folgen Sie uns', + 'Google + username' => 'Google+ Benutzername', + 'Instagram username' => 'Instagram Benutzername', + 'Pinterest username' => 'Pinterest Benutzername', + 'RSS URL' => 'RSS-URL', + 'Twitter username' => 'Twitter Benutzername', + 'Youtube URL' => 'YouTube-URL', +]; diff --git a/domokits/local/modules/HookSocial/I18n/en_US.php b/domokits/local/modules/HookSocial/I18n/en_US.php new file mode 100755 index 0000000..1752f26 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/en_US.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Facebook username', + 'Follow us' => 'Follow us', + 'Google + username' => 'Google + username', + 'Instagram username' => 'Instagram username', + 'Pinterest username' => 'Pinterest username', + 'RSS URL' => 'RSS URL', + 'Twitter username' => 'Twitter username', + 'Youtube URL' => 'Youtube URL', +]; diff --git a/domokits/local/modules/HookSocial/I18n/fr_FR.php b/domokits/local/modules/HookSocial/I18n/fr_FR.php new file mode 100755 index 0000000..dc7db56 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/fr_FR.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Nom d\'utilisateur Facebook', + 'Follow us' => 'Suivez-nous', + 'Google + username' => 'Nom d\'utilisateur Google +', + 'Instagram username' => 'Nom d\'utilisateur Instagram', + 'Pinterest username' => 'Nom d\'utilisateur Pinterest', + 'RSS URL' => 'URL du flux RSS', + 'Twitter username' => 'Nom d\'utilisateur Twitter', + 'Youtube URL' => 'URL Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..12b828e --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS-Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'YouTube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/en_US.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..7ca6666 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/en_US.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..74465e0 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'RSS' => 'Flux RSS', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..b089829 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google +', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Feed', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..defbbbe --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google+', + 'Instagram' => 'Instagram', + 'Pinterest' => 'Pinterest', + 'RSS' => 'RSS-канал', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/HookSocial/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..351bff5 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook' => 'Facebook', + 'Google+' => 'Google +', + 'Instagram' => 'Instagram', + 'RSS' => 'RSS Beslemesi', + 'Twitter' => 'Twitter', + 'Youtube' => 'Youtube', +]; diff --git a/domokits/local/modules/HookSocial/I18n/it_IT.php b/domokits/local/modules/HookSocial/I18n/it_IT.php new file mode 100644 index 0000000..c652329 --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/it_IT.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Nome utente Facebook', + 'Follow us' => 'Seguici', + 'Google + username' => 'Nome utente Google +', + 'Instagram username' => 'Nome utente Instagram', + 'Pinterest username' => 'Nome utente Pinterest', + 'RSS URL' => 'RSS URL', + 'Twitter username' => 'Nome utente Twitter', + 'Youtube URL' => 'Youtube URL', +]; diff --git a/domokits/local/modules/HookSocial/I18n/ru_RU.php b/domokits/local/modules/HookSocial/I18n/ru_RU.php new file mode 100755 index 0000000..2430ffc --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/ru_RU.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Имя пользователя Facebook', + 'Follow us' => 'Мы в соц сетях', + 'Google + username' => 'Имя пользователя Google +', + 'Instagram username' => 'Имя пользователя Instagram', + 'Pinterest username' => 'Имя пользователя Pinterest', + 'RSS URL' => 'RSS URL', + 'Twitter username' => 'Имя пользователя Twitter', + 'Youtube URL' => 'Youtube URL', +]; diff --git a/domokits/local/modules/HookSocial/I18n/tr_TR.php b/domokits/local/modules/HookSocial/I18n/tr_TR.php new file mode 100644 index 0000000..88310ac --- /dev/null +++ b/domokits/local/modules/HookSocial/I18n/tr_TR.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Facebook username' => 'Facebook kullanıcı adı', + 'Follow us' => 'Bizi takip edin', + 'Google + username' => 'Google + kullanıcı adı', + 'Instagram username' => 'Google + kullanıcı adı', + 'Pinterest username' => 'Pinterest kullanıcı adı', + 'RSS URL' => 'RSS URL', + 'Twitter username' => 'Twitter kullanıcı adı', + 'Youtube URL' => 'YouTube URL', +]; diff --git a/domokits/local/modules/HookSocial/LICENSE.txt b/domokits/local/modules/HookSocial/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/HookSocial/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/HookSocial/composer.json b/domokits/local/modules/HookSocial/composer.json new file mode 100644 index 0000000..4a18288 --- /dev/null +++ b/domokits/local/modules/HookSocial/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/hook-social-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "HookSocial" + } +} diff --git a/domokits/local/modules/HookSocial/templates/backOffice/default/assets/js/module-configuration.js b/domokits/local/modules/HookSocial/templates/backOffice/default/assets/js/module-configuration.js new file mode 100644 index 0000000..5bf3dfc --- /dev/null +++ b/domokits/local/modules/HookSocial/templates/backOffice/default/assets/js/module-configuration.js @@ -0,0 +1,28 @@ +$(document).ready(function() { + $("#hooksocial-form").on("submit", function(e, data){ + e.preventDefault(); + var form = $(this); + + $('body').append(''); + + $.ajax({ + url: form.attr('action'), + type: form.attr('method'), + data: form.serialize() + }).done(function(){ + $("#loading-event").remove(); + }) + .success(function(data) { + if (data.error != 0) { + $("#loading-event").remove(); + $('#hooksocial-failed-body').html(data.message); + $("#hooksocial-failed").modal("show"); + } + }) + .fail(function(jqXHR, textStatus, errorThrown){ + $("#loading-event").remove(); + $('#hooksocial-failed-body').html(jqXHR.responseJSON.message); + $("#hooksocial-failed").modal("show"); + }); + }); +}); \ No newline at end of file diff --git a/domokits/local/modules/HookSocial/templates/backOffice/default/module_configuration.html b/domokits/local/modules/HookSocial/templates/backOffice/default/module_configuration.html new file mode 100755 index 0000000..37f46c7 --- /dev/null +++ b/domokits/local/modules/HookSocial/templates/backOffice/default/module_configuration.html @@ -0,0 +1,94 @@ + + + +
+
+ +
+ {intl d='hooksocial.bo.default' l='Edit your social accounts.'} +
+ +
+
+ + {form name="hooksocial.configuration.form"} + + + {form_hidden_fields} + + {form_field field='twitter'} +
+ + +
+ {/form_field} + + {form_field field='facebook'} +
+ + +
+ {/form_field} + + {form_field field='google'} +
+ + +
+ {/form_field} + + {form_field field='instagram'} +
+ + +
+ {/form_field} + + {form_field field='pinterest'} +
+ + +
+ {/form_field} + + {form_field field='youtube'} +
+ + +
+ {/form_field} + + {form_field field='rss'} +
+ + +
+ {/form_field} + + + + {/form} + +
+ +
+ +
+
+ + + + diff --git a/domokits/local/modules/HookSocial/templates/frontOffice/default/assets/css/styles.css b/domokits/local/modules/HookSocial/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 0000000..e69de29 diff --git a/domokits/local/modules/HookSocial/templates/frontOffice/default/main-footer-body.html b/domokits/local/modules/HookSocial/templates/frontOffice/default/main-footer-body.html new file mode 100644 index 0000000..095d783 --- /dev/null +++ b/domokits/local/modules/HookSocial/templates/frontOffice/default/main-footer-body.html @@ -0,0 +1,86 @@ + \ No newline at end of file diff --git a/domokits/local/modules/LocalPickup/.github/workflows/release.yml b/domokits/local/modules/LocalPickup/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/LocalPickup/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/LocalPickup/Config/config.xml b/domokits/local/modules/LocalPickup/Config/config.xml new file mode 100644 index 0000000..57def57 --- /dev/null +++ b/domokits/local/modules/LocalPickup/Config/config.xml @@ -0,0 +1,22 @@ + + + + +
+ + + + + + + + + + + + + + + diff --git a/domokits/local/modules/LocalPickup/Config/module.xml b/domokits/local/modules/LocalPickup/Config/module.xml new file mode 100644 index 0000000..8423bf6 --- /dev/null +++ b/domokits/local/modules/LocalPickup/Config/module.xml @@ -0,0 +1,18 @@ + + + LocalPickup\LocalPickup + + Local Pickup + + + Retrait sur place + + 2.0.5 + + Thelia + info@thelia.net + + delivery + 2.5.0 + alpha + diff --git a/domokits/local/modules/LocalPickup/Config/routing.xml b/domokits/local/modules/LocalPickup/Config/routing.xml new file mode 100644 index 0000000..82816fd --- /dev/null +++ b/domokits/local/modules/LocalPickup/Config/routing.xml @@ -0,0 +1,9 @@ + + + + + LocalPickup\Controller\ConfigurationController::configure + + diff --git a/domokits/local/modules/LocalPickup/Controller/ConfigurationController.php b/domokits/local/modules/LocalPickup/Controller/ConfigurationController.php new file mode 100644 index 0000000..9958e4b --- /dev/null +++ b/domokits/local/modules/LocalPickup/Controller/ConfigurationController.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Copyright (c) OpenStudio */ +/* email : dev@thelia.net */ +/* web : http://www.thelia.net */ + +/* For the full copyright and license information, please view the LICENSE.txt */ +/* file that was distributed with this source code. */ + +namespace LocalPickup\Controller; + +use LocalPickup\Form\ConfigurationForm; +use LocalPickup\LocalPickup; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Tools\URL; + +/** + * Class ConfigurationController. + * + * @author Thelia + */ +class ConfigurationController extends BaseAdminController +{ + public function configure() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ['LocalPickup'], AccessManager::UPDATE)) { + return $response; + } + + $form = $this->createForm(ConfigurationForm::getName()); + $errmes = $ex = null; + + try { + $vform = $this->validateForm($form); + + $price = $vform->get('price')->getData(); + $description = $vform->get('description')->getData(); + $email = $vform->get('email')->getData(); + + LocalPickup::setConfigValue(LocalPickup::PRICE_VAR_NAME, (float) $price); + LocalPickup::setConfigValue(LocalPickup::DESCRIPTION_VAR_NAME, $description, $this->getCurrentEditionLocale()); + LocalPickup::setConfigValue(LocalPickup::EMAIL_VAR_NAME, $email, $this->getCurrentEditionLocale()); + } catch (FormValidationException $ex) { + $errmes = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + $errmes = $ex->getMessage(); + } + + if (null !== $errmes && null !== $ex) { + $this->setupFormErrorContext( + 'configuration', + $errmes, + $form, + $ex + ); + } + + return new RedirectResponse(URL::getInstance()->absoluteUrl('/admin/module/LocalPickup')); + } +} diff --git a/domokits/local/modules/LocalPickup/EventListeners/APIListener.php b/domokits/local/modules/LocalPickup/EventListeners/APIListener.php new file mode 100644 index 0000000..4e58ccc --- /dev/null +++ b/domokits/local/modules/LocalPickup/EventListeners/APIListener.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace LocalPickup\EventListeners; + +use LocalPickup\LocalPickup; +use OpenApi\Events\DeliveryModuleOptionEvent; +use OpenApi\Events\OpenApiEvents; +use OpenApi\Model\Api\DeliveryModuleOption; +use OpenApi\Model\Api\ModelFactory; +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Mailer\MailerFactory; +use Thelia\Model\ModuleQuery; +use Thelia\Model\OrderStatus; + +class APIListener implements EventSubscriberInterface +{ + /** @var ModelFactory */ + protected $modelFactory; + + /** @var RequestStack */ + protected $requestStack; + + /** + * @var MailerFactory + */ + protected $mailer; + + /** + * APIListener constructor. + * + * @param ContainerInterface $container We need the container because we use a service from another module + * which is not mandatory, and using its service without it being installed will crash + */ + public function __construct(ModelFactory $modelFactory, RequestStack $requestStack, MailerFactory $mailer) + { + $this->modelFactory = $modelFactory; + $this->requestStack = $requestStack; + $this->mailer = $mailer; + } + + + public function getDeliveryModuleOptions(DeliveryModuleOptionEvent $deliveryModuleOptionEvent): void + { + $module = ModuleQuery::create()->findOneByCode(LocalPickup::getModuleCode()); + if ($deliveryModuleOptionEvent->getModule()->getId() !== $module->getId()) { + return; + } + + $isValid = true; + $locale = $this->requestStack->getCurrentRequest()->getSession()->getLang()->getLocale(); + + $postage = LocalPickup::getConfigValue(LocalPickup::PRICE_VAR_NAME, 0); + $commentary = LocalPickup::getConfigValue( + LocalPickup::DESCRIPTION_VAR_NAME, + '', + $locale + ); + + $postageTax = 0; + + $minimumDeliveryDate = ''; + $maximumDeliveryDate = ''; + + $images = $module->getModuleImages(); + $imageId = 0; + + $title = $module->setLocale($locale)->getTitle(); + + if ($images->count() > 0) { + $imageId = $images->getFirst()->getId(); + } + + /** @var DeliveryModuleOption $deliveryModuleOption */ + $deliveryModuleOption = $this->modelFactory->buildModel('DeliveryModuleOption'); + $deliveryModuleOption + ->setCode(LocalPickup::getModuleCode()) + ->setValid($isValid) + ->setTitle($title) + ->setImage($imageId) + ->setMinimumDeliveryDate($minimumDeliveryDate) + ->setMaximumDeliveryDate($maximumDeliveryDate) + ->setPostage($postage) + ->setPostageTax($postageTax) + ->setPostageUntaxed($postage - $postageTax) + ; + + // Pre-5.3.x compatibility + if (method_exists($deliveryModuleOption, 'setDescription')) { + $deliveryModuleOption->setDescription($commentary); + } + + $deliveryModuleOptionEvent->appendDeliveryModuleOptions($deliveryModuleOption); + } + + /** + * @throws PropelException + */ + public function getOrderStatus(OrderEvent $orderEvent) + { + $order = $orderEvent->getOrder(); + + if ($order->getDeliveryModuleId() !== LocalPickup::getModuleId() || $order->getOrderStatus()->getCode() !== OrderStatus::CODE_SENT) { + return; + } + + $this->mailer->sendEmailToCustomer( + LocalPickup::EMAIL_CUSTOM_LOCAL_PICKUP, + $order->getCustomer(), + [ + 'order_id' => $order->getId(), + 'order_ref' => $order->getRef(), + 'comment' => LocalPickup::getConfigValue(LocalPickup::EMAIL_VAR_NAME, '', $order->getLang()->getLocale()), + ] + ); + } + + public static function getSubscribedEvents() + { + $listenedEvents = []; + + /* Check for old versions of Thelia where the events used by the API didn't exists */ + if (class_exists(DeliveryModuleOptionEvent::class)) { + $listenedEvents[OpenApiEvents::MODULE_DELIVERY_GET_OPTIONS] = ['getDeliveryModuleOptions', 129]; + } + + $listenedEvents[TheliaEvents::ORDER_UPDATE_STATUS] = ['getOrderStatus', 99]; + + return $listenedEvents; + } +} diff --git a/domokits/local/modules/LocalPickup/Form/ConfigurationForm.php b/domokits/local/modules/LocalPickup/Form/ConfigurationForm.php new file mode 100644 index 0000000..79f9db3 --- /dev/null +++ b/domokits/local/modules/LocalPickup/Form/ConfigurationForm.php @@ -0,0 +1,84 @@ +formBuilder + ->add( + "price", + NumberType::class, + [ + "required" => false, + "label"=>Translator::getInstance()->trans("Price", [], LocalPickup::DOMAIN_NAME), + "label_attr"=> [ + "for"=>"pricefield" + ], + "constraints"=> [ new NotBlank(), new GreaterThanOrEqual([ 'value' => 0 ]) ] + ] + ) + ->add( + "description", + TextareaType::class, + [ + "required" => false, + "label"=> Translator::getInstance()->trans("Commentary local pickup", [], LocalPickup::DOMAIN_NAME), + 'attr' => [ + 'rows' => 5, + ], + "label_attr"=> [ + "for"=>"description" + ], + ] + ) + ->add( + "email", + TextareaType::class, + [ + "required" => false, + "label"=> Translator::getInstance()->trans("Commentary email", [], LocalPickup::DOMAIN_NAME), + 'attr' => [ + 'rows' => 5, + ], + "label_attr"=> [ + "for"=>"description" + ], + ] + ) + ; + } + + /** + * @return string the name of you form. This name must be unique + */ + public static function getName() + { + return "config-localpickup"; + } + +} diff --git a/domokits/local/modules/LocalPickup/Hook/HookManager.php b/domokits/local/modules/LocalPickup/Hook/HookManager.php new file mode 100644 index 0000000..d6a185b --- /dev/null +++ b/domokits/local/modules/LocalPickup/Hook/HookManager.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Copyright (c) OpenStudio */ +/* email : dev@thelia.net */ +/* web : http://www.thelia.net */ + +/* For the full copyright and license information, please view the LICENSE.txt */ +/* file that was distributed with this source code. */ + +namespace LocalPickup\Hook; + +use LocalPickup\LocalPickup; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class HookManager. + * + * @author Thomas Arnaud + */ +class HookManager extends BaseHook +{ + public function onModuleConfiguration(HookRenderEvent $event): void + { + $locale = $this->getSession()->getAdminEditionLang()->getLocale(); + + $event->add( + $this->render( + 'module_configuration.html', + [ + 'price' => (float) LocalPickup::getConfigValue(LocalPickup::PRICE_VAR_NAME, 0), + 'description' => LocalPickup::getConfigValue(LocalPickup::DESCRIPTION_VAR_NAME, '', $locale), + 'email' => LocalPickup::getConfigValue(LocalPickup::EMAIL_VAR_NAME, '', $locale), + ] + ) + ); + } + + public function onOrderInvoiceDeliveryAddress(HookRenderEvent $event): void + { + // Show the local delivery template if we're the current delivery module. + if ((null !== $order = $this->getSession()->getOrder()) && $order->getDeliveryModuleId() == LocalPickup::getModuleId()) { + $event->add( + $this->render('localpickup/order-invoice-delivery-address.html', [ + 'order_id' => $event->getArgument('order_id'), + ]) + ); + } + } + + public function onOrderDeliveryExtra(HookRenderEvent $event): void + { + $event->add( + $this->render( + 'localpickup/delivery-address.html', + [ + 'description' => LocalPickup::getConfigValue( + LocalPickup::DESCRIPTION_VAR_NAME, '', + $this->getSession()->getLang()->getLocale() + ), + ] + ) + ); + } +} diff --git a/domokits/local/modules/LocalPickup/I18n/backOffice/default/en_US.php b/domokits/local/modules/LocalPickup/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..b43e799 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/backOffice/default/en_US.php @@ -0,0 +1,7 @@ + 'Close', + 'Configure local pickup' => 'Configure local pickup', + 'Save changes' => 'Save changes ', +); diff --git a/domokits/local/modules/LocalPickup/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/LocalPickup/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..36cae13 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,7 @@ + 'Fermer', + 'Configure local pickup' => 'Configurer le retrait sur place', + 'Save changes' => 'Enregistrer les modifications', +); diff --git a/domokits/local/modules/LocalPickup/I18n/email/default/en_US.php b/domokits/local/modules/LocalPickup/I18n/email/default/en_US.php new file mode 100644 index 0000000..f5db2f7 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/email/default/en_US.php @@ -0,0 +1,10 @@ + 'Instructions', + 'Your order is available in store' => 'Your order is available in store', + 'Come pick up your order at the store' => 'Your order has been processed. You can collect it during store opening hours.', + 'Delivery address' => 'Delivery address', + 'Thanks' => 'Thanks', + 'The %store team.' => 'The %store team.', +); diff --git a/domokits/local/modules/LocalPickup/I18n/email/default/fr_FR.php b/domokits/local/modules/LocalPickup/I18n/email/default/fr_FR.php new file mode 100644 index 0000000..d8e51d7 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/email/default/fr_FR.php @@ -0,0 +1,10 @@ + 'Instructions', + 'Your order is available in store' => 'Votre commande est disponible en magasin', + 'Come pick up your order at the store' => "Votre commande a été traité, vous pouvez venir récupérer celle-ci en respectant les horaires d'ouverture du magasin.", + 'Delivery address' => 'Adresse de livraison', + 'Thanks' => 'Merci', + "The %store team." => "L'équipe %store." +); diff --git a/domokits/local/modules/LocalPickup/I18n/en_US.php b/domokits/local/modules/LocalPickup/I18n/en_US.php new file mode 100644 index 0000000..bd1a3c0 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/en_US.php @@ -0,0 +1,8 @@ + 'Write a comment visible for the user when selecting this delivery mode', + 'Commentary email' => 'Write a comment visible for the user in the email', + 'Price' => 'Price without taxes', + 'price must be a number !' => 'price must be a number !', +); diff --git a/domokits/local/modules/LocalPickup/I18n/fr_FR.php b/domokits/local/modules/LocalPickup/I18n/fr_FR.php new file mode 100644 index 0000000..e8e520c --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/fr_FR.php @@ -0,0 +1,8 @@ + 'Vous pouvez indiquer un commentaire qui sera affiché à vos client lors de la sélection de ce mode de livraison', + 'Commentary email' => 'Vous pouvez indiquer un commentaire qui sera affiché à vos client dans l\'email', + 'Price' => 'Prix HT', + 'price must be a number !' => 'Le prix doit être un nombre !', +); diff --git a/domokits/local/modules/LocalPickup/I18n/frontOffice/default/en_US.php b/domokits/local/modules/LocalPickup/I18n/frontOffice/default/en_US.php new file mode 100644 index 0000000..9603813 --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/frontOffice/default/en_US.php @@ -0,0 +1,8 @@ + 'In-store pick-up', + 'Instructions'=> 'Instructions', + 'Your order is available in store' => 'Votre commande est disponible en magasin', + 'Come pick up your order at the store' => "Votre commande a été traité, vous pouvez venir récupérer celle-ci en respectant les horaires d'ouverture du magasin", +); diff --git a/domokits/local/modules/LocalPickup/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/LocalPickup/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..070779c --- /dev/null +++ b/domokits/local/modules/LocalPickup/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,8 @@ + 'Retrait en magasin', + 'Instructions'=> 'Instructions', + 'Your order is available in store' => 'Votre commande est disponible en magasin', + 'Come pick up your order at the store' => "Votre commande a été traité, vous pouvez venir récupérer celle-ci en respectant les horaires d'ouverture du magasin", +); diff --git a/domokits/local/modules/LocalPickup/LICENSE.txt b/domokits/local/modules/LocalPickup/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/LocalPickup/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/LocalPickup/Listener/UpdateDeliveryAddress.php b/domokits/local/modules/LocalPickup/Listener/UpdateDeliveryAddress.php new file mode 100644 index 0000000..83ee53c --- /dev/null +++ b/domokits/local/modules/LocalPickup/Listener/UpdateDeliveryAddress.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Copyright (c) OpenStudio */ +/* email : info@thelia.net */ +/* web : http://www.thelia.net */ + +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 3 of the License */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ + +namespace LocalPickup\Listener; + +use LocalPickup\LocalPickup; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Action\BaseAction; +use Thelia\Core\Event\Order\OrderAddressEvent; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Model\ConfigQuery; +use Thelia\Model\OrderAddressQuery; + +/** + * Class UpdateDeliveryAddress. + * + * @contributor Thomas Arnaud + */ +class UpdateDeliveryAddress extends BaseAction implements EventSubscriberInterface +{ + /** + * @throws \Exception + */ + public function updateAddress(OrderEvent $event, $eventName, EventDispatcherInterface $dispatcher): void + { + if ($event->getOrder()->getDeliveryModuleId() === LocalPickup::getModuleId()) { + $address_id = $event->getOrder()->getDeliveryOrderAddressId(); + $address = OrderAddressQuery::create()->findPk($address_id); + + if ($address !== null) { + $address1 = ConfigQuery::read('store_address1'); + $address2 = ConfigQuery::read('store_address2'); + $address3 = ConfigQuery::read('store_address3'); + $zipcode = ConfigQuery::read('store_zipcode'); + $city = ConfigQuery::read('store_city'); + $country = ConfigQuery::read('store_country'); + $name = ConfigQuery::read('store_name'); + + if ($address1 !== null && $zipcode !== null && $city !== null && $country !== null) { + $address_event = new OrderAddressEvent( + $address->getCustomerTitleId(), + $address->getFirstname(), + $address->getLastname(), + $address1, + $address2, + $address3, + $zipcode, + $city, + $country, + $address->getPhone(), + $name, + $address->getCellphone() + ); + + $address_event->setOrderAddress($address); + $address_event->setOrder($event->getOrder()); + $dispatcher->dispatch($address_event, TheliaEvents::ORDER_UPDATE_ADDRESS); + } + } else { + throw new \Exception("Error: order delivery address doesn't exists"); + } + } + } + + public function setAddress(OrderEvent $event): void + { + if ($event->getOrder()->getDeliveryModuleId() === LocalPickup::getModuleId()) { + $event->setDeliveryAddress(null); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_BEFORE_PAYMENT => ['updateAddress', 130], + TheliaEvents::ORDER_SET_DELIVERY_MODULE => ['setAddress', 128], + ]; + } +} diff --git a/domokits/local/modules/LocalPickup/LocalPickup.php b/domokits/local/modules/LocalPickup/LocalPickup.php new file mode 100644 index 0000000..933e81e --- /dev/null +++ b/domokits/local/modules/LocalPickup/LocalPickup.php @@ -0,0 +1,119 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace LocalPickup; + +use Propel\Runtime\Connection\ConnectionInterface; +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Install\Database; +use Thelia\Model\Country; +use Thelia\Model\Message; +use Thelia\Model\MessageQuery; +use Thelia\Model\State; +use Thelia\Module\AbstractDeliveryModuleWithState; + +/** + * Class LocalPickup + * @package LocalPickup + * @author Thelia + */ +class LocalPickup extends AbstractDeliveryModuleWithState +{ + const DOMAIN_NAME = 'localpickup'; + + const PRICE_VAR_NAME = 'price'; + const DESCRIPTION_VAR_NAME = 'description'; + const EMAIL_VAR_NAME = 'email'; + + const EMAIL_CUSTOM_LOCAL_PICKUP = 'email_custom_local_pickup'; + + /** + * @inheritdoc + */ + public function getPostage(Country $country, State $state = null) + { + return $this->buildOrderPostage(self::getConfigValue(self::PRICE_VAR_NAME, 0), $country, $this->getRequest()->getSession()->getLang()->getLocale()); + } + + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + if (null === MessageQuery::create()->findOneByName(self::EMAIL_CUSTOM_LOCAL_PICKUP)) { + $message = new Message(); + $message + ->setName(self::EMAIL_CUSTOM_LOCAL_PICKUP) + ->setHtmlTemplateFileName('order_shipping.html') + ->setHtmlLayoutFileName('') + ->setTextTemplateFileName('order_shipping.txt') + ->setTextLayoutFileName('') + ->setSecured(0) + ->setLocale('fr_FR') + ->setTitle('Confirmation de vote commande') + ->setSubject('Commande à récupérer en magasin') + ->setLocale('en_GB') + ->setTitle('Order confirmation') + ->setSubject('Order to pick up in store') + ->setLocale('de_DE') + ->setTitle('Bestellbestätigung') + ->setSubject('Bestellung im Geschäft abholen') + ->save() + ; + } + + if ($newVersion === '1.2') { + $db = new Database($con); + + // Migrate previous price from database to module config + try { + $statement = $db->execute("select price from local_pickup_shipping order by id desc limit 1"); + + $price = (float)$statement->fetchColumn(0); + + self::setConfigValue(self::PRICE_VAR_NAME, $price); + } catch (\Exception $ex) { + // Nothing special + } + } + } + + + /** + * @inheritdoc + */ + public function isValidDelivery(Country $country, State $state = null) + { + return true; + } + + public function getDeliveryMode() + { + return "localPickup"; + } + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/LocalPickup/Loop/LocalAddress.php b/domokits/local/modules/LocalPickup/Loop/LocalAddress.php new file mode 100644 index 0000000..7d07b8a --- /dev/null +++ b/domokits/local/modules/LocalPickup/Loop/LocalAddress.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Copyright (c) OpenStudio */ +/* email : info@thelia.net */ +/* web : http://www.thelia.net */ + +/* This program is free software; you can redistribute it and/or modify */ +/* it under the terms of the GNU General Public License as published by */ +/* the Free Software Foundation; either version 3 of the License */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program. If not, see . */ + +namespace LocalPickup\Loop; + +use Symfony\Component\Config\Definition\Exception\Exception; +use Thelia\Core\Template\Element\ArraySearchLoopInterface; +use Thelia\Core\Template\Element\BaseLoop; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Core\Template\Element\LoopResultRow; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Model\AddressQuery; +use Thelia\Model\ConfigQuery; + +/** + * Class LocalAddress. + * + * @author Thelia + * + * @method int getId() + */ +class LocalAddress extends BaseLoop implements ArraySearchLoopInterface +{ + /** + * {@inheritdoc} + */ + public function buildArray() + { + $id = $this->getId(); + + /** @var \Thelia\Core\HttpFoundation\Session\Session $session */ + $session = $this->requestStack->getCurrentRequest()->getSession(); + + $address = AddressQuery::create() + ->filterByCustomerId($session->getCustomerUser()->getId()) + ->findPk($id); + + if ($address === null) { + throw new Exception("The requested address doesn't exist"); + } + + /** @var \Thelia\Model\Customer $customer */ + $customer = $session->getCustomerUser(); + + return [ + 'Id' => 0, + 'Label' => $address->getLabel(), + 'CustomerId' => $address->getCustomerId(), + 'TitleId' => $address->getTitleId(), + 'Company' => ConfigQuery::read('store_name'), + 'Firstname' => $customer->getFirstname(), + 'Lastname' => $customer->getLastname(), + 'Address1' => ConfigQuery::read('store_address1'), + 'Address2' => ConfigQuery::read('store_address2'), + 'Address3' => ConfigQuery::read('store_address3'), + 'Zipcode' => ConfigQuery::read('store_zipcode'), + 'City' => ConfigQuery::read('store_city'), + 'CountryId' => ConfigQuery::read('store_country'), + 'Phone' => $address->getPhone(), + 'Cellphone' => $address->getCellphone(), + 'IsDefault' => 0, + ]; + } + + /** + * {@inheritdoc} + */ + public function parseResults(LoopResult $loopResult) + { + $address = $loopResult->getResultDataCollection(); + $loopResultRow = new LoopResultRow($address); + + $loopResultRow + ->set('ID', $address['Id']) + ->set('LABEL', $address['Label']) + ->set('CUSTOMER', $address['CustomerId']) + ->set('TITLE', $address['TitleId']) + ->set('COMPANY', $address['Company']) + ->set('FIRSTNAME', $address['Firstname']) + ->set('LASTNAME', $address['Lastname']) + ->set('ADDRESS1', $address['Address1']) + ->set('ADDRESS2', $address['Address2']) + ->set('ADDRESS3', $address['Address3']) + ->set('ZIPCODE', $address['Zipcode']) + ->set('CITY', $address['City']) + ->set('COUNTRY', $address['CountryId']) + ->set('PHONE', $address['Phone']) + ->set('CELLPHONE', $address['Cellphone']) + ->set('DEFAULT', $address['IsDefault']) + ; + + $loopResult->addRow($loopResultRow); + + return $loopResult; + } + + /** + * {@inheritdoc} + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('id', null, true) + ); + } +} diff --git a/domokits/local/modules/LocalPickup/README.md b/domokits/local/modules/LocalPickup/README.md new file mode 100644 index 0000000..c01a932 --- /dev/null +++ b/domokits/local/modules/LocalPickup/README.md @@ -0,0 +1,101 @@ +LocalPickup module +================== +author: Thelia + +Summary +------- + +fr_FR: +1. Installation +2. Utilisation +3. Boucles +4. Intégration + +en_US: +1. Install notes +2. How to use +3. Loops +4. Integration + +fr_FR +----- + +### Installation + +#### Manually + +* Copiez le module dans le dossier ```/local/modules/``` et assurez-vous que le nom du module est bien LocalPickup. +* Activez le depuis votre interface d'administration Thelia. + +#### Composer + +Ajoutez le module à votre fichier composer.json principal : + +``` +composer require thelia/local-pickup-module:~1.0 +``` + +### Utilisation + +Pour utiliser le module de retrait sur place, allez dans le back-office, onglet Modules, et activez le, +puis cliquez sur "Configurer" sur la ligne du module. Renseignez le prix que vous souhaitez donner au retrait sur place +et enregistrez. + +### Boucles + +1. `address.local` + Même sorties que la boucle `address`, mais avec l'adresse du magasin au lieu de celle du client. + - Arguments: + 1. id | obligatoire | id de l'adresse du client + - Sorties: + Les mêmes variables que la boucle address, mais l'adresse donnée est celle du magasin. + - Utilisation: + ``` + {loop type="address.local" name="yourloopname" id="1"} + + {/loop}``` + +### Intégration + +L'integration utilise les hooks et ne nécessite pas de travaux particuliers. + +en_US +----- + +### Installation notes + +#### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is LocalPickup. +* Activate it in your thelia administration panel + +#### Composer + +Add it in your main thelia composer.json file: + +``` +composer require thelia/local-pickup-module:~1.0 +``` + +### Usage + +To use the module, you first need to activate it in the back-office, tab Modules, and click on "Configure" on the line +of the module. Enter the price you want for local pickup and save. + +### Loops +1. `address.local` + Same output as the `address` loop, with the store adresse instead of the customer address. + - Arguments: + 1. id | mandatory | id of the customer's address + - Output: + The same variables as address loop, but the given address is the store's address. + - Usage: + ``` + {loop type="address.local" name="yourloopname" id="1"} + + {/loop} + ``` + +### Integration + +The modules uses hooks, and does not require specific work. diff --git a/domokits/local/modules/LocalPickup/composer.json b/domokits/local/modules/LocalPickup/composer.json new file mode 100644 index 0000000..5527cbf --- /dev/null +++ b/domokits/local/modules/LocalPickup/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/local-pickup-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "LocalPickup" + } +} \ No newline at end of file diff --git a/domokits/local/modules/LocalPickup/templates/backOffice/default/module_configuration.html b/domokits/local/modules/LocalPickup/templates/backOffice/default/module_configuration.html new file mode 100644 index 0000000..d36e1f4 --- /dev/null +++ b/domokits/local/modules/LocalPickup/templates/backOffice/default/module_configuration.html @@ -0,0 +1,53 @@ +{if isset($smarty.get.errmes) and !empty($smarty.get.errmes)} +
+ {$smarty.get.errmes} +
+{/if} + +
+
+
+ {intl l='Configure local pickup' d='localpickup.bo.default'} +
+ +
+
+ + {form name="localpickup.form"} + + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + + page_url = {url path="/admin/module/LocalPickup"} + close_url = {url path="/admin/modules"} + } + + {form_hidden_fields form=$form} + +
+
+ {custom_render_form_field field="price"} +
+ + + {currency attr="symbol"} + +
+ {/custom_render_form_field} +
+ +
+ {render_form_field field="description" extra_class="wysiwyg" value=$description} +
+ +
+ {render_form_field field="email" extra_class="wysiwyg" value=$email} +
+
+ {/form} + +
+
+
+
diff --git a/domokits/local/modules/LocalPickup/templates/email/default/order_shipping.html b/domokits/local/modules/LocalPickup/templates/email/default/order_shipping.html new file mode 100644 index 0000000..ff49f1b --- /dev/null +++ b/domokits/local/modules/LocalPickup/templates/email/default/order_shipping.html @@ -0,0 +1,52 @@ +{extends file="email-layout.tpl"} + +{* Do not provide a "Open in browser" link *} +{block name="browser"}{/block} +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}{intl l="Your order confirmation Nº %ref" ref={$order_ref}}{/block} + +{* Title *} +{block name="email-title"} + {default_translation_domain domain='localpickup.email.default'} + {intl l="Your order is available in store"} +{/block} + +{* Content *} +{block name="email-content"} + {default_translation_domain domain='localpickup.email.default'} + +

{intl l="Come pick up your order at the store"}

+ +
+ + {loop name="order.invoice" type="order" id=$order_id customer="*"} + + + + + +
+ {intl l="Delivery address"}:
+ {ifhook rel="email-html.order-confirmation.delivery-address"} + {* delivery module can customize the delivery address *} + {hook name="email-html.order-confirmation.delivery-address" module={$DELIVERY_MODULE} order=$order_id} + {/ifhook} + {elsehook rel="email-html.order-confirmation.delivery-address"} + {format_address order_address=$DELIVERY_ADDRESS locale=$locale} + {/elsehook} +
+ {intl l="Instructions"}:
+ {$comment nofilter} +
+ +
+
+ + {intl l="Thanks"}
+ {intl l="The %store team." store={config key="store_name"}} + {/loop} +{/block} + diff --git a/domokits/local/modules/LocalPickup/templates/email/default/order_shipping.txt b/domokits/local/modules/LocalPickup/templates/email/default/order_shipping.txt new file mode 100644 index 0000000..e69de29 diff --git a/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/delivery-address.html b/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/delivery-address.html new file mode 100644 index 0000000..d54f398 --- /dev/null +++ b/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/delivery-address.html @@ -0,0 +1,5 @@ + + + {$description nofilter} + + diff --git a/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/order-invoice-delivery-address.html b/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/order-invoice-delivery-address.html new file mode 100644 index 0000000..d78131d --- /dev/null +++ b/domokits/local/modules/LocalPickup/templates/frontOffice/default/localpickup/order-invoice-delivery-address.html @@ -0,0 +1,25 @@ +
+ {loop type="address.local" name="local-delivery-address" id={order attr="delivery_address"}} +
{intl l="Retrait en magasin" d="localpickup.fo.default"}
+
+ {loop type="title" name="customer.title.info" id=$TITLE|default:0}{$SHORT}{/loop} {$LASTNAME|upper} {$FIRSTNAME|ucwords} + {$COMPANY} +
+ {$ADDRESS1}
+ {if $ADDRESS2 != ""} + {$ADDRESS2}
+ {/if} + {if $ADDRESS3 != ""} + {$ADDRESS3}
+ {/if} + {$ZIPCODE} + {$CITY}
+ + {loop type="country" name="customer.country.info" id=$COUNTRY|default:0}{$TITLE}{/loop}{if $STATE|default:0}, + {loop type="state" name="customer.state.info" id=$STATE|default:0}{$TITLE}{/loop} + {/if} + +
+
+ {/loop} +
diff --git a/domokits/local/modules/OpenApi/Compiler/ModelPass.php b/domokits/local/modules/OpenApi/Compiler/ModelPass.php new file mode 100644 index 0000000..91a9be6 --- /dev/null +++ b/domokits/local/modules/OpenApi/Compiler/ModelPass.php @@ -0,0 +1,32 @@ +findTaggedServiceIds('open_api.model'); + + $modelServices = []; + foreach ($taggedServices as $id => $tags) { + foreach ($tags as $attributes) { + $classParts = explode('\\', $id); + $modelAlias = $attributes['alias'] ?? end($classParts); + $modelServices[$modelAlias] = $id; + } + if (property_exists($id, "serviceAliases")) { + foreach ($id::$serviceAliases as $alias) { + $modelServices[$alias] = $id; + } + } + } + + $containerBuilder->setParameter(OpenApi::OPEN_API_MODELS_PARAMETER_KEY, $modelServices); + } +} diff --git a/domokits/local/modules/OpenApi/Config/config.xml b/domokits/local/modules/OpenApi/Config/config.xml new file mode 100644 index 0000000..a5cf214 --- /dev/null +++ b/domokits/local/modules/OpenApi/Config/config.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/OpenApi/Config/module.xml b/domokits/local/modules/OpenApi/Config/module.xml new file mode 100644 index 0000000..b15480f --- /dev/null +++ b/domokits/local/modules/OpenApi/Config/module.xml @@ -0,0 +1,43 @@ + + + OpenApi\OpenApi + + Add an API to Thelia + + + + Ajoute une API a Thelia + + + + + en_US + fr_FR + + 2.1.9 + + + Vincent Lopes-Vicente + + + + classic + + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/OpenApi/Config/routing.xml b/domokits/local/modules/OpenApi/Config/routing.xml new file mode 100644 index 0000000..dad991d --- /dev/null +++ b/domokits/local/modules/OpenApi/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/domokits/local/modules/OpenApi/Config/schema.xml b/domokits/local/modules/OpenApi/Config/schema.xml new file mode 100644 index 0000000..353b1a4 --- /dev/null +++ b/domokits/local/modules/OpenApi/Config/schema.xml @@ -0,0 +1,29 @@ + + + + diff --git a/domokits/local/modules/OpenApi/Constraint/Length.php b/domokits/local/modules/OpenApi/Constraint/Length.php new file mode 100644 index 0000000..0433aa9 --- /dev/null +++ b/domokits/local/modules/OpenApi/Constraint/Length.php @@ -0,0 +1,29 @@ +maxMessage = $translator->trans('This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.', [], OpenApi::DOMAIN_NAME); + $this->minMessage = $translator->trans('This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.', [], OpenApi::DOMAIN_NAME); + $this->exactMessage = $translator->trans('This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.', [], OpenApi::DOMAIN_NAME); + $this->charsetMessage = $translator->trans('This value does not match the expected {{ charset }} charset.', [], OpenApi::DOMAIN_NAME); + } + + public function validatedBy() + { + return get_parent_class().'Validator'; + } +} diff --git a/domokits/local/modules/OpenApi/Constraint/NotBlank.php b/domokits/local/modules/OpenApi/Constraint/NotBlank.php new file mode 100644 index 0000000..962b7b6 --- /dev/null +++ b/domokits/local/modules/OpenApi/Constraint/NotBlank.php @@ -0,0 +1,24 @@ +message = Translator::getInstance()->trans('This value should not be blank', [], OpenApi::DOMAIN_NAME); + } + + public function validatedBy() + { + return get_parent_class().'Validator'; + } +} diff --git a/domokits/local/modules/OpenApi/Constraint/NotNull.php b/domokits/local/modules/OpenApi/Constraint/NotNull.php new file mode 100644 index 0000000..eb09ad1 --- /dev/null +++ b/domokits/local/modules/OpenApi/Constraint/NotNull.php @@ -0,0 +1,24 @@ +message = Translator::getInstance()->trans('This value should not be null', [], OpenApi::DOMAIN_NAME); + } + + public function validatedBy() + { + return get_parent_class().'Validator'; + } +} diff --git a/domokits/local/modules/OpenApi/Constraint/Zipcode.php b/domokits/local/modules/OpenApi/Constraint/Zipcode.php new file mode 100644 index 0000000..3c1a18e --- /dev/null +++ b/domokits/local/modules/OpenApi/Constraint/Zipcode.php @@ -0,0 +1,17 @@ +context->getRoot(); + $country = CountryQuery::create()->findPk($address->getCountryId()); + + if (null !== $country) { + if ($country->getNeedZipCode()) { + $zipCodeRegExp = $country->getZipCodeRE(); + if (null !== $zipCodeRegExp) { + if (!preg_match($zipCodeRegExp, $value)) { + $this->context + ->buildViolation(Translator::getInstance()->trans( + "This zip code should respect the following format : %format.", + ['%format' => $country->getZipCodeFormat()], + OpenApi::DOMAIN_NAME + )) + ->addViolation(); + } + } + } + } + + return; + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Admin/BaseAdminOpenApiController.php b/domokits/local/modules/OpenApi/Controller/Admin/BaseAdminOpenApiController.php new file mode 100644 index 0000000..1976e8d --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Admin/BaseAdminOpenApiController.php @@ -0,0 +1,16 @@ +createForm(ConfigForm::getName()); + + try { + $form = $this->validateForm($configForm); + + $data = implode(',', array_keys($form->get('enable_config')->getData())); + + OpenApi::setConfigValue('config_variables', $data); + + return $this->generateSuccessRedirect($configForm); + } catch (\Exception $exception) { + Tlog::getInstance()->error($exception->getMessage()); + + $configForm->setErrorMessage($exception->getMessage()); + + $parserContext + ->addForm($configForm) + ->setGeneralError($exception->getMessage()) + ; + + return $this->generateErrorRedirect($configForm); + } + } +} \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/Controller/Front/AddressController.php b/domokits/local/modules/OpenApi/Controller/Front/AddressController.php new file mode 100644 index 0000000..cfc69f2 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/AddressController.php @@ -0,0 +1,268 @@ +getCurrentCustomer(); + + $addresses = AddressQuery::create() + ->filterByCustomerId($currentCustomer->getId()) + ->find(); + + return OpenApiService::jsonResponse( + array_map( + function (Address $address) use ($modelFactory) { + /** @var OpenApiAddress $openApiAddress */ + $openApiAddress = $modelFactory->buildModel('Address', $address); + $openApiAddress->validate(self::GROUP_READ); + + return $openApiAddress; + }, + iterator_to_array($addresses) + ) + ); + } + + /** + * @Route("", name="add_address", methods="POST") + * + * @OA\Post( + * path="/address", + * tags={"address"}, + * summary="Add an address to current customer", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/Address") + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/Address" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function createAddress( + Request $request, + OpenApiService $openApiService, + ModelFactory $modelFactory + ) { + $currentCustomer = $openApiService->getCurrentCustomer(); + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $currentCustomer); + + /** @var OpenApiAddress $openApiAddress */ + $openApiAddress = $modelFactory->buildModel('Address', $request->getContent()); + $openApiAddress + ->setCustomer($openApiCustomer) + ->validate(self::GROUP_CREATE); + + $openApiAddress->getLabel() ?: $openApiAddress->setLabel(Translator::getInstance()->trans('Main Address')); + + /** @var Address $theliaAddress */ + $theliaAddress = $openApiAddress->toTheliaModel(); + + $oldDefaultAddress = AddressQuery::create()->filterByCustomer($currentCustomer)->filterByIsDefault(true)->findOne(); + if (null === $oldDefaultAddress || $openApiAddress->getIsDefault()) { + $theliaAddress->makeItDefault(); + } + + $theliaAddress->save(); + + return OpenApiService::jsonResponse($openApiAddress->setId($theliaAddress->getId())); + } + + /** + * @Route("/{id}", name="update_address", methods="PATCH") + * + * @OA\Patch( + * path="/address/{id}", + * tags={"address"}, + * summary="Update address", + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\RequestBody( + * required=true, + * @OA\JsonContent(ref="#/components/schemas/Address") + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Address") + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function updateAddress( + Request $request, + OpenApiService $openApiService, + ModelFactory $modelFactory, + $id + ) { + $currentCustomer = $openApiService->getCurrentCustomer(); + + $theliaAddress = AddressQuery::create() + ->filterByCustomerId($currentCustomer->getId()) + ->filterById($id) + ->findOne(); + + if (null === $theliaAddress) { + throw $openApiService->buildOpenApiException( + Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME), + Translator::getInstance()->trans(Translator::getInstance()->trans("No address found for id $id for the current customer.", [], OpenApi::DOMAIN_NAME), [], OpenApi::DOMAIN_NAME) + ); + } + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $currentCustomer); + + /** @var OpenApiAddress $openApiAddress */ + $openApiAddress = $modelFactory->buildModel('Address', $request->getContent()); + $openApiAddress + ->setId($id) + ->setCustomer($openApiCustomer) + ->validate(self::GROUP_UPDATE); + + /** @var Address $theliaAddress */ + $theliaAddress = $openApiAddress->toTheliaModel(); + + $oldDefaultAddress = AddressQuery::create()->filterByCustomer($currentCustomer)->filterByIsDefault(true)->findOne(); + $alreadyDefault = false; + + /* + * Force a default address to stay as default + * Because we can't unset a default address, this is only done when a new address is set as default + */ + if (null !== $oldDefaultAddress && $oldDefaultAddress->getId() === $theliaAddress->getId()) { + $alreadyDefault = true; + $theliaAddress->setIsDefault(true); + } + + if ((null === $oldDefaultAddress || $openApiAddress->getIsDefault()) && !$alreadyDefault) { + $theliaAddress->makeItDefault(); + } + + $theliaAddress + ->save(); + + return new JsonResponse($openApiAddress); + } + + /** + * @Route("/{id}", name="delete_address", methods="DELETE") + * + * @OA\Delete( + * path="/address/{id}", + * tags={"address"}, + * summary="Delete address", + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="204", + * description="Success" + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function deleteAddress( + OpenApiService $openApiService, + ModelFactory $modelFactory, + $id + ) { + $currentCustomer = $openApiService->getCurrentCustomer(); + + $theliaAddress = AddressQuery::create() + ->filterByCustomerId($currentCustomer->getId()) + ->filterById($id) + ->findOne(); + + if (null === $theliaAddress || $theliaAddress->getIsDefault()) { + $errorDescription = $theliaAddress ? 'Impossible to delete the default address.' : "No address found for id $id for the current customer."; + throw $openApiService->buildOpenApiException( + Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME), + Translator::getInstance()->trans($errorDescription, [], OpenApi::DOMAIN_NAME) + ); + } + + $theliaAddress->delete(); + + return new JsonResponse('Success', 204); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/AuthController.php b/domokits/local/modules/OpenApi/Controller/Front/AuthController.php new file mode 100644 index 0000000..b0ec05e --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/AuthController.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OpenApi\Controller\Front; + +use OpenApi\Annotations as OA; +use OpenApi\Model\Api\Customer as OpenApiCustomer; +use OpenApi\Model\Api\ModelFactory; +use OpenApi\OpenApi; +use OpenApi\Service\OpenApiService; +use Symfony\Component\Routing\Annotation\Route; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Thelia\Action\BaseAction; +use Thelia\Core\Event\Customer\CustomerLoginEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Security\SecurityContext; +use Thelia\Core\Security\Token\CookieTokenProvider; +use Thelia\Core\Translation\Translator; +use Thelia\Model\ConfigQuery; +use Thelia\Model\CustomerQuery; + +/** + * @Route("", name="auth") + */ +class AuthController extends BaseFrontOpenApiController +{ + /** + * @Route("/login", name="login", methods="POST") + * + * @OA\Post( + * path="/login", + * tags={"customer"}, + * summary="Log in a customer", + * security={}, + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property( + * property="email", + * type="string" + * ), + * @OA\Property( + * property="password", + * type="string" + * ), + * @OA\Property( + * property="rememberMe", + * type="boolean" + * ), + * ) + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Customer") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function customerLogin( + Request $request, + SecurityContext $securityContext, + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory + ) { + if ($securityContext->hasCustomerUser()) { + throw new \Exception(Translator::getInstance()->trans('A user is already connected. Please disconnect before trying to login in another account.')); + } + + $data = json_decode($request->getContent(), true); + + $customer = CustomerQuery::create() + ->filterByEmail($data['email']) + ->findOne() + ; + + if ($customer === null || !$customer->checkPassword($data['password'])) { + throw new \Exception(Translator::getInstance()->trans('Your username/password pair, does not correspond to any account', [], OpenApi::DOMAIN_NAME)); + } + + $dispatcher->dispatch(new CustomerLoginEvent($customer), TheliaEvents::CUSTOMER_LOGIN); + + /* If the rememberMe property is set to true, we create a new cookie to store the information */ + if (true === (bool) $data['rememberMe']) { + (new CookieTokenProvider())->createCookie( + $customer, + ConfigQuery::read('customer_remember_me_cookie_name', 'crmcn'), + ConfigQuery::read('customer_remember_me_cookie_expiration', 2592000 /* 1 month */) + ); + } + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $customer); + $openApiCustomer->setDefaultAddressId($customer->getDefaultAddress()->getId()); + + return OpenApiService::jsonResponse($openApiCustomer); + } + + /** + * @Route("/logout", name="logout", methods="POST") + * + * @OA\Post( + * path="/logout", + * tags={"customer"}, + * summary="Log out a customer", + * + * @OA\Response( + * response="204", + * description="Success", + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function customerLogout( + SecurityContext $securityContext, + EventDispatcherInterface $dispatcher + ) { + if (!$securityContext->hasCustomerUser()) { + throw new \Exception(Translator::getInstance()->trans('No user is currently logged in.')); + } + + $dispatcher->dispatch((new BaseAction()), TheliaEvents::CUSTOMER_LOGOUT); + (new CookieTokenProvider())->clearCookie(ConfigQuery::read('customer_remember_me_cookie_name', 'crmcn')); + + return OpenApiService::jsonResponse('Success', 204); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/BaseFrontOpenApiController.php b/domokits/local/modules/OpenApi/Controller/Front/BaseFrontOpenApiController.php new file mode 100644 index 0000000..fdb7d85 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/BaseFrontOpenApiController.php @@ -0,0 +1,16 @@ +createResponseFromCart($openApiService); + } + + /** + * @Route("/add", name="add_cartitem", methods="POST") + * + * @OA\Post( + * path="/cart/add", + * tags={"cart"}, + * summary="Add a PSE in a cart", + * + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property( + * property="pseId", + * type="integer" + * ), + * @OA\Property( + * property="quantity", + * type="integer" + * ), + * @OA\Property( + * property="append", + * type="boolean" + * ), + * @OA\Property( + * property="newness", + * type="boolean" + * ), + * example={"pseId": 18, "quantity": 2, "append": true, "newness": true} + * ) + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * @OA\Items( + * type="object", + * @OA\Property( + * property="cart", + * ref="#/components/schemas/Cart" + * ), + * @OA\Property( + * property="cartItem", + * ref="#/components/schemas/CartItem" + * ) + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function cartAddCartItem( + Request $request, + EventDispatcherInterface $dispatcher, + OpenApiService $openApiService, + ModelFactory $modelFactory + ) { + $cart = $request->getSession()->getSessionCart($dispatcher); + if (null === $cart) { + throw new \Exception(Translator::getInstance()->trans('No cart found', [], OpenApi::DOMAIN_NAME)); + } + + $event = new CartEvent($cart); + + $this->updateCartEventFromJson($request->getContent(), $event); + $dispatcher->dispatch($event, TheliaEvents::CART_ADDITEM); + + return OpenApiService::jsonResponse([ + 'cart' => $openApiService->getCurrentOpenApiCart(), + 'cartItem' => $modelFactory->buildModel('CartItem', $event->getCartItem()), + ]); + } + + /** + * @Route("/{cartItemId}", name="delete_cartitem", methods="DELETE") + * + * @OA\Delete( + * path="/cart/{cartItemId}", + * tags={"cart"}, + * summary="Delete an item in the current cart", + * @OA\Parameter( + * name="cartItemId", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * @OA\Items( + * type="object", + * @OA\Property( + * property="cart", + * ref="#/components/schemas/Cart" + * ), + * @OA\Property( + * property="cartItem", + * ref="#/components/schemas/CartItem" + * ) + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function cartDeleteCartItem( + Request $request, + EventDispatcherInterface $dispatcher, + OpenApiService $openApiService, + $cartItemId + ) { + $cart = $request->getSession()->getSessionCart($dispatcher); + if (null === $cart) { + throw new \Exception(Translator::getInstance()->trans('No cart found', [], OpenApi::DOMAIN_NAME)); + } + + $cartItem = CartItemQuery::create()->filterById($cartItemId)->findOne(); + + if (null === $cartItem) { + throw new \Exception(Translator::getInstance()->trans('Deletion impossible : this cart item does not exists.', [], OpenApi::DOMAIN_NAME)); + } + + $cartEvent = new CartEvent($cart); + $cartEvent->setCartItemId($cartItemId); + + $dispatcher->dispatch( + $cartEvent, + TheliaEvents::CART_DELETEITEM + ); + + return $this->createResponseFromCart($openApiService); + } + + /** + * @Route("/{cartItemId}", name="update_cartitem", methods="PATCH") + * + * @OA\Patch( + * path="/cart/{cartItemId}", + * tags={"cart"}, + * summary="Modify an item in the current cart", + * @OA\Parameter( + * name="cartItemId", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property( + * property="quantity", + * type="integer" + * ), + * example={"quantity": 0} + * ) + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Cart") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function cartUpdateCartItem( + Request $request, + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory, + OpenApiService $openApiService, + $cartItemId + ) { + $cart = $request->getSession()->getSessionCart($dispatcher); + if (null === $cart) { + throw new \Exception(Translator::getInstance()->trans('No cart found', [], OpenApi::DOMAIN_NAME)); + } + + $cartItem = CartItemQuery::create()->filterById($cartItemId)->findOne(); + + if (null === $cartItem) { + throw new \Exception(Translator::getInstance()->trans('Modification impossible : this cart item does not exists.', [], OpenApi::DOMAIN_NAME)); + } + + /* Check if cart item belongs to user's cart */ + if (!$cartItem || $cartItem->getCartId() !== $cart->getId()) { + throw new \Exception(Translator::getInstance()->trans("This cartItem doesn't belong to this cart.", [], OpenApi::DOMAIN_NAME)); + } + + $event = new CartEvent($cart); + $event->setCartItemId($cartItemId); + + if ($request->get('quantity') === 0) { + $dispatcher->dispatch( + $event, + TheliaEvents::CART_DELETEITEM + ); + } else { + $this->updateCartEventFromJson($request->getContent(), $event); + $dispatcher->dispatch($event, TheliaEvents::CART_UPDATEITEM); + } + + return OpenApiService::jsonResponse([ + 'cart' => $openApiService->getCurrentOpenApiCart(), + 'cartItem' => $modelFactory->buildModel('CartItem', $event->getCartItem()), + ]); + } + + /** + * Create a new JSON response of an OpenApi cart and returns it, from a Thelia Cart. + * + * @param Cart $cart + * + * @return JsonResponse + * + * @throws \Propel\Runtime\Exception\PropelException + */ + protected function createResponseFromCart(OpenApiService $openApiService) + { + return OpenApiService::jsonResponse($openApiService->getCurrentOpenApiCart()); + } + + /** + * @param int $quantity + * + * @return bool + */ + protected function checkAvailableStock(ProductSaleElements $pse, $quantity) + { + if ($pse && $quantity) { + return $quantity > $pse->getQuantity() && ConfigQuery::checkAvailableStock() && !$pse->getProduct()->getVirtual() === 0; + } + + throw new \Exception(Translator::getInstance()->trans('A PSE is needed in the POST request to add an item to the cart.')); + } + + /** + * Update a Cart Event from a json. + * + * @param $json + * + * @throws \Exception + */ + protected function updateCartEventFromJson($json, CartEvent $event): void + { + $data = json_decode($json, true); + + if (!isset($data['quantity'])) { + throw new \Exception(Translator::getInstance()->trans('A quantity is needed in the POST request to add an item to the cart.')); + } + + /* If the function was called from the PATCH route, we just update the quantity and return */ + if ($cartItemId = $event->getCartItemId()) { + $cartItem = CartItemQuery::create()->filterById($cartItemId)->findOne(); + if ($this->checkAvailableStock($cartItem->getProductSaleElements(), $data['quantity'])) { + throw new \Exception(Translator::getInstance()->trans('Desired quantity exceed available stock')); + } + $event->setQuantity($data['quantity']); + + return; + } + + /* If the function was called from the POST route, we need to set the pseId and append properties, as we need a new CartItem */ + if (!isset($data['pseId'])) { + throw new \Exception(Translator::getInstance()->trans('A PSE is needed in the POST request to add an item to the cart.')); + } + if (!isset($data['append'])) { + throw new \Exception(Translator::getInstance()->trans('You need to set the append value in the POST request to add an item to the cart.')); + } + + $pse = ProductSaleElementsQuery::create()->findPk($data['pseId']); + + if ($this->checkAvailableStock($pse, $data['quantity'])) { + throw new \Exception(Translator::getInstance()->trans('Desired quantity exceed available stock')); + } + + /** If newness then force new cart_item id */ + $newness = isset($data['newness']) ? (bool) $data['newness'] : false; + + $event + ->setProduct($pse->getProductId()) + ->setProductSaleElementsId($data['pseId']) + ->setQuantity($data['quantity']) + ->setAppend($data['append']) + ->setNewness($newness) + ; + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/CategoryController.php b/domokits/local/modules/OpenApi/Controller/Front/CategoryController.php new file mode 100644 index 0000000..773445b --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/CategoryController.php @@ -0,0 +1,147 @@ +baseSearchItems("category", $request); + $categories = $query->find(); + return OpenApiService::jsonResponse( + array_map(fn ($category) => $modelFactory->buildModel('Category', $category), iterator_to_array($categories)) + ); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/CheckoutController.php b/domokits/local/modules/OpenApi/Controller/Front/CheckoutController.php new file mode 100644 index 0000000..14756bc --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/CheckoutController.php @@ -0,0 +1,318 @@ +getCurrentCustomer(); + + $cart = $session->getSessionCart($dispatcher); + if ($cart === null || $cart->countCartItems() === 0) { + throw new \Exception(Translator::getInstance()->trans('Cart is empty', [], OpenApi::DOMAIN_NAME)); + } + + if (true === ConfigQuery::checkAvailableStock()) { + if (!$this->checkStockNotEmpty($cart)) { + throw new \Exception(Translator::getInstance()->trans('Not enough stock', [], OpenApi::DOMAIN_NAME)); + } + } + + $decodedContent = json_decode($request->getContent(), true); + + /** @var Checkout $checkout */ + $checkout = $modelFactory->buildModel('Checkout', $decodedContent); + + if (isset($decodedContent['needValidate']) && true === $decodedContent['needValidate']) { + $checkout->checkIsValid(); + } + + $order = $this->getOrder($request); + $orderEvent = new OrderEvent($order); + + $this->setOrderDeliveryPart( + $request, + $session, + $dispatcher, + $securityContext, + $checkout, + $orderEvent + ); + $this->setOrderInvoicePart( + $dispatcher, + $securityContext, + $checkout, + $orderEvent + ); + + $responseCheckout = $checkout + ->createFromOrder($orderEvent->getOrder()); + + return OpenApiService::jsonResponse($responseCheckout); + } + + /** + * @Route("", name="get_checkout", methods="GET") + * @OA\Get( + * path="/checkout", + * tags={"checkout"}, + * summary="get current checkout", + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Checkout"), + * ) + * ) + */ + public function getCheckout( + Request $request, + ModelFactory $modelFactory + ) { + $order = $this->getOrder($request); + + /** @var Checkout $checkout */ + $checkout = ($modelFactory->buildModel('Checkout')) + ->createFromOrder($order); + + $checkout->setPickupAddress($request->getSession()->get(OpenApi::PICKUP_ADDRESS_SESSION_KEY)); + + return OpenApiService::jsonResponse($checkout); + } + + protected function setOrderDeliveryPart( + Request $request, + Session $session, + EventDispatcherInterface $dispatcher, + SecurityContext $securityContext, + Checkout $checkout, + OrderEvent $orderEvent + ): void { + $cart = $session->getSessionCart($dispatcher); + $deliveryAddress = AddressQuery::create()->findPk($checkout->getDeliveryAddressId()); + $deliveryModule = ModuleQuery::create()->findPk($checkout->getDeliveryModuleId()); + + /** In case of pickup point delivery, we cannot use a Thelia address since it won't exist, so we get one from the request */ + $pickupAddress = $checkout->getPickupAddress(); + + if (null !== $deliveryAddress) { + if ($deliveryAddress->getCustomerId() !== $securityContext->getCustomerUser()->getId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'Delivery address does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + } + + if (null !== $pickupAddress && $deliveryAddress && $deliveryModule) { + if (null === AreaDeliveryModuleQuery::create()->findByCountryAndModule( + $deliveryAddress->getCountry(), + $deliveryModule + )) { + throw new \Exception( + Translator::getInstance()->trans( + 'Delivery module cannot be use with selected delivery address', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + } + + $postage = null; + if ($deliveryAddress && $deliveryModule) { + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, $deliveryAddress); + + $dispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + if (!$deliveryPostageEvent->isValidModule()) { + throw new DeliveryException( + Translator::getInstance()->trans('The delivery module is not valid.', [], Front::MESSAGE_DOMAIN) + ); + } + + $postage = $deliveryPostageEvent->getPostage(); + } + + $orderEvent->setDeliveryAddress($deliveryAddress !== null ? $deliveryAddress->getId() : $securityContext->getCustomerUser()?->getDefaultAddress()?->getId()); + $orderEvent->setDeliveryModule($deliveryModule?->getId()); + $orderEvent->setPostage($postage !== null ? $postage->getAmount() : 0.0); + $orderEvent->setPostageTax($postage !== null ? $postage->getAmountTax() : 0.0); + $orderEvent->setPostageTaxRuleTitle($postage !== null ? $postage->getTaxRuleTitle() : ''); + + $dispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_ADDRESS); + $dispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + $dispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + + if ($deliveryAddress && $deliveryModule) { + $this->checkValidDelivery(); + } + + $request->getSession()->set(OpenApi::PICKUP_ADDRESS_SESSION_KEY, json_encode($pickupAddress)); + } + + protected function setOrderInvoicePart( + EventDispatcherInterface $dispatcher, + SecurityContext $securityContext, + Checkout $checkout, + OrderEvent $orderEvent + ): void { + $billingAddress = AddressQuery::create()->findPk($checkout->getBillingAddressId()); + + if ($billingAddress) { + if ($billingAddress->getCustomerId() !== $securityContext->getCustomerUser()->getId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'Invoice address does not belong to the current customer', + [], + Front::MESSAGE_DOMAIN + ) + ); + } + } + + $paymentModule = ModuleQuery::create()->findPk($checkout->getPaymentModuleId()); + + $orderEvent->setInvoiceAddress($billingAddress !== null ? $billingAddress->getId() : null); + $orderEvent->setPaymentModule($paymentModule !== null ? $paymentModule->getId() : null); + $dispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_INVOICE_ADDRESS); + $dispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_PAYMENT_MODULE); + + // Only check invoice is module and address is set + if ($billingAddress && $paymentModule) { + $this->checkValidInvoice(); + } + } + + protected function getOrder(Request $request) + { + $session = $request->getSession(); + + if (null !== $order = $session->getOrder()) { + return $order; + } + + $order = new Order(); + + $session->setOrder($order); + + return $order; + } + + protected function checkValidDelivery(): void + { + $order = $this->getSession()->getOrder(); + if (null === $order + || + null === $order->getChoosenDeliveryAddress() + || + null === $order->getDeliveryModuleId() + || + null === AddressQuery::create()->findPk($order->getChoosenDeliveryAddress()) + || + null === ModuleQuery::create()->findPk($order->getDeliveryModuleId())) { + throw new \Exception(Translator::getInstance()->trans('Invalid delivery', [], OpenApi::DOMAIN_NAME)); + } + } + + protected function checkValidInvoice(): void + { + $order = $this->getSession()->getOrder(); + if (null === $order + || + null === $order->getChoosenInvoiceAddress() + || + null === $order->getPaymentModuleId() + || + null === AddressQuery::create()->findPk($order->getChoosenInvoiceAddress()) + || + null === ModuleQuery::create()->findPk($order->getPaymentModuleId())) { + throw new \Exception(Translator::getInstance()->trans('Invalid invoice', [], OpenApi::DOMAIN_NAME)); + } + } + + protected function checkStockNotEmpty(Cart $cart) + { + $cartItems = $cart->getCartItems(); + + foreach ($cartItems as $cartItem) { + $pse = $cartItem->getProductSaleElements(); + + $product = $cartItem->getProduct(); + + if ($pse->getQuantity() <= 0 && $product->getVirtual() !== 1) { + return false; + } + } + + return true; + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/ConfigController.php b/domokits/local/modules/OpenApi/Controller/Front/ConfigController.php new file mode 100644 index 0000000..5d282b4 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/ConfigController.php @@ -0,0 +1,54 @@ +filterByName($key)->findOne(); + if ($config && in_array($config->getId(), explode(',', OpenApi::getConfigValue('config_variables')))) { + return new JsonResponse($config->getValue()); + } + + return new JsonResponse(Translator::getInstance()->trans('You are not allowed to access this config'), 401); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/ContentController.php b/domokits/local/modules/OpenApi/Controller/Front/ContentController.php new file mode 100755 index 0000000..568bab0 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/ContentController.php @@ -0,0 +1,187 @@ +baseSearchItems("content", $request); + $contents = $query->find(); + + return OpenApiService::jsonResponse( + array_map(fn ($content) => $modelFactory->buildModel('Content', $content), iterator_to_array($contents)) + ); + } + + + /** + * @Route("/{id}", name="get_content", methods="GET", requirements={"collectionId"="\d+"}) + * + * @OA\Get( + * path="/content/{id}", + * tags={"content"}, + * summary="Get content values by ID", + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * example="1", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Content") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + * + * @throws Exception + */ + public function getContent(ModelFactory $modelFactory, $id) + { + $content = ContentQuery::create() + ->findOneById($id); + $apiContent = $modelFactory->buildModel('Content', $content); + + if (null === $content) { + throw new Exception(Translator::getInstance()->trans('Content does not exist.', [], OpenApi::DOMAIN_NAME)); + } + + return new JsonResponse($apiContent); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/CouponController.php b/domokits/local/modules/OpenApi/Controller/Front/CouponController.php new file mode 100644 index 0000000..7942226 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/CouponController.php @@ -0,0 +1,180 @@ +getSession()->getSessionCart($dispatcher); + if (null === $cart) { + throw new \Exception(Translator::getInstance()->trans('No cart found', [], OpenApi::DOMAIN_NAME)); + } + + /** @var Coupon $openApiCoupon */ + $openApiCoupon = $modelFactory->buildModel('Coupon', $request->getContent()); + if (null === $openApiCoupon->getCode()) { + throw new \Exception(Translator::getInstance()->trans('Coupon code cannot be null', [], OpenApi::DOMAIN_NAME)); + } + + /** We verify that the given coupon actually exists in the base */ + $theliaCoupon = CouponQuery::create()->filterByCode($openApiCoupon->getCode())->findOne(); + if (null === $theliaCoupon) { + throw new \Exception(Translator::getInstance()->trans('No coupons were found for this coupon code.', [], OpenApi::DOMAIN_NAME)); + } + + try { + $event = new CouponConsumeEvent($openApiCoupon->getCode()); + $dispatcher->dispatch($event, TheliaEvents::COUPON_CONSUME); + $openApiCoupon = $modelFactory->buildModel('Coupon', $theliaCoupon); + } catch (UnmatchableConditionException $exception) { + throw new \Exception(Translator::getInstance()->trans('You should sign in or register to use this coupon.', [], OpenApi::DOMAIN_NAME)); + } + + return OpenApiService::jsonResponse($openApiCoupon); + } + + /** + * @Route("/clear_all", name="clear_all_coupon", methods="GET") + * + * @OA\Get( + * path="/coupon/clear_all", + * tags={"coupon"}, + * summary="Clear all coupons", + * + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Cart") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function clearAllCoupon( + Request $request, + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory + ) { + $cart = $request->getSession()->getSessionCart($dispatcher); + try { + $dispatcher->dispatch((new Event()), TheliaEvents::COUPON_CLEAR_ALL); + } catch (\Exception $exception) { + throw new \Exception(Translator::getInstance()->trans('An error occurred while clearing coupons : ') . $exception->getMessage()); + } + + return OpenApiService::jsonResponse($modelFactory->buildModel('Cart', $cart)); + } + + /** + * @Route("/clear/{id}", name="clear_coupon", methods="GET") + * + * @OA\Get( + * path="/coupon/clear/{id}", + * tags={"coupon"}, + * summary="Clear a specific coupon from cart", + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * example="1", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/Cart") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function clearCoupon( + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory, + Session $session, + $id + ) { + $cart = $session->getSessionCart($dispatcher); + + try { + $coupon = CouponQuery::create()->findOneById($id); + + if (null === $coupon) { + throw new Exception(); + } + + $consumedCoupons = $session->getConsumedCoupons(); + + unset($consumedCoupons[$coupon->getCode()]); + + $session->setConsumedCoupons($consumedCoupons); + } catch (Exception $e) { + throw new Exception(Translator::getInstance()->trans('An error occurred while clearing coupon ' . $id . ' : ') . $e->getMessage()); + } + + return OpenApiService::jsonResponse($modelFactory->buildModel('Cart', $cart)); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/CustomerController.php b/domokits/local/modules/OpenApi/Controller/Front/CustomerController.php new file mode 100644 index 0000000..64dcc1c --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/CustomerController.php @@ -0,0 +1,230 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OpenApi\Controller\Front; + +use OpenApi\Annotations as OA; +use OpenApi\Model\Api\Address as OpenApiAddress; +use OpenApi\Model\Api\Customer as OpenApiCustomer; +use OpenApi\Model\Api\ModelFactory; +use OpenApi\OpenApi; +use OpenApi\Service\OpenApiService; +use Propel\Runtime\Propel; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Security\SecurityContext; +use Thelia\Core\Translation\Translator; +use Thelia\Model\Address; +use Thelia\Model\Customer; + +/** + * @Route("/customer", name="customer") + */ +class CustomerController extends BaseFrontOpenApiController +{ + /** + * @Route("", name="get_customer", methods="GET") + * + * @OA\Get( + * path="/customer", + * tags={"customer"}, + * summary="Get current customer", + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/Customer" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + * ) + */ + public function getCustomer( + OpenApiService $openApiService, + ModelFactory $modelFactory + ) { + $currentCustomer = $openApiService->getCurrentCustomer(); + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $currentCustomer); + $openApiCustomer->validate(self::GROUP_READ); + + return OpenApiService::jsonResponse($openApiCustomer); + } + + /** + * @Route("", name="add_customer", methods="POST") + * + * @OA\Post( + * path="/customer", + * tags={"customer"}, + * summary="Create a new customer", + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property( + * property="customer", + * type="object", + * ref="#/components/schemas/Customer", + * ), + * @OA\Property( + * property="address", + * type="object", + * ref="#/components/schemas/Address", + * ), + * @OA\Property( + * property="password", + * type="string", + * ), + * ), + * ), + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/Customer" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function createCustomer(Request $request, ModelFactory $modelFactory) + { + $data = json_decode($request->getContent(), true); + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $data['customer']); + $openApiCustomer->validate(self::GROUP_CREATE); + + /** We create a Propel transaction to save the customer and get its ID necessary for the validation + * of the address without actually commiting to the base until everything is in order. + */ + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + /** @var Customer $theliaCustomer */ + $theliaCustomer = $openApiCustomer->toTheliaModel(); + $theliaCustomer->setPassword($data['password'])->save(); + $openApiCustomer->setId($theliaCustomer->getId()); + + /** We must catch the validation exception if it is thrown to rollback the Propel transaction before throwing the exception again */ + /** @var OpenApiAddress $openApiAddress */ + $openApiAddress = $modelFactory->buildModel('Address', $data['address']); + $openApiAddress->setCustomer($openApiCustomer)->validate(self::GROUP_CREATE); + + /** @var Address $theliaAddress */ + $theliaAddress = $openApiAddress->toTheliaModel(); + $theliaAddress + ->setLabel(Translator::getInstance()->trans('Main Address', [], OpenApi::DOMAIN_NAME)) + ->setIsDefault(1) + ->save() + ; + } catch (\Exception $exception) { + $con->rollBack(); + throw $exception; + } + + /* If everything went fine, we actually commit the changes to the base. */ + $con->commit(); + + $openApiCustomer->setDefaultAddressId($theliaAddress->getId()); + + return OpenApiService::jsonResponse($openApiCustomer); + } + + /** + * @Route("", name="update_customer", methods="PATCH") + * + * @OA\Patch( + * path="/customer", + * tags={"customer"}, + * summary="Edit the current customer", + * @OA\RequestBody( + * @OA\MediaType( + * mediaType="application/json", + * @OA\Schema( + * @OA\Property( + * property="customer", + * type="object", + * ref="#/components/schemas/Customer", + * ), + * @OA\Property( + * property="password", + * type="string", + * ), + * ), + * ), + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/Customer" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function updateCustomer( + Request $request, + SecurityContext $securityContext, + OpenApiService $openApiService, + ModelFactory $modelFactory + ) { + $currentCustomer = $openApiService->getCurrentCustomer(); + + $data = json_decode($request->getContent(), true); + + /** @var OpenApiCustomer $openApiCustomer */ + $openApiCustomer = $modelFactory->buildModel('Customer', $data['customer']); + $openApiCustomer->setId($currentCustomer->getId())->validate(self::GROUP_UPDATE); + + /** @var Customer $theliaCustomer */ + $theliaCustomer = $openApiCustomer->toTheliaModel(); + $theliaCustomer->setNew(false); + + if (\array_key_exists('password', $data) && null !== $newPassword = $data['password']) { + $theliaCustomer->setPassword($newPassword); + } + + $theliaCustomer->save(); + + $securityContext->setCustomerUser($theliaCustomer); + + return OpenApiService::jsonResponse($openApiCustomer); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/DeliveryController.php b/domokits/local/modules/OpenApi/Controller/Front/DeliveryController.php new file mode 100644 index 0000000..b1c0163 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/DeliveryController.php @@ -0,0 +1,403 @@ +get('stateId') ? (StateQuery::create())->filterById($request->get('stateId'))->findOne() : null; + $country = $request->get('countryId') ? (CountryQuery::create())->filterById($request->get('countryId'))->findOne() : null; + $pickupLocationEvent = new PickupLocationEvent( + null, + $request->get('radius'), + $request->get('maxRelays'), + $request->get('address'), + $request->get('city'), + $request->get('zipCode'), + $request->get('orderWeight'), + $state, + $country, + $request->get('moduleIds') + ); + + $dispatcher->dispatch($pickupLocationEvent, TheliaEvents::MODULE_DELIVERY_GET_PICKUP_LOCATIONS); + + return OpenApiService::jsonResponse( + array_map( + fn (PickupLocation $pickupLocation) => $pickupLocation->toArray(), + $pickupLocationEvent->getLocations() + ) + ); + } + + /** + * @Route("/simple-modules", name="delivery_simple_modules", methods="GET") + * + * @OA\Get( + * path="/delivery/simple-modules", + * tags={"delivery", "modules"}, + * summary="List all delivery modules as simple list (without postages and options)", + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/DeliveryModule" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function getSimpleDeliveryModules(ModelFactory $modelFactory) + { + $modules = ModuleQuery::create() + ->filterByActivate(1) + ->filterByType(BaseModule::DELIVERY_MODULE_TYPE) + ->find(); + + return OpenApiService::jsonResponse( + array_map( + function (Module $module) use ($modelFactory) { + /** @var AbstractDeliveryModule $moduleInstance */ + $moduleInstance = $module->getDeliveryModuleInstance($this->container); + + /** @var DeliveryModule $deliveryModule */ + $deliveryModule = $modelFactory->buildModel('DeliveryModule', $module); + $deliveryModule->setDeliveryMode($moduleInstance->getDeliveryMode()); + + return $deliveryModule; + }, + iterator_to_array($modules) + ) + ); + } + + /** + * @Route("/modules", name="delivery_modules", methods="GET") + * + * @OA\Get( + * path="/delivery/modules", + * tags={"delivery", "modules"}, + * summary="List all available delivery modules", + * @OA\Parameter( + * name="addressId", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Parameter( + * name="moduleId", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * ref="#/components/schemas/DeliveryModule" + * ) + * ) + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function getDeliveryModules( + Request $request, + SecurityContext $securityContext, + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory + ) { + $deliveryAddress = $this->getDeliveryAddress($request, $securityContext); + + if (null === $deliveryAddress) { + throw new \Exception(Translator::getInstance()->trans('You must either pass an address id or have a customer connected', [], OpenApi::DOMAIN_NAME)); + } + + $cart = $request->getSession()->getSessionCart($dispatcher); + $country = $deliveryAddress->getCountry(); + $state = $deliveryAddress->getState(); + + $moduleQuery = ModuleQuery::create() + ->filterByActivate(1) + ->filterByType(BaseModule::DELIVERY_MODULE_TYPE); + + if (null !== $moduleId = $request->get('moduleId')) { + $moduleQuery->filterById($moduleId); + } + + $modules = $moduleQuery->find(); + + $class = $this; + + return OpenApiService::jsonResponse( + array_map( + fn ($module) => $class->getDeliveryModule($module, $dispatcher, $cart, $modelFactory, $deliveryAddress, $country, $state), + iterator_to_array($modules) + ) + ); + } + + /** + * @Route("/set-delivery", name="set_delivery_modules", methods="GET") + * + * @OA\Get( + * path="/delivery/set-delivery", + * tags={"delivery", "modules"}, + * summary="Set delivery module on session to calculate postage", + * @OA\Parameter( + * name="delivery_module_id", + * in="query", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function setDeliveryModules(Request $request) + { + $deliveryModuleId = $request->get('delivery_module_id'); + $session = $request->getSession(); + $order = $session->getOrder(); + + if (!$order) { + return new JsonResponse(); + } + + $order->setDeliveryModuleId($deliveryModuleId); + $session->setOrder($order); + + return new JsonResponse(); + } + + protected function getDeliveryModule( + Module $theliaDeliveryModule, + EventDispatcherInterface $dispatcher, + Cart $cart, + ModelFactory $modelFactory, + $address, + $country, + $state + ) { + $areaDeliveryModule = AreaDeliveryModuleQuery::create() + ->findByCountryAndModule($country, $theliaDeliveryModule, $state); + $isCartVirtual = $cart->isVirtual(); + + $isValid = true; + if (false === $isCartVirtual && null === $areaDeliveryModule) { + $isValid = false; + } + + $moduleInstance = $theliaDeliveryModule->getDeliveryModuleInstance($this->container); + + if (true === $isCartVirtual && false === $moduleInstance->handleVirtualProductDelivery()) { + $isValid = false; + } + + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, $address, $country, $state); + try { + $dispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + } catch (DeliveryException $exception) { + $isValid = false; + } + + if (!$deliveryPostageEvent->isValidModule()) { + $isValid = false; + } + + $deliveryModuleOptionEvent = new DeliveryModuleOptionEvent($theliaDeliveryModule, $address, $cart, $country, $state); + + $dispatcher->dispatch( + $deliveryModuleOptionEvent, + OpenApiEvents::MODULE_DELIVERY_GET_OPTIONS + ); + + /** @var DeliveryModule $deliveryModule */ + $deliveryModule = $modelFactory->buildModel('DeliveryModule', $theliaDeliveryModule); + + $deliveryModule + ->setDeliveryMode($deliveryPostageEvent->getDeliveryMode()) + ->setValid($isValid) + ->setOptions($deliveryModuleOptionEvent->getDeliveryModuleOptions()) + ; + + return $deliveryModule; + } + + protected function getDeliveryAddress(Request $request, SecurityContext $securityContext) + { + $addressId = $request->get('addressId'); + + if (null === $addressId) { + $addressId = $request->getSession()->getOrder()->getChoosenDeliveryAddress(); + } + + if (null !== $addressId) { + $address = AddressQuery::create()->findPk($addressId); + if (null !== $address) { + return $address; + } + } + + // If no address in request or in order take customer default address + $currentCustomer = $securityContext->getCustomerUser(); + + if (null === $currentCustomer) { + return null; + } + + return $currentCustomer->getDefaultAddress(); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/FolderController.php b/domokits/local/modules/OpenApi/Controller/Front/FolderController.php new file mode 100644 index 0000000..0f45f15 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/FolderController.php @@ -0,0 +1,147 @@ +baseSearchItems("folder", $request); + $folders = $query->find(); + return OpenApiService::jsonResponse( + array_map(fn ($folder) => $modelFactory->buildModel('Folder', $folder), iterator_to_array($folders)) + ); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/PaymentController.php b/domokits/local/modules/OpenApi/Controller/Front/PaymentController.php new file mode 100644 index 0000000..1779b14 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/PaymentController.php @@ -0,0 +1,115 @@ +getSession()->getSessionCart($dispatcher); + $lang = $request->getSession()->getLang(); + $moduleQuery = ModuleQuery::create() + ->filterByActivate(1) + ->filterByType(BaseModule::PAYMENT_MODULE_TYPE) + ->orderByPosition(); + + if (null !== $moduleId = $request->get('moduleId')) { + $moduleQuery->filterById($moduleId); + } + + $modules = $moduleQuery->find(); + + // Return formatted valid payment + return OpenApiService::jsonResponse( + array_map( + fn ($module) => $this->getPaymentModule($dispatcher, $modelFactory, $module, $cart, $lang), + iterator_to_array($modules) + ) + ); + } + + protected function getPaymentModule( + EventDispatcherInterface $dispatcher, + ModelFactory $modelFactory, + Module $paymentModule, + Cart $cart, + Lang $lang + ) { + $paymentModule->setLocale($lang->getLocale()); + $moduleInstance = $paymentModule->getPaymentModuleInstance($this->container); + + $isValidPaymentEvent = new IsValidPaymentEvent($moduleInstance, $cart); + $dispatcher->dispatch( + $isValidPaymentEvent, + TheliaEvents::MODULE_PAYMENT_IS_VALID + ); + + /** @var PaymentModule $paymentModule */ + $paymentModule = $modelFactory->buildModel('PaymentModule', $paymentModule); + $paymentModule->setValid($isValidPaymentEvent->isValidModule()) + ->setCode($moduleInstance->getCode()) + ->setMinimumAmount($isValidPaymentEvent->getMinimumAmount()) + ->setMaximumAmount($isValidPaymentEvent->getMaximumAmount()); + + return $paymentModule; + } +} diff --git a/domokits/local/modules/OpenApi/Controller/Front/ProductController.php b/domokits/local/modules/OpenApi/Controller/Front/ProductController.php new file mode 100644 index 0000000..16cb416 --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/Front/ProductController.php @@ -0,0 +1,213 @@ +get('id')) { + $productQuery->filterById($id); + } + + if (null !== $ids = $request->get('ids')) { + $productQuery->filterById($ids, Criteria::IN); + } + + if (null !== $reference = $request->get('reference')) { + $productQuery->filterByRef($reference); + } + + $productQuery->filterByVisible((bool) json_decode(json_encode($request->get('visible', true)))); + + $order = $request->get('order', 'alpha'); + $locale = $request->get('locale', $request->getSession()->getLang()->getLocale()); + $title = $request->get('title'); + $description = $request->get('description'); + $chapo = $request->get('chapo'); + $postscriptum = $request->get('postscriptum'); + + $productQuery + ->limit($request->get('limit', 20)) + ->offset($request->get('offset', 0)); + + switch ($order) { + case 'created': + $productQuery->orderByCreatedAt(); + break; + case 'created_reverse': + $productQuery->orderByCreatedAt(Criteria::DESC); + break; + } + + if (null !== $title || null !== $description || null !== $chapo || null !== $postscriptum) { + $productI18nQuery = $productQuery + ->useProductI18nQuery() + ->filterByLocale($locale); + + if (null !== $title) { + $productI18nQuery->filterByTitle('%'.$title.'%', Criteria::LIKE); + } + + if (null !== $description) { + $productI18nQuery->filterByDescription('%'.$description.'%', Criteria::LIKE); + } + + if (null !== $chapo) { + $productI18nQuery->filterByChapo('%'.$chapo.'%', Criteria::LIKE); + } + + if (null !== $postscriptum) { + $productI18nQuery->filterByPostscriptum('%'.$postscriptum.'%', Criteria::LIKE); + } + + switch ($order) { + case 'alpha': + $productI18nQuery->orderByTitle(); + break; + case 'alpha_reverse': + $productI18nQuery->orderByTitle(Criteria::DESC); + break; + } + + $productI18nQuery->endUse(); + } + + $products = $productQuery->find(); + + $products = array_map(fn ($product) => $modelFactory->buildModel('Product', $product), iterator_to_array($products)); + + return OpenApiService::jsonResponse($products); + } +} diff --git a/domokits/local/modules/OpenApi/Controller/OpenApiController.php b/domokits/local/modules/OpenApi/Controller/OpenApiController.php new file mode 100644 index 0000000..e65a2ce --- /dev/null +++ b/domokits/local/modules/OpenApi/Controller/OpenApiController.php @@ -0,0 +1,56 @@ +toJson(), true); + + $modelAnnotations = $annotations['components']['schemas']; + foreach ($modelAnnotations as $modelName => $modelAnnotation) { + $isExtend = preg_match('/.*(Extend)(.*)/', $modelName, $matches); + if (!$isExtend) { + continue; + } + + $modelExtendedName = $matches[2]; + + $modelAnnotations[$modelExtendedName] = array_replace_recursive($modelAnnotations[$modelExtendedName], $modelAnnotation); + unset($modelAnnotations[$modelName]); + } + + $annotations['components']['schemas'] = $modelAnnotations; + + $host = $request->getSchemeAndHttpHost(); + $annotations['servers'] = [ + ['url' => $host.'/open_api'], + ['url' => $host.'/index_dev.php/open_api'], + ]; + + return $this->render('swagger-ui', [ + 'spec' => json_encode($annotations), + ]); + } +} diff --git a/domokits/local/modules/OpenApi/EventListener/ExceptionListener.php b/domokits/local/modules/OpenApi/EventListener/ExceptionListener.php new file mode 100644 index 0000000..31872c4 --- /dev/null +++ b/domokits/local/modules/OpenApi/EventListener/ExceptionListener.php @@ -0,0 +1,77 @@ +modelFactory = $modelFactory; + } + + /** + * Convert all exception to OpenApiException if route is an open api route. + */ + public function catchAllException(ExceptionEvent $event): void + { + // Do nothing if this is already an Open Api Exception + if ($event->getThrowable() instanceof OpenApiException) { + return; + } + + // Do nothing on non-api routes + if (!$event->getRequest()->attributes->get(OpenApi::OPEN_API_ROUTE_REQUEST_KEY, false)) { + return; + } + + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + [ + 'title' => Translator::getInstance()->trans('Unexpected error', [], OpenApi::DOMAIN_NAME), + 'description' => $event->getThrowable()->getMessage(), + ] + ); + + $event->setThrowable((new OpenApiException($error))); + } + + /** + * Format OpenApiException to JSON response. + */ + public function catchOpenApiException(ExceptionEvent $event): void + { + if (!$event->getThrowable() instanceof OpenApiException) { + return; + } + + /** @var OpenApiException $openApiException */ + $openApiException = $event->getThrowable(); + + $response = new JsonResponse($openApiException->getError(), $openApiException->getHttpCode()); + $event->setResponse($response); + } + + public static function getSubscribedEvents() + { + return [ + KernelEvents::EXCEPTION => [ + ['catchOpenApiException', 256], + ['catchAllException', 512], + ], + ]; + } +} diff --git a/domokits/local/modules/OpenApi/EventListener/LogoutListener.php b/domokits/local/modules/OpenApi/EventListener/LogoutListener.php new file mode 100644 index 0000000..6354800 --- /dev/null +++ b/domokits/local/modules/OpenApi/EventListener/LogoutListener.php @@ -0,0 +1,33 @@ +request = $request; + } + + + public static function getSubscribedEvents() + { + return [ + TheliaEvents::CUSTOMER_LOGOUT => ['emptyOrderSession', 30] + ]; + } + + public function emptyOrderSession() + { + $this->request->getSession()->set('thelia.order', null); + } +} \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/EventListener/OrderListener.php b/domokits/local/modules/OpenApi/EventListener/OrderListener.php new file mode 100644 index 0000000..7fe8e35 --- /dev/null +++ b/domokits/local/modules/OpenApi/EventListener/OrderListener.php @@ -0,0 +1,58 @@ +request = $requestStack->getCurrentRequest(); + $this->modelFactory = $modelFactory; + } + + public function setPickupAddress(OrderEvent $event): void + { + /** @var Address $pickupAddress */ + $pickupAddressJson = $this->request->getSession()->get(OpenApi::PICKUP_ADDRESS_SESSION_KEY); + $pickupAddress = $this->modelFactory->buildModel('Address', $pickupAddressJson); + + if (null === $pickupAddress || null === $pickupAddress->getAddress1() || null === $pickupAddress->getCity()) { + return; + } + + $orderAddress = OrderAddressQuery::create() + ->findPK($event->getOrder()->getDeliveryOrderAddressId()) + ->setCompany($pickupAddress->getCompany()) + ->setAddress1($pickupAddress->getAddress1()) + ->setAddress2($pickupAddress->getAddress2()) + ->setAddress3($pickupAddress->getAddress3()) + ->setZipcode($pickupAddress->getZipcode()) + ->setCity($pickupAddress->getCity()) + ; + $orderAddress->save(); + $event->setDeliveryAddress($orderAddress->getId()); + + // Reset pickup address + $this->request->getSession()->set(OpenApi::PICKUP_ADDRESS_SESSION_KEY, null); + } + + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_BEFORE_PAYMENT => ['setPickupAddress', 256], + ]; + } +} diff --git a/domokits/local/modules/OpenApi/EventListener/RequestListener.php b/domokits/local/modules/OpenApi/EventListener/RequestListener.php new file mode 100644 index 0000000..4effe92 --- /dev/null +++ b/domokits/local/modules/OpenApi/EventListener/RequestListener.php @@ -0,0 +1,41 @@ +securityContext = $securityContext; + } + + public function markRequestAsOpenApi(ControllerEvent $event): void + { + $controller = $event->getController(); + if (is_array($controller) && isset($controller[0]) && $controller[0] instanceof BaseFrontOpenApiController) { + $currentRequest = $event->getRequest(); + $currentRequest->attributes->set('_previous_url', 'dont-save'); + $currentRequest->attributes->set(OpenApi::OPEN_API_ROUTE_REQUEST_KEY, true); + } + } + + public static function getSubscribedEvents() + { + return [ + KernelEvents::CONTROLLER => [ + ['markRequestAsOpenApi', 512], + ], + ]; + } +} diff --git a/domokits/local/modules/OpenApi/Events/DeliveryModuleOptionEvent.php b/domokits/local/modules/OpenApi/Events/DeliveryModuleOptionEvent.php new file mode 100644 index 0000000..f6f572c --- /dev/null +++ b/domokits/local/modules/OpenApi/Events/DeliveryModuleOptionEvent.php @@ -0,0 +1,189 @@ +module = $module; + $this->address = $address; + $this->cart = $cart; + $this->country = $country; + $this->state = $state; + + if (null === $this->module || null === $this->address) { + throw new \Exception(Translator::getInstance()->trans('Not enough informations to retrieve module options')); + } + + if (!$module->isDeliveryModule()) { + throw new \Exception(Translator::getInstance()->trans($module->getTitle().' is not a delivery module.')); + } + } + + /** + * @return array + */ + public function getDeliveryModuleOptions() + { + return $this->deliveryModuleOptions; + } + + /** + * @param array $deliveryModuleOptions + * + * @return DeliveryModuleOptionEvent + */ + public function setDeliveryModuleOptions($deliveryModuleOptions) + { + $this->deliveryModuleOptions = $deliveryModuleOptions; + + return $this; + } + + /** + * @param DeliveryModuleOption $deliveryModuleOption + * + * @return DeliveryModuleOptionEvent + */ + public function appendDeliveryModuleOptions($deliveryModuleOption) + { + $this->deliveryModuleOptions[] = $deliveryModuleOption; + + return $this; + } + + /** + * @return Module + */ + public function getModule() + { + return $this->module; + } + + /** + * @param Module $module + * + * @return DeliveryModuleOptionEvent + */ + public function setModule($module) + { + $this->module = $module; + + return $this; + } + + /** + * @return Cart + */ + public function getCart() + { + return $this->cart; + } + + /** + * @param Cart $cart + * + * @return DeliveryModuleOptionEvent + */ + public function setCart($cart) + { + $this->cart = $cart; + + return $this; + } + + /** + * @return Address + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param Address $address + * + * @return DeliveryModuleOptionEvent + */ + public function setAddress($address) + { + $this->address = $address; + + return $this; + } + + /** + * @return Country + */ + public function getCountry() + { + return $this->country; + } + + /** + * @param Country $country + * + * @return DeliveryModuleOptionEvent + */ + public function setCountry($country) + { + $this->country = $country; + + return $this; + } + + /** + * @return State + */ + public function getState() + { + return $this->state; + } + + /** + * @param State $state + * + * @return DeliveryModuleOptionEvent + */ + public function setState($state) + { + $this->state = $state; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Events/ModelExtendDataEvent.php b/domokits/local/modules/OpenApi/Events/ModelExtendDataEvent.php new file mode 100644 index 0000000..8e2fae8 --- /dev/null +++ b/domokits/local/modules/OpenApi/Events/ModelExtendDataEvent.php @@ -0,0 +1,101 @@ +data; + } + + /** + * @param mixed $data + * + * @return ModelExtendDataEvent + */ + public function setData($data) + { + $this->data = $data; + + return $this; + } + + /** + * @return mixed + */ + public function getLocale() + { + return $this->locale; + } + + /** + * @param mixed $locale + * + * @return ModelExtendDataEvent + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * @return BaseApiModel + */ + public function getModel() + { + return $this->model; + } + + /** + * @param BaseApiModel $model + * + * @return ModelExtendDataEvent + */ + public function setModel($model) + { + $this->model = $model; + + return $this; + } + + /** + * @return mixed + */ + public function getExtendData() + { + return $this->extendedData; + } + + /** + * @param $key + * @param $value + * + * @return $this + */ + public function setExtendDataKeyValue($key, $value) + { + $this->extendedData[$key] = $value; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Events/ModelValidationEvent.php b/domokits/local/modules/OpenApi/Events/ModelValidationEvent.php new file mode 100644 index 0000000..a9a70ce --- /dev/null +++ b/domokits/local/modules/OpenApi/Events/ModelValidationEvent.php @@ -0,0 +1,123 @@ +model = $model; + $this->modelFactory = $modelFactory; + $this->propertyPatchPrefix = $propertyPatchPrefix; + $this->violations = $violations; + $this->groups = $groups; + } + + + /** + * @return BaseApiModel + */ + public function getModel(): BaseApiModel + { + return $this->model; + } + + /** + * @param BaseApiModel $model + */ + public function setModel(BaseApiModel $model): void + { + $this->model = $model; + } + + /** + * @return array + */ + public function getViolations() + { + return $this->violations; + } + + /** + * @param array $violations + */ + public function setViolations($violations): void + { + $this->violations = $violations; + } + + /** + * @return ModelFactory + */ + public function getModelFactory(): ModelFactory + { + return $this->modelFactory; + } + + /** + * @param ModelFactory $modelFactory + */ + public function setModelFactory(ModelFactory $modelFactory): void + { + $this->modelFactory = $modelFactory; + } + + /** + * @return string + */ + public function getPropertyPatchPrefix(): string + { + return $this->propertyPatchPrefix; + } + + /** + * @param string $propertyPatchPrefix + */ + public function setPropertyPatchPrefix(string $propertyPatchPrefix): void + { + $this->propertyPatchPrefix = $propertyPatchPrefix; + } + + /** + * @return mixed + */ + public function getGroups() + { + return $this->groups; + } + + /** + * @param mixed $groups + */ + public function setGroups($groups): void + { + $this->groups = $groups; + } + +} \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/Events/OpenApiEvents.php b/domokits/local/modules/OpenApi/Events/OpenApiEvents.php new file mode 100644 index 0000000..14941e8 --- /dev/null +++ b/domokits/local/modules/OpenApi/Events/OpenApiEvents.php @@ -0,0 +1,8 @@ +error = $error; + $this->httpCode = $httpCode; + } + + /** + * @return string + */ + public function getHttpCode() + { + return $this->httpCode; + } + + /** + * @param string $httpCode + * + * @return OpenApiException + */ + public function setHttpCode($httpCode) + { + $this->httpCode = $httpCode; + + return $this; + } + + /** + * @return Error + */ + public function getError() + { + return $this->error; + } + + /** + * @param Error $error + * + * @return OpenApiException + */ + public function setError($error) + { + $this->error = $error; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Form/ConfigForm.php b/domokits/local/modules/OpenApi/Form/ConfigForm.php new file mode 100644 index 0000000..d9e9f3e --- /dev/null +++ b/domokits/local/modules/OpenApi/Form/ConfigForm.php @@ -0,0 +1,28 @@ +formBuilder + ->add( + 'enable_config', + CollectionType::class, + [ + 'entry_type' => CheckboxType::class, + 'allow_add' => true, + 'allow_delete' => true, + ] + ) + ; + } + +} \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/Hook/BackHook.php b/domokits/local/modules/OpenApi/Hook/BackHook.php new file mode 100644 index 0000000..06fef12 --- /dev/null +++ b/domokits/local/modules/OpenApi/Hook/BackHook.php @@ -0,0 +1,17 @@ +add($this->render('configuration.html', ['configs' => $configVariables])); + } +} \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/I18n/en_US.php b/domokits/local/modules/OpenApi/I18n/en_US.php new file mode 100755 index 0000000..59f1911 --- /dev/null +++ b/domokits/local/modules/OpenApi/I18n/en_US.php @@ -0,0 +1,5 @@ + 'TEST translation constraints', +]; diff --git a/domokits/local/modules/OpenApi/I18n/fr_FR.php b/domokits/local/modules/OpenApi/I18n/fr_FR.php new file mode 100755 index 0000000..be498d9 --- /dev/null +++ b/domokits/local/modules/OpenApi/I18n/fr_FR.php @@ -0,0 +1,11 @@ + 'Le contenu demandé n\'éxiste pas.', + 'No coupons were found for this coupon code.' => 'Aucun code promo correspondant', + 'No customer found for this email.' => 'Aucun compte client correspondant', + 'This value should not be blank' => 'Cette valeur ne peut pas être vide', + 'You should sign in or register to use this coupon.' => 'Vous devez vous connecter ou vous inscrire pour utiliser ce coupon', + 'This zip code should respect the following format : %format.' => 'Ce code postal doit respecter le format suivant : %format.', + 'Your username/password pair, does not correspond to any account' => 'Votre couple identifiant/mot de passe, ne correspond à aucun compte' +]; \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/Model/Api/Address.php b/domokits/local/modules/OpenApi/Model/Api/Address.php new file mode 100644 index 0000000..3b4a1da --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Address.php @@ -0,0 +1,638 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OpenApi\Model\Api; + +use OpenApi\Annotations as OA; +use OpenApi\Constraint; +use Thelia\Model\Address as TheliaAddress; +use Thelia\Model\CountryQuery; +use Thelia\Model\StateQuery; + +/** + * @OA\Schema( + * schema="Address", + * title="Address", + * description="Address model" + * ) + */ +class Address extends BaseApiModel +{ + public static $serviceAliases = ['PickupAddress']; + + /** + * @var int + * @OA\Property( + * type="integer" + * ) + * @Constraint\NotBlank(groups={"read", "update"}) + */ + protected $id; + + /** + * @var bool + * @OA\Property( + * type="boolean" + * ) + * @Constraint\NotNull(groups={"create", "update"}) + */ + protected $isDefault; + + /** + * @var string + * @OA\Property( + * type="string", + * description="The name for this address", + * ) + * @Constraint\NotBlank(groups={"create","update"}) + */ + protected $label; + + /** + * @var Customer + * @OA\Property( + * ref="#/components/schemas/Customer" + * ), + * @Constraint\NotNull(groups={"create","update"}) + */ + protected $customer; + + /** + * @var CivilityTitle + * @OA\Property( + * ref="#/components/schemas/CivilityTitle" + * ), + * @Constraint\NotNull(groups={"create","update"}) + */ + protected $civilityTitle; + + /** + * @var string + * @OA\Property( + * type="string", + * ), + * @Constraint\NotNull(groups={"create","update"}) + */ + protected $firstName; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotNull(groups={"create","update"}) + */ + protected $lastName; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $cellphone; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $phone; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $company; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"create","update"}) + */ + protected $address1; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $address2; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $address3; + + /** + * @var string + * @OA\Property( + * type="string", + * ), + * @Constraint\NotBlank(groups={"create","update"}) + * @Constraint\Zipcode(groups={"create","update"}) + */ + protected $zipCode; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"create","update"}) + */ + protected $city; + + /** + * @var string + * @OA\Property( + * type="string", + * description="Country ISO 3166-1 alpha-2 code" + * ) + * @Constraint\NotBlank(groups={"create","update"}) + * @Constraint\Length( + * min = 2, + * max = 2 + * ) + */ + protected $countryCode; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $stateCode; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $stateName; + + /** + * @var object + * @OA\Property( + * type="object" + * ) + */ + protected $additionalData; + + /** + * @param TheliaAddress $address + * @param string $locale + * + * @return $this|Address + * + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createFromTheliaModel($address, $locale = 'en_US') + { + parent::createFromTheliaModel($address, $locale); + + $customerTitle = $address->getCustomerTitle() + ->setLocale($locale); + + /** @var CivilityTitle $civ */ + $civ = $this->modelFactory->buildModel('Title', $customerTitle); + + $this + ->setCivilityTitle($civ) + ->setCountryCode($address->getCountry()->getIsoalpha2()) + ; + if (null !== $state = $address->getState()) { + $this + ->setStateCode($state->getIsocode()) + ->setStateName($state->setLocale($locale)->getTitle()); + } + + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return Address + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function getIsDefault() + { + return $this->isDefault; + } + + /** + * @param bool $isDefault + * + * @return Address + */ + public function setIsDefault($isDefault) + { + $this->isDefault = $isDefault; + + return $this; + } + + /** + * @return string + */ + public function getLabel() + { + return $this->label; + } + + /** + * @param string $label + * + * @return Address + */ + public function setLabel($label) + { + $this->label = $label; + + return $this; + } + + /** + * @return Customer + */ + public function getCustomer() + { + return $this->customer; + } + + /** + * @param Customer $customer + * + * @return Address + */ + public function setCustomer($customer) + { + $this->customer = $customer; + + return $this; + } + + /** + * @return CivilityTitle + */ + public function getCivilityTitle() + { + return $this->civilityTitle; + } + + /** + * @param CivilityTitle $civilityTitle + * + * @return Address + */ + public function setCivilityTitle($civilityTitle) + { + $this->civilityTitle = $civilityTitle; + + return $this; + } + + /** + * @return string + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @param string $firstName + * + * @return Address + */ + public function setFirstName($firstName) + { + $this->firstName = $firstName; + + return $this; + } + + /** + * @return string + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @param string $lastName + * + * @return Address + */ + public function setLastName($lastName) + { + $this->lastName = $lastName; + + return $this; + } + + /** + * @return string + */ + public function getCompany() + { + return $this->company; + } + + /** + * @param string $company + * + * @return Address + */ + public function setCompany($company) + { + $this->company = $company; + + return $this; + } + + /** + * @return string + */ + public function getAddress1() + { + return $this->address1; + } + + /** + * @param string $address1 + * + * @return Address + */ + public function setAddress1($address1) + { + $this->address1 = $address1; + + return $this; + } + + /** + * @return string + */ + public function getAddress2() + { + return $this->address2; + } + + /** + * @param string $address2 + * + * @return Address + */ + public function setAddress2($address2) + { + $this->address2 = $address2; + + return $this; + } + + /** + * @return string + */ + public function getAddress3() + { + return $this->address3; + } + + /** + * @param string $address3 + * + * @return Address + */ + public function setAddress3($address3) + { + $this->address3 = $address3; + + return $this; + } + + /** + * @return string + */ + public function getZipCode() + { + return $this->zipCode; + } + + /** + * @param string $zipCode + * + * @return Address + */ + public function setZipCode($zipCode) + { + $this->zipCode = $zipCode; + + return $this; + } + + /** + * @return string + */ + public function getCity() + { + return $this->city; + } + + /** + * @param string $city + * + * @return Address + */ + public function setCity($city) + { + $this->city = $city; + + return $this; + } + + /** + * @return string + */ + public function getCountryCode() + { + return $this->countryCode; + } + + /** + * @param string $countryCode + * + * @return Address + */ + public function setCountryCode($countryCode) + { + $this->countryCode = $countryCode; + + return $this; + } + + /** + * @return object + */ + public function getAdditionalData() + { + return $this->additionalData; + } + + /** + * @param object $additionalData + * + * @return Address + */ + public function setAdditionalData($additionalData) + { + $this->additionalData = $additionalData; + + return $this; + } + + /** Thelia model creation functions */ + + /** + * @return int + */ + public function getTitleId() + { + $civilityTitle = $this->getCivilityTitle(); + + return null !== $civilityTitle ? $civilityTitle->getId() : null; + } + + /** + * @return int + */ + public function getCustomerId() + { + $customer = $this->getCustomer(); + + return null !== $customer ? $customer->getId() : null; + } + + public function getCountryId() + { + $country = CountryQuery::create()->filterByIsoalpha2($this->getCountryCode())->findOne(); + + return null !== $country ? $country->getId() : null; + } + + /* + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /* + * @return string + */ + public function getCellphone() + { + return $this->cellphone; + } + + /** + * @param string $phone + * + * @return Address + */ + public function setPhone($phone) + { + $this->phone = $phone; + + return $this; + } + + /** + * @param string $cellphone + * + * @return Address + */ + public function setCellphone($cellphone) + { + $this->cellphone = $cellphone; + + return $this; + } + + public function getStateCode(): ?string + { + return $this->stateCode; + } + + public function setStateCode(string $stateCode = null) + { + $this->stateCode = $stateCode; + + return $this; + } + + public function getStateId() + { + $state = StateQuery::create()->filterByCountryId($this->getCountryId())->filterByIsocode($this->getStateCode())->findOne(); + + return null !== $state ? $state->getId() : null; + } + + /** + * @return string + */ + public function getStateName(): ?string + { + return $this->stateName; + } + + /** + * @param string $stateName + */ + public function setStateName(string $stateName = null) + { + $this->stateName = $stateName; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Attribute.php b/domokits/local/modules/OpenApi/Model/Api/Attribute.php new file mode 100644 index 0000000..2210a39 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Attribute.php @@ -0,0 +1,103 @@ +id; + } + + /** + * @param int $id + * + * @return Attribute + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * @param array $values + * + * @return Attribute + */ + public function setValues($values) + { + $this->values = $values; + + return $this; + } + + /** + * "setAttributeId" alias to fit Thelia model. + * + * @param int $id + * + * @return Attribute + */ + public function setAttributeId($id) + { + return $this->setId($id); + } + + /** + * "setValues" alias to fit Thelia model. + * + * @param array $values + * + * @return Attribute + */ + public function setAttributeAvs($values) + { + return $this->setValues($values); + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/AttributeValue.php b/domokits/local/modules/OpenApi/Model/Api/AttributeValue.php new file mode 100644 index 0000000..2bd7908 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/AttributeValue.php @@ -0,0 +1,60 @@ +id; + } + + /** + * @param int $id + * + * @return AttributeValue + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * "setAttributeAvId" alias to fit Thelia model. + * + * @param int $id + * + * @return AttributeValue + */ + public function setAttributeAvId($id) + { + return $this->setId($id); + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/BaseApiModel.php b/domokits/local/modules/OpenApi/Model/Api/BaseApiModel.php new file mode 100644 index 0000000..d3ed923 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/BaseApiModel.php @@ -0,0 +1,352 @@ +dispatcher = $dispatcher; + $this->validator = Validation::createValidatorBuilder() + ->enableAnnotationMapping() + ->getValidator(); + + $this->modelFactory = $modelFactory; + $this->request = $requestStack->getCurrentRequest(); + $this->country = $taxEngine->getDeliveryCountry(); + $this->state = $taxEngine->getDeliveryState(); + + if (method_exists($this, 'initI18n')) { + $this->initI18n($modelFactory); + } + } + + public function getCurrentLocale() + { + return $this->request?->getSession()?->getLang(true)->getLocale(); + } + + /** + * @param $groups + * + * @return BaseApiModel + * + * @throws OpenApiException + */ + public function validate($groups, $recursively = true) + { + $violations = $this->getViolations($groups, $recursively); + + if (empty($violations)) { + return $this; + } + + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + ['title' => Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME)] + ); + + $error->setSchemaViolations($violations); + + throw new OpenApiException($error); + } + + public function getViolations($groups, $recursively = true, $propertyPatchPrefix = '') + { + $modelFactory = $this->modelFactory; + $violations = array_reduce( + iterator_to_array($this->validator->validate($this, null, $groups)), + function ($carry, $violation) use ($modelFactory, $propertyPatchPrefix) { + $carry[$propertyPatchPrefix.$violation->getPropertyPath()] = $modelFactory->buildModel('SchemaViolation', ['message' => $violation->getMessage()]); + return $carry; + }, + [] + ); + + if ($recursively === true) { + foreach (get_object_vars($this) as $key => $value) { + if ($value instanceof self) { + $violations = array_merge($violations, $value->getViolations('read', true, $propertyPatchPrefix.$key.'.')); + } + } + } + + $event = new ModelValidationEvent($this, $modelFactory, $groups, $propertyPatchPrefix); + $this->dispatcher->dispatch($event, ModelValidationEvent::MODEL_VALIDATION_EVENT_PREFIX.$this->snakeCaseName()); + + return array_merge($violations, $event->getViolations()); + } + + public function jsonSerialize() + { + $normalizer = new ModelApiNormalizer(); + $serializer = new Serializer([$normalizer]); + + return $serializer->normalize($this, null); + } + + public function createOrUpdateFromData($data, $locale = null): void + { + if (null === $locale) { + $locale = $this->getCurrentLocale(); + } + + if (\is_object($data)) { + $this->createFromTheliaModel($data, $locale); + } + + if (\is_string($data)) { + $data = json_decode($data, true); + } + + if (\is_array($data) || $data instanceof \Traversable) { + foreach ($data as $key => $value) { + $setMethodName = 'set'.ucfirst($key); + $getMethodName = 'get'.ucfirst($key); + if (method_exists($this, $setMethodName)) { + if (\is_array($value)) { + if (method_exists($this, $getMethodName) && $this->$getMethodName() instanceof self) { + $this->$setMethodName($this->$getMethodName()->updateFromData($value)); + continue; + } + $openApiModel = $this->modelFactory->buildModel(ucfirst($key), $value); + $value = null !== $openApiModel ? $openApiModel : $value; + } + $this->$setMethodName($value); + } + } + } + + $modelExtendEvent = (new ModelExtendDataEvent()) + ->setData($data) + ->setLocale($locale) + ->setModel($this); + + $this->dispatcher->dispatch( + $modelExtendEvent, + ModelExtendDataEvent::ADD_EXTEND_DATA_PREFIX.$this->snakeCaseName() + ); + + $this->setExtendData($modelExtendEvent->getExtendData()); + } + + /** + * Override to return the propel model associated with the OpenApi model instead of null. + * + * @return mixed + */ + protected function getTheliaModel($propelModelName = null) + { + if (null === $propelModelName) { + $propelModelName = "Thelia\Model\\".basename(str_replace('\\', '/', static::class)); + } + + if (!class_exists($propelModelName)) { + return null; + } + + if (method_exists($this, 'getId') && null !== $id = $this->getId()) { + $theliaModelQueryName = $propelModelName.'Query'; + + return $theliaModelQueryName::create()->filterById($id)->findOne(); + } + + /** @var ActiveRecordInterface $newTheliaModel */ + $newTheliaModel = new $propelModelName(); + $newTheliaModel->setNew(true); + + return $newTheliaModel; + } + + public function toTheliaModel($locale = null) + { + if (null === $theliaModel = $this->getTheliaModel()) { + throw new \Exception(Translator::getInstance()->trans('Propel model not found automatically for class %className%. Please override the getTheliaModel method to use the toTheliaModel method.', ['%className%' => basename(static::class)], OpenApi::DOMAIN_NAME)); + } + + // If model need locale, set it + if (method_exists($theliaModel, 'setLocale')) { + $theliaModel->setLocale($locale !== null ? $locale : $this->getCurrentLocale()); + } + + // Look all method of Open API model + foreach (get_class_methods($this) as $methodName) { + $getter = $methodName; + $setter = null; + + // If it's not a getter skip it + if (0 === strncasecmp('get', $methodName, 3)) { + // Build thelia setter name + $setter = 'set'.substr($getter, 3); + } + + // For boolean method like "isVisible" + if ($setter === null && 0 === strncasecmp('is', $methodName, 2)) { + // Build thelia setter name + $setter = 'set'.substr($getter, 2); + } + + // Check if setter exist in Thelia model + if (null === $setter || !method_exists($theliaModel, $setter)) { + continue; + } + + $value = $this->$getter(); + + // If Values are the same skip this property + if (method_exists($theliaModel, $getter) && $theliaModel->$getter() === $value) { + continue; + } + + // if the property is another Api model + if ($value instanceof self) { + // If it doesn't have a correspondant thelia model skip it + if (null === $value->getTheliaModel()) { + continue; + } + + // Else try to set the model id + $setModelIdMethod = $setter.'Id'; + if (!method_exists($theliaModel, $setModelIdMethod)) { + continue; + } + $setter = $setModelIdMethod; + $value = $value->getId(); + } + + // Todo transform array to collection + if (is_array($value)) { + continue; + } + + $theliaModel->$setter($value); + } + + return $theliaModel; + } + + public function createFromTheliaModel($theliaModel, $locale = null) + { + if (method_exists($theliaModel, 'setLocale')) { + $theliaModel->setLocale($locale !== null ? $locale : $this->getCurrentLocale()); + } + + foreach (get_class_methods($this) as $modelMethod) { + if (0 === strncasecmp('set', $modelMethod, 3)) { + $property = ucfirst(substr($modelMethod, 3)); + $lowercaseProperty = ucfirst(strtolower($property)); + + // List all possible getters for this property in propel + $propelPossibleMethods = [ // EXAMPLE : + 'get'.$property, // getProductSaleElements + 'get'.$property.'s', // getProductSaleElementss + 'get'.$lowercaseProperty, // getProductsaleelements + 'get'.$lowercaseProperty.'s', // getProductsaleelementss + 'get'.$property.'Model', // getProductSaleElementsModel + 'get'.$lowercaseProperty.'Model', // getProductsaleelementsModel + 'get'.substr(\get_class($theliaModel), strrpos(\get_class($theliaModel), '\\') + 1).$property, // getCartProductSaleElements + 'get'.substr(\get_class($theliaModel), strrpos(\get_class($theliaModel), '\\') + 1).$lowercaseProperty, // getCartProductsaleelements + ]; + + $availableMethods = array_filter(array_intersect($propelPossibleMethods, get_class_methods($theliaModel))); + + if (empty($availableMethods)) { + continue; + } + + $theliaValue = null; + while (!empty($availableMethods) && ($theliaValue === null || empty($theliaValue))) { + $theliaMethod = array_pop($availableMethods); + + $theliaValue = $theliaModel->$theliaMethod(); + + if ($theliaValue instanceof Collection) { + $theliaValue = array_filter(array_map(fn ($value) => $this->modelFactory->buildModel($property, $value), iterator_to_array($theliaValue))); + continue; + } + + if (\is_object($theliaValue) && $this->modelFactory->modelExists($property)) { + $theliaValue = $this->modelFactory->buildModel($property, $theliaValue); + } + } + + $this->$modelMethod($theliaValue); + } + } + + return $this; + } + + public function setExtendData($extendedData) + { + $this->extendedData = $extendedData; + + return $this; + } + + public function extendedDataValue() + { + return $this->extendedData; + } + + protected function snakeCaseName() + { + $name = basename(str_replace('\\', '/', static::class)); + + preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $name, $matches); + $ret = $matches[0]; + foreach ($ret as &$match) { + $match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match); + } + + return implode('_', $ret); + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Brand.php b/domokits/local/modules/OpenApi/Model/Api/Brand.php new file mode 100644 index 0000000..49bebac --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Brand.php @@ -0,0 +1,74 @@ +id; + } + + /** + * @param int $id + * + * @return Brand + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return Brand + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Cart.php b/domokits/local/modules/OpenApi/Model/Api/Cart.php new file mode 100644 index 0000000..8519ed3 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Cart.php @@ -0,0 +1,467 @@ +container = $container; + } + + public function createFromTheliaModel($theliaModel, $locale = null): void + { + parent::createFromTheliaModel($theliaModel, $locale); + $postageInfo = $this->getEstimatedPostageForCountry($theliaModel, $this->country, $this->state); + $estimatedPostage = $postageInfo['postage']; + $postageTax = $postageInfo['tax']; + + $consumedCoupons = $this->request->getSession()->getConsumedCoupons(); + $coupons = $this->createOpenApiCouponsFromCouponsCodes($consumedCoupons); + + $modelFactory = $this->modelFactory; + $deliveryCountry = $this->country; + $cartItems = array_map( + function ($theliaCartItem) use ($modelFactory, $deliveryCountry) { + /** @var CartItem $cartItem */ + $cartItem = $modelFactory->buildModel('CartItem', $theliaCartItem); + $cartItem->fillFromTheliaCartItemAndCountry($theliaCartItem, $deliveryCountry); + + return $cartItem; + }, + iterator_to_array($theliaModel->getCartItems()) + ); + + $this + ->setDeliveryTax($postageTax) + ->setTaxes($theliaModel->getTotalVAT($deliveryCountry, null, false)) + ->setDelivery($estimatedPostage) + ->setCoupons($coupons) + ->setTotal($theliaModel->getTaxedAmount($deliveryCountry, false, null)) + ->setCurrency($theliaModel->getCurrency()->getSymbol()) + ->setItems($cartItems) + ->setVirtual($theliaModel->isVirtual()); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return Cart + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return float + */ + public function getTaxes() + { + return $this->taxes; + } + + /** + * @param float $taxes + * + * @return Cart + */ + public function setTaxes($taxes) + { + $this->taxes = $taxes; + + return $this; + } + + /** + * @return float + */ + public function getDelivery() + { + return $this->delivery; + } + + /** + * @param float $delivery + * + * @return Cart + */ + public function setDelivery($delivery) + { + $this->delivery = $delivery; + + return $this; + } + + /** + * @return array + */ + public function getCoupons() + { + return $this->coupons; + } + + /** + * @param array $coupons + * + * @return Cart + */ + public function setCoupons($coupons) + { + $this->coupons = $coupons; + + return $this; + } + + /** + * @return float + */ + public function getDeliveryTax() + { + return $this->deliveryTax; + } + + /** + * @param float $deliveryTax + */ + public function setDeliveryTax($deliveryTax) + { + $this->deliveryTax = $deliveryTax; + + return $this; + } + + + + /** + * @return float + */ + public function getDiscount() + { + return $this->discount; + } + + /** + * @param float $discount + * + * @return Cart + */ + public function setDiscount($discount) + { + $this->discount = (float)$discount; + return $this; + } + + /** + * @return boolean + */ + public function getVirtual() + { + return $this->virtual; + } + + /** + * @param boolean $virtual + * @return Cart + */ + public function setVirtual($virtual) + { + $this->virtual = $virtual; + return $this; + } + + /** + * @return float + */ + public function getTotal() + { + return $this->total; + } + + /** + * @param float $total + * + * @return Cart + */ + public function setTotal($total) + { + $this->total = $total; + + return $this; + } + + /** + * @return string + */ + public function getCurrency() + { + return $this->currency; + } + + /** + * @param string $currency + * + * @return Cart + */ + public function setCurrency($currency) + { + $this->currency = $currency; + + return $this; + } + + /** + * @return array + */ + public function getItems() + { + return $this->items; + } + + /** + * @param array $items + * + * @return Cart + */ + public function setItems($items) + { + $this->items = $items; + + return $this; + } + + /** + * Creates an array of OpenApi coupons from an array of coupons codes, then returns it. + * + * @param $couponsCodes + * + * @return array + */ + protected function createOpenApiCouponsFromCouponsCodes($couponsCodes) + { + $coupons = CouponQuery::create()->filterByCode($couponsCodes)->find(); + + $factory = $this->modelFactory; + + return array_map( + fn ($coupon) => $factory->buildModel('Coupon', $coupon), + iterator_to_array($coupons) + ); + } + + /** + * Return the minimum expected postage for a cart in a given country. + * + * @return array + * + * @throws \Propel\Runtime\Exception\PropelException + */ + protected function getEstimatedPostageForCountry(\Thelia\Model\Cart $cart, Country $country, State $state = null) + { + $orderSession = $this->request->getSession()->getOrder(); + $deliveryModules = []; + + if ($deliveryModule = ModuleQuery::create()->findPk($orderSession->getDeliveryModuleId())) { + $deliveryModules[] = $deliveryModule; + } + + if (empty($deliveryModules)) { + $deliveryModules = ModuleQuery::create() + ->filterByActivate(1) + ->filterByType(BaseModule::DELIVERY_MODULE_TYPE, Criteria::EQUAL) + ->find(); + } + + $virtual = $cart->isVirtual(); + $postage = null; + $postageTax = null; + + /** @var \Thelia\Model\Module $deliveryModule */ + foreach ($deliveryModules as $deliveryModule) { + $areaDeliveryModule = AreaDeliveryModuleQuery::create() + ->findByCountryAndModule($country, $deliveryModule, $state); + + if (null === $areaDeliveryModule && false === $virtual) { + continue; + } + + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + + if (true === $virtual && false === $moduleInstance->handleVirtualProductDelivery()) { + continue; + } + + try { + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, null, $country, $state); + $this->dispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + if ($deliveryPostageEvent->isValidModule()) { + $modulePostage = $deliveryPostageEvent->getPostage(); + + if (null === $postage || $postage > $modulePostage->getAmount()) { + $postage = $modulePostage->getAmount() - $modulePostage->getAmountTax(); + $postageTax = $modulePostage->getAmountTax(); + } + } + } catch (DeliveryException $ex) { + // Module is not available + } + } + + return [ + 'postage' => $postage, + 'tax' => $postageTax + ]; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/CartItem.php b/domokits/local/modules/OpenApi/Model/Api/CartItem.php new file mode 100644 index 0000000..69e2d80 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/CartItem.php @@ -0,0 +1,309 @@ +id = $cartItem->getId(); + /** @var Product $product */ + $product = $this->modelFactory->buildModel('Product', $cartItem->getProduct()); + $this->product = $product; + + /** @var ProductSaleElement $productSaleElements */ + $productSaleElements = $this->modelFactory->buildModel('ProductSaleElement'); + $productSaleElements->fillFromTheliaPseAndCountry($cartItem->getProductSaleElements(), $country); + $this->productSaleElement = $productSaleElements; + + $this->isPromo = (bool) $cartItem->getPromo(); + $this->price = $this->modelFactory->buildModel( + 'Price', + [ + 'taxed' => $cartItem->getTaxedPrice($country), + 'untaxed' => $cartItem->getPrice(), + ] + ); + $this->promoPrice = $this->modelFactory->buildModel( + 'Price', + [ + 'taxed' => $cartItem->getTaxedPromoPrice($country), + 'untaxed' => $cartItem->getPromoPrice(), + ] + ); + $this->quantity = $cartItem->getQuantity(); + + /** If there are PSE specific images, we use them. Otherwise, we just use the product images */ + $modelFactory = $this->modelFactory; + + try { + $images = array_map( + fn ($productSaleElementsImage) => $modelFactory->buildModel('Image', $productSaleElementsImage->getProductImage()), + iterator_to_array($cartItem->getProductSaleElements()->getProductSaleElementsProductImages()) + ); + } catch (\Exception $exception) { + $images = []; + } + + $this->images = !empty($images) ? $images : $this->product->getImages(); + + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return CartItem + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isPromo() + { + return $this->isPromo; + } + + /** + * @param bool $isPromo + * + * @return CartItem + */ + public function setIsPromo($isPromo) + { + $this->isPromo = $isPromo; + + return $this; + } + + /** + * @return Product + */ + public function getProduct() + { + return $this->product; + } + + /** + * @param Product $product + * + * @return CartItem + */ + public function setProduct($product) + { + $this->product = $product; + + return $this; + } + + /** + * @return ProductSaleElement + */ + public function getProductSaleElement() + { + return $this->productSaleElement; + } + + /** + * @param ProductSaleElement $productSaleElement + * + * @return CartItem + */ + public function setProductSaleElement($productSaleElement) + { + $this->productSaleElement = $productSaleElement; + + return $this; + } + + /** + * @return array + */ + public function getImages() + { + return $this->images; + } + + /** + * @param array $images + * + * @return CartItem + */ + public function setImages($images) + { + $this->images = $images; + + return $this; + } + + /** + * @return Price + */ + public function getPrice() + { + return $this->price; + } + + /** + * @param Price $price + * + * @return CartItem + */ + public function setPrice($price) + { + $this->price = $price; + + return $this; + } + + /** + * @return Price + */ + public function getPromoPrice() + { + return $this->promoPrice; + } + + /** + * @param Price $promoPrice + * + * @return CartItem + */ + public function setPromoPrice($promoPrice) + { + $this->promoPrice = $promoPrice; + + return $this; + } + + /** + * @return int + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param int $quantity + * + * @return CartItem + */ + public function setQuantity($quantity) + { + $this->quantity = $quantity; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Category.php b/domokits/local/modules/OpenApi/Model/Api/Category.php new file mode 100644 index 0000000..0c3b0e7 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Category.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OpenApi\Model\Api; + +use OpenApi\Annotations as OA; +use OpenApi\Constraint; +use OpenApi\Model\Api\ModelTrait\translatable; + +/** + * Class Category. + * + * @OA\Schema( + * description="A Category" + * ) + */ +class Category extends BaseApiModel +{ + use translatable; + + /** + * @var int + * + * @OA\Property( + * type="integer", + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $id; + + /** + * @var bool + * + * @OA\Property( + * type="boolean", + * ) + */ + protected $visible; + + /** + * @var string + * + * @OA\Property( + * type="string", + * ) + */ + protected $url; + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return Category + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return Category + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + * + * @return Category + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Checkout.php b/domokits/local/modules/OpenApi/Model/Api/Checkout.php new file mode 100644 index 0000000..7c0e0e6 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Checkout.php @@ -0,0 +1,309 @@ +setDeliveryAddressId($order->getChoosenDeliveryAddress()); + $this->setBillingAddressId($order->getChoosenInvoiceAddress()); + + $this->setDeliveryModuleId($order->getDeliveryModuleId()) + ->setPaymentModuleId($order->getPaymentModuleId()); + + return $this; + } + + /** + * @OA\Property( + * property="isComplete", + * description="Tell if a checkout has defined a Module and an Address for both delivery and billing", + * type="boolean" + * ) + * + * @return bool + */ + public function getIsComplete() + { + if (null === $this->getDeliveryModuleId()) { + return false; + } + + if (null === $this->getDeliveryAddressId()) { + return false; + } + + if (null === $this->getBillingAddressId()) { + return false; + } + + if (null === $this->getPaymentModuleId()) { + return false; + } + + return true; + } + + /** + * @throws \Exception + */ + public function checkIsValid(): void + { + if (null === $this->getDeliveryModuleId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'You must choose a delivery module', + [], + OpenApi::DOMAIN_NAME + ) + ); + } + + if (null === $this->getPaymentModuleId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'You must choose a payment module', + [], + OpenApi::DOMAIN_NAME + ) + ); + } + + if (null === $this->getDeliveryAddressId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'You must choose a delivery address', + [], + OpenApi::DOMAIN_NAME + ) + ); + } + + if (null === $this->getBillingAddressId()) { + throw new \Exception( + Translator::getInstance()->trans( + 'You must choose a billing address', + [], + OpenApi::DOMAIN_NAME + ) + ); + } + + if (false === $this->isAcceptedTermsAndConditions()) { + throw new \Exception( + Translator::getInstance()->trans( + 'You must accept the terms and conditions', + [], + OpenApi::DOMAIN_NAME + ) + ); + } + } + + /** + * @return int + */ + public function getDeliveryModuleId() + { + return $this->deliveryModuleId; + } + + /** + * @param int $deliveryModuleId + * + * @return Checkout + */ + public function setDeliveryModuleId($deliveryModuleId) + { + $this->deliveryModuleId = $deliveryModuleId; + + return $this; + } + + /** + * @return int + */ + public function getPaymentModuleId() + { + return $this->paymentModuleId; + } + + /** + * @param int $paymentModuleId + * + * @return Checkout + */ + public function setPaymentModuleId($paymentModuleId) + { + $this->paymentModuleId = $paymentModuleId; + + return $this; + } + + /** + * @return int + */ + public function getBillingAddressId() + { + return $this->billingAddressId; + } + + /** + * @param int $billingAddressId + * + * @return Checkout + */ + public function setBillingAddressId($billingAddressId) + { + $this->billingAddressId = $billingAddressId; + + return $this; + } + + /** + * @return int + */ + public function getDeliveryAddressId() + { + return $this->deliveryAddressId; + } + + /** + * @param int $deliveryAddressId + * + * @return Checkout + */ + public function setDeliveryAddressId($deliveryAddressId) + { + $this->deliveryAddressId = $deliveryAddressId; + + return $this; + } + + /** + * @return Address + */ + public function getPickupAddress() + { + return $this->pickupAddress; + } + + /** + * @param Address $pickupAddress + * + * @return Checkout + */ + public function setPickupAddress($pickupAddress) + { + $this->pickupAddress = $pickupAddress; + + return $this; + } + + public function isAcceptedTermsAndConditions(): bool + { + return $this->acceptedTermsAndConditions; + } + + public function setAcceptedTermsAndConditions(bool $acceptedTermsAndConditions): self + { + $this->acceptedTermsAndConditions = $acceptedTermsAndConditions; + + return $this; + } + + /** + * @return string + */ + public function getDeliveryModuleOptionCode() + { + return $this->deliveryModuleOptionCode; + } + + /** + * @param $deliveryModuleOptionCode + * @return Checkout + */ + public function setDeliveryModuleOptionCode($deliveryModuleOptionCode) + { + $this->deliveryModuleOptionCode = $deliveryModuleOptionCode; + return $this; + } + + +} diff --git a/domokits/local/modules/OpenApi/Model/Api/CivilityTitle.php b/domokits/local/modules/OpenApi/Model/Api/CivilityTitle.php new file mode 100644 index 0000000..d77ed64 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/CivilityTitle.php @@ -0,0 +1,112 @@ +id; + } + + /** + * @param int $id + * + * @return CivilityTitle + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getShort() + { + return $this->short; + } + + /** + * @param string $short + * + * @return CivilityTitle + */ + public function setShort($short) + { + $this->short = $short; + + return $this; + } + + /** + * @return string + */ + public function getLong() + { + return $this->long; + } + + /** + * @param string $long + * + * @return CivilityTitle + */ + public function setLong($long) + { + $this->long = $long; + + return $this; + } + + /** + * @return CustomerTitle + */ + protected function getCustomerTitle() + { + return new CustomerTitle(); + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Content.php b/domokits/local/modules/OpenApi/Model/Api/Content.php new file mode 100644 index 0000000..e5e8b91 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Content.php @@ -0,0 +1,75 @@ +id; + } + + /** + * @param int $id + * + * @return Content + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return Content + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Coupon.php b/domokits/local/modules/OpenApi/Model/Api/Coupon.php new file mode 100644 index 0000000..4837106 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Coupon.php @@ -0,0 +1,104 @@ +id; + } + + /** + * @param int $id + * + * @return Coupon + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + * + * @return Coupon + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return float + */ + public function getAmount() + { + return $this->amount; + } + + /** + * @param float $amount + * + * @return Coupon + */ + public function setAmount($amount) + { + $this->amount = $amount; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Customer.php b/domokits/local/modules/OpenApi/Model/Api/Customer.php new file mode 100644 index 0000000..cf08b33 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Customer.php @@ -0,0 +1,381 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace OpenApi\Model\Api; + +use OpenApi\Annotations as OA; +use OpenApi\Constraint; + +/** + * @OA\Schema( + * schema="Customer", + * title="Customer", + * description="Customer model" + * ) + */ +class Customer extends BaseApiModel +{ + /** + * @var int + * @OA\Property( + * type="integer", + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $id; + + /** + * @var CivilityTitle + * @OA\Property( + * type="object", + * ref="#/components/schemas/CivilityTitle", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $civilityTitle; + + /** + * @var Lang + * @OA\Property( + * type="object", + * ref="#/components/schemas/Lang", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $lang; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"update"}) + */ + protected $reference; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $firstName; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $lastName; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $email; + + /** + * @var bool + * @OA\Property( + * type="boolean", + * ) + */ + protected $rememberMe; + + /** + * @var float + * @OA\Property( + * type="number", + * format="float", + * ) + * @Constraint\NotBlank(groups={"create", "update"}) + */ + protected $discount; + + /** + * @var bool + * @OA\Property( + * type="boolean", + * ) + */ + protected $reseller; + + /** + * @var int + * @OA\Property( + * type="integer", + * ) + */ + protected $defaultAddressId; + + public function createFromTheliaModel($theliaModel, $locale = null): void + { + parent::createFromTheliaModel($theliaModel, $locale); + $this->setDefaultAddressId($theliaModel->getDefaultAddress()->getId()); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return Customer + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return CivilityTitle + */ + public function getCivilityTitle() + { + return $this->civilityTitle; + } + + /** + * @param CivilityTitle $civilityTitle + * + * @return Customer + */ + public function setCivilityTitle($civilityTitle) + { + $this->civilityTitle = $civilityTitle; + + return $this; + } + + /** + * @return Lang + */ + public function getLang() + { + return $this->lang; + } + + /** + * @param Lang $lang + * + * @return Customer + */ + public function setLang($lang) + { + $this->lang = $lang; + + return $this; + } + + /** + * @return string + */ + public function getReference() + { + return $this->reference; + } + + /** + * @param string $reference + * + * @return Customer + */ + public function setReference($reference) + { + $this->reference = $reference; + + return $this; + } + + /** + * @return string + */ + public function getFirstName() + { + return $this->firstName; + } + + /** + * @param string $firstName + * + * @return Customer + */ + public function setFirstname($firstName) + { + $this->firstName = $firstName; + + return $this; + } + + /** + * @return string + */ + public function getLastName() + { + return $this->lastName; + } + + /** + * @param string $lastName + * + * @return Customer + */ + public function setLastname($lastName) + { + $this->lastName = $lastName; + + return $this; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @param string $email + * + * @return Customer + */ + public function setEmail($email) + { + $this->email = $email; + + return $this; + } + + /** + * @return bool + */ + public function isRememberMe() + { + return $this->rememberMe; + } + + /** + * @param bool $rememberMe + * + * @return Customer + */ + public function setRememberMe($rememberMe) + { + $this->rememberMe = $rememberMe; + + return $this; + } + + /** + * @return float + */ + public function getDiscount() + { + return $this->discount; + } + + /** + * @param float $discount + * + * @return Customer + */ + public function setDiscount($discount) + { + $this->discount = $discount; + + return $this; + } + + /** + * @return bool + */ + public function isReseller() + { + return $this->reseller; + } + + /** + * @param bool $reseller + * + * @return Customer + */ + public function setReseller($reseller) + { + $this->reseller = $reseller; + + return $this; + } + + /** + * @return int + */ + public function getTitleId() + { + return $this->getCivilityTitle()->getId(); + } + + /** + * @return int + */ + public function getLangId() + { + return $this->getLang()->getId(); + } + + public function setTitle(CivilityTitle $civilityTitle) + { + $this->civilityTitle = $civilityTitle; + + return $this; + } + + public function setRef($ref) + { + $this->reference = $ref; + + return $this; + } + + /** + * @return int + */ + public function getDefaultAddressId() + { + return $this->defaultAddressId; + } + + /** + * @param int $id + * + * @return Customer + */ + public function setDefaultAddressId($id) + { + $this->defaultAddressId = $id; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/DeliveryModule.php b/domokits/local/modules/OpenApi/Model/Api/DeliveryModule.php new file mode 100644 index 0000000..81a7e2d --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/DeliveryModule.php @@ -0,0 +1,214 @@ + At customer adress; pickup -> At carrier pickup point; localPickup -> At store pickup point", + * enum={"delivery", "pickup", "localPickup"} + * ) + */ + protected $deliveryMode; + + /** + * @var int + * @OA\Property( + * type="integer" + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $id; + + /** + * @var bool + * @OA\Property( + * type="boolean" + * ) + */ + protected $valid; + + /** + * @var string + * @OA\Property( + * type="string" + * ) + */ + protected $code; + + /** + * @var array + * @OA\Property( + * type="array", + * @OA\Items( + * ref="#/components/schemas/DeliveryModuleOption" + * ) + * ) + */ + protected $options; + + /** + * @var array + * @OA\Property( + * type="array", + * @OA\Items( + * ref="#/components/schemas/File" + * ) + * ) + */ + protected $images = []; + + /** + * @return string + */ + public function getDeliveryMode() + { + return $this->deliveryMode; + } + + /** + * @param string $deliveryMode + * + * @return DeliveryModule + * + * @throws \Exception + */ + public function setDeliveryMode($deliveryMode) + { + if (!\in_array($deliveryMode, ['delivery', 'pickup', 'localPickup'])) { + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + [ + 'title' => Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME), + 'description' => Translator::getInstance()->trans("Delivery mode can only be 'delivery', 'pickup' or 'localPickup'", [], OpenApi::DOMAIN_NAME), + ] + ); + + throw new OpenApiException($error); + } + + $this->deliveryMode = $deliveryMode; + + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return DeliveryModule + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isValid() + { + return $this->valid; + } + + /** + * @param bool $valid + * + * @return DeliveryModule + */ + public function setValid($valid) + { + $this->valid = $valid; + + return $this; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + * + * @return DeliveryModule + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @param array $options + * + * @return DeliveryModule + */ + public function setOptions($options) + { + $this->options = $options; + + return $this; + } + + /** + * @return array + */ + public function getImages() + { + return $this->images; + } + + /** + * @param array $images + * + * @return DeliveryModule + */ + public function setImages($images) + { + $this->images = $images; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/DeliveryModuleOption.php b/domokits/local/modules/OpenApi/Model/Api/DeliveryModuleOption.php new file mode 100644 index 0000000..9ca2ee3 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/DeliveryModuleOption.php @@ -0,0 +1,275 @@ +code; + } + + /** + * @param string $code + * + * @return DeliveryModuleOption + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return bool + */ + public function isValid() + { + return $this->valid; + } + + /** + * @param bool $valid + * + * @return DeliveryModuleOption + */ + public function setValid($valid) + { + $this->valid = $valid; + + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + * + * @return DeliveryModuleOption + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getImage() + { + return $this->image; + } + + /** + * @param string $image + * + * @return DeliveryModuleOption + */ + public function setImage($image) + { + $this->image = $image; + + return $this; + } + + /** + * @return string + */ + public function getMinimumDeliveryDate() + { + return $this->minimumDeliveryDate; + } + + /** + * @param string $minimumDeliveryDate + * + * @return DeliveryModuleOption + */ + public function setMinimumDeliveryDate($minimumDeliveryDate) + { + $this->minimumDeliveryDate = $minimumDeliveryDate; + + return $this; + } + + /** + * @return string + */ + public function getMaximumDeliveryDate() + { + return $this->maximumDeliveryDate; + } + + /** + * @param string $maximumDeliveryDate + * + * @return DeliveryModuleOption + */ + public function setMaximumDeliveryDate($maximumDeliveryDate) + { + $this->maximumDeliveryDate = $maximumDeliveryDate; + + return $this; + } + + /** + * @return float + */ + public function getPostage() + { + return $this->postage; + } + + /** + * @param float $postage + * + * @return DeliveryModuleOption + */ + public function setPostage($postage) + { + $this->postage = $postage; + + return $this; + } + + /** + * @return float + */ + public function getPostageTax() + { + return $this->postageTax; + } + + /** + * @param float $postageTax + * + * @return DeliveryModuleOption + */ + public function setPostageTax($postageTax) + { + $this->postageTax = $postageTax; + + return $this; + } + + /** + * @return float + */ + public function getPostageUntaxed() + { + return $this->postageUntaxed; + } + + /** + * @param float $postageUntaxed + * + * @return DeliveryModuleOption + */ + public function setPostageUntaxed($postageUntaxed) + { + $this->postageUntaxed = $postageUntaxed; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Document.php b/domokits/local/modules/OpenApi/Model/Api/Document.php new file mode 100644 index 0000000..01f51c7 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Document.php @@ -0,0 +1,37 @@ +documentService = $documentService; + } + + /** + * @param $theliaModel + * @param null $locale + * @param null $type + * + * @return $this + */ + public function createFromTheliaModel($theliaModel, $locale = null, $type = null) + { + parent::createFromTheliaModel($theliaModel, $locale); + $this->url = $this->documentService->getDocumentUrl($theliaModel, $type); + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Error.php b/domokits/local/modules/OpenApi/Model/Api/Error.php new file mode 100644 index 0000000..13aec31 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Error.php @@ -0,0 +1,115 @@ +title; + } + + /** + * @param string $title + * + * @return Error + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + * + * @return Error + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * @return array + */ + public function getSchemaViolations() + { + return $this->schemaViolations; + } + + /** + * @param array $schemaViolations + * + * @return Error + */ + public function setSchemaViolations($schemaViolations) + { + $this->schemaViolations = $schemaViolations; + + return $this; + } + + /** + * @param $schemaViolation SchemaViolation + * + * @return $this + */ + public function appendViolation($schemaViolation) + { + $this->schemaViolations[] = $schemaViolation; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Feature.php b/domokits/local/modules/OpenApi/Model/Api/Feature.php new file mode 100644 index 0000000..52fd2d8 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Feature.php @@ -0,0 +1,91 @@ +id; + } + + /** + * @param int $id + * + * @return Feature + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return array + */ + public function getValues() + { + return $this->values; + } + + /** + * @param array $values + * + * @return Feature + */ + public function setValues($values) + { + $this->values = $values; + + return $this; + } + + /** + * "setValues" alias to fit Thelia model. + * + * @param array $values + * + * @return Feature + */ + public function setFeatureAvs($values) + { + return $this->setValues($values); + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/FeatureValue.php b/domokits/local/modules/OpenApi/Model/Api/FeatureValue.php new file mode 100644 index 0000000..28f6cf7 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/FeatureValue.php @@ -0,0 +1,48 @@ +id; + } + + /** + * @param int $id + * + * @return FeatureValue + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/File.php b/domokits/local/modules/OpenApi/Model/Api/File.php new file mode 100644 index 0000000..0467ec9 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/File.php @@ -0,0 +1,131 @@ +id; + } + + /** + * @param int $id + * + * @return File + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + * + * @return File + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @param int $position + * + * @return File + */ + public function setPosition($position) + { + $this->position = $position; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return File + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Folder.php b/domokits/local/modules/OpenApi/Model/Api/Folder.php new file mode 100644 index 0000000..3ec32a7 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Folder.php @@ -0,0 +1,76 @@ +id; + } + + /** + * @param int $id + * + * @return Folder + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return Folder + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/I18n.php b/domokits/local/modules/OpenApi/Model/Api/I18n.php new file mode 100644 index 0000000..e9ec962 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/I18n.php @@ -0,0 +1,211 @@ +title; + } + + /** + * @param string $title + * + * @return I18n + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @param string $description + * + * @return I18n + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * @return string + */ + public function getChapo() + { + return $this->chapo; + } + + /** + * @param string $chapo + * + * @return I18n + */ + public function setChapo($chapo) + { + $this->chapo = $chapo; + + return $this; + } + + /** + * @return string + */ + public function getPostscriptum() + { + return $this->postscriptum; + } + + /** + * @param string $postscriptum + * + * @return I18n + */ + public function setPostscriptum($postscriptum) + { + $this->postscriptum = $postscriptum; + + return $this; + } + + /** + * @return string + */ + public function getMetaTitle() + { + return $this->metaTitle; + } + + /** + * @param string $metaTitle + * + * @return I18n + */ + public function setMetaTitle($metaTitle) + { + $this->metaTitle = $metaTitle; + + return $this; + } + + /** + * @return string + */ + public function getMetaDescription() + { + return $this->metaDescription; + } + + /** + * @param string $metaDescription + * + * @return I18n + */ + public function setMetaDescription($metaDescription) + { + $this->metaDescription = $metaDescription; + + return $this; + } + + /** + * @return string + */ + public function getMetaKeywords() + { + return $this->metaKeywords; + } + + /** + * @param string $metaKeywords + * + * @return I18n + */ + public function setMetaKeywords($metaKeywords) + { + $this->metaKeywords = $metaKeywords; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Image.php b/domokits/local/modules/OpenApi/Model/Api/Image.php new file mode 100644 index 0000000..39c69c7 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Image.php @@ -0,0 +1,35 @@ +imageService = $imageService; + } + + /** + * @param $theliaModel + * @param null $locale + * @param null $type + * + * @return $this + */ + public function createFromTheliaModel($theliaModel, $locale = null, $type = null) + { + parent::createFromTheliaModel($theliaModel, $locale); + $this->url = $this->imageService->getImageUrl($theliaModel, $type); + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Lang.php b/domokits/local/modules/OpenApi/Model/Api/Lang.php new file mode 100644 index 0000000..5f3f014 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Lang.php @@ -0,0 +1,411 @@ +id; + } + + /** + * @param int $id + * + * @return Lang + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + * + * @return Lang + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * @param string $code + * + * @return Lang + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * @param string $locale + * + * @return Lang + */ + public function setLocale($locale) + { + $this->locale = $locale; + + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + * + * @return Lang + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * @return string + */ + public function getDateFormat() + { + return $this->dateFormat; + } + + /** + * @param string $dateFormat + * + * @return Lang + */ + public function setDateFormat($dateFormat) + { + $this->dateFormat = $dateFormat; + + return $this; + } + + /** + * @return string + */ + public function getTimeFormat() + { + return $this->timeFormat; + } + + /** + * @param string $timeFormat + * + * @return Lang + */ + public function setTimeFormat($timeFormat) + { + $this->timeFormat = $timeFormat; + + return $this; + } + + /** + * @return string + */ + public function getDatetimeFormat() + { + return $this->datetimeFormat; + } + + /** + * @param string $datetimeFormat + * + * @return Lang + */ + public function setDatetimeFormat($datetimeFormat) + { + $this->datetimeFormat = $datetimeFormat; + + return $this; + } + + /** + * @return string + */ + public function getDecimalSeparator() + { + return $this->decimalSeparator; + } + + /** + * @param string $decimalSeparator + * + * @return Lang + */ + public function setDecimalSeparator($decimalSeparator) + { + $this->decimalSeparator = $decimalSeparator; + + return $this; + } + + /** + * @return string + */ + public function getThousandsSeparator() + { + return $this->thousandsSeparator; + } + + /** + * @param string $thousandsSeparator + * + * @return Lang + */ + public function setThousandsSeparator($thousandsSeparator) + { + $this->thousandsSeparator = $thousandsSeparator; + + return $this; + } + + /** + * @return bool + */ + public function isActive() + { + return $this->active; + } + + /** + * @param bool $active + * + * @return Lang + */ + public function setActive($active) + { + $this->active = $active; + + return $this; + } + + /** + * @return bool + */ + public function isVisible() + { + return $this->visible; + } + + /** + * @param bool $visible + * + * @return Lang + */ + public function setVisible($visible) + { + $this->visible = $visible; + + return $this; + } + + /** + * @return string + */ + public function getDecimals() + { + return $this->decimals; + } + + /** + * @param string $decimals + * + * @return Lang + */ + public function setDecimals($decimals) + { + $this->decimals = $decimals; + + return $this; + } + + /** + * @return bool + */ + public function isByDefault() + { + return $this->byDefault; + } + + /** + * @param bool $byDefault + * + * @return Lang + */ + public function setByDefault($byDefault) + { + $this->byDefault = $byDefault; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/ModelFactory.php b/domokits/local/modules/OpenApi/Model/Api/ModelFactory.php new file mode 100644 index 0000000..ed3036c --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/ModelFactory.php @@ -0,0 +1,61 @@ +container = $container; + } + + public function buildModel($modelName, $data = null, $locale = null) + { + try { + $openApiModels = $this->container->getParameter(OpenApi::OPEN_API_MODELS_PARAMETER_KEY); + + // If no correspondent OpenApi model was found + if (!\is_array($openApiModels) || !\array_key_exists($modelName, $openApiModels)) { + $modelName = rtrim($modelName, 's'); + // Try to remove trailing "s" for plural + if (!\array_key_exists($modelName, $openApiModels)) { + return null; + } + } + + $modelServiceId = $openApiModels[$modelName]; + + /** @var BaseApiModel $model */ + $model = $this->container->get($modelServiceId); + + if (null !== $data) { + $model->createOrUpdateFromData($data, $locale); + } + + return $model; + } catch (\Exception $exception) { + Tlog::getInstance()->addError("Error for building api model \"$modelName\" : ".$exception->getMessage()); + + return null; + } + } + + public function modelExists($modelName) + { + $openApiModels = $this->container->getParameter(OpenApi::OPEN_API_MODELS_PARAMETER_KEY); + + if (!\is_array($openApiModels) || !\array_key_exists($modelName, $openApiModels)) { + return false; + } + + return true; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/ModelTrait/translatable.php b/domokits/local/modules/OpenApi/Model/Api/ModelTrait/translatable.php new file mode 100644 index 0000000..9629a47 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/ModelTrait/translatable.php @@ -0,0 +1,120 @@ +i18n = $modelFactory->buildModel('I18n'); + } + + public function getI18n() + { + return $this->i18n; + } + + /** + * @param string $title + * + * @return translatable + */ + public function setTitle($title) + { + $this->i18n->setTitle($title); + + return $this; + } + + /** + * @param string $description + * + * @return translatable + */ + public function setDescription($description) + { + $this->i18n->setDescription($description); + + return $this; + } + + /** + * @param string $chapo + * + * @return translatable + */ + public function setChapo($chapo) + { + $this->i18n->setChapo($chapo); + + return $this; + } + + /** + * @param string $postscriptum + * + * @return translatable + */ + public function setPostscriptum($postscriptum) + { + $this->i18n->setPostscriptum($postscriptum); + + return $this; + } + + /** + * @param string $metaTitle + * + * @return translatable + */ + public function setMetaTitle($metaTitle) + { + $this->i18n->setMetaTitle($metaTitle); + + return $this; + } + + /** + * @param string $metaDescription + * + * @return translatable + */ + public function setMetaDescription($metaDescription) + { + $this->i18n->setMetaDescription($metaDescription); + + return $this; + } + + /** + * @param string $metaKeywords + * + * @return translatable + */ + public function setMetaKeywords($metaKeywords) + { + $this->i18n->setMetaKeywords($metaKeywords); + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/PaymentModule.php b/domokits/local/modules/OpenApi/Model/Api/PaymentModule.php new file mode 100644 index 0000000..25a8759 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/PaymentModule.php @@ -0,0 +1,192 @@ +id; + } + + /** + * @param int $id + * + * @return PaymentModule + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isValid() + { + return $this->valid; + } + + /** + * @param bool $valid + * + * @return PaymentModule + */ + public function setValid($valid) + { + $this->valid = $valid; + + return $this; + } + + /** + * @return int + */ + public function getCode() + { + return $this->code; + } + + /** + * @param int $code + * + * @return PaymentModule + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * @return float + */ + public function getMinimumAmount() + { + return $this->minimumAmount; + } + + /** + * @param float $minimumAmount + * + * @return PaymentModule + */ + public function setMinimumAmount($minimumAmount) + { + $this->minimumAmount = $minimumAmount; + + return $this; + } + + /** + * @return float + */ + public function getMaximumAmount() + { + return $this->maximumAmount; + } + + /** + * @param float $maximumAmount + * + * @return PaymentModule + */ + public function setMaximumAmount($maximumAmount) + { + $this->maximumAmount = $maximumAmount; + + return $this; + } + + /** + * @return array + */ + public function getImages() + { + return $this->images; + } + + /** + * @param array $images + * + * @return PaymentModule + */ + public function setImages($images) + { + $this->images = $images; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/PickupLocation.php b/domokits/local/modules/OpenApi/Model/Api/PickupLocation.php new file mode 100644 index 0000000..3fdfec9 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/PickupLocation.php @@ -0,0 +1,158 @@ +id; + } + + /** + * @param int $id + * + * @return PickupLocation + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return float + */ + public function getLatitude() + { + return $this->latitude; + } + + /** + * @param float $latitude + * + * @return PickupLocation + */ + public function setLatitude($latitude) + { + $this->latitude = $latitude; + + return $this; + } + + /** + * @return float + */ + public function getLongitude() + { + return $this->longitude; + } + + /** + * @param float $longitude + * + * @return PickupLocation + */ + public function setLongitude($longitude) + { + $this->longitude = $longitude; + + return $this; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @param string $title + * + * @return PickupLocation + */ + public function setTitle($title) + { + $this->title = $title; + + return $this; + } + + /** + * @return Address + */ + public function getAddress() + { + return $this->address; + } + + /** + * @param Address $address + * + * @return PickupLocation + */ + public function setAddress($address) + { + $this->address = $address; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Price.php b/domokits/local/modules/OpenApi/Model/Api/Price.php new file mode 100644 index 0000000..768ba50 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Price.php @@ -0,0 +1,96 @@ +filterByProductSaleElements($pse)->findOne(); + $pse->setVirtualColumn('price_PRICE', (float) $price->getPrice()); + $pse->setVirtualColumn('price_PROMO_PRICE', (float) $price->getPromoPrice()); + $this->untaxed = $isPromo ? $pse->getPromoPrice() : $pse->getPrice(); + $this->taxed = $isPromo ? $pse->getTaxedPromoPrice($country) : $pse->getTaxedPrice($country); + + return $this; + } + + /** + * @return float + */ + public function getUntaxed() + { + return $this->untaxed; + } + + /** + * @param float $untaxed + * + * @return Price + */ + public function setUntaxed($untaxed) + { + $this->untaxed = $untaxed; + + return $this; + } + + /** + * @return float + */ + public function getTaxed() + { + return $this->taxed; + } + + /** + * @param float $taxed + * + * @return Price + */ + public function setTaxed($taxed) + { + $this->taxed = $taxed; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Product.php b/domokits/local/modules/OpenApi/Model/Api/Product.php new file mode 100644 index 0000000..a16acd3 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Product.php @@ -0,0 +1,428 @@ +modelFactory; + $this->features = array_filter( + array_map( + function (FeatureProduct $featureProduct) use ($modelFactory) { + $propelFeature = $featureProduct->getFeature(); + if (null === $propelFeature){ + return false; + } + + if (null !== $featureProduct->getFeatureAv()) { + // Temporary set only product feature av to build good feature av list + $propelFeature->addFeatureAv($featureProduct->getFeatureAv()); + } + $propelFeature->resetPartialFeatureAvs(false); + + $feature = $modelFactory->buildModel('Feature', $propelFeature); + + $propelFeature->clearFeatureAvs(); + + return $feature; + }, + iterator_to_array($theliaModel->getFeatureProducts())), + function($value){ + return $value; + } + ); + + $this->categories = array_map( + function (ProductCategory $productCategory) use ($modelFactory) { + $propelCategory = $productCategory->getCategory(); + + $category = $modelFactory->buildModel('Category', $propelCategory); + + if ($productCategory->isDefaultCategory()) { + $this->defaultCategory = $category; + } + + return $category; + }, + iterator_to_array($theliaModel->getProductCategories()) + ); + + usort($this->images, function ($item1, $item2) { + return $item1->getPosition() <=> $item2->getPosition(); + }); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return Product + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return string + */ + public function getReference() + { + return $this->reference; + } + + /** + * @param string $reference + * + * @return Product + */ + public function setReference($reference) + { + $this->reference = $reference; + + return $this; + } + + /** + * Method alias to match thelia getter name. + * + * @param string $reference + * + * @return Product + */ + public function setRef($reference) + { + $this->setReference($reference); + + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + * + * @return Product + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * @return array + */ + public function getImages() + { + return $this->images; + } + + /** + * @param array $images + * + * @return Product + */ + public function setImages($images) + { + $this->images = $images; + + return $this; + } + + /** + * @return array + */ + public function getProductSaleElements() + { + return $this->productSaleElements; + } + + /** + * @param array $productSaleElements + * + * @return Product + */ + public function setProductSaleElements($productSaleElements) + { + $this->productSaleElements = $productSaleElements; + + return $this; + } + + public function isVirtual(): bool + { + return $this->virtual; + } + + public function setVirtual(bool $virtual): self + { + $this->virtual = $virtual; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + public function setVisible(bool $visible): self + { + $this->visible = $visible; + + return $this; + } + + /** + * @return Brand + */ + public function getBrand(): ?Brand + { + return $this->brand; + } + + /** + * @param Brand $brand + */ + public function setBrand(Brand $brand = null): self + { + $this->brand = $brand; + + return $this; + } + + public function getDefaultCategory(): Category + { + return $this->defaultCategory; + } + + public function setDefaultCategory(Category $defaultCategory): self + { + $this->defaultCategory = $defaultCategory; + + return $this; + } + + public function getCategories(): array + { + return $this->categories; + } + + public function setCategories(array $categories): self + { + $this->categories = $categories; + + return $this; + } + + /** + * Method alias to match thelia getter name. + */ + public function setProductCategories(array $categories = []): self + { + return $this->setCategories($categories); + } + + public function getContents(): array + { + return $this->contents; + } + + public function setContents(array $contents = []): self + { + $this->contents = $contents; + + return $this; + } + + public function getDocuments(): array + { + return $this->documents; + } + + public function setDocuments(array $documents = []): self + { + $this->documents = $documents; + + return $this; + } + + /** + * Method alias to match thelia getter name. + */ + public function setProductDocuments(array $documents = []): self + { + return $this->setDocuments($documents); + } + + public function getFeatures(): array + { + return $this->features; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/ProductSaleElement.php b/domokits/local/modules/OpenApi/Model/Api/ProductSaleElement.php new file mode 100644 index 0000000..a6e1b6a --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/ProductSaleElement.php @@ -0,0 +1,502 @@ +filterByProductSaleElements($theliaModel)->findOne(); + $theliaModel->setVirtualColumn('price_PRICE', (float) $price->getPrice()); + $theliaModel->setVirtualColumn('price_PROMO_PRICE', (float) $price->getPromoPrice()); + + parent::createFromTheliaModel($theliaModel, $locale); + + $modelFactory = $this->modelFactory; + $this->attributes = array_map( + function (AttributeCombination $attributeCombination) use ($modelFactory) { + $propelAttribute = $attributeCombination->getAttribute(); + + // Temporary set only pse attribute av to build good attribute av list + $propelAttribute->addAttributeAv($attributeCombination->getAttributeAv()); + + $attribute = $modelFactory->buildModel('Attribute', $propelAttribute); + + // Reset attribute av to all for next use of attribute (because of propel "cache") + $propelAttribute->clearAttributeAvs(); + + return $attribute; + }, + iterator_to_array($theliaModel->getAttributeCombinations()) + ); + + $this->isPromo = (bool) $theliaModel->getPromo(); + $this->price = $this->modelFactory->buildModel('Price', ['untaxed' => $theliaModel->getPrice(), 'taxed' => $theliaModel->getTaxedPrice($this->country)]); + $this->promoPrice = $this->modelFactory->buildModel('Price', ['untaxed' => $theliaModel->getPromoPrice(), 'taxed' => $theliaModel->getTaxedPromoPrice($this->country)]); + } + + /** + * Create an OpenApi ProductSaleElement from a Thelia ProductSaleElements and a Country, then returns it. + * + * @return $this + * + * @throws \Propel\Runtime\Exception\PropelException + */ + public function fillFromTheliaPseAndCountry(ProductSaleElements $pse, Country $country) + { + $modelFactory = $this->modelFactory; + $attributes = array_map( + function (AttributeCombination $attributeCombination) use ($modelFactory) { + $attribute = $attributeCombination->getAttribute(); + $attribute->setAttributeAvs((new Collection())); + $attribute->addAttributeAv($attributeCombination->getAttributeAv()); + + return $modelFactory->buildModel('Attribute', $attribute); + }, + iterator_to_array($pse->getAttributeCombinations()) + ); + + $this->id = $pse->getId(); + $this->isPromo = (bool) $pse->getPromo(); + + /** @var Price $price */ + $price = $this->modelFactory->buildModel('Price'); + $price->fillFromTheliaPseAndCountry($pse, $country); + $this->price = $price; + + /** @var Price $promoPrice */ + $promoPrice = $this->modelFactory->buildModel('Price'); + $promoPrice->fillFromTheliaPseAndCountry($pse, $country); + $this->promoPrice = $price; + $this->weight = $pse->getWeight(); + + $this->reference = $pse->getRef(); + $this->attributes = $attributes; + + $this->quantity = $pse->getQuantity(); + + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param int $id + * + * @return ProductSaleElement + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return bool + */ + public function isPromo() + { + return $this->isPromo; + } + + /** + * @param bool $isPromo + * + * @return ProductSaleElement + */ + public function setIsPromo($isPromo) + { + $this->isPromo = $isPromo; + + return $this; + } + + /** + * @return string + */ + public function getReference() + { + return $this->reference; + } + + /** + * @param string $reference + * + * @return ProductSaleElement + */ + public function setReference($reference) + { + $this->reference = $reference; + + return $this; + } + + /** + * @param $reference + * + * @return $this + */ + public function setRef($reference) + { + return $this->setReference($reference); + } + + /** + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @param array $attributes + * + * @return ProductSaleElement + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + + return $this; + } + + /** + * @return float + */ + public function getQuantity() + { + return $this->quantity; + } + + /** + * @param float $quantity + * + * @return ProductSaleElement + */ + public function setQuantity($quantity) + { + $this->quantity = $quantity; + + return $this; + } + + /** + * @return bool + */ + public function isNewness() + { + return $this->newness; + } + + /** + * @param bool $newness + * + * @return ProductSaleElement + */ + public function setNewness($newness) + { + $this->newness = $newness; + + return $this; + } + + /** + * @return float + */ + public function getWeight() + { + return $this->weight; + } + + /** + * @param float $weight + * + * @return ProductSaleElement + */ + public function setWeight($weight) + { + $this->weight = $weight; + + return $this; + } + + /** + * @return bool + */ + public function isDefault() + { + return $this->isDefault; + } + + /** + * @param bool $isDefault + * + * @return ProductSaleElement + */ + public function setIsDefault($isDefault) + { + $this->isDefault = $isDefault; + + return $this; + } + + /** + * @return string + */ + public function getEan() + { + return $this->ean; + } + + /** + * @param string $ean + * + * @return ProductSaleElement + */ + public function setEan($ean) + { + $this->ean = $ean; + + return $this; + } + + /** + * @param string $ean + * + * @return ProductSaleElement + */ + public function setEanCode($ean) + { + return $this->setEan($ean); + } + + /** + * @return array + */ + public function getImages() + { + return $this->images; + } + + /** + * @param array $images + * + * @return ProductSaleElement + */ + public function setImages($images) + { + $this->images = $images; + + return $this; + } + + /** + * @return array + */ + public function getDocuments() + { + return $this->documents; + } + + /** + * @param array $documents + * + * @return ProductSaleElement + */ + public function setDocuments($documents) + { + $this->documents = $documents; + + return $this; + } + + /** + * @return Price + */ + public function getPrice() + { + return $this->price; + } + + /** + * @param Price $price + * + * @return ProductSaleElement + */ + public function setPrice($price) + { + $this->price = $price; + + return $this; + } + + /** + * @return Price + */ + public function getPromoPrice() + { + return $this->promoPrice; + } + + /** + * @param Price $promoPrice + * + * @return ProductSaleElement + */ + public function setPromoPrice($promoPrice) + { + $this->promoPrice = $promoPrice; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Result.php b/domokits/local/modules/OpenApi/Model/Api/Result.php new file mode 100644 index 0000000..03c7723 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Result.php @@ -0,0 +1,100 @@ +weight; + } + + /** + * @param float $weight + * + * @return Result + */ + public function setWeight($weight) + { + $this->weight = $weight; + + return $this; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param string $type + * + * @return Result + */ + public function setType($type) + { + $this->type = $type; + + return $this; + } + + /** + * @return BaseApiModel + */ + public function getObject() + { + return $this->object; + } + + /** + * @param BaseApiModel $object + * + * @return Result + */ + public function setObject($object) + { + $this->object = $object; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/SchemaViolation.php b/domokits/local/modules/OpenApi/Model/Api/SchemaViolation.php new file mode 100644 index 0000000..0a3bde9 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/SchemaViolation.php @@ -0,0 +1,43 @@ +message; + } + + /** + * @param string $message + * + * @return SchemaViolation + */ + public function setMessage($message) + { + $this->message = $message; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Model/Api/Search.php b/domokits/local/modules/OpenApi/Model/Api/Search.php new file mode 100644 index 0000000..1d5b401 --- /dev/null +++ b/domokits/local/modules/OpenApi/Model/Api/Search.php @@ -0,0 +1,133 @@ +results; + } + + /** + * @param Result $results + * + * @return Search + */ + public function setResults($results) + { + $this->results = $results; + + return $this; + } + + /** + * @return int + */ + public function getPage() + { + return $this->page; + } + + /** + * @param int $page + * + * @return Search + */ + public function setPage($page) + { + $this->page = $page; + + return $this; + } + + /** + * @return int + */ + public function getPageTotal() + { + return $this->pageTotal; + } + + /** + * @param int $pageTotal + * + * @return Search + */ + public function setPageTotal($pageTotal) + { + $this->pageTotal = $pageTotal; + + return $this; + } + + /** + * @return int + */ + public function getTotal() + { + return $this->total; + } + + /** + * @param int $total + * + * @return Search + */ + public function setTotal($total) + { + $this->total = $total; + + return $this; + } +} diff --git a/domokits/local/modules/OpenApi/Normalizer/ModelApiNormalizer.php b/domokits/local/modules/OpenApi/Normalizer/ModelApiNormalizer.php new file mode 100644 index 0000000..b821e4f --- /dev/null +++ b/domokits/local/modules/OpenApi/Normalizer/ModelApiNormalizer.php @@ -0,0 +1,25 @@ +extendedDataValue())) { + foreach ($object->extendedDataValue() as $key => $value) { + if (isset($data[$key])) { + continue; + } + + $data[$key] = $value; + } + } + + return $data; + } +} diff --git a/domokits/local/modules/OpenApi/OpenApi.php b/domokits/local/modules/OpenApi/OpenApi.php new file mode 100644 index 0000000..47d6ae3 --- /dev/null +++ b/domokits/local/modules/OpenApi/OpenApi.php @@ -0,0 +1,69 @@ +load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } + + public static function loadConfiguration(ContainerBuilder $containerBuilder): void + { + $containerBuilder->registerForAutoconfiguration(BaseApiModel::class) + ->setPublic(true) + ->setShared(false) + ->setParent("open_api.base.model") + ->addTag('open_api.model'); + } + + public static function getAnnotationRoutePrefix(): string + { + return 'open_api'; + } +} diff --git a/domokits/local/modules/OpenApi/Readme.md b/domokits/local/modules/OpenApi/Readme.md new file mode 100644 index 0000000..7dd89d0 --- /dev/null +++ b/domokits/local/modules/OpenApi/Readme.md @@ -0,0 +1,3 @@ +# WIP + +This module is under development, please do not try to use it. \ No newline at end of file diff --git a/domokits/local/modules/OpenApi/Service/DocumentService.php b/domokits/local/modules/OpenApi/Service/DocumentService.php new file mode 100644 index 0000000..3d0265e --- /dev/null +++ b/domokits/local/modules/OpenApi/Service/DocumentService.php @@ -0,0 +1,62 @@ +dispatcher = $dispatcher; + } + + /** + * Returns an document URL. + * + * @param $documentModel + * @param $documentType + * + * @return string + */ + public function getDocumentUrl($documentModel, $documentType = null) + { + if (null === $documentType) { + $documentType = str_replace(['document', 'thelia\\model\\'], '', strtolower(\get_class($documentModel))); + } + + $baseSourceFilePath = ConfigQuery::read('documents_library_path'); + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'documents'; + } else { + $baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath; + } + + $event = new DocumentEvent(); + // Put source document file path + $sourceFilePath = sprintf( + '%s/%s/%s', + $baseSourceFilePath, + $documentType, + $documentModel->getFile() + ); + + $event->setSourceFilepath($sourceFilePath); + $event->setCacheSubdirectory($documentType); + + $this->dispatcher->dispatch($event, TheliaEvents::DOCUMENT_PROCESS); + + return $event->getDocumentUrl(); + } +} diff --git a/domokits/local/modules/OpenApi/Service/ImageService.php b/domokits/local/modules/OpenApi/Service/ImageService.php new file mode 100644 index 0000000..c034d43 --- /dev/null +++ b/domokits/local/modules/OpenApi/Service/ImageService.php @@ -0,0 +1,142 @@ +dispatcher = $dispatcher; + } + + /** + * Returns an image URL. + * + * @param $imageModel + * @param $imageType + * + * @return string + */ + public function getImageUrl($imageModel, $imageType = null) + { + return $this->transformImage($imageModel, $imageType); + } + + /** + * Transform an image according to the parameters, and returns the + * transformed image URL. + * + * imageFile can be of type Thelia\Model\ProductImage, ContentImage etc + * + * @param ProductImage|ContentImage|BrandImage|CategoryImage|FolderImage|ModuleImage $imageModel + * @param $imageType + * @param bool $allowZoom + * @param string $resize + * @param null $width + * @param null $height + * @param null $rotation + * @param null $backgroundColor + * @param null $quality + * @param null $effects + * + * @return string The transformed Image URL + */ + public function transformImage( + $imageModel, + $imageType = null, + $allowZoom = false, + $resize = 'none', + $width = null, + $height = null, + $rotation = null, + $backgroundColor = null, + $quality = null, + $effects = null + ) { + switch ($resize) { + case 'crop': + $resizeMode = \Thelia\Action\Image::EXACT_RATIO_WITH_CROP; + break; + + case 'borders': + $resizeMode = \Thelia\Action\Image::EXACT_RATIO_WITH_BORDERS; + break; + + case 'none': + default: + $resizeMode = \Thelia\Action\Image::KEEP_IMAGE_RATIO; + } + + $event = $this->createImageEvent($imageModel, $imageType); + $event + ->setAllowZoom($allowZoom) + ->setResizeMode($resizeMode) + ->setWidth($width) + ->setHeight($height) + ->setRotation($rotation) + ->setBackgroundColor($backgroundColor) + ->setQuality($quality) + ; + + /* Needed as setting effects as null will throw an exception during dispatch */ + if ($effects) { + $event->setEffects($effects); + } + + $this->dispatcher->dispatch($event, TheliaEvents::IMAGE_PROCESS); + + return $event->getFileUrl(); + } + + /** + * @param ProductImage|ContentImage|BrandImage|CategoryImage|FolderImage|ModuleImage $imageModel + * @param null $imageType + * + * @return ImageEvent + */ + protected function createImageEvent($imageModel, $imageType = null) + { + $imageEvent = new ImageEvent(); + $baseSourceFilePath = ConfigQuery::read('images_library_path'); + + if (null === $imageType) { + $imageType = str_replace(['image', 'thelia\\model\\'], '', strtolower(\get_class($imageModel))); + } + + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'images'; + } else { + $baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath; + } + + /** Put source image file path */ + $sourceFilePath = sprintf( + '%s/%s/%s', + $baseSourceFilePath, + $imageType, + $imageModel->getFile() + ); + + $imageEvent->setSourceFilepath($sourceFilePath); + $imageEvent->setCacheSubdirectory($imageType); + + return $imageEvent; + } +} diff --git a/domokits/local/modules/OpenApi/Service/OpenApiService.php b/domokits/local/modules/OpenApi/Service/OpenApiService.php new file mode 100644 index 0000000..d9c28a6 --- /dev/null +++ b/domokits/local/modules/OpenApi/Service/OpenApiService.php @@ -0,0 +1,149 @@ +securityContext = $securityContext; + $this->modelFactory = $modelFactory; + $this->requestStack = $requestStack; + $this->currentRequest = $requestStack->getCurrentRequest(); + $this->dispatcher = $dispatcher; + } + + public static function jsonResponse($data, $code = 200, $cors = '*') + { + $response = (new JsonResponse()) + ->setContent(json_encode($data)); + + $response->headers->set('Access-Control-Allow-Origin', $cors); + $response->setStatusCode($code); + + return $response; + } + + /** + * @param bool $throwExceptionIfNull + * + * @return \Thelia\Model\Customer + * + * @throws OpenApiException + */ + public function getCurrentCustomer($throwExceptionIfNull = true) + { + $currentCustomer = $this->securityContext->getCustomerUser(); + + if (null === $currentCustomer && $throwExceptionIfNull) { + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + [ + 'title' => Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME), + 'description' => Translator::getInstance()->trans('No customer found', [], OpenApi::DOMAIN_NAME), + ] + ); + throw new OpenApiException($error); + } + + return $currentCustomer; + } + + /** + * @param bool $throwExceptionIfNull + * + * @return \Thelia\Model\Cart + * + * @throws OpenApiException + */ + public function getSessionCart($throwExceptionIfNull = true) + { + $cart = $this->currentRequest->getSession()->getSessionCart($this->dispatcher); + + if (null === $cart && $throwExceptionIfNull) { + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + [ + 'title' => Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME), + 'description' => Translator::getInstance()->trans('No cart found', [], OpenApi::DOMAIN_NAME), + ] + ); + throw new OpenApiException($error); + } + + return $cart; + } + + public function getRequestValue($key, $default = null) + { + $requestData = json_decode($this->currentRequest->getContent(), true); + + if (!isset($requestData[$key]) || null === $requestData[$key]) { + return $default; + } + + return $requestData[$key]; + } + + public function buildOpenApiException($title, $description = ''): OpenApiException + { + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + [ + 'title' => $title, + 'description' => $description, + ] + ); + + return new OpenApiException($error); + } + + public function getCurrentOpenApiCart() + { + $cart = $this->currentRequest->getSession()->getSessionCart($this->dispatcher); + + return $this->modelFactory->buildModel('Cart', $cart); + } +} diff --git a/domokits/local/modules/OpenApi/Service/SearchService.php b/domokits/local/modules/OpenApi/Service/SearchService.php new file mode 100644 index 0000000..d040950 --- /dev/null +++ b/domokits/local/modules/OpenApi/Service/SearchService.php @@ -0,0 +1,88 @@ +get('id')) { + $itemQuery->filterById($id); + } + + if (null !== $ids = $request->get('ids')) { + $itemQuery->filterById($ids, Criteria::IN); + } + + if ((null !== $parentsIds = $request->get('parentsIds')) && method_exists($itemQuery, "filterByParent")) { + $itemQuery->filterByParent($parentsIds, Criteria::IN); + } + + $itemQuery->filterByVisible((bool) json_decode(json_encode($request->get('visible', true)))); + + $order = $request->get('order', 'alpha'); + $locale = $request->get('locale', $request->getSession()->getLang()->getLocale()); + $title = $request->get('title'); + $description = $request->get('description'); + $chapo = $request->get('chapo'); + $postscriptum = $request->get('postscriptum'); + + $itemQuery + ->limit($request->get('limit', 20)) + ->offset($request->get('offset', 0)); + + switch ($order) { + case 'created': + $itemQuery->orderByCreatedAt(); + break; + case 'created_reverse': + $itemQuery->orderByCreatedAt(Criteria::DESC); + break; + } + + if (null !== $title || null !== $description || null !== $chapo || null !== $postscriptum) { + $useI18nMethodName = "use".ucfirst($itemType)."I18nQuery"; + $itemI18nQuery = $itemQuery + ->$useI18nMethodName() + ->filterByLocale($locale); + + if (null !== $title) { + $itemI18nQuery->filterByTitle('%'.$title.'%', Criteria::LIKE); + } + + if (null !== $description) { + $itemI18nQuery->filterByDescription('%'.$description.'%', Criteria::LIKE); + } + + if (null !== $chapo) { + $itemI18nQuery->filterByChapo('%'.$chapo.'%', Criteria::LIKE); + } + + if (null !== $postscriptum) { + $itemI18nQuery->filterByPostscriptum('%'.$postscriptum.'%', Criteria::LIKE); + } + + switch ($order) { + case 'alpha': + $itemI18nQuery->orderByTitle(); + break; + case 'alpha_reverse': + $itemI18nQuery->orderByTitle(Criteria::DESC); + break; + } + + $itemI18nQuery->endUse(); + } + + return $itemQuery; + } +} diff --git a/domokits/local/modules/OpenApi/composer.json b/domokits/local/modules/OpenApi/composer.json new file mode 100644 index 0000000..ce36a22 --- /dev/null +++ b/domokits/local/modules/OpenApi/composer.json @@ -0,0 +1,13 @@ +{ + "name": "thelia/open-api-module", + "description": "A modern and documented open api for Thelia", + "license": "LGPL-3.0-or-later", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "zircote/swagger-php": "~3.1.0" + }, + "extra": { + "installer-name": "OpenApi" + } +} diff --git a/domokits/local/modules/OpenApi/templates/backOffice/default/configuration.html b/domokits/local/modules/OpenApi/templates/backOffice/default/configuration.html new file mode 100644 index 0000000..62bfb8b --- /dev/null +++ b/domokits/local/modules/OpenApi/templates/backOffice/default/configuration.html @@ -0,0 +1,42 @@ +
+
{intl l="OpenAPI configurations" d="openapi.bo.default"}
+ {form name="openapi_form_config_form"} +
+ {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + hide_flags = true + hide_save_and_close_button = true + + page_url = {url path="/admin/module/OpenApi"} + close_url = {url path="/admin/modules"} + } + {if $form_error|default:null}
{$form_error_message}
{/if} +
+ {form_hidden_fields} + {render_form_field field="success_url" value={url path='/admin/module/OpenApi'}} + {render_form_field field="error_url" value={url path='/admin/module/OpenApi'}} + + + + + + + + + {loop type="config" name="config-loop"} + + + + + {/loop} + +
{intl l="Active" d="openapi.bo.default"}{intl l="Name" d="openapi.bo.default"}
+ {form_field field="enable_config" value_key=$ID} + + {/form_field} + {$NAME}
+
+
+ {/form} +
\ No newline at end of file diff --git a/domokits/local/modules/OpenApi/templates/frontOffice/default/swagger-ui.html b/domokits/local/modules/OpenApi/templates/frontOffice/default/swagger-ui.html new file mode 100644 index 0000000..0ec5ae5 --- /dev/null +++ b/domokits/local/modules/OpenApi/templates/frontOffice/default/swagger-ui.html @@ -0,0 +1,22 @@ + + + + + + Swagger UI + + + + + +
+ + + + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/.github/workflows/release.yml b/domokits/local/modules/PayPal/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/PayPal/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/PayPal/Config/Update/3.0.2.sql b/domokits/local/modules/PayPal/Config/Update/3.0.2.sql new file mode 100644 index 0000000..c3df234 --- /dev/null +++ b/domokits/local/modules/PayPal/Config/Update/3.0.2.sql @@ -0,0 +1,234 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- paypal_customer +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_customer` +( + `id` INTEGER NOT NULL, + `paypal_user_id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `name` VARCHAR(255), + `given_name` VARCHAR(255), + `family_name` VARCHAR(255), + `middle_name` VARCHAR(255), + `picture` VARCHAR(255), + `email_verified` TINYINT, + `gender` VARCHAR(255), + `birthday` VARCHAR(255), + `zoneinfo` VARCHAR(255), + `locale` VARCHAR(255), + `language` VARCHAR(255), + `verified` TINYINT, + `phone_number` VARCHAR(255), + `verified_account` VARCHAR(255), + `account_type` VARCHAR(255), + `age_range` VARCHAR(255), + `payer_id` VARCHAR(255), + `postal_code` VARCHAR(255), + `locality` VARCHAR(255), + `region` VARCHAR(255), + `country` VARCHAR(255), + `street_address` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`,`paypal_user_id`), + CONSTRAINT `fk_paypal_payer_customer_id` + FOREIGN KEY (`id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_planified_payment` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `frequency` VARCHAR(255) NOT NULL, + `frequency_interval` INTEGER NOT NULL, + `cycle` INTEGER NOT NULL, + `min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `position` INTEGER DEFAULT 0 NOT NULL, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_cart +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_cart` +( + `id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `planified_payment_id` INTEGER, + `express_payment_id` VARCHAR(255), + `express_payer_id` VARCHAR(255), + `express_token` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `FI_paypal_cart_planified_payment_id` (`planified_payment_id`), + CONSTRAINT `fk_paypal_cart_cart_id` + FOREIGN KEY (`id`) + REFERENCES `cart` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_cart_planified_payment_id` + FOREIGN KEY (`planified_payment_id`) + REFERENCES `paypal_planified_payment` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_order +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_order` +( + `id` INTEGER NOT NULL, + `payment_id` VARCHAR(50), + `agreement_id` VARCHAR(255), + `credit_card_id` VARCHAR(40), + `state` VARCHAR(20), + `amount` DECIMAL(16,6) DEFAULT 0.000000, + `description` LONGTEXT, + `payer_id` VARCHAR(255), + `token` VARCHAR(255), + `planified_title` VARCHAR(255) NOT NULL, + `planified_description` LONGTEXT, + `planified_frequency` VARCHAR(255) NOT NULL, + `planified_frequency_interval` INTEGER NOT NULL, + `planified_cycle` INTEGER NOT NULL, + `planified_actual_cycle` INTEGER DEFAULT 0 NOT NULL, + `planified_min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `planified_max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `created_at` DATETIME, + `updated_at` DATETIME, + `version` INTEGER DEFAULT 0, + `version_created_at` DATETIME, + `version_created_by` VARCHAR(100), + PRIMARY KEY (`id`), + CONSTRAINT `fk_paypal_order_order_id` + FOREIGN KEY (`id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_plan +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_plan` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `paypal_order_id` INTEGER NOT NULL, + `plan_id` VARCHAR(255), + `state` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `FI_paypal_plan_paypal_order_id` (`paypal_order_id`), + CONSTRAINT `fk_paypal_plan_paypal_order_id` + FOREIGN KEY (`paypal_order_id`) + REFERENCES `paypal_order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_log +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_log` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER, + `order_id` INTEGER, + `hook` VARCHAR(255), + `channel` VARCHAR(255), + `level` INTEGER, + `message` LONGTEXT, + `time` INTEGER, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `FI_paypal_log_customer_id` (`customer_id`), + INDEX `FI_paypal_log_order_id` (`order_id`), + CONSTRAINT `fk_paypal_log_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_log_order_id` + FOREIGN KEY (`order_id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment_i18n +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_planified_payment_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `title` VARCHAR(255) NOT NULL, + `description` LONGTEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `paypal_planified_payment_i18n_FK_1` + FOREIGN KEY (`id`) + REFERENCES `paypal_planified_payment` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_order_version +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_order_version` +( + `id` INTEGER NOT NULL, + `payment_id` VARCHAR(50), + `agreement_id` VARCHAR(255), + `credit_card_id` VARCHAR(40), + `state` VARCHAR(20), + `amount` DECIMAL(16,6) DEFAULT 0.000000, + `description` LONGTEXT, + `payer_id` VARCHAR(255), + `token` VARCHAR(255), + `planified_title` VARCHAR(255) NOT NULL, + `planified_description` LONGTEXT, + `planified_frequency` VARCHAR(255) NOT NULL, + `planified_frequency_interval` INTEGER NOT NULL, + `planified_cycle` INTEGER NOT NULL, + `planified_actual_cycle` INTEGER DEFAULT 0 NOT NULL, + `planified_min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `planified_max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `created_at` DATETIME, + `updated_at` DATETIME, + `version` INTEGER DEFAULT 0 NOT NULL, + `version_created_at` DATETIME, + `version_created_by` VARCHAR(100), + `id_version` INTEGER DEFAULT 0, + PRIMARY KEY (`id`,`version`), + CONSTRAINT `paypal_order_version_FK_1` + FOREIGN KEY (`id`) + REFERENCES `paypal_order` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/PayPal/Config/config.xml b/domokits/local/modules/PayPal/Config/config.xml new file mode 100644 index 0000000..0ffddf3 --- /dev/null +++ b/domokits/local/modules/PayPal/Config/config.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/PayPal/Config/create.sql b/domokits/local/modules/PayPal/Config/create.sql new file mode 100644 index 0000000..79aba18 --- /dev/null +++ b/domokits/local/modules/PayPal/Config/create.sql @@ -0,0 +1,195 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- paypal_customer +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_customer` +( + `id` INTEGER NOT NULL, + `paypal_user_id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `name` VARCHAR(255), + `given_name` VARCHAR(255), + `family_name` VARCHAR(255), + `middle_name` VARCHAR(255), + `picture` VARCHAR(255), + `email_verified` TINYINT, + `gender` VARCHAR(255), + `birthday` VARCHAR(255), + `zoneinfo` VARCHAR(255), + `locale` VARCHAR(255), + `language` VARCHAR(255), + `verified` TINYINT, + `phone_number` VARCHAR(255), + `verified_account` VARCHAR(255), + `account_type` VARCHAR(255), + `age_range` VARCHAR(255), + `payer_id` VARCHAR(255), + `postal_code` VARCHAR(255), + `locality` VARCHAR(255), + `region` VARCHAR(255), + `country` VARCHAR(255), + `street_address` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`,`paypal_user_id`), + CONSTRAINT `fk_paypal_payer_customer_id` + FOREIGN KEY (`id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_planified_payment` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `frequency` VARCHAR(255) NOT NULL, + `frequency_interval` INTEGER NOT NULL, + `cycle` INTEGER NOT NULL, + `min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `position` INTEGER DEFAULT 0 NOT NULL, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_cart +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_cart` +( + `id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `planified_payment_id` INTEGER, + `express_payment_id` VARCHAR(255), + `express_payer_id` VARCHAR(255), + `express_token` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_cart_planified_payment_id` (`planified_payment_id`), + CONSTRAINT `fk_paypal_cart_cart_id` + FOREIGN KEY (`id`) + REFERENCES `cart` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_cart_planified_payment_id` + FOREIGN KEY (`planified_payment_id`) + REFERENCES `paypal_planified_payment` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_order +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_order` +( + `id` INTEGER NOT NULL, + `payment_id` VARCHAR(50), + `agreement_id` VARCHAR(255), + `credit_card_id` VARCHAR(40), + `state` VARCHAR(20), + `amount` DECIMAL(16,6) DEFAULT 0.000000, + `description` LONGTEXT, + `payer_id` VARCHAR(255), + `token` VARCHAR(255), + `planified_title` VARCHAR(255) NOT NULL, + `planified_description` LONGTEXT, + `planified_frequency` VARCHAR(255) NOT NULL, + `planified_frequency_interval` INTEGER NOT NULL, + `planified_cycle` INTEGER NOT NULL, + `planified_actual_cycle` INTEGER DEFAULT 0 NOT NULL, + `planified_min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `planified_max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + CONSTRAINT `fk_paypal_order_order_id` + FOREIGN KEY (`id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_plan +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_plan` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `paypal_order_id` INTEGER NOT NULL, + `plan_id` VARCHAR(255), + `state` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_plan_paypal_order_id` (`paypal_order_id`), + CONSTRAINT `fk_paypal_plan_paypal_order_id` + FOREIGN KEY (`paypal_order_id`) + REFERENCES `paypal_order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_log +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_log` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER, + `order_id` INTEGER, + `hook` VARCHAR(255), + `channel` VARCHAR(255), + `level` INTEGER, + `message` LONGTEXT, + `time` INTEGER, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_log_customer_id` (`customer_id`), + INDEX `fi_paypal_log_order_id` (`order_id`), + CONSTRAINT `fk_paypal_log_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_log_order_id` + FOREIGN KEY (`order_id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment_i18n +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `paypal_planified_payment_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `title` VARCHAR(255) NOT NULL, + `description` LONGTEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `paypal_planified_payment_i18n_fk_c9dfe7` + FOREIGN KEY (`id`) + REFERENCES `paypal_planified_payment` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/PayPal/Config/module.xml b/domokits/local/modules/PayPal/Config/module.xml new file mode 100644 index 0000000..78ea5b5 --- /dev/null +++ b/domokits/local/modules/PayPal/Config/module.xml @@ -0,0 +1,28 @@ + + + PayPal\PayPal + + PayPal + + + PayPal + + + + + en_US + fr_FR + + 4.0.9 + + + gbarral + gbarral@openstudio.fr + + + payment + 2.4.0 + other + diff --git a/domokits/local/modules/PayPal/Config/routing.xml b/domokits/local/modules/PayPal/Config/routing.xml new file mode 100644 index 0000000..2efe25d --- /dev/null +++ b/domokits/local/modules/PayPal/Config/routing.xml @@ -0,0 +1,108 @@ + + + + + + + diff --git a/domokits/local/modules/PayPal/Config/schema.xml b/domokits/local/modules/PayPal/Config/schema.xml new file mode 100644 index 0000000..8d3aea5 --- /dev/null +++ b/domokits/local/modules/PayPal/Config/schema.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/PayPal/Config/sqldb.map b/domokits/local/modules/PayPal/Config/sqldb.map new file mode 100644 index 0000000..63a93ba --- /dev/null +++ b/domokits/local/modules/PayPal/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +thelia.sql=thelia diff --git a/domokits/local/modules/PayPal/Config/thelia.sql b/domokits/local/modules/PayPal/Config/thelia.sql new file mode 100644 index 0000000..5eb2a2a --- /dev/null +++ b/domokits/local/modules/PayPal/Config/thelia.sql @@ -0,0 +1,210 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- paypal_customer +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_customer`; + +CREATE TABLE `paypal_customer` +( + `id` INTEGER NOT NULL, + `paypal_user_id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `name` VARCHAR(255), + `given_name` VARCHAR(255), + `family_name` VARCHAR(255), + `middle_name` VARCHAR(255), + `picture` VARCHAR(255), + `email_verified` TINYINT, + `gender` VARCHAR(255), + `birthday` VARCHAR(255), + `zoneinfo` VARCHAR(255), + `locale` VARCHAR(255), + `language` VARCHAR(255), + `verified` TINYINT, + `phone_number` VARCHAR(255), + `verified_account` VARCHAR(255), + `account_type` VARCHAR(255), + `age_range` VARCHAR(255), + `payer_id` VARCHAR(255), + `postal_code` VARCHAR(255), + `locality` VARCHAR(255), + `region` VARCHAR(255), + `country` VARCHAR(255), + `street_address` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`,`paypal_user_id`), + CONSTRAINT `fk_paypal_payer_customer_id` + FOREIGN KEY (`id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_planified_payment`; + +CREATE TABLE `paypal_planified_payment` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `frequency` VARCHAR(255) NOT NULL, + `frequency_interval` INTEGER NOT NULL, + `cycle` INTEGER NOT NULL, + `min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `position` INTEGER DEFAULT 0 NOT NULL, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_cart +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_cart`; + +CREATE TABLE `paypal_cart` +( + `id` INTEGER NOT NULL, + `credit_card_id` VARCHAR(40), + `planified_payment_id` INTEGER, + `express_payment_id` VARCHAR(255), + `express_payer_id` VARCHAR(255), + `express_token` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_cart_planified_payment_id` (`planified_payment_id`), + CONSTRAINT `fk_paypal_cart_cart_id` + FOREIGN KEY (`id`) + REFERENCES `cart` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_cart_planified_payment_id` + FOREIGN KEY (`planified_payment_id`) + REFERENCES `paypal_planified_payment` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_order +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_order`; + +CREATE TABLE `paypal_order` +( + `id` INTEGER NOT NULL, + `payment_id` VARCHAR(50), + `agreement_id` VARCHAR(255), + `credit_card_id` VARCHAR(40), + `state` VARCHAR(20), + `amount` DECIMAL(16,6) DEFAULT 0.000000, + `description` LONGTEXT, + `payer_id` VARCHAR(255), + `token` VARCHAR(255), + `planified_title` VARCHAR(255) NOT NULL, + `planified_description` LONGTEXT, + `planified_frequency` VARCHAR(255) NOT NULL, + `planified_frequency_interval` INTEGER NOT NULL, + `planified_cycle` INTEGER NOT NULL, + `planified_actual_cycle` INTEGER DEFAULT 0 NOT NULL, + `planified_min_amount` DECIMAL(16,6) DEFAULT 0.000000, + `planified_max_amount` DECIMAL(16,6) DEFAULT 0.000000, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + CONSTRAINT `fk_paypal_order_order_id` + FOREIGN KEY (`id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_plan +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_plan`; + +CREATE TABLE `paypal_plan` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `paypal_order_id` INTEGER NOT NULL, + `plan_id` VARCHAR(255), + `state` VARCHAR(255), + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_plan_paypal_order_id` (`paypal_order_id`), + CONSTRAINT `fk_paypal_plan_paypal_order_id` + FOREIGN KEY (`paypal_order_id`) + REFERENCES `paypal_order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_log +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_log`; + +CREATE TABLE `paypal_log` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER, + `order_id` INTEGER, + `hook` VARCHAR(255), + `channel` VARCHAR(255), + `level` INTEGER, + `message` LONGTEXT, + `time` INTEGER, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_paypal_log_customer_id` (`customer_id`), + INDEX `fi_paypal_log_order_id` (`order_id`), + CONSTRAINT `fk_paypal_log_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE, + CONSTRAINT `fk_paypal_log_order_id` + FOREIGN KEY (`order_id`) + REFERENCES `order` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- paypal_planified_payment_i18n +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `paypal_planified_payment_i18n`; + +CREATE TABLE `paypal_planified_payment_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `title` VARCHAR(255) NOT NULL, + `description` LONGTEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `paypal_planified_payment_i18n_fk_c9dfe7` + FOREIGN KEY (`id`) + REFERENCES `paypal_planified_payment` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/PayPal/Controller/ConfigurationController.php b/domokits/local/modules/PayPal/Controller/ConfigurationController.php new file mode 100644 index 0000000..5b50312 --- /dev/null +++ b/domokits/local/modules/PayPal/Controller/ConfigurationController.php @@ -0,0 +1,119 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Controller; + +use PayPal\Form\ConfigurationForm; +use PayPal\PayPal; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Core\Thelia; +use Thelia\Core\Translation\Translator; +use Thelia\Form\Exception\FormValidationException; +use Thelia\Tools\URL; +use Thelia\Tools\Version\Version; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/admin/module/paypal/configure", name="paypal_configure") + * Class ConfigurePaypal + * @package Paypal\Controller + */ +class ConfigurationController extends BaseAdminController +{ + /* + * Checks paypal.configure || paypal.configure.sandbox form and save config into json file + */ + /** + * @return mixed|\Symfony\Component\HttpFoundation\Response|\Thelia\Core\HttpFoundation\Response + * @Route("", name="_save", methods="POSt") + */ + public function configureAction(RequestStack $requestStack, Translator $translator) + { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'Paypal', AccessManager::UPDATE)) { + return $response; + } + + $configurationForm = $this->createForm(ConfigurationForm::getName()); + + try { + $form = $this->validateForm($configurationForm, "POST"); + + // Get the form field values + $data = $form->getData(); + + foreach ($data as $name => $value) { + if (is_array($value)) { + $value = implode(';', $value); + } + + Paypal::setConfigValue($name, $value); + } + + $this->adminLogAppend( + "paypal.configuration.message", + AccessManager::UPDATE, + sprintf("Paypal configuration updated") + ); + + if ($requestStack->getCurrentRequest()->get('save_mode') === 'stay') { + // If we have to stay on the same page, redisplay the configuration page/ + $url = '/admin/module/Paypal'; + } else { + // If we have to close the page, go back to the module back-office page. + $url = '/admin/modules'; + } + + return $this->generateRedirect(URL::getInstance()->absoluteUrl($url)); + } catch (FormValidationException $ex) { + $error_msg = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + $error_msg = $ex->getMessage(); + } + + $this->setupFormErrorContext( + $translator->trans("Paypal configuration", [], PayPal::DOMAIN_NAME), + $error_msg, + $configurationForm, + $ex + ); + + // Before 2.2, the errored form is not stored in session + if (Version::test(Thelia::THELIA_VERSION, '2.2', false, "<")) { + return $this->render('module-configure', [ 'module_code' => PayPal::getModuleCode()]); + } else { + return $this->generateRedirect(URL::getInstance()->absoluteUrl('/admin/module/PayPal')); + } + } + + /** + * @return \Thelia\Core\HttpFoundation\Response + * @Route("/log", name="_log", methods="GET") + */ + public function logAction() + { + return $this->render('paypal/paypal-log'); + } +} diff --git a/domokits/local/modules/PayPal/Controller/PayPalPlanifiedPaymentController.php b/domokits/local/modules/PayPal/Controller/PayPalPlanifiedPaymentController.php new file mode 100644 index 0000000..aed050a --- /dev/null +++ b/domokits/local/modules/PayPal/Controller/PayPalPlanifiedPaymentController.php @@ -0,0 +1,356 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Controller; + +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalPlanifiedPaymentEvent; +use PayPal\Form\PayPalFormFields; +use PayPal\Form\PayPalPlanifiedPaymentCreateForm; +use PayPal\Form\PayPalPlanifiedPaymentUpdateForm; +use PayPal\Model\PaypalPlanifiedPayment; +use PayPal\Model\PaypalPlanifiedPaymentQuery; +use PayPal\PayPal; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\Translation\TranslatorInterface; +use Thelia\Controller\Admin\AbstractCrudController; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Template\ParserContext; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Tools\TokenProvider; + +/** + * @Route("/admin/module/paypal/configure/planified", name="configure_planified") + * Class PayPalPlanifiedPaymentController + * @package PayPal\Controller + */ +class PayPalPlanifiedPaymentController extends AbstractCrudController +{ + /** @var string */ + protected $currentRouter = PayPal::ROUTER; + + /** + * PayPalPlanifiedPaymentController constructor. + */ + public function __construct() + { + parent::__construct( + 'team', + 'id', + 'order', + 'paypal.back.planified.payment', + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_CREATE, + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_UPDATE, + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_DELETE + ); + } + + /** + * The default action is displaying the list. + * + * @return Response + * @Route("", name="_render", methods="GET") + */ + public function defaultAction() + { + // Check current user authorization + if (null !== $response = $this->checkAuth($this->resourceCode, $this->getModuleCode(), AccessManager::VIEW)) { + return $response; + } + + return $this->renderList(); + } + + /** + * @Route("/create", name="_create", methods="POST") + */ + public function createAction(EventDispatcherInterface $eventDispatcher, TranslatorInterface $translator) + { + return parent::createAction($eventDispatcher, $translator); + } + + /** + * @Route("/create/delete", name="_delete", methods="POST") + */ + public function deleteAction(Request $request, TokenProvider $tokenProvider, EventDispatcherInterface $eventDispatcher, ParserContext $parserContext) + { + return parent::deleteAction($request, $tokenProvider, $eventDispatcher, $parserContext); + } + + /** + * @Route("/{planifiedPaymentId}", name="_update", methods="GET") + */ + public function updateAction(ParserContext $parserContext) + { + return parent::updateAction($parserContext); + } + + /** + * @Route("/{planifiedPaymentId}", name="_process_update", methods="POST") + */ + public function processUpdateAction(Request $request, EventDispatcherInterface $eventDispatcher, TranslatorInterface $translator) + { + return parent::processUpdateAction($request, $eventDispatcher, $translator); + } + + + /** + * Return the creation form for this object + * @return PayPalPlanifiedPaymentCreateForm + */ + protected function getCreationForm() + { + return $this->createForm(PayPalPlanifiedPaymentCreateForm::getName()); + } + + /** + * Return the update form for this object + * @return PayPalPlanifiedPaymentUpdateForm + */ + protected function getUpdateForm() + { + return $this->createForm(PayPalPlanifiedPaymentUpdateForm::getName()); + } + + /** + * Hydrate the update form for this object, before passing it to the update template + * + * @param PaypalPlanifiedPayment $object + * @return PayPalPlanifiedPaymentUpdateForm + */ + protected function hydrateObjectForm(ParserContext $parserContext, $object) + { + /** @var \Thelia\Model\Lang $lang */ + $parserContext->getSession()->get('thelia.current.lang'); + $object->getTranslation($lang->getLocale()); + + $data = [ + PayPalFormFields::FIELD_PP_ID => $object->getId(), + PayPalFormFields::FIELD_PP_TITLE => $object->getTitle(), + PayPalFormFields::FIELD_PP_DESCRIPTION => $object->getDescription(), + PayPalFormFields::FIELD_PP_FREQUENCY => $object->getFrequency(), + PayPalFormFields::FIELD_PP_FREQUENCY_INTERVAL => $object->getFrequencyInterval(), + PayPalFormFields::FIELD_PP_CYCLE => $object->getCycle(), + PayPalFormFields::FIELD_PP_MIN_AMOUNT => $object->getMinAmount(), + PayPalFormFields::FIELD_PP_MAX_AMOUNT => $object->getMaxAmount(), + PayPalFormFields::FIELD_PP_POSITION => $object->getPosition() + ]; + + return $this->createForm(PayPalPlanifiedPaymentUpdateForm::getName(), 'form', $data); + } + + /** + * Creates the creation event with the provided form data + * + * @param mixed $formData + * @return PayPalPlanifiedPaymentEvent + */ + protected function getCreationEvent($formData) + { + $planifiedPayment = new PaypalPlanifiedPayment(); + + $planifiedPayment = $this->fillObjectWithDataForm($planifiedPayment, $formData); + + $planifiedPaymentEvent = new PayPalPlanifiedPaymentEvent($planifiedPayment); + + return $planifiedPaymentEvent; + } + + /** + * Creates the update event with the provided form data + * + * @param mixed $formData + * @return PayPalPlanifiedPaymentEvent + */ + protected function getUpdateEvent($formData) + { + if (null === $planifiedPayment = PaypalPlanifiedPaymentQuery::create()->findOneById($formData[PayPalFormFields::FIELD_PP_ID])) { + throw new \InvalidArgumentException( + $this->getTranslator()->trans( + 'Invalid planified payment id : %id', + ['%id' => $formData[PayPalFormFields::FIELD_PP_ID]], + PayPal::DOMAIN_NAME + ) + ); + } + + $planifiedPayment = $this->fillObjectWithDataForm($planifiedPayment, $formData); + + $planifiedPaymentEvent = new PayPalPlanifiedPaymentEvent($planifiedPayment); + + return $planifiedPaymentEvent; + } + + /** + * @param PaypalPlanifiedPayment $planifiedPayment + * @param $formData + * @return PaypalPlanifiedPayment + */ + protected function fillObjectWithDataForm(PaypalPlanifiedPayment $planifiedPayment, $formData) + { + $planifiedPayment + ->setFrequency($formData[PayPalFormFields::FIELD_PP_FREQUENCY]) + ->setFrequencyInterval($formData[PayPalFormFields::FIELD_PP_FREQUENCY_INTERVAL]) + ->setCycle($formData[PayPalFormFields::FIELD_PP_CYCLE]) + ->setMinAmount($formData[PayPalFormFields::FIELD_PP_MIN_AMOUNT]) + ->setMaxAmount($formData[PayPalFormFields::FIELD_PP_MAX_AMOUNT]) + ->setLocale($formData[PayPalFormFields::FIELD_PP_LOCALE]) + ->setTitle($formData[PayPalFormFields::FIELD_PP_TITLE]) + ->setDescription($formData[PayPalFormFields::FIELD_PP_DESCRIPTION]) + ; + + return $planifiedPayment; + } + + /** + * Creates the delete event with the provided form data + * @return PayPalPlanifiedPaymentEvent + */ + protected function getDeleteEvent() + { + return new PayPalPlanifiedPaymentEvent( + $this->getExistingObject() + ); + } + + /** + * Return true if the event contains the object, e.g. the action has updated the object in the event. + * + * @param PayPalPlanifiedPaymentEvent $event + * @return bool + */ + protected function eventContainsObject($event) + { + return $event->getPayPalPlanifiedPayment() ? true : false; + } + + /** + * Get the created object from an event. + * @param PayPalPlanifiedPaymentEvent $event + * @return PaypalPlanifiedPayment + */ + protected function getObjectFromEvent($event) + { + return $event->getPayPalPlanifiedPayment(); + } + + /** + * Load an existing object from the database + * @return PaypalPlanifiedPayment + */ + protected function getExistingObject() + { + if (null === $planifiedPayment = PaypalPlanifiedPaymentQuery::create()->findOneById((int)$this->getRequest()->get('planifiedPaymentId'))) { + throw new \InvalidArgumentException( + $this->getTranslator()->trans('Invalid planified payment id : %id', + ['%id' => (int)$this->getRequest()->get('planifiedPaymentId')], PayPal::DOMAIN_NAME) + ); + } + + return $planifiedPayment; + } + + /** + * Returns the object label form the object event (name, title, etc.) + * + * @param PaypalPlanifiedPayment $object + * @return string + */ + protected function getObjectLabel($object) + { + return $object->getTitle(); + } + + /** + * Returns the object ID from the object + * + * @param PaypalPlanifiedPayment $object + * @return int + */ + protected function getObjectId($object) + { + return $object->getId(); + } + + /** + * Render the main list template + * + * @param mixed $currentOrder , if any, null otherwise. + * @return Response + */ + protected function renderListTemplate($currentOrder) + { + $this->getListOrderFromSession('planified_payment', 'order', 'manual'); + + return $this->render( + 'paypal/planified-payment', + [ + 'order' => $currentOrder, + 'selected_menu' => 'planified' + ] + ); + } + + /** + * Render the edition template + * @return Response + */ + protected function renderEditionTemplate() + { + return $this->render('paypal/planified-payment-edit', $this->getEditionArguments()); + } + + /** + * Must return a RedirectResponse instance + * @return Response + */ + protected function redirectToEditionTemplate() + { + return $this->generateRedirectFromRoute( + 'paypal.admin.configuration.planified.update', + [], + $this->getEditionArguments() + ); + } + + /** + * Must return a RedirectResponse instance + * @return Response + */ + protected function redirectToListTemplate() + { + return $this->generateRedirectFromRoute('paypal.admin.configuration.planified'); + } + + /** + * @return array + */ + private function getEditionArguments() + { + return [ + 'planifiedPaymentId' => (int)$this->getRequest()->get('planifiedPaymentId') + ]; + } +} diff --git a/domokits/local/modules/PayPal/Controller/PayPalResponseController.php b/domokits/local/modules/PayPal/Controller/PayPalResponseController.php new file mode 100644 index 0000000..30493eb --- /dev/null +++ b/domokits/local/modules/PayPal/Controller/PayPalResponseController.php @@ -0,0 +1,970 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Controller; + +use Front\Controller\OrderController; +use Monolog\Logger; +use PayPal\Api\Details; +use PayPal\Api\PayerInfo; +use PayPal\Event\PayPalCartEvent; +use PayPal\Event\PayPalCustomerEvent; +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalOrderEvent; +use PayPal\Exception\PayPalConnectionException; +use PayPal\Model\PaypalCart; +use PayPal\Model\PaypalCartQuery; +use PayPal\Model\PaypalCustomer; +use PayPal\Model\PaypalCustomerQuery; +use PayPal\Model\PaypalOrder; +use PayPal\Model\PaypalOrderQuery; +use PayPal\PayPal; +use PayPal\Service\PayPalAgreementService; +use PayPal\Service\PayPalCustomerService; +use PayPal\Service\PayPalLoggerService; +use PayPal\Service\PayPalPaymentService; +use Propel\Runtime\Propel; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\Router; +use Thelia\Core\Event\Address\AddressCreateOrUpdateEvent; +use Thelia\Core\Event\Customer\CustomerCreateOrUpdateEvent; +use Thelia\Core\Event\Customer\CustomerLoginEvent; +use Thelia\Core\Event\Delivery\DeliveryPostageEvent; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\Order\OrderManualEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Security\SecurityContext; +use Thelia\Core\Translation\Translator; +use Thelia\Model\AddressQuery; +use Thelia\Model\CartQuery; +use Thelia\Model\Country; +use Thelia\Model\CountryQuery; +use Thelia\Model\CustomerQuery; +use Thelia\Model\CustomerTitleQuery; +use Thelia\Model\ModuleQuery; +use Thelia\Model\Order; +use Thelia\Model\OrderQuery; +use Thelia\Model\OrderStatusQuery; +use Thelia\Module\Exception\DeliveryException; +use Thelia\Tools\URL; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("", name="paypal") + * Class PayPalResponseController + * @package PayPal\Controller + */ +class PayPalResponseController extends OrderController +{ + /** + * @param $orderId + * @param EventDispatcherInterface $eventDispatcher + * @Route("/module/paypal/cancel/{orderId}", name="_cancel", methods="GET") + */ + public function cancelAction($orderId, EventDispatcherInterface $eventDispatcher) + { + if (!$order = OrderQuery::create()->findOneById($orderId)) { + return $this->pageNotFound(); + } + + $event = new OrderEvent($order); + $event->setStatus(OrderStatusQuery::getCancelledStatus()->getId()); + $eventDispatcher->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + + $orderId = $order->getId(); + $message = Translator::getInstance()->trans('Order cancel', [], PayPal::DOMAIN_NAME); + + return $this->generateRedirect(URL::getInstance()->absoluteUrl("/order/failed/$orderId/$message")); + } + + /** + * @param $orderId + * @param RequestStack $requestStack + * @param EventDispatcherInterface $eventDispatcher + * @return RedirectResponse + * @Route("/module/paypal/ok/{orderId}", name="_ok", methods="GET") + */ + public function okAction($orderId, RequestStack $requestStack, EventDispatcherInterface $eventDispatcher) + { + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + $request = $requestStack->getCurrentRequest(); + $payerId = $request->query->get('PayerID'); + $token = $request->query->get('token'); + $payPalOrder = PaypalOrderQuery::create()->findOneById($orderId); + + if (null !== $payPalOrder && null !== $payerId) { + + $response = $this->executePayment($eventDispatcher, $payPalOrder, $payPalOrder->getPaymentId(), $payerId, $token); + } else { + $con->rollBack(); + $message = Translator::getInstance()->trans( + 'Method okAction => One of this parameter is invalid : $payerId = %payer_id, $orderId = %order_id', + [ + '%payer_id' => $payerId, + '%order_id' => $orderId + ], + PayPal::DOMAIN_NAME + ); + + PayPalLoggerService::log( + $message, + [ + 'order_id' => $orderId + ], + Logger::CRITICAL + ); + + $response = $this->getPaymentFailurePageUrl($orderId, $message); + } + } catch (PayPalConnectionException $e) { + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'order_id' => $orderId + ], + Logger::CRITICAL + ); + $response = $this->getPaymentFailurePageUrl($orderId, $e->getMessage()); + } catch (\Exception $e) { + PayPalLoggerService::log( + $e->getMessage(), + [ + 'order_id' => $orderId + ], + Logger::CRITICAL + ); + + $response = $this->getPaymentFailurePageUrl($orderId, $e->getMessage()); + } + + $con->commit(); + return $response; + } + + + /** + * @param RequestStack $requestStack + * @param EventDispatcherInterface $dispatcher + * @param string $routeId + * @param bool $fromCartView + * @return RedirectResponse + * @Route("/module/paypal/express/checkout", name="_express_checkout", methods="POST") + */ + public function expressCheckoutAction(RequestStack $requestStack, EventDispatcherInterface $dispatcher, $routeId = 'cart.view', $fromCartView = true) + { + $session = $requestStack->getCurrentRequest()->getSession(); + $cart = $session->getSessionCart($dispatcher); + + if (null !== $cart) { + /** @var PayPalPaymentService $payPalService */ + $payPalService = $this->getContainer()->get(PayPal::PAYPAL_PAYMENT_SERVICE_ID); + + $payment = $payPalService->makePaymentFromCart( + $cart, + null, + false, + $fromCartView + ); + $response = new RedirectResponse($payment->getApprovalLink()); + + return $response; + } + + return $this->getUrlFromRouteId('cart.view'); + } + + /** + * @Route("/module/paypal/invoice/express/checkout", name="_invoice_express_checkout", methods="POST") + */ + public function invoiceExpressCheckoutAction(RequestStack $requestStack, EventDispatcherInterface $dispatcher) + { + return $this->expressCheckoutAction($requestStack, $dispatcher, 'order.invoice', false); + } + + /** + * @param int $cartId + * @return RedirectResponse + * @throws PayPalConnectionException + * @throws \Exception + * @Route("/module/paypal/invoice/express/checkout/ok/{cartId}", name="_invoice_express_checkout_ok", methods="GET") + */ + public function invoiceExpressCheckoutOkAction($cartId, RequestStack $requestStack, EventDispatcherInterface $eventDispatcher, SecurityContext $securityContext, Translator $translator) + { + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + $this->fillCartWithExpressCheckout($requestStack->getCurrentRequest(), $eventDispatcher, $securityContext); + + $response = $this->executeExpressCheckoutAction($requestStack, $eventDispatcher,$translator,false); + + } catch (PayPalConnectionException $e) { + $con->rollBack(); + + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + throw $e; + } catch(\Exception $e) { + $con->rollBack(); + + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + throw $e; + } + + $con->commit(); + return $response; + } + + /** + * @Route("/module/paypal/invoice/express/checkout/ko/{cartId}", name="_invoice_express_checkout_ko", methods="GET") + */ + public function invoiceExpressCheckoutKoAction($cartId) + { + return $this->getUrlFromRouteId('order.invoice'); + } + + /** + * @return RedirectResponse + * @throws PayPalConnectionException + * @throws \Exception + * @Route("/module/paypal/express/checkout/ok/{cartId}", name="_express_checkout_ok", methods="POST") + */ + public function expressCheckoutOkAction(RequestStack $requestStack, EventDispatcherInterface $eventDispatcher, SecurityContext $securityContext) + { + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + $this->fillCartWithExpressCheckout($requestStack->getCurrentRequest(), $eventDispatcher, $securityContext); + + $response = $this->getUrlFromRouteId('order.delivery'); + + + } catch (PayPalConnectionException $e) { + $con->rollBack(); + + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + throw $e; + } catch(\Exception $e) { + $con->rollBack(); + + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + throw $e; + } + + $con->commit(); + return $response; + } + + /** + * @return RedirectResponse|\Symfony\Component\HttpFoundation\Response + * @Route("/order/delivery", name="_order_delivery", methods="POST") + */ + public function executeExpressCheckoutAction(RequestStack $requestStack, EventDispatcherInterface $eventDispatcher, Translator $translator, $fromCartView = true) + { + if (null === $responseParent = parent::deliver($eventDispatcher)) { + + if ($fromCartView) { + return $responseParent; + } + } + + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + $session = $requestStack->getCurrentRequest()->getSession(); + $cart = $session->getSessionCart($eventDispatcher); + + if (null === $payPalCart = PaypalCartQuery::create()->findOneById($cart->getId())) { + $con->rollBack(); + return $responseParent; + } + + if (null === $payPalCart->getExpressPaymentId() || null === $payPalCart->getExpressPayerId() || null === $payPalCart->getExpressToken()) { + $con->rollBack(); + return $responseParent; + } + + /** @var PayPalPaymentService $payPalPaymentService */ + $payPalPaymentService = $this->container->get(PayPal::PAYPAL_PAYMENT_SERVICE_ID); + $payment = $payPalPaymentService->getPaymentDetails($payPalCart->getExpressPaymentId()); + + $payerInfo = $payment->getPayer()->getPayerInfo(); + + //Check if invoice adresse already exist + if (null === $payerInfo->getBillingAddress()) { + $line1 = $payerInfo->getShippingAddress()->getLine1(); + $zipCode = $payerInfo->getShippingAddress()->getPostalCode(); + } else { + $line1 = $payerInfo->getBillingAddress()->getLine1(); + $zipCode = $payerInfo->getBillingAddress()->getPostalCode(); + } + + /** @var \Thelia\Model\Address $invoiceAddress */ + if (null === $invoiceAddress = AddressQuery::create() + ->filterByCustomerId($cart->getCustomerId()) + ->filterByIsDefault(0) + ->filterByAddress1($line1) + ->filterByZipcode($zipCode) + ->findOne()) { + + $event = $this->createAddressEvent($payerInfo); + $event->setCustomer($cart->getCustomer()); + + $eventDispatcher->dispatch($event, TheliaEvents::ADDRESS_CREATE); + $invoiceAddress = $event->getAddress(); + } + + if (null === $payPalCustomer = PaypalCustomerQuery::create()->findOneById($cart->getCustomerId())) { + $payPalCustomer = new PaypalCustomer(); + $payPalCustomer->setId($cart->getCustomerId()); + } + + $payPalCustomer + ->setPaypalUserId($payerInfo->getPayerId()) + ->setName($payerInfo->getFirstName()) + ->setGivenName($payerInfo->getFirstName() . ' ' . $payerInfo->getLastName()) + ->setFamilyName($payerInfo->getLastName()) + ->setMiddleName($payerInfo->getMiddleName()) + ->setBirthday($payerInfo->getBirthDate()) + ->setLocale($requestStack->getCurrentRequest()->getSession()->getLang()->getLocale()) + ->setPhoneNumber($payerInfo->getPhone()) + ->setPayerId($payerInfo->getPayerId()) + ->setPostalCode($payerInfo->getShippingAddress()->getPostalCode()) + ->setCountry($payerInfo->getShippingAddress()->getCountryCode()) + ->setStreetAddress($payerInfo->getShippingAddress()->getLine1() . $payerInfo->getShippingAddress()->getLine2()) + ; + + $payPalCustomerEvent = new PayPalCustomerEvent($payPalCustomer); + $eventDispatcher->dispatch($payPalCustomerEvent, PayPalEvents::PAYPAL_CUSTOMER_UPDATE); + + /** @var \Thelia\Model\Address $deliveryAddress */ + $deliveryAddress = $cart->getCustomer()->getDefaultAddress(); + + /** @var \Thelia\Model\Module $deliveryModule */ + $deliveryModule = ModuleQuery::create()->filterByActivate(1)->findOne(); + /** @var \Thelia\Model\Module $paymentModule */ + $paymentModule = ModuleQuery::create()->findPk(PayPal::getModuleId()); + + /** @var \Thelia\Model\Currency $currency */ + $currency = $cart->getCurrency(); + $lang = $requestStack->getCurrentRequest()->getSession()->getLang(); + + $order = new Order(); + $order + ->setCustomerId($cart->getCustomerId()) + ->setCurrencyId($currency->getId()) + ->setCurrencyRate($currency->getRate()) + ->setStatusId(OrderStatusQuery::getNotPaidStatus()->getId()) + ->setLangId($lang->getDefaultLanguage()->getId()) + ->setChoosenDeliveryAddress($deliveryAddress) + ->setChoosenInvoiceAddress($invoiceAddress) + ; + + $orderEvent = new OrderEvent($order); + + /* get postage amount */ + $moduleInstance = $deliveryModule->getDeliveryModuleInstance($this->container); + $deliveryPostageEvent = new DeliveryPostageEvent($moduleInstance, $cart, $deliveryAddress); + + $eventDispatcher->dispatch( + $deliveryPostageEvent, + TheliaEvents::MODULE_DELIVERY_GET_POSTAGE + ); + + if (!$deliveryPostageEvent->isValidModule() || null === $deliveryPostageEvent->getPostage()) { + throw new DeliveryException( + $translator->trans('The delivery module is not valid.', [], PayPal::DOMAIN_NAME) + ); + } + + $postage = $deliveryPostageEvent->getPostage(); + + $orderEvent->setPostage($postage->getAmount()); + $orderEvent->setPostageTax($postage->getAmountTax()); + $orderEvent->setPostageTaxRuleTitle($postage->getTaxRuleTitle()); + $orderEvent->setDeliveryAddress($deliveryAddress->getId()); + $orderEvent->setInvoiceAddress($invoiceAddress->getId()); + $orderEvent->setDeliveryModule($deliveryModule->getId()); + $orderEvent->setPaymentModule($paymentModule->getId()); + + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_ADDRESS); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_INVOICE_ADDRESS); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_POSTAGE); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_DELIVERY_MODULE); + $eventDispatcher->dispatch($orderEvent, TheliaEvents::ORDER_SET_PAYMENT_MODULE); + + $orderManualEvent = new OrderManualEvent( + $orderEvent->getOrder(), + $orderEvent->getOrder()->getCurrency(), + $orderEvent->getOrder()->getLang(), + $cart, + $cart->getCustomer() + ); + + $eventDispatcher->dispatch($orderManualEvent, TheliaEvents::ORDER_CREATE_MANUAL); + $order = $orderManualEvent->getPlacedOrder(); + + $payPalOrderEvent = $payPalPaymentService->generatePayPalOrder($order); + $payPalPaymentService->updatePayPalOrder($payPalOrderEvent->getPayPalOrder(), $payment->getState(), $payment->getId()); + + $response = $this->executePayment( + $eventDispatcher, + $payPalOrderEvent->getPayPalOrder(), + $payPalCart->getExpressPaymentId(), + $payPalCart->getExpressPayerId(), + $payPalCart->getExpressToken(), + PayPal::PAYPAL_METHOD_EXPRESS_CHECKOUT, + $payPalPaymentService->createDetails( + $order->getPostage(), + $order->getPostageTax(), + $order->getTotalAmount($tax, false) + ) + ); + + $con->commit(); + } catch (PayPalConnectionException $e) { + $con->rollBack(); + + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + $response = $responseParent; + } catch(\Exception $e) { + $con->rollBack(); + + $customerId = null; + if (isset($customer)) { + $customerId = $customer->getId(); + } + + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $customerId + ], + Logger::CRITICAL + ); + $response = $responseParent; + } + + $con->commit(); + return $response; + } + + /** + * @Route("/module/paypal/express/checkout/ko/{cartId}", name="_express_checkout_ko", methods="POST") + */ + public function expressCheckoutKoAction() + { + PayPalLoggerService::log( + Translator::getInstance()->trans('Express Checkout login failed', [], PayPal::DOMAIN_NAME), + [], + Logger::WARNING + ); + return $this->getUrlFromRouteId('cart.view'); + } + + /** + * Method called when a customer log in with PayPal. + * @param RequestStack $requestStack + * @param EventDispatcherInterface $eventDispatcher + * @return RedirectResponse + * @throws \Propel\Runtime\Exception\PropelException + * @Route("/module/paypal/login/ok", name="_login_ok", methods="GET") + */ + public function loginOkAction(RequestStack $requestStack, EventDispatcherInterface $eventDispatcher) + { + if (null !== $authorizationCode = $requestStack->getCurrentRequest()->query->get('code')) { + + /** @var PayPalCustomerService $payPalCustomerService */ + $payPalCustomerService = $this->container->get(PayPal::PAYPAL_CUSTOMER_SERVICE_ID); + $openIdUserinfo = $payPalCustomerService->getUserInfoWithAuthorizationCode($authorizationCode); + + $payPalCustomer = $payPalCustomerService->getCurrentPayPalCustomer(); + $payPalCustomer + ->setPaypalUserId($openIdUserinfo->getUserId()) + ->setName($openIdUserinfo->getName()) + ->setGivenName($openIdUserinfo->getGivenName()) + ->setFamilyName($openIdUserinfo->getFamilyName()) + ->setMiddleName($openIdUserinfo->getMiddleName()) + ->setPicture($openIdUserinfo->getPicture()) + ->setEmailVerified($openIdUserinfo->getEmailVerified()) + ->setGender($openIdUserinfo->getGender()) + ->setBirthday($openIdUserinfo->getBirthday()) + ->setZoneinfo($openIdUserinfo->getZoneinfo()) + ->setLocale($openIdUserinfo->getLocale()) + ->setLanguage($openIdUserinfo->getLanguage()) + ->setVerified($openIdUserinfo->getVerified()) + ->setPhoneNumber($openIdUserinfo->getPhoneNumber()) + ->setVerifiedAccount($openIdUserinfo->getVerifiedAccount()) + ->setAccountType($openIdUserinfo->getAccountType()) + ->setAgeRange($openIdUserinfo->getAgeRange()) + ->setPayerId($openIdUserinfo->getPayerId()) + ->setPostalCode($openIdUserinfo->getAddress()->getPostalCode()) + ->setLocality($openIdUserinfo->getAddress()->getLocality()) + ->setRegion($openIdUserinfo->getAddress()->getRegion()) + ->setCountry($openIdUserinfo->getAddress()->getCountry()) + ->setStreetAddress($openIdUserinfo->getAddress()->getStreetAddress()) + ; + + $payPalCustomerEvent = new PayPalCustomerEvent($payPalCustomer); + $eventDispatcher->dispatch($payPalCustomerEvent, PayPalEvents::PAYPAL_CUSTOMER_UPDATE); + + $eventDispatcher->dispatch(new CustomerLoginEvent($payPalCustomerEvent->getPayPalCustomer()->getCustomer()), TheliaEvents::CUSTOMER_LOGIN); + } + + return new RedirectResponse(URL::getInstance()->absoluteUrl($requestStack->getCurrentRequest()->getSession()->getReturnToUrl())); + } + + /** + * @Route("/module/paypal/agreement/ok/{orderId}", name="_agreement_ok", methods="GET") + */ + public function agreementOkAction($orderId, RequestStack $requestStack, EventDispatcherInterface $eventDispatcher) + { + $con = Propel::getConnection(); + $con->beginTransaction(); + + $token = $requestStack->getCurrentRequest()->query->get('token'); + $payPalOrder = PaypalOrderQuery::create()->findOneById($orderId); + + if (null !== $payPalOrder && null !== $token) { + + try { + /** @var PayPalAgreementService $payPalAgreementService */ + $payPalAgreementService = $this->container->get(PayPal::PAYPAL_AGREEMENT_SERVICE_ID); + $agreement = $payPalAgreementService->activateBillingAgreementByToken($token); + + $payPalOrder + ->setState($agreement->getState()) + ->setAgreementId($agreement->getId()) + ->setPayerId($agreement->getPayer()->getPayerInfo()->getPayerId()) + ->setToken($token) + ; + $payPalOrderEvent = new PayPalOrderEvent($payPalOrder); + $eventDispatcher->dispatch($payPalOrderEvent, PayPalEvents::PAYPAL_ORDER_UPDATE); + + $event = new OrderEvent($payPalOrder->getOrder()); + $event->setStatus(OrderStatusQuery::getPaidStatus()->getId()); + $eventDispatcher->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + + $response = $this->getPaymentSuccessPageUrl($orderId); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order payed with success in PayPal with method : %method', + [ + '%method' => PayPal::PAYPAL_METHOD_PLANIFIED_PAYMENT + ], + PayPal::DOMAIN_NAME + ), + [ + 'order_id' => $payPalOrder->getId(), + 'customer_id' => $payPalOrder->getOrder()->getCustomerId() + ], + Logger::INFO + ); + } catch (PayPalConnectionException $e) { + $con->rollBack(); + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $orderId + ], + Logger::CRITICAL + ); + + $response = $this->getPaymentFailurePageUrl($orderId, $e->getMessage()); + } catch (\Exception $e) { + $con->rollBack(); + PayPalLoggerService::log( + $e->getMessage(), + [ + 'order_id' => $orderId + ], + Logger::CRITICAL + ); + + $response = $this->getPaymentFailurePageUrl($orderId, $e->getMessage()); + } + + } else { + $con->rollBack(); + $message = Translator::getInstance()->trans( + 'Method agreementOkAction => One of this parameter is invalid : $token = %token, $orderId = %order_id', + [ + '%token' => $token, + '%order_id' => $orderId + ], + PayPal::DOMAIN_NAME + ); + + PayPalLoggerService::log( + $message, + [ + 'order_id' => $orderId + ], + Logger::CRITICAL + ); + + $response = $this->getPaymentFailurePageUrl($orderId, $message); + } + + $con->commit(); + return $response; + } + + /** + * @Route("/module/paypal/ipn/{orderId}", name="_ipn", methods="GET") + */ + public function ipnAction($orderId, RequestStack $requestStack) + { + PayPalLoggerService::log('GUIGIT', ['hook' => 'guigit', 'order_id' => $orderId], Logger::DEBUG); + + PayPalLoggerService::log( + print_r($requestStack->getCurrentRequest()->request, true), + [ + 'hook' => 'guigit', + 'order_id' => $orderId + ], + Logger::DEBUG + ); + PayPalLoggerService::log( + print_r($this->getRequest()->attributes, true), + [ + 'hook' => 'guigit', + 'order_id' => $orderId + ], + Logger::DEBUG + ); + } + + /** + * Return the order payment success page URL + * + * @param $orderId + * @return RedirectResponse + */ + public function getPaymentSuccessPageUrl($orderId) + { + return $this->getUrlFromRouteId('order.placed', ['order_id' => $orderId]); + } + + /** + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + protected function fillCartWithExpressCheckout(Request $request, EventDispatcherInterface $eventDispatcher, SecurityContext $securityContext) + { + $paymentId = $request->get('paymentId'); + $token = $request->get('token'); + $payerId = $request->get('PayerID'); + $cartId = $request->get('cartId'); + $cart = CartQuery::create()->findOneById($request->get('cartId')); + + if (null === $paymentId || null === $token || null === $payerId || null === $cart) { + PayPalLoggerService::log( + Translator::getInstance()->trans('Express checkout failed in expressCheckoutOkAction() function', [], PayPal::DOMAIN_NAME), + [], + Logger::CRITICAL + ); + } + + PayPalLoggerService::log( + Translator::getInstance()->trans('Express checkout begin with cart %id', ['%id' => $cartId], PayPal::DOMAIN_NAME) + ); + + /** @var PayPalPaymentService $payPalPaymentService */ + $payPalPaymentService = $this->container->get(PayPal::PAYPAL_PAYMENT_SERVICE_ID); + $payment = $payPalPaymentService->getPaymentDetails($paymentId); + + $payerInfo = $payment->getPayer()->getPayerInfo(); + if (null === $customer = CustomerQuery::create()->findOneByEmail($payment->getPayer()->getPayerInfo()->getEmail())) { + + $customerCreateEvent = $this->createEventInstance($payerInfo, $request); + + $eventDispatcher->dispatch($customerCreateEvent, TheliaEvents::CUSTOMER_CREATEACCOUNT); + + $customer = $customerCreateEvent->getCustomer(); + + } + + //Save informations to use them after customer has choosen the delivery method + if (null === $payPalCart = PaypalCartQuery::create()->findOneById($cartId)) { + $payPalCart = new PaypalCart(); + $payPalCart->setId($cartId); + } + + $payPalCart + ->setExpressPaymentId($paymentId) + ->setExpressPayerId($payerId) + ->setExpressToken($token) + ; + $payPalCartEvent = new PayPalCartEvent($payPalCart); + $eventDispatcher->dispatch($payPalCartEvent, PayPalEvents::PAYPAL_CART_UPDATE); + + $cart->setCustomerId($customer->getId())->save(); + $clonedCart = clone $cart; + $this->dispatch(TheliaEvents::CUSTOMER_LOGIN, new CustomerLoginEvent($customer)); + + //In case of the current customer has changed, re affect the correct cart and customer session + $securityContext->setCustomerUser($customer); + $clonedCart->save(); + $request->getSession()->set("thelia.cart_id", $clonedCart->getId()); + } + + /** + * @param $routeId + * @param array $params + * @return RedirectResponse + */ + protected function getUrlFromRouteId($routeId, $params = []) + { + $frontOfficeRouter = $this->getContainer()->get('router.front'); + + return new RedirectResponse( + URL::getInstance()->absoluteUrl( + $frontOfficeRouter->generate( + $routeId, + $params, + Router::ABSOLUTE_URL + ) + ) + ); + } + + /** + * Redirect the customer to the failure payment page. if $message is null, a generic message is displayed. + * + * @param $orderId + * @param $message + * @return RedirectResponse + */ + public function getPaymentFailurePageUrl($orderId, $message) + { + $frontOfficeRouter = $this->getContainer()->get('router.front'); + + return new RedirectResponse( + URL::getInstance()->absoluteUrl( + $frontOfficeRouter->generate( + "order.failed", + array( + "order_id" => $orderId, + "message" => $message + ), + Router::ABSOLUTE_URL + ) + ) + ); + } + + /** + * @param PaypalOrder $payPalOrder + * @param $paymentId + * @param $payerId + * @param $token + * @param string $method + * @param Details|null $details + * @return RedirectResponse + */ + protected function executePayment(EventDispatcherInterface $eventDispatcher, PaypalOrder $payPalOrder, $paymentId, $payerId, $token, $method = PayPal::PAYPAL_METHOD_PAYPAL, Details $details = null) + { + /** @var PayPalPaymentService $payPalService */ + $payPalService = $this->getContainer()->get(PayPal::PAYPAL_PAYMENT_SERVICE_ID); + $payment = $payPalService->executePayment($paymentId, $payerId, $details); + + $payPalOrder + ->setState($payment->getState()) + ->setPayerId($payerId) + ->setToken($token) + ; + $payPalOrderEvent = new PayPalOrderEvent($payPalOrder); + $eventDispatcher->dispatch($payPalOrderEvent, PayPalEvents::PAYPAL_ORDER_UPDATE); + + $event = new OrderEvent($payPalOrder->getOrder()); + $event->setStatus(OrderStatusQuery::getPaidStatus()->getId()); + $eventDispatcher->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + + $response = $this->getPaymentSuccessPageUrl($payPalOrder->getId()); + + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order payed with success in PayPal with method : %method', + [ + '%method' => $method + ], + PayPal::DOMAIN_NAME + ), + [ + 'order_id' => $payPalOrder->getId(), + 'customer_id' => $payPalOrder->getOrder()->getCustomerId() + ], + Logger::INFO + ); + + + return $response; + } + + /** + * @param PayerInfo $payerInfo + * @return \Thelia\Core\Event\Customer\CustomerCreateOrUpdateEvent + */ + protected function createEventInstance(PayerInfo $payerInfo, Request $request) + { + if (null === $country = CountryQuery::create()->findOneByIsoalpha2($payerInfo->getShippingAddress()->getCountryCode())) { + $country = Country::getDefaultCountry(); + } + + $customerCreateEvent = new CustomerCreateOrUpdateEvent( + CustomerTitleQuery::create()->findOne()->getId(), + $payerInfo->getFirstName(), + $payerInfo->getLastName(), + $payerInfo->getShippingAddress()->getLine1(), + $payerInfo->getShippingAddress()->getLine2(), + null, + $payerInfo->getPhone(), + null, + $payerInfo->getShippingAddress()->getPostalCode(), + $payerInfo->getShippingAddress()->getCity(), + $country->getId(), + $payerInfo->getEmail(), + 'random', + $request->getSession()->getLang()->getId(), + null, + null, + null, + null, + null, + null + ); + + return $customerCreateEvent; + } + + /** + * @param PayerInfo $payerInfo + * @return AddressCreateOrUpdateEvent + */ + protected function createAddressEvent(PayerInfo $payerInfo) + { + if (null !== $payerInfo->getBillingAddress()) { + $countryCode = $payerInfo->getBillingAddress()->getCountryCode(); + $line1 = $payerInfo->getBillingAddress()->getLine1(); + $line2 = $payerInfo->getBillingAddress()->getLine2(); + $zipCode = $payerInfo->getBillingAddress()->getPostalCode(); + $city = $payerInfo->getBillingAddress()->getCity(); + } else { + $countryCode = $payerInfo->getShippingAddress()->getCountryCode(); + $line1 = $payerInfo->getShippingAddress()->getLine1(); + $line2 = $payerInfo->getShippingAddress()->getLine2(); + $zipCode = $payerInfo->getShippingAddress()->getPostalCode(); + $city = $payerInfo->getShippingAddress()->getCity(); + } + + if (null === $country = CountryQuery::create()->findOneByIsoalpha2($countryCode)) { + $country = Country::getDefaultCountry(); + } + + return new AddressCreateOrUpdateEvent( + 'Express checkout PayPal', + CustomerTitleQuery::create()->findOne()->getId(), + $payerInfo->getFirstName(), + $payerInfo->getLastName(), + $line1, + ($line2)?$line2:'', + '', + $zipCode, + $city, + $country->getId(), + $payerInfo->getPhone(), + $payerInfo->getPhone(), + '', + 0, + null + ); + } +} diff --git a/domokits/local/modules/PayPal/Controller/PayPalWebHookController.php b/domokits/local/modules/PayPal/Controller/PayPalWebHookController.php new file mode 100644 index 0000000..c254fa5 --- /dev/null +++ b/domokits/local/modules/PayPal/Controller/PayPalWebHookController.php @@ -0,0 +1,344 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Controller; + +use Monolog\Logger; +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalOrderEvent; +use PayPal\Exception\PayPalConnectionException; +use PayPal\Model\PaypalOrderQuery; +use PayPal\Model\PaypalPlanQuery; +use PayPal\PayPal; +use PayPal\Service\PayPalAgreementService; +use PayPal\Service\PayPalLoggerService; +use Propel\Runtime\Propel; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Translation\Translator; +use Thelia\Model\OrderStatusQuery; +use Symfony\Component\Routing\Annotation\Route; + +/** + * @Route("/module/paypal/webhook/all/events", name="paypal_webhook_events") + * Class PayPalWebHookController + * @package PayPal\Controller + */ +class PayPalWebHookController extends BaseFrontController +{ + const HOOK_BILLING_PLAN_CREATED = 'BILLING.PLAN.CREATED'; + const HOOK_BILLING_PLAN_UPDATED = 'BILLING.PLAN.UPDATED'; + const HOOK_BILLING_SUBSCRIPTION_CREATED = 'BILLING.SUBSCRIPTION.CREATED'; + + const HOOK_PAYMENT_SALE_COMPLETED = 'PAYMENT.SALE.COMPLETED'; + const HOOK_PAYMENT_SALE_DENIED = 'PAYMENT.SALE.DENIED'; + + //Classic PayPal payment + const RESOURCE_TYPE_SALE = 'sale'; + + //Planified payment + const RESOURCE_TYPE_PLAN = 'plan'; + const RESOURCE_TYPE_AGREEMENT = 'agreement'; + + /** + * Example of array received in posted params : + * + * + * Array ( + * 'id' => 'WH-0LU96374794024348-4WG31854RU4949452', + * 'event_version' => 1.0, + * 'create_time' => '2017-02-03T15:31:29Z', + * 'resource_type' => 'plan', + * 'event_type' => 'BILLING.PLAN.CREATED', + * 'summary' => 'A billing plan was created', + * 'resource' => Array ( + * 'merchant_preferences' => Array ( + * 'setup_fee' => Array ( + * 'currency' => 'EUR', + * 'value' => 0 + * ), + * 'return_url' => 'http://25b3ee89.ngrok.io/thelia_2_3_3/web/module/paypal/agreement/ok/208', + * 'cancel_url' => 'http://25b3ee89.ngrok.io/thelia_2_3_3/web/module/paypal/agreement/ko/208', + * 'auto_bill_amount' => 'NO', + * 'initial_fail_amount_action' => 'CONTINUE', + * 'max_fail_attempts' => 0 + * ), + * 'update_time' => '2017-02-03T15:31:29.348Z', + * 'create_time' => '2017-02-03T15:31:29.348Z', + * 'name' => 'plan for order 208', + * 'description' => false, + * 'links' => Array ( + * 0 => Array ( + * 'href' => 'api.sandbox.paypal.com/v1/payments/billing-plans/P-2DV20774VJ3968037ASNA3RA', + * 'rel' => 'self', + * 'method' => 'GET' + * ) + * ), + * 'payment_definitions' => Array ( + * 0 => Array ( + * 'name' => 'payment definition for order 208', + * 'type' => 'REGULAR', + * 'frequency' => 'Day', + * 'frequency_interval' => 1, + * 'amount' => Array ( + * 'currency' => 'EUR', + * 'value' => 3.9 + * ), + * 'cycles' => 5, + * 'charge_models' => Array ( + * 0 => Array ( + * 'type' => 'SHIPPING', + * 'amount' => Array ( + * 'currency' => 'EUR', + * 'value' => 0 + * ), + * 'id' => 'CHM-26B03456D8799461GASNA3RA' + * ) + * ), + * 'id' => 'PD-3FB00313143031422ASNA3RA' + * ) + * ), + * 'id' => 'P-2DV20774VJ3968037ASNA3RA', + * 'state' => 'CREATED', + * 'type' => 'FIXED' + * ), + * 'links' => Array ( + * 0 => Array ( + * 'href' => 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0LU96374794024348-4WG31854RU4949452', + * 'rel' => 'self', + * 'method' => 'GET' + * ), + * 1 => Array ( + * 'href' => 'https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-0LU96374794024348-4WG31854RU4949452/resend', + * 'rel' => 'resend', + * 'method' => 'POST' + * ) + * ) + * ); + * + * @Route("", name="_all", methods="GET") + */ + public function allAction(RequestStack $requestStack, EventDispatcherInterface $eventDispatcher) + { + $request = $requestStack->getCurrentRequest(); + $eventType = $request->request->get('event_type'); + $resource = $request->request->get('resource'); + $resourceType = $request->request->get('resource_type'); + + $details = [ + 'request' => $this->getRequest()->request->all() + ]; + + $params = [ + 'hook' => $eventType + ]; + + $con = Propel::getConnection(); + $con->beginTransaction(); + + try { + + $title = $this->getTitle($request); + + if (is_array($resource)) { + + switch (strtolower($resourceType)) { + + case self::RESOURCE_TYPE_SALE: + if (isset($resource['parent_payment'])) { + $params = $this->getParamsForSale($resource['parent_payment'], $params, $eventType); + } + if (isset($resource['billing_agreement_id'])) { + $params = $this->getParamsForAgreement($eventDispatcher, $resource['billing_agreement_id'], $params); + } + break; + + case self::RESOURCE_TYPE_PLAN: + if (isset($resource['id'])) { + $params = $this->getParamsForPlan($resource['id'], $params); + } + break; + + case self::RESOURCE_TYPE_AGREEMENT: + if (isset($resource['id'])) { + $params = $this->getParamsForAgreement($eventDispatcher, $resource['id'], $params); + } + break; + + default: + break; + } + } + + PayPalLoggerService::log( + '

' . $title . '

' . $this->printRecursiveData($details), + $params, + Logger::INFO + ); + + $con->commit(); + } catch (PayPalConnectionException $e) { + + $con->rollBack(); + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log($message, $params, Logger::CRITICAL); + PayPalLoggerService::log($this->printRecursiveData($this->getRequest()->request), $params, Logger::CRITICAL); + + } catch (\Exception $e) { + + $con->rollBack(); + PayPalLoggerService::log($e->getMessage(), $params, Logger::CRITICAL); + PayPalLoggerService::log($this->printRecursiveData($this->getRequest()->request), $params, Logger::CRITICAL); + + } + } + + /** + * @param Request $request + * @return string + */ + protected function getTitle(Request $request) + { + $summary = $request->request->get('summary'); + + $title = ''; + if (null !== $request->get('event_type')) { + $title .= $request->get('event_type') . ' : '; + } + $title .= $summary; + + return $title; + } + + /** + * @param null $paymentId + * @param array $params + * @param null $eventType + * @return array + */ + protected function getParamsForSale($paymentId = null, $params = [], $eventType = null) + { + if (null !== $payPalOrder = PaypalOrderQuery::create()->findOneByPaymentId($paymentId)) { + $params['order_id'] = $payPalOrder->getId(); + $params['customer_id'] = $payPalOrder->getOrder()->getCustomerId(); + + if ($eventType === self::HOOK_PAYMENT_SALE_DENIED) { + $event = new OrderEvent($payPalOrder->getOrder()); + $event->setStatus(OrderStatusQuery::getCancelledStatus()->getId()); + $this->dispatch(TheliaEvents::ORDER_UPDATE_STATUS, $event); + } + } + + return $params; + } + + /** + * @param null $planId + * @param array $params + * @return array + */ + protected function getParamsForPlan($planId = null, $params = []) + { + if (null !== $payPalPlan = PaypalPlanQuery::create()->findOneByPlanId($planId)) { + + $params['order_id'] = $payPalPlan->getPaypalOrderId(); + $params['customer_id'] = $payPalPlan->getPaypalOrder()->getOrder()->getCustomerId(); + + } + + return $params; + } + + /** + * @param null $agreementId + * @param array $params + * @return array + */ + protected function getParamsForAgreement(EventDispatcherInterface $eventDispatcher, $agreementId = null, $params = []) + { + if (null !== $payPalOrder = PaypalOrderQuery::create()->filterByAgreementId($agreementId)->orderById()->findOne()) { + + // Do not duplicate order for the first PayPal payment because order has just been created. + // We will duplicate this order for the next PayPal payment :) + if ($payPalOrder->getPlanifiedActualCycle() > 0) { + $params['order_id'] = $payPalOrder->getId(); + $params['customer_id'] = $payPalOrder->getOrder()->getCustomerId(); + + /** @var PayPalAgreementService $payPalAgreementService */ + $payPalAgreementService = $this->container->get(PayPal::PAYPAL_AGREEMENT_SERVICE_ID); + $newOrder = $payPalAgreementService->duplicateOrder($payPalOrder->getOrder()); + + Translator::getInstance()->trans( + 'New recursive invoice from order %id', + ['%id' => $payPalOrder->getId()], + PayPal::DOMAIN_NAME + ); + + PayPalLoggerService::log( + '

New recursive invoice from order ' . $payPalOrder->getId() . '

', + [ + 'order_id' => $newOrder->getId(), + 'customer_id' => $payPalOrder->getOrder()->getCustomerId() + ], + Logger::INFO + ); + } + + $payPalOrder->setPlanifiedActualCycle($payPalOrder->getPlanifiedActualCycle() + 1); + $payPalOrderEvent = new PayPalOrderEvent($payPalOrder); + $eventDispatcher->dispatch($payPalOrderEvent, PayPalEvents::PAYPAL_ORDER_UPDATE); + } + + return $params; + } + + /** + * @param array $data + * @param int $deep + * @return string + */ + protected function printRecursiveData($data = [], $deep = 0) + { + $formatedString = ''; + foreach ($data as $key => $value) { + + for ($i = 0; $i <= $deep; $i++) { + $formatedString .= '    '; + } + + if (is_array($value)) { + $formatedString .= '' . $key . ' : 
' . $this->printRecursiveData($value, $deep + 1); + } else { + $formatedString .= '' . $key . ' : ' . $value . '
'; + } + + } + + return $formatedString; + } +} diff --git a/domokits/local/modules/PayPal/Event/PayPalCartEvent.php b/domokits/local/modules/PayPal/Event/PayPalCartEvent.php new file mode 100644 index 0000000..432fbba --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalCartEvent.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + +use PayPal\Model\PaypalCart; +use Thelia\Core\Event\ActionEvent; + +/** + * Class PayPalCartEvent + * @package PayPal\Event + */ +class PayPalCartEvent extends ActionEvent +{ + /** @var PaypalCart */ + protected $payPalCart; + + /** + * PayPalCartEvent constructor. + * @param PaypalCart $payPalCart + */ + public function __construct(PaypalCart $payPalCart) + { + $this->payPalCart = $payPalCart; + } + + /** + * @return PaypalCart + */ + public function getPayPalCart() + { + return $this->payPalCart; + } + + /** + * @param PaypalCart $payPalCart + * + * @return $this + */ + public function setPayPalCart($payPalCart) + { + $this->payPalCart = $payPalCart; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Event/PayPalCustomerEvent.php b/domokits/local/modules/PayPal/Event/PayPalCustomerEvent.php new file mode 100644 index 0000000..419afe6 --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalCustomerEvent.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + +use PayPal\Model\PaypalCustomer; +use Thelia\Core\Event\ActionEvent; + +/** + * Class PayPalCustomerEvent + * @package PayPal\Event + */ +class PayPalCustomerEvent extends ActionEvent +{ + /** @var PaypalCustomer */ + protected $payPalCustomer; + + /** + * PayPalCustomerEvent constructor. + * @param PaypalCustomer $payPalCustomer + */ + public function __construct(PaypalCustomer $payPalCustomer) + { + $this->payPalCustomer = $payPalCustomer; + } + + /** + * @return PaypalCustomer + */ + public function getPayPalCustomer() + { + return $this->payPalCustomer; + } + + /** + * @param PaypalCustomer $payPalCustomer + * + * @return $this + */ + public function setPayPalCustomer($payPalCustomer) + { + $this->payPalCustomer = $payPalCustomer; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Event/PayPalEvents.php b/domokits/local/modules/PayPal/Event/PayPalEvents.php new file mode 100644 index 0000000..16a5e5a --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalEvents.php @@ -0,0 +1,57 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + + +/** + * Class PayPalEvents + * @package PayPal\Event + */ +class PayPalEvents +{ + const PAYPAL_ORDER_CREATE = 'action.paypal.order.create'; + const PAYPAL_ORDER_UPDATE = 'action.paypal.order.update'; + const PAYPAL_ORDER_DELETE = 'action.paypal.order.delete'; + const PAYPAL_RECURSIVE_PAYMENT_CREATE = 'action.paypal.recursive.payment.create'; + + const PAYPAL_AGREEMENT_CREATE = 'action.paypal.agreement.create'; + const PAYPAL_AGREEMENT_UPDATE = 'action.paypal.agreement.update'; + const PAYPAL_AGREEMENT_DELETE = 'action.paypal.agreement.delete'; + + const PAYPAL_PLAN_CREATE = 'action.paypal.plan.create'; + const PAYPAL_PLAN_UPDATE = 'action.paypal.plan.update'; + const PAYPAL_PLAN_DELETE = 'action.paypal.plan.delete'; + + const PAYPAL_CUSTOMER_CREATE = 'action.paypal.customer.create'; + const PAYPAL_CUSTOMER_UPDATE = 'action.paypal.customer.update'; + const PAYPAL_CUSTOMER_DELETE = 'action.paypal.customer.delete'; + + const PAYPAL_CART_CREATE = 'action.paypal.cart.create'; + const PAYPAL_CART_UPDATE = 'action.paypal.cart.update'; + const PAYPAL_CART_DELETE = 'action.paypal.cart.delete'; + + const PAYPAL_PLANIFIED_PAYMENT_CREATE = 'action.paypal.planified.payment.create'; + const PAYPAL_PLANIFIED_PAYMENT_UPDATE = 'action.paypal.planified.payment.update'; + const PAYPAL_PLANIFIED_PAYMENT_DELETE = 'action.paypal.planified.payment.delete'; +} diff --git a/domokits/local/modules/PayPal/Event/PayPalOrderEvent.php b/domokits/local/modules/PayPal/Event/PayPalOrderEvent.php new file mode 100644 index 0000000..2193bb3 --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalOrderEvent.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + +use PayPal\Model\PaypalOrder; +use Thelia\Core\Event\ActionEvent; + +/** + * Class PayPalOrderEvent + * @package PayPal\Event + */ +class PayPalOrderEvent extends ActionEvent +{ + /** @var PaypalOrder */ + protected $payPalOrder; + + /** + * PayPalOrderEvent constructor. + * @param PaypalOrder $payPalOrder + */ + public function __construct(PaypalOrder $payPalOrder) + { + $this->payPalOrder = $payPalOrder; + } + + /** + * @return PaypalOrder + */ + public function getPayPalOrder() + { + return $this->payPalOrder; + } + + /** + * @param PaypalOrder $payPalOrder + * + * @return $this + */ + public function setPayPalOrder($payPalOrder) + { + $this->payPalOrder = $payPalOrder; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Event/PayPalPlanEvent.php b/domokits/local/modules/PayPal/Event/PayPalPlanEvent.php new file mode 100644 index 0000000..b1ff2d4 --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalPlanEvent.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + +use PayPal\Model\PaypalPlan; +use Thelia\Core\Event\ActionEvent; + +/** + * Class PayPalPlanEvent + * @package PayPal\Event + */ +class PayPalPlanEvent extends ActionEvent +{ + /** @var PaypalPlan */ + protected $payPalPlan; + + /** + * PayPalPlanEvent constructor. + * @param PaypalPlan $payPalPlan + */ + public function __construct(PaypalPlan $payPalPlan) + { + $this->payPalPlan = $payPalPlan; + } + + /** + * @return PaypalPlan + */ + public function getPayPalPlan() + { + return $this->payPalPlan; + } + + /** + * @param PaypalPlan $payPalPlan + * + * @return $this + */ + public function setPayPalPlan($payPalPlan) + { + $this->payPalPlan = $payPalPlan; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Event/PayPalPlanifiedPaymentEvent.php b/domokits/local/modules/PayPal/Event/PayPalPlanifiedPaymentEvent.php new file mode 100644 index 0000000..2e7988a --- /dev/null +++ b/domokits/local/modules/PayPal/Event/PayPalPlanifiedPaymentEvent.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Event; + +use PayPal\Model\PaypalPlanifiedPayment; +use Thelia\Core\Event\ActionEvent; + +/** + * Class PayPalPlanifiedPaymentEvent + * @package PayPal\Event + */ +class PayPalPlanifiedPaymentEvent extends ActionEvent +{ + /** @var PaypalPlanifiedPayment */ + protected $payPalPlanifiedPayment; + + /** + * PayPalPlanifiedPaymentEvent constructor. + * @param PaypalPlanifiedPayment $payPalPlanifiedPayment + */ + public function __construct(PaypalPlanifiedPayment $payPalPlanifiedPayment) + { + $this->payPalPlanifiedPayment = $payPalPlanifiedPayment; + } + + /** + * @return PaypalPlanifiedPayment + */ + public function getPayPalPlanifiedPayment() + { + return $this->payPalPlanifiedPayment; + } + + /** + * @param PaypalPlanifiedPayment $payPalPlanifiedPayment + * + * @return $this + */ + public function setPayPalPlanifiedPayment($payPalPlanifiedPayment) + { + $this->payPalPlanifiedPayment = $payPalPlanifiedPayment; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/Form/TheliaOrderPaymentForm.php b/domokits/local/modules/PayPal/EventListeners/Form/TheliaOrderPaymentForm.php new file mode 100644 index 0000000..84627b8 --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/Form/TheliaOrderPaymentForm.php @@ -0,0 +1,172 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners\Form; + +use PayPal\Form\PayPalFormFields; +use PayPal\Form\Type\PayPalCreditCardType; +use PayPal\Model\PaypalPlanifiedPayment; +use PayPal\Model\PaypalPlanifiedPaymentQuery; +use PayPal\PayPal; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Event\TheliaFormEvent; +use Thelia\Core\HttpFoundation\Session\Session; +use Thelia\Core\Translation\Translator; +use Thelia\Model\Cart; +use Thelia\Model\Country; +use Thelia\Model\Order; + +/** + * Class TheliaOrderPaymentForm + * @package PayPal\EventListeners\Form + */ +class TheliaOrderPaymentForm implements EventSubscriberInterface +{ + /** @var RequestStack */ + protected $requestStack; + + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** + * TheliaOrderPaymentForm constructor. + * @param RequestStack $requestStack + */ + public function __construct(RequestStack $requestStack, EventDispatcherInterface $dispatcher) + { + $this->requestStack = $requestStack; + $this->dispatcher = $dispatcher; + } + + /** + * @param TheliaFormEvent $event + */ + public function afterBuildTheliaOrderPayment(TheliaFormEvent $event) + { + $event->getForm()->getFormBuilder() + ->add( + PayPalFormFields::FIELD_PAYPAL_METHOD, + ChoiceType::class, + [ + 'choices' => [ + PayPal::PAYPAL_METHOD_PAYPAL => PayPal::PAYPAL_METHOD_PAYPAL, + PayPal::PAYPAL_METHOD_EXPRESS_CHECKOUT => PayPal::PAYPAL_METHOD_EXPRESS_CHECKOUT, + PayPal::PAYPAL_METHOD_CREDIT_CARD => PayPal::PAYPAL_METHOD_CREDIT_CARD, + PayPal::PAYPAL_METHOD_PLANIFIED_PAYMENT => PayPal::PAYPAL_METHOD_PLANIFIED_PAYMENT + ], + 'label' => Translator::getInstance()->trans('PayPal method', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PAYPAL_METHOD], + 'required' => false, + ] + ) + ->add( + PayPalCreditCardType::TYPE_NAME, + PayPalCreditCardType::class, + [ + 'label_attr' => [ + 'for' => PayPalCreditCardType::TYPE_NAME + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PAYPAL_PLANIFIED_PAYMENT, + ChoiceType::class, + [ + 'choices' => $this->getAllowedPlanifiedPayments(), + 'choice_label' => function ($value, $key, $index) { + return $value->getTitle(); + }, + 'choice_value' => function ($value) { + if ($value !== null) { + return $value->getId(); + } + + return null; + }, + "required" => false, + 'empty_data' => null, + 'label' => Translator::getInstance()->trans('Frequency', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PAYPAL_PLANIFIED_PAYMENT], + ] + ) + ; + } + + /** + * @return array|mixed|\Propel\Runtime\Collection\ObjectCollection + */ + protected function getAllowedPlanifiedPayments() + { + /** @var Session $session */ + $session = $this->requestStack->getCurrentRequest()->getSession(); + + /** @var \Thelia\Model\Lang $lang */ + $lang = $session->getLang(); + + /** @var Cart $cart */ + $cart = $session->getSessionCart($this->dispatcher); + + /** @var Order $order */ + $order = $session->get('thelia.order'); + + $country = Country::getDefaultCountry(); + + $planifiedPayments = (new PaypalPlanifiedPaymentQuery())->joinWithI18n($lang->getLocale())->find(); + if (null !== $cart && null !== $order && null !== $country) { + $totalAmount = $cart->getTaxedAmount($country) + (float)$order->getPostage(); + + $restrictedPlanifiedAmounts = []; + /** @var PaypalPlanifiedPayment $planifiedPayment */ + foreach ($planifiedPayments as $planifiedPayment) { + + if ($planifiedPayment->getMinAmount() > 0 && $planifiedPayment->getMinAmount() > $totalAmount) { + continue; + } + + if ($planifiedPayment->getMaxAmount() > 0 && $planifiedPayment->getMaxAmount() < $totalAmount) { + continue; + } + + $restrictedPlanifiedAmounts[] = $planifiedPayment; + } + + $planifiedPayments = $restrictedPlanifiedAmounts; + } + + return $planifiedPayments; + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::FORM_AFTER_BUILD . '.thelia_order_payment' => ['afterBuildTheliaOrderPayment', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/OrderListener.php b/domokits/local/modules/PayPal/EventListeners/OrderListener.php new file mode 100644 index 0000000..c288f9c --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/OrderListener.php @@ -0,0 +1,273 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalCartEvent; +use PayPal\Event\PayPalEvents; +use PayPal\Form\PayPalFormFields; +use PayPal\Form\Type\PayPalCreditCardType; +use PayPal\PayPal; +use PayPal\Service\PayPalAgreementService; +use PayPal\Service\PayPalPaymentService; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Mailer\MailerFactory; + +/** + * Class OrderListener + * @package PayPal\EventListeners + */ +class OrderListener implements EventSubscriberInterface +{ + /** @var MailerFactory */ + protected $mailer; + + /** @var EventDispatcherInterface */ + protected $dispatcher; + + /** @var RequestStack */ + protected $requestStack; + + /** @var PayPalPaymentService */ + protected $payPalPaymentService; + + /** @var PayPalAgreementService */ + protected $payPalAgreementService; + + /** + * @param MailerFactory $mailer + * @param EventDispatcherInterface $dispatcher + * @param RequestStack $requestStack + * @param PayPalPaymentService $payPalPaymentService + * @param PayPalAgreementService $payPalAgreementService + */ + public function __construct(MailerFactory $mailer, EventDispatcherInterface $dispatcher, RequestStack $requestStack, PayPalPaymentService $payPalPaymentService, PayPalAgreementService $payPalAgreementService) + { + $this->dispatcher = $dispatcher; + $this->mailer = $mailer; + $this->requestStack = $requestStack; + $this->payPalPaymentService = $payPalPaymentService; + $this->payPalAgreementService = $payPalAgreementService; + } + + /** + * @param OrderEvent $event + */ + public function CancelPayPalTransaction(OrderEvent $event) + { + // @TODO : Inform PayPal that this payment is canceled ? + } + + /** + * @param OrderEvent $event + * + * @throws \Exception if the message cannot be loaded. + */ + public function sendConfirmationEmail(OrderEvent $event) + { + if (PayPal::getConfigValue('send_confirmation_message_only_if_paid')) { + // We send the order confirmation email only if the order is paid + $order = $event->getOrder(); + + if (! $order->isPaid() && $order->getPaymentModuleId() == Paypal::getModuleId()) { + $event->stopPropagation(); + } + } + } + + /** + * Checks if order payment module is paypal and if order new status is paid, send an email to the customer. + * + * @param OrderEvent $event + */ + public function updateStatus(OrderEvent $event) + { + $order = $event->getOrder(); + + if ($order->isPaid() && $order->getPaymentModuleId() === Paypal::getModuleId()) { + if (Paypal::getConfigValue('send_payment_confirmation_message')) { + $this->mailer->sendEmailToCustomer( + PayPal::CONFIRMATION_MESSAGE_NAME, + $order->getCustomer(), + [ + 'order_id' => $order->getId(), + 'order_ref' => $order->getRef() + ] + ); + } + + // Send confirmation email if required. + if (Paypal::getConfigValue('send_confirmation_message_only_if_paid')) { + $this->dispatcher->dispatch($event, TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL); + } + } + } + + /** + * @param OrderEvent $event + * @throws \Exception + */ + public function checkPayPalMethod(OrderEvent $event) + { + //First be sure that there is no OLD CREDIT card saved in paypal_cart because of fatal error + $payPalCartEvent = new PayPalCartEvent($this->payPalPaymentService->getCurrentPayPalCart()); + $this->dispatcher->dispatch($payPalCartEvent, PayPalEvents::PAYPAL_CART_DELETE); + + $postedData = $this->requestStack->getCurrentRequest()->get('thelia_order_payment'); + + if (isset($postedData[PayPalFormFields::FIELD_PAYMENT_MODULE]) && PayPal::getModuleId() === $event->getOrder()->getPaymentModuleId()) { + $this->usePayPalMethod($postedData); + } + } + + /** + * @param OrderEvent $event + */ + public function recursivePayment(OrderEvent $event) + { + $this->payPalAgreementService->duplicateOrder($event->getOrder()); + + if (PayPal::getConfigValue('send_recursive_message')) { + $this->mailer->sendEmailToCustomer( + PayPal::RECURSIVE_MESSAGE_NAME, + $event->getOrder()->getCustomer(), + [ + 'order_id' => $event->getOrder()->getId(), + 'order_ref' => $event->getOrder()->getRef() + ] + ); + } + } + + /** + * @param array $postedData + */ + protected function usePayPalMethod($postedData = []) + { + if (isset($postedData[PayPalFormFields::FIELD_PAYPAL_METHOD])) { + $payPalMethod = $postedData[PayPalFormFields::FIELD_PAYPAL_METHOD]; + + switch ($payPalMethod) { + case PayPal::PAYPAL_METHOD_CREDIT_CARD: + $this->usePayPalCreditCardMethod($postedData); + break; + + case PayPal::PAYPAL_METHOD_PLANIFIED_PAYMENT: + $this->usePayPalPlanifiedPaymentMethod($postedData); + break; + } + } + } + + /** + * @param array $postedData + * @throws \Exception + */ + protected function usePayPalCreditCardMethod($postedData = []) + { + if ($this->isValidPaidByPayPalCreditCard($postedData)) { + //save credit card in cart because we will need it in pay() method for payment module + + $creditCardId = $this->payPalPaymentService->getPayPalCreditCardId( + $postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_TYPE], + $postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_NUMBER], + $postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_MONTH], + $postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_YEAR], + $postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_CVV] + ); + + $payPalCart = $this->payPalPaymentService->getCurrentPayPalCart(); + $payPalCart->setCreditCardId($creditCardId); + $payPalCartEvent = new PayPalCartEvent($payPalCart); + $this->dispatcher->dispatch($payPalCartEvent, PayPalEvents::PAYPAL_CART_UPDATE); + } + } + + /** + * @param array $postedData + * @return bool + */ + protected function isValidPaidByPayPalCreditCard($postedData = []) + { + $isValid = false; + + if (isset($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_TYPE]) && $this->isNotBlank($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_TYPE]) && + isset($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_NUMBER]) && $this->isNotBlank($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_NUMBER]) && + isset($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_MONTH]) && $this->isNotBlank($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_MONTH]) && + isset($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_YEAR]) && $this->isNotBlank($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_EXPIRE_YEAR]) && + isset($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_CVV]) && $this->isNotBlank($postedData[PayPalCreditCardType::TYPE_NAME][PayPalFormFields::FIELD_CARD_CVV])) { + $isValid = true; + } + + return $isValid; + } + + /** + * @param array $postedData + */ + protected function usePayPalPlanifiedPaymentMethod($postedData = []) + { + if (isset($postedData[PayPalFormFields::FIELD_PAYPAL_PLANIFIED_PAYMENT]) && + $this->isNotBlank($postedData[PayPalFormFields::FIELD_PAYPAL_PLANIFIED_PAYMENT])) { + + $payPalCart = $this->payPalPaymentService->getCurrentPayPalCart(); + $payPalCart->setPlanifiedPaymentId($postedData[PayPalFormFields::FIELD_PAYPAL_PLANIFIED_PAYMENT]); + $payPalCartEvent = new PayPalCartEvent($payPalCart); + $this->dispatcher->dispatch($payPalCartEvent, PayPalEvents::PAYPAL_CART_UPDATE); + + } + } + + /** + * @param $value + * @return bool + */ + protected function isNotBlank($value) + { + if (false === $value || (empty($value) && '0' != $value)) { + return false; + } + + return true; + } + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_UPDATE_STATUS => [ + ['CancelPayPalTransaction', 128], + ['updateStatus', 128], + ], + TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL => ['sendConfirmationEmail', 129], + TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL => ['sendConfirmationEmail', 129], + TheliaEvents::ORDER_SET_PAYMENT_MODULE => ['checkPayPalMethod', 120], + PayPalEvents::PAYPAL_RECURSIVE_PAYMENT_CREATE => ['recursivePayment', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/PayPalCartListener.php b/domokits/local/modules/PayPal/EventListeners/PayPalCartListener.php new file mode 100644 index 0000000..69a0430 --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/PayPalCartListener.php @@ -0,0 +1,67 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalCartEvent; +use PayPal\Event\PayPalEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Class PayPalCartListener + * @package PayPal\EventListeners + */ +class PayPalCartListener implements EventSubscriberInterface +{ + /** + * @param PayPalCartEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createOrUpdate(PayPalCartEvent $event) + { + $event->getPayPalCart()->save(); + } + + /** + * @param PayPalCartEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function delete(PayPalCartEvent $event) + { + $event->getPayPalCart()->delete(); + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + PayPalEvents::PAYPAL_CART_CREATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_CART_UPDATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_CART_DELETE => ['delete', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/PayPalCustomerListener.php b/domokits/local/modules/PayPal/EventListeners/PayPalCustomerListener.php new file mode 100644 index 0000000..58cc1f7 --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/PayPalCustomerListener.php @@ -0,0 +1,87 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalCustomerEvent; +use PayPal\Event\PayPalEvents; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Class PayPalCustomerListener + * @package PayPal\EventListeners + */ +class PayPalCustomerListener implements EventSubscriberInterface +{ + /** @var RequestStack */ + protected $requestStack; + + /** @var EventDispatcher */ + protected $dispatcher; + + /** + * PayPalCustomerListener constructor. + * @param RequestStack $requestStack + * @param EventDispatcher $dispatcher + */ + public function __construct(RequestStack $requestStack, EventDispatcherInterface $dispatcher) + { + $this->requestStack = $requestStack; + $this->dispatcher = $dispatcher; + } + + /** + * @param PayPalCustomerEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createOrUpdate(PayPalCustomerEvent $event) + { + $event->getPayPalCustomer()->save(); + } + + /** + * @param PayPalCustomerEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function delete(PayPalCustomerEvent $event) + { + $event->getPayPalCustomer()->delete(); + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + PayPalEvents::PAYPAL_CUSTOMER_CREATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_CUSTOMER_UPDATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_CUSTOMER_DELETE => ['delete', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/PayPalOrderListener.php b/domokits/local/modules/PayPal/EventListeners/PayPalOrderListener.php new file mode 100644 index 0000000..b8b021f --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/PayPalOrderListener.php @@ -0,0 +1,68 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalOrderEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + + +/** + * Class PayPalOrderListener + * @package PayPal\EventListeners + */ +class PayPalOrderListener implements EventSubscriberInterface +{ + /** + * @param PayPalOrderEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createOrUpdate(PayPalOrderEvent $event) + { + $event->getPayPalOrder()->save(); + } + + /** + * @param PayPalOrderEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function delete(PayPalOrderEvent $event) + { + $event->getPayPalOrder()->delete(); + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + PayPalEvents::PAYPAL_ORDER_CREATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_ORDER_UPDATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_ORDER_DELETE => ['delete', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/PayPalPlanListener.php b/domokits/local/modules/PayPal/EventListeners/PayPalPlanListener.php new file mode 100644 index 0000000..be9f093 --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/PayPalPlanListener.php @@ -0,0 +1,63 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalPlanEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class PayPalPlanListener implements EventSubscriberInterface +{ + /** + * @param PayPalPlanEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createOrUpdate(PayPalPlanEvent $event) + { + $event->getPayPalPlan()->save(); + } + + /** + * @param PayPalPlanEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function delete(PayPalPlanEvent $event) + { + $event->getPayPalPlan()->delete(); + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + PayPalEvents::PAYPAL_PLAN_CREATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_PLAN_UPDATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_PLAN_DELETE => ['delete', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/EventListeners/PayPalPlanifiedPaymentListener.php b/domokits/local/modules/PayPal/EventListeners/PayPalPlanifiedPaymentListener.php new file mode 100644 index 0000000..9118a5c --- /dev/null +++ b/domokits/local/modules/PayPal/EventListeners/PayPalPlanifiedPaymentListener.php @@ -0,0 +1,63 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\EventListeners; + +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalPlanifiedPaymentEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class PayPalPlanifiedPaymentListener implements EventSubscriberInterface +{ + /** + * @param PayPalPlanifiedPaymentEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function createOrUpdate(PayPalPlanifiedPaymentEvent $event) + { + $event->getPayPalPlanifiedPayment()->save(); + } + + /** + * @param PayPalPlanifiedPaymentEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function delete(PayPalPlanifiedPaymentEvent $event) + { + $event->getPayPalPlanifiedPayment()->delete(); + } + + /** + * @return array The event names to listen to + */ + public static function getSubscribedEvents() + { + return [ + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_CREATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_UPDATE => ['createOrUpdate', 128], + PayPalEvents::PAYPAL_PLANIFIED_PAYMENT_DELETE => ['delete', 128] + ]; + } +} diff --git a/domokits/local/modules/PayPal/Form/ConfigurationForm.php b/domokits/local/modules/PayPal/Form/ConfigurationForm.php new file mode 100644 index 0000000..a1cf3be --- /dev/null +++ b/domokits/local/modules/PayPal/Form/ConfigurationForm.php @@ -0,0 +1,333 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Form; + +use PayPal\PayPal; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\TextareaType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Form\BaseForm; + +/** + * Class ConfigurePaypal + * @package Paypal\Form + * @author Thelia + */ +class ConfigurationForm extends BaseForm +{ + const FORM_NAME = 'paypal_form_configure'; + + protected function buildForm() + { + $this->formBuilder + ->add( + 'login', + TextType::class, + [ + 'constraints' => [ new NotBlank() ], + 'label' => $this->translator->trans('login', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('Your Paypal login', [], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'password', + TextType::class, + [ + 'constraints' => [ new NotBlank() ], + 'label' => $this->translator->trans('password', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('Your Paypal password', [], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'merchant_id', + TextType::class, + [ + 'label' => $this->translator->trans('Merchant ID', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('The Paypal identity merchant account', ['%url' => 'https://www.paypal.com/businessprofile/settings/'], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'sandbox', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Activate sandbox mode', [], PayPal::DOMAIN_NAME), + ] + ) + ->add( + 'sandbox_login', + TextType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('login', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('Your Paypal sandbox login', [], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'sandbox_password', + TextType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('password', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('Your Paypal sandbox password', [], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'sandbox_merchant_id', + TextType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Merchant ID', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans('The Paypal identity merchant account', ['%url' => 'https://www.paypal.com/businessprofile/settings/'], PayPal::DOMAIN_NAME) + ] + ] + ) + ->add( + 'allowed_ip_list', + TextareaType::class, + [ + 'required' => false, + 'label' => $this->translator->trans('Allowed IPs in test mode', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'List of IP addresses allowed to use this payment on the front-office when in test mode (your current IP is %ip). One address per line', + [ '%ip' => $this->getRequest()->getClientIp() ], + PayPal::DOMAIN_NAME + ) + ], + 'attr' => [ + 'rows' => 3 + ] + ] + ) + ->add( + 'minimum_amount', + TextType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(array('value' => 0)) + ], + 'required' => false, + 'label' => $this->translator->trans('Minimum order total', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'Minimum order total in the default currency for which this payment method is available. Enter 0 for no minimum', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'maximum_amount', + TextType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(array('value' => 0)) + ], + 'required' => false, + 'label' => $this->translator->trans('Maximum order total', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'Maximum order total in the default currency for which this payment method is available. Enter 0 for no maximum', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'cart_item_count', + TextType::class, + [ + 'constraints' => [ + new NotBlank(), + new GreaterThanOrEqual(array('value' => 0)) + ], + 'required' => false, + 'label' => $this->translator->trans('Maximum items in cart', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'Maximum number of items in the customer cart for which this payment method is available.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'method_paypal', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Activate payment with PayPal account', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, the order can be paid by PayPal account.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'method_paypal_with_in_context', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Use InContext mode for classic PayPal payment', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, a PayPal popup will be used to execute the payment.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'method_express_checkout', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Activate Express Checkout payment with PayPal', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, the order can be paid directly from cart.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'method_credit_card', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Activate payment with credit card', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, the order can be paid by credit card.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'method_planified_payment', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Activate payment with planified payment', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, the order can be paid by planified payement.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'send_confirmation_message_only_if_paid', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Send order confirmation on payment success', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'send_payment_confirmation_message', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Send a payment confirmation e-mail', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, a payment confirmation e-mail is sent to the customer.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ->add( + 'send_recursive_message', + CheckboxType::class, + [ + 'value' => 1, + 'required' => false, + 'label' => $this->translator->trans('Send a recursive payment confirmation e-mail', [], PayPal::DOMAIN_NAME), + 'label_attr' => [ + 'help' => $this->translator->trans( + 'If checked, a payment confirmation e-mail is sent to the customer after each PayPal transaction.', + [], + PayPal::DOMAIN_NAME + ) + ] + ] + ) + ; + } + + /** + * @return string the name of your form. This name must be unique + */ + public static function getName() + { + return self::FORM_NAME; + } +} diff --git a/domokits/local/modules/PayPal/Form/PayPalFormFields.php b/domokits/local/modules/PayPal/Form/PayPalFormFields.php new file mode 100644 index 0000000..6af046e --- /dev/null +++ b/domokits/local/modules/PayPal/Form/PayPalFormFields.php @@ -0,0 +1,56 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Form; + +/** + * Class PayPalFormFields + * @package PayPal\Form + */ +class PayPalFormFields +{ + const FIELD_PAYMENT_MODULE = 'payment-module'; + + // \Thelia\Form\OrderPayment + const FIELD_PAYPAL_METHOD = 'paypal_method'; + const FIELD_PAYPAL_PLANIFIED_PAYMENT = 'paypal_planified_payment'; + + // \Form\Type\PayPalCreditCardType + const FIELD_CARD_TYPE = 'card_type'; + const FIELD_CARD_NUMBER = 'card_number'; + const FIELD_CARD_EXPIRE_MONTH = 'card_expire_month'; + const FIELD_CARD_EXPIRE_YEAR = 'card_expire_year'; + const FIELD_CARD_CVV = 'card_cvv'; + + // \Form\PayPalPlanifiedPaymentForm + const FIELD_PP_ID = 'id'; + const FIELD_PP_LOCALE = 'locale'; + const FIELD_PP_TITLE = 'title'; + const FIELD_PP_DESCRIPTION = 'description'; + const FIELD_PP_FREQUENCY = 'frequency'; + const FIELD_PP_FREQUENCY_INTERVAL = 'frequency_interval'; + const FIELD_PP_CYCLE = 'cycle'; + const FIELD_PP_MIN_AMOUNT = 'min_amount'; + const FIELD_PP_MAX_AMOUNT = 'max_amount'; + const FIELD_PP_POSITION = 'position'; +} diff --git a/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentCreateForm.php b/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentCreateForm.php new file mode 100644 index 0000000..2570bb0 --- /dev/null +++ b/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentCreateForm.php @@ -0,0 +1,194 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Form; + +use PayPal\PayPal; +use PayPal\Service\PayPalAgreementService; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\NumberType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\Validator\Constraints\GreaterThanOrEqual; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Core\Translation\Translator; +use Thelia\Form\BaseForm; + +class PayPalPlanifiedPaymentCreateForm extends BaseForm +{ + const FORM_NAME = 'paypal_planified_payment_create_form'; + + /** + * @return null + */ + protected function buildForm() + { + /** @var \Thelia\Model\Lang $lang */ + $lang = $this->getRequest()->getSession()->get('thelia.current.lang'); + + $this->formBuilder + ->add( + PayPalFormFields::FIELD_PP_LOCALE, + HiddenType::class, + [ + 'constraints' => [ + new NotBlank() + ], + 'required' => true, + 'data' => $lang->getLocale(), + 'label' => $this->trans('The locale of the planified payment'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_LOCALE] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_TITLE, + TextType::class, + [ + 'constraints' => [ + new NotBlank() + ], + 'required' => true, + 'label' => $this->trans('The title of the planified payment'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_TITLE] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_DESCRIPTION, + TextType::class, + [ + 'required' => false, + 'label' => $this->trans('The description of the planified payment'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_DESCRIPTION] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_FREQUENCY_INTERVAL, + IntegerType::class, + [ + 'label' => $this->trans('Frequency interval'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_FREQUENCY_INTERVAL], + 'required' => true, + 'constraints' => [ + new NotBlank(), + new GreaterThan(['value' => 0]) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_FREQUENCY, + ChoiceType::class, + [ + 'choices' => array_flip(PayPalAgreementService::getAllowedPaymentFrequency()), + 'label' => $this->trans('Frequency'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_FREQUENCY], + 'required' => true, + 'constraints' => [ + new NotBlank() + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_CYCLE, + IntegerType::class, + [ + 'label' => $this->trans('Cycle'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_CYCLE], + 'required' => true, + 'constraints' => [ + new NotBlank(), + new GreaterThan(['value' => 0]) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_MIN_AMOUNT, + NumberType::class, + [ + 'label' => $this->trans('Min amount'), + 'label_attr' => [ + 'for' => PayPalFormFields::FIELD_PP_MIN_AMOUNT, + 'help' => $this->trans("Let value to 0 if you don't want a minimum") + ], + 'required' => false, + 'constraints' => [ + new GreaterThanOrEqual(['value' => 0]) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_MAX_AMOUNT, + NumberType::class, + [ + 'label' => $this->trans('Max amount'), + 'label_attr' => [ + 'for' => PayPalFormFields::FIELD_PP_MAX_AMOUNT, + 'help' => $this->trans("Let value to 0 if you don't want a maximum") + ], + 'required' => false, + 'constraints' => [ + new GreaterThanOrEqual(['value' => 0]) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_PP_POSITION, + IntegerType::class, + [ + 'label' => $this->trans('Position'), + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_POSITION], + 'required' => false + ] + ) + ; + } + + /** + * @return string the name of your form. This name must be unique + */ + public static function getName() + { + return self::FORM_NAME; + } + + /** + * Translates the given message. + * + * @param string $id The message id (may also be an object that can be cast to string) + * @param array $parameters An array of parameters for the message + * @param string|null $domain The domain for the message or null to use the default + * + * @throws \InvalidArgumentException If the locale contains invalid characters + * + * @return string The translated string + */ + protected function trans($id, array $parameters = [], $domain = null) + { + return Translator::getInstance()->trans( + $id, + $parameters, + $domain === null ? PayPal::DOMAIN_NAME : $domain + ); + } +} diff --git a/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentUpdateForm.php b/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentUpdateForm.php new file mode 100644 index 0000000..d0ef1b7 --- /dev/null +++ b/domokits/local/modules/PayPal/Form/PayPalPlanifiedPaymentUpdateForm.php @@ -0,0 +1,56 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Form; + +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Component\Validator\Constraints\NotBlank; + +class PayPalPlanifiedPaymentUpdateForm extends PayPalPlanifiedPaymentCreateForm +{ + const FORM_NAME = 'paypal_planified_payment_update_form'; + + protected function buildForm() + { + parent::buildForm(); + + $this->formBuilder + ->add( + PayPalFormFields::FIELD_PP_ID, + HiddenType::class, + [ + 'required' => true, + 'label_attr' => ['for' => PayPalFormFields::FIELD_PP_ID], + 'constraints' => [ + new NotBlank() + ] + ] + ) + ; + } + + public static function getName() + { + return self::FORM_NAME; + } +} diff --git a/domokits/local/modules/PayPal/Form/Type/PayPalCreditCardType.php b/domokits/local/modules/PayPal/Form/Type/PayPalCreditCardType.php new file mode 100644 index 0000000..4a37abf --- /dev/null +++ b/domokits/local/modules/PayPal/Form/Type/PayPalCreditCardType.php @@ -0,0 +1,247 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Form\Type; + +use PayPal\Form\PayPalFormFields; +use PayPal\PayPal; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Validator\Constraints\Callback; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Thelia\Core\Form\Type\AbstractTheliaType; +use Thelia\Core\Translation\Translator; + + +/** + * Class PayPalCreditCardType + * @package PayPal\Form\Type + */ +class PayPalCreditCardType extends AbstractTheliaType +{ + const TYPE_NAME = 'paypal_credit_card_type'; + + /** + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add( + PayPalFormFields::FIELD_CARD_TYPE, + ChoiceType::class, + [ + 'choices' => $this->getTypes(), + 'label' => Translator::getInstance()->trans('Card type', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_CARD_TYPE], + 'required' => false, + 'constraints' => [ + new Callback( + [$this, 'verifyCardType'] + ) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_CARD_NUMBER, + TextType::class, + [ + 'label' => Translator::getInstance()->trans('Card number', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_CARD_NUMBER], + 'required' => false, + 'constraints' => [ + new Callback( + [$this, 'verifyCardNumber'] + ) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_CARD_EXPIRE_MONTH, + ChoiceType::class, + [ + 'choices' => $this->getMonths(), + 'label' => Translator::getInstance()->trans('Expire month', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_CARD_EXPIRE_MONTH], + 'required' => false, + 'constraints' => [ + new Callback( + [$this, 'verifyCardExpireMonth'] + ) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_CARD_EXPIRE_YEAR, + ChoiceType::class, + [ + 'choices' => $this->getYears(), + 'label' => Translator::getInstance()->trans('Expire year', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_CARD_EXPIRE_YEAR], + 'required' => false, + 'constraints' => [ + new Callback( + [$this, 'verifyCardExpireYear'] + ) + ] + ] + ) + ->add( + PayPalFormFields::FIELD_CARD_CVV, + TextType::class, + [ + 'label' => Translator::getInstance()->trans('CVV', [], PayPal::DOMAIN_NAME), + 'label_attr' => ['for' => PayPalFormFields::FIELD_CARD_CVV], + 'required' => false, + 'constraints' => [ + new Callback( + [$this, 'verifyCardCVV'] + ) + ] + ] + ) + ; + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + public function verifyCardType($value, ExecutionContextInterface $context) + { + $this->checkNotBlank($value, $context); + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + public function verifyCardNumber($value, ExecutionContextInterface $context) + { + $this->checkNotBlank($value, $context); + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + public function verifyCardExpireMonth($value, ExecutionContextInterface $context) + { + $this->checkNotBlank($value, $context); + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + public function verifyCardExpireYear($value, ExecutionContextInterface $context) + { + $this->checkNotBlank($value, $context); + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + public function verifyCardCVV($value, ExecutionContextInterface $context) + { + $this->checkNotBlank($value, $context); + } + + /** + * @param $value + * @param ExecutionContextInterface $context + */ + protected function checkNotBlank($value, ExecutionContextInterface $context) + { + $data = $context->getRoot()->getData(); + if (isset($data[PayPalFormFields::FIELD_PAYMENT_MODULE]) && PayPal::getModuleId() === $data[PayPalFormFields::FIELD_PAYMENT_MODULE]) { + if (isset($data[PayPalFormFields::FIELD_PAYPAL_METHOD]) && PayPal::PAYPAL_METHOD_CREDIT_CARD === $data[PayPalFormFields::FIELD_PAYPAL_METHOD]) { + if (false === $value || (empty($value) && '0' != $value)) { + $context->addViolation( + Translator::getInstance()->trans('This value should not be blank', [], PayPal::DOMAIN_NAME) + ); + } + } + } + } + + /** + * @inheritDoc + */ + public static function getName() + { + return self::TYPE_NAME; + } + + /** + * @return array + */ + protected function getTypes() + { + return [ + 'Visa' => PayPal::CREDIT_CARD_TYPE_VISA, + 'MasterCard' => PayPal::CREDIT_CARD_TYPE_MASTERCARD, + 'Discover' => PayPal::CREDIT_CARD_TYPE_DISCOVER, + 'Amex' => PayPal::CREDIT_CARD_TYPE_AMEX + ]; + } + + /** + * @return array + */ + protected function getMonths() + { + return [ + '01' => 1, + '02' => 2, + '03' => 3, + '04' => 4, + '05' => 5, + '06' => 6, + '07' => 7, + '08' => 8, + '09' => 9, + '10' => 10, + '11' => 11, + '12' => 12 + ]; + } + + /** + * @return array + */ + protected function getYears() + { + $actualYear = date("Y"); + + $years = []; + $years[(int)$actualYear] = $actualYear; + for ($i = 1; $i <= 10; $i++) { + $years[$actualYear + $i] = (int)($actualYear + $i); + } + return $years; + } +} diff --git a/domokits/local/modules/PayPal/Hook/BackHookManager.php b/domokits/local/modules/PayPal/Hook/BackHookManager.php new file mode 100644 index 0000000..4c477e7 --- /dev/null +++ b/domokits/local/modules/PayPal/Hook/BackHookManager.php @@ -0,0 +1,95 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Hook; + +use PayPal\Model\PaypalOrderQuery; +use PayPal\PayPal; +use PayPal\Service\Base\PayPalBaseService; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Model\ModuleConfig; +use Thelia\Model\ModuleConfigQuery; + + +/** + * Class BackHookManager + * @package PayPal\Hook + */ +class BackHookManager extends BaseHook +{ + /** + * @param HookRenderEvent $event + */ + public function onModuleConfigure(HookRenderEvent $event) + { + $vars = []; + if (null !== $moduleConfigs = ModuleConfigQuery::create()->findByModuleId(PayPal::getModuleId())) { + /** @var ModuleConfig $moduleConfig */ + foreach ($moduleConfigs as $moduleConfig) { + $vars[ $moduleConfig->getName() ] = $moduleConfig->getValue(); + } + } + + $vars['paypal_appid'] = PayPalBaseService::getLogin(); + $vars['paypal_authend'] = PayPalBaseService::getMode(); + + $event->add( + $this->render('paypal/module-configuration.html', $vars) + ); + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderEditPaymentModuleBottom(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + + if (null !== $payPalOrder = PaypalOrderQuery::create()->findOneById($event->getArgument('order_id'))) { + $event->add( + $this->render( + 'paypal/payment-information.html', + $templateData + ) + ); + } + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderEditJs(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + + if (null !== $payPalOrder = PaypalOrderQuery::create()->findOneById($event->getArgument('order_id'))) { + $event->add( + $this->render( + 'paypal/order-edit-js.html', + $templateData + ) + ); + } + } +} diff --git a/domokits/local/modules/PayPal/Hook/FrontHookManager.php b/domokits/local/modules/PayPal/Hook/FrontHookManager.php new file mode 100644 index 0000000..421a41b --- /dev/null +++ b/domokits/local/modules/PayPal/Hook/FrontHookManager.php @@ -0,0 +1,208 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Hook; + +use PayPal\Model\PaypalCartQuery; +use PayPal\PayPal; +use PayPal\Service\Base\PayPalBaseService; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Core\HttpFoundation\Session\Session; + + +/** + * Class FrontHookManager + * @package PayPal\Hook + */ +class FrontHookManager extends BaseHook +{ + /** @var RequestStack */ + protected $requestStack; + + /** @var ContainerInterface */ + protected $container; + + /** + * FrontHookManager constructor. + * @param RequestStack $requestStack + * @param ContainerInterface $container + */ + public function __construct(RequestStack $requestStack, ContainerInterface $container) + { + $this->requestStack = $requestStack; + $this->container = $container; + } + + /** + * @param HookRenderEvent $event + */ + public function onLoginMainBottom(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + $templateData['paypal_appid'] = PayPalBaseService::getLogin(); + $templateData['paypal_authend'] = PayPalBaseService::getMode(); + + $event->add( + $this->render( + 'paypal/login-bottom.html', + $templateData + ) + ); + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderInvoicePaymentExtra(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + $templateData['method_paypal_with_in_context'] = PayPal::getConfigValue('method_paypal_with_in_context'); + $event->add( + $this->render( + 'paypal/order-invoice-payment-extra.html', + $templateData + ) + ); + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderInvoiceBottom(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + $templateData['paypal_mode'] = PayPalBaseService::getMode(); + $templateData['paypal_merchantid'] = PayPalBaseService::getMerchantId(); + + $event->add( + $this->render( + 'paypal/order-invoice-bottom.html', + $templateData + ) + ); + } + + public function onOrderInvoiceJavascriptInitialization(HookRenderEvent $event) + { + $render = $this->render( + 'paypal/order-invoice-js.html', + [ + 'module_id' => PayPal::getModuleId(), + ] + ); + + $event->add($render); + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderPlacedAdditionalPaymentInfo(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + $event->add( + $this->render( + 'paypal/order-placed-additional-payment-info.html', + $templateData + ) + ); + } + + /** + * @param HookRenderEvent $event + */ + public function onCartBottom(HookRenderEvent $event) + { + $payPal = new PayPal(); + $payPal->setContainer($this->container); + + if (PayPal::getConfigValue('method_express_checkout') == 1 && $payPal->isValidPayment()) { + $templateData = $event->getArguments(); + $templateData['paypal_mode'] = PayPalBaseService::getMode(); + $templateData['paypal_merchantid'] = PayPalBaseService::getMerchantId(); + $event->add( + $this->render( + 'paypal/cart-bottom.html', + $templateData + ) + ); + } + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderDeliveryFormBottom(HookRenderEvent $event) + { + if ($this->isValidExpressCheckout()) { + $templateData = $event->getArguments(); + $event->add( + $this->render( + 'paypal/order-delivery-bottom.html', + $templateData + ) + ); + } + } + + /** + * @param HookRenderEvent $event + */ + public function onOrderAfterJavascriptInclude(HookRenderEvent $event) + { + if ($this->isValidExpressCheckout()) { + $templateData = $event->getArguments(); + $event->add( + $this->render( + 'paypal/order-delivery-bottom-js.html', + $templateData + ) + ); + } + } + + protected function isValidExpressCheckout() + { + $isValid = false; + + /** @var Session $session */ + $session = $this->requestStack->getCurrentRequest()->getSession(); + $cart = $session->getSessionCart($this->dispatcher); + + $payPal = new PayPal(); + $payPal->setContainer($this->container); + + if (PayPal::getConfigValue('method_express_checkout') == 1 && $payPal->isValidPayment()) { + if (null !== $payPalCart = PaypalCartQuery::create()->findOneById($cart->getId())) { + if ($payPalCart->getExpressPaymentId() && $payPalCart->getExpressPayerId() && $payPalCart->getExpressToken()) { + $isValid = true; + } + } + } + + return $isValid; + } +} diff --git a/domokits/local/modules/PayPal/Hook/PdfHookManager.php b/domokits/local/modules/PayPal/Hook/PdfHookManager.php new file mode 100644 index 0000000..18f9b35 --- /dev/null +++ b/domokits/local/modules/PayPal/Hook/PdfHookManager.php @@ -0,0 +1,45 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +class PdfHookManager extends BaseHook +{ + /** + * @param HookRenderEvent $event + */ + public function onAfterPaymentModule(HookRenderEvent $event) + { + $templateData = $event->getArguments(); + + $event->add( + $this->render( + 'paypal/after-payment-module.html', + $templateData + ) + ); + } +} diff --git a/domokits/local/modules/PayPal/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/PayPal/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..62e1d8f --- /dev/null +++ b/domokits/local/modules/PayPal/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,92 @@ + 'Accept payments', + 'Account Information' => 'Account Information', + 'Add Webhook' => 'Cliquer sur Add Webhook', + 'Add a planified payment' => 'Ajouter un paiement planifié', + 'Address Information' => 'Address Information', + 'Allow the customers who haven\'t yet confirmed their email address with PayPal, to log in to your app' => 'Allow the customers who haven\'t yet confirmed their email address with PayPal, to log in to your app', + 'And configure it like the SandBox' => 'Et remplissez le formulaire comme pour la SandBox', + 'Automatic PayPal logs' => 'Logs automatiques de PayPal', + 'Billing agreements' => 'Billing agreements', + 'Check' => 'Cocher', + 'Click on Create App' => ' Cliquer sur Create App ', + 'Click on the Live Button' => 'Cliquer sur le button "Live"', + 'Configuration' => 'Configuration', + 'Copy & Paste the Client ID in the form below' => 'Copier & Coller le Client ID dans le formulaire ci-dessous', + 'Copy & Paste the Client SECRET in the form below' => 'Copier & Coller le Client SECRET dans le formulaire ci-dessous', + 'Create REST API apps here' => 'Créer une REST API apps ici ', + 'Create a new planified payment' => 'Créer un nouveau paiement récurrent', + 'Create this planified payment' => 'Créer ce paiement récurrent', + 'critical_100' => 'DEBUG', + 'critical_200' => 'INFO', + 'critical_250' => 'NOTICE', + 'critical_300' => 'WARNING', + 'critical_400' => 'ERROR', + 'critical_500' => 'CRITICAL', + 'critical_550' => 'ALERT', + 'critical_600' => 'EMERGENCY', + 'Customer ID' => 'Client ID', + 'Cycle' => 'Cycle', + 'Date' => 'Date', + 'Delete planified payment' => 'Supprimer paiement récurrent', + 'Delete this planified payment' => 'Supprimer ce paiement récurrent', + 'Details' => 'Détails', + 'Do you really want to delete this planified payment ?' => 'Voulez-vous vraiment supprimer ce paiement récurrent ?', + 'Edit planified payment' => 'Modifier un paiement récurrent', + 'Edit planified payment %title' => 'Modifier du paiement récurrent "%title" ', + 'Edit this customer' => 'Modifier ce client', + 'Edit this planified payment' => 'Modifier ce paiement récurrent', + 'Editing planified payment "%title"' => 'Modification du paiement récurrent "%title" ', + 'Fill the fields : App Name & Sandbox developer account' => 'Remplir les champs : App Name & Sandbox developer account ', + 'Frequency' => 'Fréquence', + 'Frequency interval' => 'Interval de la fréquence', + 'Future Payments' => 'Future Payments', + 'General configuration' => 'Configuration générale', + 'General description' => 'Description générale', + 'Global informations of this planified payment' => 'Informations générales de ce paiement récurrent', + 'Help' => 'Aide', + 'Home' => 'Accueil', + 'How to configure Plannified payment' => 'Comment configurer un paiement récurrent', + 'In SANDBOX APP SETTINGS' => 'Dans la section SANDBOX APP SETTINGS ', + 'In SANDBOX WEBHOOKS' => 'Dans la section SANDBOX WEBHOOKS', + 'In your PayPal page API configuration' => 'Dans votre page de configuration de votre API', + 'Invoicing' => 'Invoicing', + 'Level' => 'Niveau', + 'List of planified payments' => 'Liste des paiements récurrents', + 'Log' => 'Log', + 'Log In on developer.paypal.com' => 'Se connecter sur https://developer.paypal.com ', + 'Log In with PayPal' => 'Log In with PayPal', + 'Max amount' => 'Montant maximum', + 'Min amount' => 'Montant minium', + 'New recursive invoice from order %id' => 'Nouvelle commande récursive créée à partir de la commande %id', + 'No planified payment has been created yet. Click the + button to create one.' => 'Aucun paiement récurrent trouvé. Cliquer sur le bouton + ci-dessu pour en créer un', + 'None' => 'Aucun', + 'Order ID' => 'Commande ID', + 'PayPal Here' => 'PayPal Here', + 'Payment configuration' => 'Configuration paiement', + 'Payouts' => 'Payouts', + 'Personal Information' => 'Personal Information', + 'Planified payment' => 'Paiement récurrent', + 'Planified payment configuration' => 'Configuration paiement récurrent', + 'Planified payment created on %date_create. Last modification: %date_change' => 'Paiement récurrent créé le %date_create. Dernière modification : %date_change ', + 'Planified payments' => 'Paiements récurrents', + 'Production configuration' => 'Configuration mode Production', + 'Return URL' => ' Return URL ', + 'SandBox configuration' => 'Configuration mode Bac à Sable', + 'See webhook details' => 'Voir détails du webhook', + 'That\'s it !' => 'Et voilà !', + 'These planned payments will appear in step 4 of the purchase tunnel when selecting the payment method.' => 'Ces paiements récurrents apparaitront à l\'étape 4 du tunnel d\'achat lors de la sélection du moyen de paiement.', + 'This feature uses PayPal\'s Billing Plan and Agreement. It allows debiting a client recursively directly from PayPal.' => 'Cette fonctionnalité se sert des Billing Plan et Agreement de PayPal. Elle permet de débiter un client de manière récursive directement depuis PayPal.', + 'This informations can be found directly in concerned order details.' => 'Ces informations peuvent se retrouver directement sur les détails des commandes concernées.', + 'This is where we log all the transactions made with PayPal. PayPal webhooks also automatically feed these logs.' => 'C\'est ici que nous loggons l\'ensemble des transactions réalisées avec PayPal. Les webhooks de PayPal viennent aussi alimenter automatiquement ces logs.', + 'This method use PayPal webhooks and works only in HTTPS !' => 'Cette méthode utilise les WEBHOOKs de PayPal et ne fonctionne donc QUE ET UNIQUEMENT QUE en HTTPS !', + 'This method works only with PayPal PRO UK account. Please contact PayPal to upgrade your account if you need this service. For more informations, go here' => 'Ce moyen de paiement ne fonctionne QUE ET UNIQUEMENT QUE si vous avez un compte PayPal PRO UK. Merci de contacter PayPal pour mettre à jour votre compte si vous avez besoin de ce service. Pour plus d\'informations, rendez-vous ici', + 'This urls can take 3 hours to be taken in consideration' => ' Ces urls peuvent mettre 3 heures pour être prises en considération', + 'Title' => 'Titre', + 'Use Seamless Checkout' => 'Use Seamless Checkout', + 'You can edit the payment confirmation email sent to the customer after a successful payment.' => 'Vous pouvez modifier l\'email de confirmation de paiement envoyé au client après un paiement réalisé avec succès.', + 'You can add some planified payment here.' => 'Vous pouvez ajouter des paiements récurrents ici. ', + 'critical_{$log->getLevel()}' => 'critical_{$log->getLevel()}', +); diff --git a/domokits/local/modules/PayPal/I18n/en_US.php b/domokits/local/modules/PayPal/I18n/en_US.php new file mode 100644 index 0000000..0b4fa14 --- /dev/null +++ b/domokits/local/modules/PayPal/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/domokits/local/modules/PayPal/I18n/fr_FR.php b/domokits/local/modules/PayPal/I18n/fr_FR.php new file mode 100644 index 0000000..fa5515d --- /dev/null +++ b/domokits/local/modules/PayPal/I18n/fr_FR.php @@ -0,0 +1,76 @@ + 'Activer le paiement : "PAYPAL EXPRESS"', + 'Activate payment with PayPal account' => 'Activer le paiement par : "PAYPAL"', + 'Activate payment with credit card' => 'Activer le paiement par : "CARTE BANCAIRE"', + 'Activate payment with planified payment' => 'Activer le paiement par : "PAIEMENT RECURRENT"', + 'Activate sandbox mode' => 'Activer le mode Bac à Sable', + 'Allowed IPs in test mode' => 'IP autorisées pour le mode Bac à sable', + 'CVV' => 'Code secret (3 chiffres)', + 'Card number' => 'Numéro de la carte', + 'Card type' => 'Type de carte', + 'Credit card is invalid' => 'Carte bancaire invalide', + 'Cycle' => 'Cycle', + 'Expire month' => 'Mois d\'expiration', + 'Expire year' => 'Année d\'expiration', + 'Express checkout begin with cart %id' => 'Début du paiement EXPRESS CHECKOUT avec la panier : %id', + 'Express checkout failed in expressCheckoutOkAction() function' => 'Echec de la méthode Express Checkout de PayPal dans la function expressCheckoutOkAction()', + 'Express Checkout login failed' => 'Echec de la connexion avec EXPRESS CHECKOUT depuis le panier', + 'Frequency' => 'Fréquence', + 'Frequency interval' => 'Interval de la fréquence', + 'If checked, a payment confirmation e-mail is sent to the customer after each PayPal transaction.' => 'Si cochée, un mail de confirmation de paiement sera envoyés grâce aux webhook directement rattachés à PayPal', + 'If checked, a payment confirmation e-mail is sent to the customer.' => 'Si cochée, le client sera informé dès que sa commande passera en payée.', + 'If checked, a PayPal popup will be used to execute the payment.' => 'Si cochée, le paiement se déroulera via une popup PayPal pour rester sur le site marchand.', + 'If checked, the order can be paid by PayPal account.' => 'Si cochée, la commande pourra être payée par un simple compte PayPal', + 'If checked, the order can be paid by credit card.' => 'Si cochée, la commande pourra être payée par carte bancaire (PayPal se chargera de vérifier sa validité)', + 'If checked, the order can be paid by planified payement.' => 'Si cochée, la commande pourra être payée par paiement récurrent (PayPal agreement)', + 'If checked, the order can be paid directly from cart.' => 'Si cochée, la commande pourra être payée directement depuis le panier', + 'If checked, the order confirmation message is sent to the customer only when the payment is successful. The order notification is always sent to the shop administrator' => 'Si cochée, le mail de confirmation de commande ne sera pas envoyé au client dès que celui-ci cliquera sur l\'une des méthodes de paiement de PayPal', + 'Invalid charge type send to create charge model' => 'Paramètre "type" invalide pour générer un "Charge Model"', + 'Invalid fail action send to create merchant preference' => 'Paramètre "fail action" invalide pour générer un "Merchant Preference"', + 'Invalid number of charge models send to create payment definition' => 'Nombre de "Charge Models" insuffisant pour générer un "Payment Definition"', + 'Invalid number of payment definition send to generate billing plan' => 'Nombre de "Payment definition" insuffisant pour générer un "Billing Plan"', + 'Invalid payment frequency send to create payment definition' => 'Paramètre "frequency" invalide pour générer un "Payment Definition"', + 'Invalid payment type send to create payment definition' => 'Paramètre "type" invalide pour générer un "Payment Definition"', + 'Invalid planified payment id : %id' => 'ID du paiement récurrent incorrect : %id', + 'Invalid type send to generate billing plan' => 'Paramètre "type" invalide pour générer un "Billing Plan"', + 'Let value to 0 if you don\'t want a maximum' => 'Laisser 0 si vous ne voulez pas de maximum', + 'Let value to 0 if you don\'t want a minimum' => 'Laisser 0 si vous ne voulez pas de minimum', + 'List of IP addresses allowed to use this payment on the front-office when in test mode (your current IP is %ip). One address per line' => 'Liste des adresses IP autorisées pour payer en Front lorsque le mmode Bac à Sable est activé (votre adresse IP actuelle est %ip). Une adresse IP par ligne.', + 'Max amount' => 'Montant maximum', + 'Maximum items in cart' => 'Nombre d\'articles maximum dans le panier', + 'Maximum number of items in the customer cart for which this payment method is available.' => 'Nombre maximum d\'articles dans le panier pour que le moyen de paiement soit valide.', + 'Maximum order total' => 'Montant maximum de la commande', + 'Maximum order total in the default currency for which this payment method is available. Enter 0 for no maximum' => 'Montant maximum de la commande dans la devise courante pour autoriser le paiement. Mettre 0 pour ne pas avoir de maximum.', + 'Merchant ID' => 'Identifiant du marchand', + 'Method agreementOkAction => One of this parameter is invalid : $token = %token, $orderId = %order_id' => 'Method agreementOkAction => L\'un de ces paramètres est incorrecte : $token = %token, $orderId = %order_id', + 'Method okAction => One of this parameter is invalid : $payerId = %payer_id, $orderId = %order_id' => 'Method okAction => L\'un de ces paramètres est incorrecte : $payerId = %payer_id, $orderId = %order_id', + 'Min amount' => 'Montant minium', + 'Minimum order total' => 'Montant minimum de la commande', + 'Minimum order total in the default currency for which this payment method is available. Enter 0 for no minimum' => ' Montant minimum de la commande dans la devise courante pour autoriser le paiement. Mettre 0 pour ne pas avoir de minimum.', + 'Order address no found to generate PayPal shipping address' => 'Adresse de la commande non trouvée pour générer l\'adresse de livraison PayPal', + 'Order created with success in PayPal with method : %method' => 'Commande créée avec succès success via PayPal avec la méthode : %method ', + 'Order failed with method : %method' => 'Echec du paiement avec la méthode : %method', + 'Order payed with success in PayPal with method : %method' => 'Commande créée avec succès avec PayPal avec la méthode : %method ', + 'Order payed with success with method : %method' => 'Paiement réalisé avec succès avec la méthode : %method', + 'PayPal method' => 'Méthode PayPal', + 'Paypal configuration' => 'Configuration PayPal', + 'Position' => 'Position', + 'Send a payment confirmation e-mail' => 'Envoyer le mail de confirmation de paiement', + 'Send a recursive payment confirmation e-mail' => 'Envoyer le mail de confirmation de paiement pour les commandes récursives', + 'Send order confirmation on payment success' => 'Bloquer le mail de confirmation de commande (mail envoyé dès qu\'une commande est créée même si elle n\'est pas payée)', + 'The Paypal identity merchant account' => 'L\'indentifiant PayPal unique du compte marchand ', + 'The delivery module is not valid.' => 'Le module de transport n\'est pas valide.', + 'The description of the planified payment' => 'La description du paiement récurrent', + 'The locale of the planified payment' => 'La locale du paiement récurrent', + 'The title of the planified payment' => 'Le titre du paiement récurrent', + 'This value should not be blank' => 'Cette valeur ne doit pas être vide', + 'Use InContext mode for classic PayPal payment' => 'Utiliser le mode InContext pour le payment classic PayPal', + 'Your Paypal login' => 'Client ID', + 'Your Paypal password' => 'Client SECRET', + 'Your Paypal sandbox login' => 'Client ID', + 'Your Paypal sandbox password' => 'Client SECRET', + 'login' => 'Client ID', + 'password' => 'Client SECRET', +); diff --git a/domokits/local/modules/PayPal/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/PayPal/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..82d6a7f --- /dev/null +++ b/domokits/local/modules/PayPal/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,12 @@ + 'jours', + 'MONTH' => 'Mois', + 'Finish payment with PayPal' => 'Terminer le paiement avec PayPal', + 'Payment in %x times every %frequency_interval %frequency' => 'Paiement en %x fois, tout les %frequency_interval %frequency', + 'Planified payment' => 'Paiement récurrent', + 'planified_payment' => 'Paiement récurrent', + 'WEEK' => 'Semaines', + 'YEAR' => 'Années', +); diff --git a/domokits/local/modules/PayPal/I18n/pdf/default/fr_FR.php b/domokits/local/modules/PayPal/I18n/pdf/default/fr_FR.php new file mode 100644 index 0000000..630b4f0 --- /dev/null +++ b/domokits/local/modules/PayPal/I18n/pdf/default/fr_FR.php @@ -0,0 +1,10 @@ + 'Jours', + 'MONTH' => 'Mois', + 'Payment in %x times every %frequency_interval %frequency' => 'Paiement en %x fois, tout les %frequency_interval %frequency', + 'Planified payment' => 'Paiement récurrent', + 'WEEK' => 'Semaines', + 'YEAR' => 'Années', +); diff --git a/domokits/local/modules/PayPal/Loop/PayPalLogLoop.php b/domokits/local/modules/PayPal/Loop/PayPalLogLoop.php new file mode 100644 index 0000000..8509345 --- /dev/null +++ b/domokits/local/modules/PayPal/Loop/PayPalLogLoop.php @@ -0,0 +1,154 @@ +getResultDataCollection() as $model) { + + $row = new LoopResultRow($model); + + $row->set('log', $model); + + $this->addOutputFields($row, $model); + + $loopResult->addRow($row); + } + + return $loopResult; + } + + /** + * @return PaypalLogQuery + */ + public function buildModelCriteria() + { + $query = new PaypalLogQuery(); + + if (null != $orderId = $this->getOrderId()) { + $query->filterByOrderId($orderId); + } + + if (null != $customerId = $this->getCustomerId()) { + $query->filterByCustomerId($customerId); + } + + if (null != $channel = $this->getChannel()) { + $query->filterByChannel($channel); + } + + if (null != $level = $this->getLevel()) { + $query->filterByLevel($level); + } + + $this->buildModelCriteriaOrder($query); + $query->groupById(); + + return $query; + } + + /** + * @param PaypalLogQuery $query + */ + protected function buildModelCriteriaOrder(PaypalLogQuery $query) + { + foreach ($this->getOrder() as $order) { + switch ($order) { + case 'id': + $query->orderById(); + break; + case 'id-reverse': + $query->orderById(Criteria::DESC); + break; + case 'order-id': + $query->orderById(); + break; + case 'order-id-reverse': + $query->orderById(Criteria::DESC); + break; + case 'customer-id': + $query->addAscendingOrderByColumn('i18n_TITLE'); + break; + case 'customer-id-reverse': + $query->addDescendingOrderByColumn('i18n_TITLE'); + break; + case 'date': + $query->orderByCreatedAt(); + break; + case 'date-reverse': + $query->orderByCreatedAt(Criteria::DESC); + break; + default: + $query->orderById(); + break; + } + } + } + + /** + * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('order_id'), + Argument::createIntTypeArgument('customer_id'), + Argument::createAnyTypeArgument('channel'), + Argument::createIntTypeArgument('level'), + Argument::createEnumListTypeArgument( + 'order', + [ + 'id', + 'id-reverse', + 'order-id', + 'order-id-reverse', + 'customer-id', + 'customer-id-reverse', + 'date', + 'date-reverse', + ], + 'id' + ) + ); + } +} diff --git a/domokits/local/modules/PayPal/Loop/PayPalOrderLoop.php b/domokits/local/modules/PayPal/Loop/PayPalOrderLoop.php new file mode 100644 index 0000000..ed07f81 --- /dev/null +++ b/domokits/local/modules/PayPal/Loop/PayPalOrderLoop.php @@ -0,0 +1,109 @@ +getResultDataCollection() as $model) { + $row = new LoopResultRow($model); + + $row->set('paypal_order', $model); + + $this->addOutputFields($row, $model); + + $loopResult->addRow($row); + } + + return $loopResult; + } + + /** + * @return PaypalOrderQuery + */ + public function buildModelCriteria() + { + $query = new PaypalOrderQuery(); + + if (null != $id = $this->getId()) { + $query->filterById($id); + } + + $this->buildModelCriteriaOrder($query); + + return $query; + } + + /** + * @param PaypalOrderQuery $query + */ + protected function buildModelCriteriaOrder(PaypalOrderQuery $query) + { + foreach ($this->getOrder() as $order) { + switch ($order) { + case 'id': + $query->orderById(); + break; + case 'id-reverse': + $query->orderById(Criteria::DESC); + break; + default: + break; + } + } + } + + /** + * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntListTypeArgument('id'), + Argument::createEnumListTypeArgument( + 'order', + [ + 'id', + 'id-reverse' + ], + 'id' + ) + ); + } +} diff --git a/domokits/local/modules/PayPal/Loop/PayPalPlanifiedPaymentLoop.php b/domokits/local/modules/PayPal/Loop/PayPalPlanifiedPaymentLoop.php new file mode 100644 index 0000000..c488183 --- /dev/null +++ b/domokits/local/modules/PayPal/Loop/PayPalPlanifiedPaymentLoop.php @@ -0,0 +1,133 @@ +getCurrentRequest()->getSession()->get('thelia.current.lang'); + + /** + * @var PaypalPlanifiedPayment $model + */ + foreach ($loopResult->getResultDataCollection() as $model) { + $model->getTranslation($lang->getLocale()); + $row = new LoopResultRow($model); + + $row->set('planifiedPayment', $model); + + $this->addOutputFields($row, $model); + + $loopResult->addRow($row); + } + + return $loopResult; + } + + /** + * @return PaypalPlanifiedPaymentQuery + */ + public function buildModelCriteria() + { + $query = new PaypalPlanifiedPaymentQuery(); + + if (null != $id = $this->getId()) { + $query->filterById($id); + } + + /* manage translations */ + $this->configureI18nProcessing( + $query, + array( + 'TITLE', + 'DESCRIPTION' + ) + ); + + $this->buildModelCriteriaOrder($query); + $query->groupById(); + + return $query; + } + + /** + * @param PaypalPlanifiedPaymentQuery $query + */ + protected function buildModelCriteriaOrder(PaypalPlanifiedPaymentQuery $query) + { + foreach ($this->getOrder() as $order) { + switch ($order) { + case 'id': + $query->orderById(); + break; + case 'id-reverse': + $query->orderById(Criteria::DESC); + break; + case 'position': + $query->orderById(); + break; + case 'position-reverse': + $query->orderById(Criteria::DESC); + break; + case 'title': + $query->addAscendingOrderByColumn('i18n_TITLE'); + break; + case 'title-reverse': + $query->addDescendingOrderByColumn('i18n_TITLE'); + break; + default: + $query->orderById(); + break; + } + } + } + + /** + * @return \Thelia\Core\Template\Loop\Argument\ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntListTypeArgument('id'), + Argument::createEnumListTypeArgument( + 'order', + [ + 'id', + 'id-reverse', + 'position', + 'position-reverse', + 'title', + 'title-reverse', + ], + 'id' + ) + ); + } +} diff --git a/domokits/local/modules/PayPal/Model/PaypalCart.php b/domokits/local/modules/PayPal/Model/PaypalCart.php new file mode 100644 index 0000000..4603a7c --- /dev/null +++ b/domokits/local/modules/PayPal/Model/PaypalCart.php @@ -0,0 +1,48 @@ +aCart === null && ($this->id !== null)) { + $this->aCart = CartQuery::create()->findPk($this->id, $con); + } + + return $this->aCart; + } + + /** + * Declares an association between this object and a ChildCart object. + * + * @param Cart $cart + * @return \PayPal\Model\PaypalCart The current object (for fluent API support) + * @throws PropelException + */ + public function setCart(Cart $cart = null) + { + if ($cart === null) { + $this->setId(NULL); + } else { + $this->setId($cart->getId()); + } + + $this->aCart = $cart; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Model/PaypalCartQuery.php b/domokits/local/modules/PayPal/Model/PaypalCartQuery.php new file mode 100644 index 0000000..a73f9fe --- /dev/null +++ b/domokits/local/modules/PayPal/Model/PaypalCartQuery.php @@ -0,0 +1,21 @@ +aCustomer === null && ($this->id !== null)) { + $this->aCustomer = CustomerQuery::create()->findPk($this->id, $con); + } + + return $this->aCustomer; + } + + /** + * Declares an association between this object and a ChildCustomer object. + * + * @param Customer $customer + * @return \PayPal\Model\PaypalCustomer The current object (for fluent API support) + * @throws PropelException + */ + public function setCustomer(Customer $customer = null) + { + if ($customer === null) { + $this->setId(NULL); + } else { + $this->setId($customer->getId()); + } + + $this->aCustomer = $customer; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Model/PaypalCustomerQuery.php b/domokits/local/modules/PayPal/Model/PaypalCustomerQuery.php new file mode 100644 index 0000000..cad81bc --- /dev/null +++ b/domokits/local/modules/PayPal/Model/PaypalCustomerQuery.php @@ -0,0 +1,21 @@ +aOrder === null && ($this->id !== null)) { + $this->aOrder = OrderQuery::create()->findPk($this->id, $con); + } + + return $this->aOrder; + } + + /** + * Declares an association between this object and a ChildOrder object. + * + * @param Order $order + * @return \PayPal\Model\PaypalOrder The current object (for fluent API support) + * @throws PropelException + */ + public function setOrder(Order $order = null) + { + if ($order === null) { + $this->setId(NULL); + } else { + $this->setId($order->getId()); + } + + $this->aOrder = $order; + + return $this; + } +} diff --git a/domokits/local/modules/PayPal/Model/PaypalOrderQuery.php b/domokits/local/modules/PayPal/Model/PaypalOrderQuery.php new file mode 100644 index 0000000..0f70b7a --- /dev/null +++ b/domokits/local/modules/PayPal/Model/PaypalOrderQuery.php @@ -0,0 +1,21 @@ +beginTransaction(); + + try { + /** @var PayPalPaymentService $payPalService */ + $payPalService = $this->getContainer()->get(self::PAYPAL_PAYMENT_SERVICE_ID); + /** @var PayPalAgreementService $payPalAgreementService */ + $payPalAgreementService = $this->getContainer()->get(self::PAYPAL_AGREEMENT_SERVICE_ID); + + if (null !== $payPalCart = PaypalCartQuery::create()->findOneById($order->getCartId())) { + + if (null !== $payPalCart->getCreditCardId()) { + $payment = $payPalService->makePayment($order, $payPalCart->getCreditCardId()); + + //This payment method does not have a callback URL... So we have to check the payment status + if ($payment->getState() === PayPal::PAYMENT_STATE_APPROVED) { + $event = new OrderEvent($order); + $event->setStatus(OrderStatusQuery::getPaidStatus()->getId()); + $this->getDispatcher()->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + $response = new RedirectResponse(URL::getInstance()->absoluteUrl('/order/placed/' . $order->getId())); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order payed with success with method : %method', + [ + '%method' => self::PAYPAL_METHOD_CREDIT_CARD + ], + self::DOMAIN_NAME + ), + [ + 'order_id' => $order->getId(), + 'customer_id' => $order->getCustomerId() + ], + Logger::INFO + ); + } else { + $response = new RedirectResponse(URL::getInstance()->absoluteUrl('/module/paypal/cancel/' . $order->getId())); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order failed with method : %method', + [ + '%method' => self::PAYPAL_METHOD_CREDIT_CARD + ], + self::DOMAIN_NAME + ), + [ + 'order_id' => $order->getId(), + 'customer_id' => $order->getCustomerId() + ], + Logger::CRITICAL + ); + } + } elseif (null !== $planifiedPayment = PaypalPlanifiedPaymentQuery::create()->findOneById($payPalCart->getPlanifiedPaymentId())) { + //Agreement Payment + $agreement = $payPalAgreementService->makeAgreement($order, $planifiedPayment); + $response = new RedirectResponse($agreement->getApprovalLink()); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order created with success in PayPal with method : %method', + [ + '%method' => self::PAYPAL_METHOD_PLANIFIED_PAYMENT + ], + self::DOMAIN_NAME + ), + [ + 'order_id' => $order->getId(), + 'customer_id' => $order->getCustomerId() + ], + Logger::INFO + ); + } else { + //Classic Payment + $payment = $payPalService->makePayment($order); + $response = new RedirectResponse($payment->getApprovalLink()); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order created with success in PayPal with method : %method', + [ + '%method' => self::PAYPAL_METHOD_PAYPAL + ], + self::DOMAIN_NAME + ), + [ + 'order_id' => $order->getId(), + 'customer_id' => $order->getCustomerId() + ], + Logger::INFO + ); + } + + } else { + //Classic Payment + $payment = $payPalService->makePayment($order); + $response = new RedirectResponse($payment->getApprovalLink()); + PayPalLoggerService::log( + Translator::getInstance()->trans( + 'Order created with success in PayPal with method : %method', + [ + '%method' => self::PAYPAL_METHOD_PAYPAL + ], + self::DOMAIN_NAME + ), + [ + 'order_id' => $order->getId(), + 'customer_id' => $order->getCustomerId() + ], + Logger::INFO + ); + + //Future Payment NOT OPERATIONNEL IN PAYPAL API REST YET ! + //$payment = $payPalService->makePayment($order, null, null, true); + //$response = new RedirectResponse($payment->getApprovalLink()); + } + + $con->commit(); + + return $response; + } catch (PayPalConnectionException $e) { + $con->rollBack(); + + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } catch(\Exception $e) { + $con->rollBack(); + + + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } + } + + /** + * + * This method is call on Payment loop. + * + * If you return true, the payment method will de display + * If you return false, the payment method will not be display + * + * @return boolean + */ + public function isValidPayment() + { + $isValid = false; + + // Check if total order amount is within the module's limits + $order_total = $this->getCurrentOrderTotalAmount(); + + $min_amount = Paypal::getConfigValue('minimum_amount', 0); + $max_amount = Paypal::getConfigValue('maximum_amount', 0); + + if ( + ($order_total > 0) + && + ($min_amount <= 0 || $order_total >= $min_amount) + && + ($max_amount <= 0 || $order_total <= $max_amount) + ) { + // Check cart item count + $cartItemCount = $this->getRequest()->getSession()->getSessionCart($this->getDispatcher())->countCartItems(); + + if ($cartItemCount <= Paypal::getConfigValue('cart_item_count', 9)) { + $isValid = true; + + if (PayPalBaseService::isSandboxMode()) { + // In sandbox mode, check the current IP + $raw_ips = explode("\n", Paypal::getConfigValue('allowed_ip_list', '')); + + $allowed_client_ips = array(); + + foreach ($raw_ips as $ip) { + $allowed_client_ips[] = trim($ip); + } + + $client_ip = $this->getRequest()->getClientIp(); + + $isValid = in_array($client_ip, $allowed_client_ips); + } + } + } + + return $isValid; + } + + /** + * if you want, you can manage stock in your module instead of order process. + * Return false to decrease the stock when order status switch to pay + * + * @return bool + */ + public function manageStockOnCreation() + { + return false; + } + + /** + * @param \Propel\Runtime\Connection\ConnectionInterface $con + */ + public function postActivation(ConnectionInterface $con = null): void + { + $database = new Database($con); + $database->insertSql(null, array(__DIR__ . "/Config/create.sql")); + + // Setup some default values at first install + if (null === self::getConfigValue('minimum_amount', null)) { + self::setConfigValue('minimum_amount', 0); + self::setConfigValue('maximum_amount', 0); + self::setConfigValue('send_payment_confirmation_message', 1); + self::setConfigValue('cart_item_count', 999); + } + + if (null === MessageQuery::create()->findOneByName(self::CONFIRMATION_MESSAGE_NAME)) { + $message = new Message(); + + $message + ->setName(self::CONFIRMATION_MESSAGE_NAME) + ->setHtmlTemplateFileName('paypal-payment-confirmation.html') + ->setTextTemplateFileName('paypal-payment-confirmation.txt') + ->setLocale('en_US') + ->setTitle('Paypal payment confirmation') + ->setSubject('Payment of order {$order_ref}') + ->setLocale('fr_FR') + ->setTitle('Confirmation de paiement par Paypal') + ->setSubject('Confirmation du paiement de votre commande {$order_ref}') + ->save() + ; + } + + if (null === MessageQuery::create()->findOneByName(self::RECURSIVE_MESSAGE_NAME)) { + $message = new Message(); + + $message + ->setName(self::RECURSIVE_MESSAGE_NAME) + ->setHtmlTemplateFileName('paypal-recursive-payment-confirmation.html') + ->setTextTemplateFileName('paypal-recursive-payment-confirmation.txt') + ->setLocale('en_US') + ->setTitle('Paypal payment confirmation') + ->setSubject('Payment of order {$order_ref}') + ->setLocale('fr_FR') + ->setTitle('Confirmation de paiement par Paypal') + ->setSubject('Confirmation du paiement de votre commande {$order_ref}') + ->save() + ; + } + + /* Deploy the module's image */ + $module = $this->getModuleModel(); + + if (ModuleImageQuery::create()->filterByModule($module)->count() == 0) { + $this->deployImageFolder($module, sprintf('%s/images', __DIR__), $con); + } + } + + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + $finder = (new Finder()) + ->files() + ->name('#.*?\.sql#') + ->sortByName() + ->in(__DIR__ . DS . 'Config' . DS . 'Update') + ; + + $database = new Database($con); + + /** @var \Symfony\Component\Finder\SplFileInfo $updateSQLFile */ + foreach ($finder as $updateSQLFile) { + if (version_compare($currentVersion, str_replace('.sql', '', $updateSQLFile->getFilename()), '<')) { + $database->insertSql( + null, + [ + $updateSQLFile->getPathname() + ] + ); + } + } + } + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/PayPal/README.md b/domokits/local/modules/PayPal/README.md new file mode 100644 index 0000000..1f7d9f8 --- /dev/null +++ b/domokits/local/modules/PayPal/README.md @@ -0,0 +1,56 @@ +# PayPal + +* I) Install notes +* II) Configure your PayPal account +* III) Module options payments + +## I) Installation + +### Composer + +> **WARNING** : A console access is required to update dependencies. If you don't have a console access, please get the latest 2.x version of the module here : https://github.com/thelia-modules/Paypal/tree/2.x + +To install the module with Composer, open a console, navigate to the Thelia diorectory and type the following command to add the dependency to Thelia composer.json file. + +``` +composer require thelia/paypal-module:~4.0.0 +``` + +## II) Configure your PayPal account + +- Log In on [developer.paypal.com] (https://developer.paypal.com "developer.paypal.com") +- Create REST API apps [here] (https://developer.paypal.com/developer/applications/ "here") +- Click on Create App +- Fill the fields : App Name & Sandbox developer account +- Click on Create App +- Note the Client ID to use it later in the module configuration +- Note the Client SECRET to use it later in the module configuration + +#### In SANDBOX WEBHOOKS +- To fill this part, go to your module configuration page to see the urls to implement + +#### In SANDBOX APP SETTINGS +- To fill this part, go to your module configuration page to see the urls to implement + + +## III) Module options payments + +#### Classic PayPal payment +![alt classic paypal payment](https://github.com/thelia-modules/Paypal/blob/master/images/payment_classic.png?raw=true) +- This method will redirect to the PayPal platform to proceed payment + +#### InContext Classic PayPal payment +![alt classic paypal payment](https://github.com/thelia-modules/Paypal/blob/master/images/payment_classic_incontext.png?raw=true) +- This method will allow the customer to pay from a PayPal inContext popup directly from your website (no redirection to the PayPal plateform) + +#### Credit card +![alt classic paypal payment](https://github.com/thelia-modules/Paypal/blob/master/images/payment_credit_card.png?raw=true) +- This method allow the customer to pay directly by a credit card without a PayPal account. 'The merchant must have a Pro PayPal account UK and the website must be in HTTPS' + +#### Recursive payment +![alt classic paypal payment](https://github.com/thelia-modules/Paypal/blob/master/images/payment_recursive.png?raw=true) +- This method use the 'PayPal AGRREMENTS' and allow you to use recursive payments on your website. If you want to log all PayPal actions, you need to configure the PayPal webhooks and to have a wabsite in HTTPS + +#### Express checkout +![alt classic paypal payment](https://github.com/thelia-modules/Paypal/blob/master/images/payment_express_checkout.png?raw=true) +- This method allow the customer to proceed the payment directly from the cart from a PayPal inContext popup. diff --git a/domokits/local/modules/PayPal/Service/Base/PayPalBaseService.php b/domokits/local/modules/PayPal/Service/Base/PayPalBaseService.php new file mode 100644 index 0000000..a623c0d --- /dev/null +++ b/domokits/local/modules/PayPal/Service/Base/PayPalBaseService.php @@ -0,0 +1,414 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Service\Base; + +use Exception; +use Monolog\Logger; +use PayPal\Api\Amount; +use PayPal\Api\FuturePayment; +use PayPal\Api\Payer; +use PayPal\Api\PayerInfo; +use PayPal\Api\ShippingAddress; +use PayPal\Api\Transaction; +use PayPal\Auth\OAuthTokenCredential; +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalOrderEvent; +use PayPal\Model\PaypalCart; +use PayPal\Model\PaypalCartQuery; +use PayPal\Model\PaypalOrder; +use PayPal\Model\PaypalPlanifiedPayment; +use PayPal\PayPal; +use PayPal\Rest\ApiContext; +use PayPal\Service\PayPalLoggerService; +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RouterInterface; +use Thelia\Core\Event\Cart\CartRestoreEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\HttpFoundation\Session\Session; +use Thelia\Core\Translation\Translator; +use Thelia\Model\Cart; +use Thelia\Model\Country; +use Thelia\Model\Currency; +use Thelia\Model\Lang; +use Thelia\Model\Order; +use Thelia\Model\OrderAddressQuery; + +class PayPalBaseService +{ + /** @var EventDispatcherInterface */ + protected EventDispatcherInterface $dispatcher; + + /** @var RequestStack */ + protected RequestStack $requestStack; + + /** @var RouterInterface */ + protected RouterInterface $router; + + /** @var OAuthTokenCredential */ + protected OAuthTokenCredential $authTokenCredential; + + /** + * PayPalBaseService constructor. + * @param EventDispatcherInterface $dispatcher + * @param RequestStack $requestStack + * @param RouterInterface $router + */ + public function __construct(EventDispatcherInterface $dispatcher, RequestStack $requestStack, RouterInterface $router) + { + $this->dispatcher = $dispatcher; + $this->requestStack = $requestStack; + $this->router = $router; + + $this->authTokenCredential = new OAuthTokenCredential(self::getLogin(), self::getPassword()); + } + + /** + * @param Order $order + * @param null $creditCardId + * @param PaypalPlanifiedPayment|null $planifiedPayment + * @return PayPalOrderEvent + * @throws PropelException + */ + public function generatePayPalOrder(Order $order, $creditCardId = null, PaypalPlanifiedPayment $planifiedPayment = null) + { + $payPalOrder = new PaypalOrder(); + $payPalOrder + ->setId($order->getId()) + ->setAmount($order->getTotalAmount()) + ; + + if (null !== $creditCardId) { + $payPalOrder->setCreditCardId($creditCardId); + } + + if (null !== $planifiedPayment) { + /** @var Lang $lang */ + $lang = $this->requestStack->getCurrentRequest()->getSession()->get('thelia.current.lang'); + $planifiedPayment->getTranslation($lang->getLocale()); + + $payPalOrder + ->setPlanifiedTitle($planifiedPayment->getTitle()) + ->setPlanifiedDescription($planifiedPayment->getDescription()) + ->setPlanifiedFrequency($planifiedPayment->getFrequency()) + ->setPlanifiedFrequencyInterval($planifiedPayment->getFrequencyInterval()) + ->setPlanifiedCycle($planifiedPayment->getCycle()) + ->setPlanifiedMinAmount($planifiedPayment->getMinAmount()) + ->setPlanifiedMaxAmount($planifiedPayment->getMaxAmount()) + ; + } + + $payPalOrderEvent = new PayPalOrderEvent($payPalOrder); + $this->dispatcher->dispatch($payPalOrderEvent, PayPalEvents::PAYPAL_ORDER_CREATE); + + return $payPalOrderEvent; + } + + /** + * @param PaypalOrder $payPalOrder + * @param $state + * @param string|null $paymentId + * @param string|null $agreementId + * @return PayPalOrderEvent + */ + public function updatePayPalOrder(PaypalOrder $payPalOrder, $state, string $paymentId = null, string $agreementId = null) + { + $payPalOrder->setState($state); + + if (null !== $paymentId) { + $payPalOrder->setPaymentId($paymentId); + } + + if (null !== $agreementId) { + $payPalOrder->setAgreementId($agreementId); + } + + $payPalOrderEvent = new PayPalOrderEvent($payPalOrder); + $this->dispatcher->dispatch($payPalOrderEvent, PayPalEvents::PAYPAL_ORDER_UPDATE); + + return $payPalOrderEvent; + } + + /** + * @return PaypalCart + */ + public function getCurrentPayPalCart() + { + /** @var Session $session */ + $session = $this->requestStack->getCurrentRequest()->getSession(); + $cart = $session->getSessionCart($this->dispatcher); + + if (null === $cart) { + $cartEvent = new CartRestoreEvent(); + $this->dispatcher->dispatch($cartEvent, TheliaEvents::CART_RESTORE_CURRENT); + + $cart = $cartEvent->getCart(); + } + + if (null === $payPalCart = PaypalCartQuery::create()->findOneById($cart->getId())) { + $payPalCart = new PaypalCart(); + $payPalCart->setId($cart->getId()); + } + + return $payPalCart; + } + + /** + * @param string $method + * @param array $fundingInstruments + * @param PayerInfo|null $payerInfo + * @return Payer + */ + public static function generatePayer(string $method = PayPal::PAYPAL_METHOD_PAYPAL, array $fundingInstruments = [], PayerInfo $payerInfo = null) + { + $payer = new Payer(); + $payer->setPaymentMethod($method); + + // Never set empty instruments when communicating with PayPal + if (count($fundingInstruments) > 0) { + $payer->setFundingInstruments($fundingInstruments); + } + + if (null !== $payerInfo) { + $payer->setPayerInfo($payerInfo); + } + + return $payer; + } + + public static function generatePayerInfo($data = []) + { + return new PayerInfo($data); + } + + /** + * @throws PropelException + * @throws Exception + */ + public static function generateShippingAddress(Order $order) + { + if (null !== $orderAddress = OrderAddressQuery::create()->findOneById($order->getDeliveryOrderAddressId())) { + $shippingAddress = new ShippingAddress(); + + if (null !== $state = $orderAddress->getState()) { + $payPalState = $state->getIsocode(); + } else { + $payPalState = 'CA'; + } + + $shippingAddress + ->setLine1($orderAddress->getAddress1()) + ->setCity($orderAddress->getCity()) + ->setPostalCode($orderAddress->getZipcode()) + ->setCountryCode($orderAddress->getCountry()->getIsoalpha2()) + ->setState($payPalState) + ; + + if (null !== $orderAddress->getAddress2()) { + + if (null !== $orderAddress->getAddress3()) { + $shippingAddress->setLine2($orderAddress->getAddress2() . ' ' . $orderAddress->getAddress3()); + } else { + $shippingAddress->setLine2($orderAddress->getAddress2()); + } + } elseif (null !== $orderAddress->getAddress3()) { + $shippingAddress->setLine2($orderAddress->getAddress3()); + } + + return $shippingAddress; + } + + $message = Translator::getInstance()->trans( + 'Order address no found to generate PayPal shipping address', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + /** + * @param Order $order + * @param Currency $currency + * @return Amount + * @throws PropelException + */ + public function generateAmount(Order $order, Currency $currency) + { + // Specify the payment amount. + $amount = new Amount(); + $amount->setCurrency($currency->getCode()); + $amount->setTotal($order->getTotalAmount()); + + return $amount; + } + + /** + * @param Cart $cart + * @param Currency $currency + * @return Amount + * @throws PropelException + */ + public function generateAmountFromCart(Cart $cart, Currency $currency) + { + // Specify the payment amount. + $amount = new Amount(); + $amount->setCurrency($currency->getCode()); + $amount->setTotal($cart->getTaxedAmount(Country::getDefaultCountry())); + + return $amount; + } + + /** + * @param Amount $amount + * @param string|null $description + * @return Transaction + */ + public function generateTransaction(Amount $amount, ?string $description = '') + { + // ###Transaction + // A transaction defines the contract of a + // payment - what is the payment for and who + // is fulfilling it. Transaction is created with + // a `Payee` and `Amount` types + $transaction = new Transaction(); + $transaction->setAmount($amount); + $transaction->setDescription($description); + + return $transaction; + } + + public function getAccessToken() + { + $config = self::getApiContext()->getConfig(); + $accessToken = $this->authTokenCredential->getAccessToken($config); + + return $accessToken; + } + + public function getRefreshToken() + { + $refreshToken = FuturePayment::getRefreshToken($this->getAccessToken(), self::getApiContext()); + + return $refreshToken; + } + + /** + * SDK Configuration + * + *@return ApiContext + */ + public static function getApiContext() + { + $apiContext = new ApiContext(); + + // Alternatively pass in the configuration via a hashmap. + // The hashmap can contain any key that is allowed in + // sdk_config.ini + $apiContext->setConfig([ + 'acct1.ClientId' => self::getLogin(), + 'acct1.ClientSecret' => self::getPassword(), + 'http.ConnectionTimeOut' => 30, + 'http.Retry' => 1, + 'mode' => self::getMode(), + 'log.LogEnabled' => true, + 'log.FileName' => '../var/log/PayPal.log', + 'log.LogLevel' => 'INFO', + 'cache.enabled' => true, + 'cache.FileName' => '../var/cache/prod/PayPal.cache', + 'http.headers.PayPal-Partner-Attribution-Id' => 'Thelia_Cart', + ]); + + return $apiContext; + } + + /** + * @return string + */ + public static function getLogin() + { + if ((bool)PayPal::getConfigValue('sandbox') === true) { + $login = PayPal::getConfigValue('sandbox_login'); + } else { + $login = PayPal::getConfigValue('login'); + } + + return $login; + } + + /** + * @return string + */ + public static function getPassword() + { + if ((bool)PayPal::getConfigValue('sandbox') === true) { + $password = PayPal::getConfigValue('sandbox_password'); + } else { + $password = PayPal::getConfigValue('password'); + } + + return $password; + } + + /** + * @return string + */ + public static function getMerchantId() + { + if ((bool)PayPal::getConfigValue('sandbox') === true) { + $login = PayPal::getConfigValue('sandbox_merchant_id'); + } else { + $login = PayPal::getConfigValue('merchant_id'); + } + + return $login; + } + + /** + * @return string + */ + public static function getMode() + { + if ((bool)PayPal::getConfigValue('sandbox') === true) { + $mode = 'sandbox'; + } else { + $mode = 'live'; + } + + return $mode; + } + + public static function isSandboxMode(): bool + { + return self::getMode() === 'sandbox'; + } +} diff --git a/domokits/local/modules/PayPal/Service/MyOwnSQLHandler.php b/domokits/local/modules/PayPal/Service/MyOwnSQLHandler.php new file mode 100644 index 0000000..b555091 --- /dev/null +++ b/domokits/local/modules/PayPal/Service/MyOwnSQLHandler.php @@ -0,0 +1,30 @@ + + * Projet: parquets-et-lambris-de-vallereuil.com + * Date: 16/03/2023 + */ +class MyOwnSQLHandler extends MySQLHandler +{ + public function __construct(PDO|ConnectionInterface $pdo = null, string $table, array $additionalFields = [], bool|int $level = Logger::DEBUG, bool $bubble = true) + { + $this->pdo = $pdo; + + parent::__construct(null, $table, $additionalFields, $level, $bubble); + } +} diff --git a/domokits/local/modules/PayPal/Service/PayPalAgreementService.php b/domokits/local/modules/PayPal/Service/PayPalAgreementService.php new file mode 100644 index 0000000..1b8569b --- /dev/null +++ b/domokits/local/modules/PayPal/Service/PayPalAgreementService.php @@ -0,0 +1,953 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Service; + +use Datetime; +use Exception; +use Monolog\Logger; +use PayPal\Api\Agreement; +use PayPal\Api\AgreementStateDescriptor; +use PayPal\Api\AgreementTransactions; +use PayPal\Api\ChargeModel; +use PayPal\Api\CreditCard; +use PayPal\Api\CreditCardToken; +use PayPal\Api\Currency; +use PayPal\Api\FundingInstrument; +use PayPal\Api\MerchantPreferences; +use PayPal\Api\Patch; +use PayPal\Api\PatchRequest; +use PayPal\Api\Payer; +use PayPal\Api\PaymentDefinition; +use PayPal\Api\Plan; +use PayPal\Api\PlanList; +use PayPal\Common\PayPalModel; +use PayPal\Event\PayPalEvents; +use PayPal\Event\PayPalOrderEvent; +use PayPal\Event\PayPalPlanEvent; +use PayPal\Exception\PayPalConnectionException; +use PayPal\Model\PaypalOrder; +use PayPal\Model\PaypalOrderQuery; +use PayPal\Model\PaypalPlan; +use PayPal\Model\PaypalPlanifiedPayment; +use PayPal\Model\PaypalPlanQuery; +use PayPal\PayPal; +use PayPal\Service\Base\PayPalBaseService; +use Propel\Runtime\Exception\PropelException; +use Symfony\Component\Routing\Router; +use Thelia\Core\Translation\Translator; +use Thelia\Model\CurrencyQuery; +use Thelia\Model\Order; +use Thelia\Model\OrderProduct; +use Thelia\Model\OrderProductQuery; +use Thelia\Model\OrderProductTax; +use Thelia\Model\OrderProductTaxQuery; +use Thelia\Tools\URL; + +class PayPalAgreementService extends PayPalBaseService +{ + public const PLAN_TYPE_FIXED = 'FIXED'; + public const PLAN_TYPE_INFINITE = 'INFINITE'; + + public const PAYMENT_TYPE_REGULAR = 'REGULAR'; + public const PAYMENT_TYPE_TRIAL = 'TRIAL'; + + public const CHARGE_TYPE_SHIPPING = 'SHIPPING'; + public const CHARGE_TYPE_TAX = 'TAX'; + + public const PAYMENT_FREQUENCY_DAY = 'DAY'; + public const PAYMENT_FREQUENCY_WEEK = 'WEEK'; + public const PAYMENT_FREQUENCY_MONTH = 'MONTH'; + public const PAYMENT_FREQUENCY_YEAR = 'YEAR'; + + public const FAIL_AMOUNT_ACTION_CONTINUE = 'CONTINUE'; + public const FAIL_AMOUNT_ACTION_CANCEL = 'CANCEL'; + + public const MAX_API_LENGHT = 128; + + /** + * @param Order $order + * @param PaypalPlanifiedPayment $planifiedPayment + * @param string|null $description + * @return Agreement + * @throws PayPalConnectionException + * @throws Exception + */ + public function makeAgreement(Order $order, PaypalPlanifiedPayment $planifiedPayment, string $description = null) + { + //Sadly, this description can NOT be null + if (null === $description) { + $description = 'Thelia order ' . $order->getId(); + } + + $payPalOrderEvent = $this->generatePayPalOrder($order, null, $planifiedPayment); + + $merchantPreferences = $this->createMerchantPreferences($order); + $chargeModel = $this->createChargeModel($order); + + $totalAmount = $order->getTotalAmount(); + $cycleAmount = round($totalAmount / $planifiedPayment->getCycle(), 2); + + $paymentDefinition = $this->createPaymentDefinition( + $order, + 'payment definition for order ' . $order->getId(), + [$chargeModel], + $cycleAmount, + self::PAYMENT_TYPE_REGULAR, + $planifiedPayment->getFrequency(), + $planifiedPayment->getFrequencyInterval(), + $planifiedPayment->getCycle() + ); + + $plan = $this->generateBillingPlan($order, 'plan for order ' . $order->getId(), $merchantPreferences, [$paymentDefinition]); + $plan = $this->createBillingPlan($order, $plan); + $plan = $this->activateBillingPlan($order, $plan); + + $newPlan = new Plan(); + $newPlan->setId($plan->getId()); + + // There is no Billing agreement possible with credit card + $agreement = $this->createBillingAgreementWithPayPal($order, $newPlan, 'agreement ' . $order->getId(), $description); + + //We must update concerned order_product price... order discount... order postage... PayPal will create one invoice each cycle + $this->updateTheliaOrderForCycle($order, $planifiedPayment->getCycle(), $cycleAmount); + + $this->updatePayPalOrder($payPalOrderEvent->getPayPalOrder(), $agreement->getState(), null, $agreement->getId()); + + return $agreement; + } + + /** + * @throws PropelException + */ + public function updateTheliaOrderForCycle(Order $order, $cycle, $cycleAmount) + { + //Be sure that there is no rounding price lost with this method + $moneyLeft = $cycleAmount; + + $newPostage = round($order->getPostage() / $cycle, 2); + $newPostageTax = round($order->getPostageTax() / $cycle, 2); + $newDiscount = round($order->getDiscount() / $cycle, 2); + + $moneyLeft -= ($newPostage + $newPostageTax + $newDiscount); + $orderProducts = OrderProductQuery::create()->filterByOrderId($order->getId())->find(); + + /** @var OrderProduct $orderProduct */ + foreach ($orderProducts as $orderProduct) { + $newPrice = round($orderProduct->getPrice() / $cycle, 2); + $newPromoPrice = round($orderProduct->getPrice() / $cycle, 2); + + if ($orderProduct->getWasInPromo()) { + $moneyLeft -= $newPromoPrice; + } else { + $moneyLeft -= $newPrice; + } + + $orderProduct + ->setPrice($newPrice) + ->setPromoPrice($newPromoPrice) + ->save() + ; + $taxes = OrderProductTaxQuery::create()->filterByOrderProductId($orderProduct->getId())->find(); + + /** @var OrderProductTax $tax */ + foreach ($taxes as $tax) { + $newAmount = round($tax->getAmount() / $cycle, 2); + $newPromoAmount = round($tax->getPromoAmount() / $cycle, 2); + + if ($orderProduct->getWasInPromo()) { + $moneyLeft -= $newPromoAmount; + } else { + $moneyLeft -= $newAmount; + } + + $tax + ->setAmount($newAmount) + ->setPromoAmount($newPromoAmount) + ->save() + ; + } + } + + //Normally, $moneyLeft == 0 here. But in case of rouding price, adjust the rounding in the postage column + $newPostage += $moneyLeft; + + $order + ->setPostage($newPostage) + ->setPostageTax($newPostageTax) + ->setDiscount($newDiscount) + ->save() + ; + + return $order; + } + + /** + * @param $billingPlanId + * @return Plan + */ + public function getBillingPlan($billingPlanId) + { + $plan = Plan::get($billingPlanId, self::getApiContext()); + + return $plan; + } + + /** + * @param Order $order + * @param Plan $plan + * @return Plan + */ + public function activateBillingPlan(Order $order, Plan $plan) + { + $patch = new Patch(); + + $value = new PayPalModel('{ + "state":"ACTIVE" + }'); + + $patch + ->setOp('replace') + ->setPath('/') + ->setValue($value) + ; + + $patchRequest = new PatchRequest(); + $patchRequest->addPatch($patch); + + $plan->update($patchRequest, self::getApiContext()); + $plan = $this->getBillingPlan($plan->getId()); + + $this->setAndDispatchPaypalPlanEvent($order, $plan, 'update'); + + return $plan; + } + + /** + * This function construct and dispatch a PayPalPlan based on $eventName + * @param Order $order + * @param Plan $plan + * @param string $eventName ("create" | "update") + * @return void + */ + private function setAndDispatchPaypalPlanEvent(Order $order, Plan $plan, string $eventName) { + if (null === $payPalPlan = PaypalPlanQuery::create() + ->filterByPaypalOrderId($order->getId()) + ->filterByPlanId($plan->getId()) + ->findOne()) { + $payPalPlan = new PaypalPlan(); + $payPalPlan + ->setPaypalOrderId($order->getId()) + ->setPlanId($plan->getId()) + ; + } + + $payPalPlan->setState($plan->getState()); + $payPalPlanEvent = new PayPalPlanEvent($payPalPlan); + if ($eventName === "create") { + $this->dispatcher->dispatch($payPalPlanEvent, PayPalEvents::PAYPAL_PLAN_CREATE); + } else { + $this->dispatcher->dispatch($payPalPlanEvent, PayPalEvents::PAYPAL_PLAN_UPDATE); + } + } + + /** + * @param $token + * @param null $orderId + * @return Agreement + * @throws PayPalConnectionException + * @throws Exception + */ + public function activateBillingAgreementByToken($token, $orderId = null) + { + $agreement = new Agreement(); + + try { + $agreement->execute($token, self::getApiContext()); + + return $this->getBillingAgreement($agreement->getId()); + } catch (PayPalConnectionException $e) { + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $orderId + ], + Logger::CRITICAL + ); + throw $e; + } catch (Exception $e) { + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $orderId + ], + Logger::CRITICAL + ); + throw $e; + } + } + + /** + * @param Order $order + * @param $name + * @param $merchantPreferences + * @param array $paymentDefinitions + * @param string|null $description + * @param string $type + * @return Plan + * @throws Exception + */ + public function generateBillingPlan(Order $order, $name, $merchantPreferences, array $paymentDefinitions = [], ?string $description = '', string $type = self::PLAN_TYPE_FIXED) + { + if (!in_array($type, self::getAllowedPlanType())) { + $message = Translator::getInstance()->trans( + 'Invalid type send to generate billing plan', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + if (!is_array($paymentDefinitions) || count($paymentDefinitions) <= 0) { + $message = Translator::getInstance()->trans( + 'Invalid number of payment definition send to generate billing plan', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + $plan = new Plan(); + $plan + ->setName(substr($name, 0, self::MAX_API_LENGHT)) + ->setDescription(substr($description, 0, self::MAX_API_LENGHT)) + ->setType($type) + ->setPaymentDefinitions($paymentDefinitions) + ->setMerchantPreferences($merchantPreferences) + ; + + return $plan; + } + + /** + * @param Plan $plan + * @return bool + */ + public function deleteBillingPlan(Plan $plan) + { + $isDeleted = $plan->delete(self::getApiContext()); + + return $isDeleted; + } + + /** + * @param int $pageSize + * @return PlanList + */ + public function listBillingPlans(int $pageSize = 2) + { + $planList = Plan::all(['page_size' => $pageSize], self::getApiContext()); + + return $planList; + } + + /** + * @param Order $order + * @param Plan $plan + * @return Plan + * @throws PayPalConnectionException + * @throws Exception + */ + public function createBillingPlan(Order $order, Plan $plan) + { + try { + $plan = $plan->create(self::getApiContext()); + $this->setAndDispatchPaypalPlanEvent($order, $plan, 'create'); + + return $plan; + + } catch (PayPalConnectionException $e) { + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } catch (Exception $e) { + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } + } + + /** + * @param Order $order + * @param Plan $plan + * @param $creditCardId + * @param $name + * @param $description + * @return Agreement + * @throws PropelException + * @throws Exception + */ + public function createBillingAgreementWithCreditCard(Order $order, Plan $plan, $creditCardId, $name, $description) + { + $creditCardToken = new CreditCardToken(); + $creditCardToken->setCreditCardId($creditCardId); + + $fundingInstrument = new FundingInstrument(); + //$fundingInstrument->setCreditCardToken($creditCardToken); + + $card = new CreditCard(); + $card + ->setType('visa') + ->setNumber('4491759698858890') + ->setExpireMonth('12') + ->setExpireYear('2017') + ->setCvv2('128') + ; + $fundingInstrument->setCreditCard($card); + + $payer = self::generatePayer( + PayPal::PAYPAL_METHOD_CREDIT_CARD, + [$fundingInstrument], + self::generatePayerInfo(['email' => $order->getCustomer()->getEmail()]) + ); + + $agreement = $this->generateAgreement($order, $plan, $payer, $name, $description); + + $agreement = $agreement->create(self::getApiContext()); + + return $agreement; + } + + /** + * @param Order $order + * @param Plan $plan + * @param $name + * @param $description + * @return Agreement + * @throws Exception + */ + public function createBillingAgreementWithPayPal(Order $order, Plan $plan, $name, $description) + { + $payer = self::generatePayer(PayPal::PAYPAL_METHOD_PAYPAL); + + $agreement = $this->generateAgreement($order, $plan, $payer, $name, $description); + + $agreement = $agreement->create(self::getApiContext()); + + return $agreement; + } + + /** + * @param $agreementId + * @return Agreement + */ + public function getBillingAgreement($agreementId) + { + $agreement = Agreement::get($agreementId, self::getApiContext()); + + return $agreement; + } + + /** + * @param $agreementId + * @param array $params + * @return AgreementTransactions + */ + public function getBillingAgreementTransactions($agreementId, array $params = []) + { + if (is_array($params) || count($params) === 0) { + $params = [ + 'start_date' => date('Y-m-d', strtotime('-15 years')), + 'end_date' => date('Y-m-d', strtotime('+5 days')) + ]; + } + + $agreementTransactions = Agreement::searchTransactions($agreementId, $params, self::getApiContext()); + + return $agreementTransactions; + } + + /** + * @param Agreement $agreement + * @param string $note + * @return Agreement + */ + public function suspendBillingAgreement(Agreement $agreement, string $note = 'Suspending the agreement') + { + //Create an Agreement State Descriptor, explaining the reason to suspend. + $agreementStateDescriptor = new AgreementStateDescriptor(); + $agreementStateDescriptor->setNote($note); + + $agreement->suspend($agreementStateDescriptor, self::getApiContext()); + + $agreement = $this->getBillingAgreement($agreement->getId()); + + return $agreement; + } + + /** + * @param Agreement $agreement + * @param string $note + * @return Agreement + */ + public function reActivateBillingAgreement(Agreement $agreement, string $note = 'Reactivating the agreement') + { + //Create an Agreement State Descriptor, explaining the reason to re activate. + $agreementStateDescriptor = new AgreementStateDescriptor(); + $agreementStateDescriptor->setNote($note); + + $agreement->reActivate($agreementStateDescriptor, self::getApiContext()); + + $agreement = $this->getBillingAgreement($agreement->getId()); + + return $agreement; + } + + /** + * @param Order $order + * @param $name + * @param array $chargeModels + * @param null $cycleAmount + * @param string $type + * @param string $frequency + * @param int $frequencyInterval + * @param int $cycles + * @return PaymentDefinition + * @throws Exception + */ + public function createPaymentDefinition(Order $order, $name, array $chargeModels = [], $cycleAmount = null, string $type = self::PAYMENT_TYPE_REGULAR, string $frequency = self::PAYMENT_FREQUENCY_DAY, int $frequencyInterval = 1, int $cycles = 1) + { + if (!in_array($type, self::getAllowedPaymentType())) { + $message = Translator::getInstance()->trans( + 'Invalid payment type send to create payment definition', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + if (!in_array($frequency, self::getAllowedPaymentFrequency())) { + $message = Translator::getInstance()->trans( + 'Invalid payment frequency send to create payment definition', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + if (!is_array($chargeModels) || count($chargeModels) <= 0) { + $message = Translator::getInstance()->trans( + 'Invalid number of charge models send to create payment definition', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + $paymentDefinition = new PaymentDefinition(); + + if (null === $cycleAmount) { + $totalAmount = $order->getTotalAmount(); + $cycleAmount = round($totalAmount / $cycles, 2); + } + + $paymentDefinition + ->setName(substr($name, 0, self::MAX_API_LENGHT)) + ->setType($type) + ->setFrequency($frequency) + ->setFrequencyInterval($frequencyInterval) + ->setCycles($cycles) + ->setAmount(new Currency(['value' => $cycleAmount, 'currency' => self::getOrderCurrencyCode($order)])) + ->setChargeModels($chargeModels) + ; + + return $paymentDefinition; + } + + /** + * @param Order $order + * @param int $chargeAmount + * @param string $type + * @return ChargeModel + * @throws Exception + */ + public function createChargeModel(Order $order, int $chargeAmount = 0, string $type = self::CHARGE_TYPE_SHIPPING) + { + if (!in_array($type, self::getAllowedChargeType())) { + $message = Translator::getInstance()->trans( + 'Invalid charge type send to create charge model', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + $chargeModel = new ChargeModel(); + $chargeModel + ->setType($type) + ->setAmount(new Currency(['value' => $chargeAmount, 'currency' => self::getOrderCurrencyCode($order)])) + ; + + return $chargeModel; + } + + /** + * @param Order $order + * @param bool $autoBillAmount + * @param string $failAction + * @param int $maxFailAttempts + * @param int $feeAmount + * @return MerchantPreferences + * @throws Exception + */ + public function createMerchantPreferences(Order $order, bool $autoBillAmount = false, string $failAction = self::FAIL_AMOUNT_ACTION_CONTINUE, int $maxFailAttempts = 0, int $feeAmount = 0) + { + if (!in_array($failAction, self::getAllowedFailedAction())) { + $message = Translator::getInstance()->trans( + 'Invalid fail action send to create merchant preference', + [], + PayPal::DOMAIN_NAME + ); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::ERROR + ); + throw new Exception($message); + } + + $merchantPreferences = new MerchantPreferences(); + + $urlOk = URL::getInstance()->absoluteUrl( + $this->router->generate( + "paypal.agreement.ok", + [ + 'orderId' => $order->getId() + ], + Router::ABSOLUTE_URL + ) + ); + $urlKo = URL::getInstance()->absoluteUrl( + $this->router->generate( + "paypal.agreement.ko", + [ + 'orderId' => $order->getId() + ], + Router::ABSOLUTE_URL + ) + ); + + if ($autoBillAmount) { + $autoBillAmountStr = 'YES'; + } else { + $autoBillAmountStr = 'NO'; + } + + $merchantPreferences + ->setReturnUrl($urlOk) + ->setCancelUrl($urlKo) + ->setAutoBillAmount($autoBillAmountStr) + ->setInitialFailAmountAction($failAction) + ->setMaxFailAttempts($maxFailAttempts) + ->setSetupFee(new Currency(['value' => $feeAmount, 'currency' => self::getOrderCurrencyCode($order)])) + ; + + return $merchantPreferences; + } + + /** + * @param Order $order + * @return Order + * @throws Exception + * @throws PropelException + */ + public function duplicateOrder(Order $order) + { + $today = new Datetime; + $newOrder = new Order(); + $newOrder + ->setCustomerId($order->getCustomerId()) + ->setInvoiceOrderAddressId($order->getInvoiceOrderAddressId()) + ->setDeliveryOrderAddressId($order->getDeliveryOrderAddressId()) + ->setInvoiceDate($today->format('Y-m-d H:i:s')) + ->setCurrencyId($order->getCurrencyId()) + ->setCurrencyRate($order->getCurrencyRate()) + ->setDeliveryRef($order->getDeliveryRef()) + ->setInvoiceRef($order->getInvoiceRef()) + ->setDiscount($order->getDiscount()) + ->setPostage($order->getPostage()) + ->setPostageTax($order->getPostageTax()) + ->setPostageTaxRuleTitle($order->getPostageTaxRuleTitle()) + ->setPaymentModuleId($order->getPaymentModuleId()) + ->setDeliveryModuleId($order->getDeliveryModuleId()) + ->setStatusId($order->getStatusId()) + ->setLangId($order->getLangId()) + ->setCartId($order->getCartId()) + ->save() + ; + + $orderProducts = OrderProductQuery::create()->filterByOrderId($order->getId())->find(); + + /** @var OrderProduct $orderProduct */ + foreach ($orderProducts as $orderProduct) { + $newOrderProduct = new OrderProduct(); + $newOrderProduct + ->setOrderId($newOrder->getId()) + ->setProductRef($orderProduct->getProductRef()) + ->setProductSaleElementsRef($orderProduct->getProductSaleElementsRef()) + ->setProductSaleElementsId($orderProduct->getProductSaleElementsId()) + ->setTitle($orderProduct->getTitle()) + ->setChapo($orderProduct->getChapo()) + ->setDescription($orderProduct->getDescription()) + ->setPostscriptum($orderProduct->getPostscriptum()) + ->setQuantity($orderProduct->getQuantity()) + ->setPrice($orderProduct->getPrice()) + ->setPromoPrice($orderProduct->getPromoPrice()) + ->setWasNew($orderProduct->getWasNew()) + ->setWasInPromo($orderProduct->getWasInPromo()) + ->setWeight($orderProduct->getWeight()) + ->setEanCode($orderProduct->getEanCode()) + ->setTaxRuleTitle($orderProduct->getTaxRuleTitle()) + ->setTaxRuleDescription($orderProduct->getTaxRuleDescription()) + ->setParent($orderProduct->getParent()) + ->setVirtual($orderProduct->getVirtual()) + ->setVirtualDocument($orderProduct->getVirtualDocument()) + ->save() + ; + + $orderProductTaxes = OrderProductTaxQuery::create()->filterByOrderProductId($orderProduct->getId())->find(); + + /** @var OrderProductTax $orderProductTax */ + foreach ($orderProductTaxes as $orderProductTax) { + + $newOrderProductTax = new OrderProductTax(); + $newOrderProductTax + ->setOrderProductId($newOrderProduct->getId()) + ->setTitle($orderProductTax->getTitle()) + ->setDescription($orderProductTax->getDescription()) + ->setAmount($orderProductTax->getAmount()) + ->setPromoAmount($orderProductTax->getPromoAmount()) + ->save() + ; + } + } + + if (null !== $payPalOrder = PaypalOrderQuery::create()->findOneById($order->getId())) { + $newPayPalOrder = new PaypalOrder(); + $newPayPalOrder + ->setId($newOrder->getId()) + ->setPaymentId($payPalOrder->getPaymentId()) + ->setAgreementId($payPalOrder->getAgreementId()) + ->setCreditCardId($payPalOrder->getCreditCardId()) + ->setState($payPalOrder->getState()) + ->setAmount($payPalOrder->getAmount()) + ->setDescription($payPalOrder->getDescription()) + ->setPayerId($payPalOrder->getPayerId()) + ->setToken($payPalOrder->getToken()) + ; + $newPayPalOrderEvent = new PayPalOrderEvent($newPayPalOrder); + $this->dispatcher->dispatch($newPayPalOrderEvent, PayPalEvents::PAYPAL_ORDER_CREATE); + + $payPalPlans = PaypalPlanQuery::create()->filterByPaypalOrderId($payPalOrder->getId()); + + /** @var PaypalPlan $payPalPlan */ + foreach ($payPalPlans as $payPalPlan) { + + $newPayPalPlan = new PaypalPlan(); + $newPayPalPlan + ->setPaypalOrderId($newPayPalOrderEvent->getPayPalOrder()->getId()) + ->setPlanId($payPalPlan->getPlanId()) + ->setState($payPalPlan->getState()) + ; + + $newPayPalPlanEvent = new PayPalPlanEvent($newPayPalPlan); + $this->dispatcher->dispatch($newPayPalPlanEvent, PayPalEvents::PAYPAL_PLAN_CREATE); + } + } + + return $newOrder; + } + + /** + * @param Order $order + * @param Plan $plan + * @param Payer $payer + * @param $name + * @param string $description + * @return Agreement + * @throws Exception + */ + public function generateAgreement(Order $order, Plan $plan, Payer $payer, $name, string $description = '') + { + $agreement = new Agreement(); + $agreement + ->setName($name) + ->setDescription($description) + ->setStartDate((new Datetime)->format('Y-m-d\TG:i:s\Z')) + ->setPlan($plan) + ; + + //Add Payer to Agreement + $agreement + ->setPayer($payer) + ->setShippingAddress(self::generateShippingAddress($order)) + ; + + return $agreement; + } + + /** + * @param Order $order + * @return string + */ + public static function getOrderCurrencyCode(Order $order) + { + if (null === $currency = CurrencyQuery::create()->findOneById($order->getCurrencyId())) { + $currency = \Thelia\Model\Currency::getDefaultCurrency(); + } + + return $currency->getCode(); + } + + /** + * @return array + */ + public static function getAllowedPlanType() + { + return [ + self::PLAN_TYPE_FIXED, + self::PLAN_TYPE_INFINITE + ]; + } + + /** + * @return array + */ + public static function getAllowedPaymentType() + { + return [ + self::PAYMENT_TYPE_REGULAR, + self::PAYMENT_TYPE_TRIAL + ]; + } + + /** + * @return array + */ + public static function getAllowedChargeType() + { + return [ + self::CHARGE_TYPE_SHIPPING, + self::CHARGE_TYPE_TAX + ]; + } + + /** + * @return array + */ + public static function getAllowedPaymentFrequency() + { + return [ + self::PAYMENT_FREQUENCY_DAY => self::PAYMENT_FREQUENCY_DAY, + self::PAYMENT_FREQUENCY_WEEK => self::PAYMENT_FREQUENCY_WEEK, + self::PAYMENT_FREQUENCY_MONTH => self::PAYMENT_FREQUENCY_MONTH, + self::PAYMENT_FREQUENCY_YEAR => self::PAYMENT_FREQUENCY_YEAR + ]; + } + + /** + * @return array + */ + public static function getAllowedFailedAction() + { + return [ + self::FAIL_AMOUNT_ACTION_CANCEL, + self::FAIL_AMOUNT_ACTION_CONTINUE + ]; + } +} diff --git a/domokits/local/modules/PayPal/Service/PayPalCustomerService.php b/domokits/local/modules/PayPal/Service/PayPalCustomerService.php new file mode 100644 index 0000000..d3707b4 --- /dev/null +++ b/domokits/local/modules/PayPal/Service/PayPalCustomerService.php @@ -0,0 +1,164 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Service; + +use Monolog\Logger; +use PayPal\Api\OpenIdSession; +use PayPal\Api\OpenIdTokeninfo; +use PayPal\Api\OpenIdUserinfo; +use PayPal\Model\PaypalCustomer; +use PayPal\Model\PaypalCustomerQuery; +use PayPal\Service\Base\PayPalBaseService; +use Thelia\Core\Security\SecurityContext; + +/** + * Class PayPalCustomerService + * @package PayPal\Service + */ +class PayPalCustomerService +{ + /** @var SecurityContext */ + protected $securityContext; + + /** + * PayPalService constructor. + * @param SecurityContext $securityContext + */ + public function __construct(SecurityContext $securityContext) + { + $this->securityContext = $securityContext; + } + + /** + * @param $authorizationCode + * @return OpenIdUserinfo + * @throws \Exception + */ + public function getUserInfoWithAuthorizationCode($authorizationCode) + { + try { + $accessToken = OpenIdTokeninfo::createFromAuthorizationCode( + ['code' => $authorizationCode], + null, + null, + PayPalBaseService::getApiContext() + ); + + return $this->getUserInfo($accessToken->getAccessToken()); + } catch (\Exception $ex) { + PayPalLoggerService::log($ex->getMessage(), [], Logger::ERROR); + throw $ex; + } + } + + /** + * @param $accessToken + * @return OpenIdUserinfo + */ + public function getUserInfo($accessToken) + { + $params = array('access_token' => $accessToken); + $userInfo = OpenIdUserinfo::getUserinfo($params, PayPalBaseService::getApiContext()); + + return $userInfo; + } + + /** + * @return PaypalCustomer + */ + public function getCurrentPayPalCustomer() + { + $payPalCustomer = new PaypalCustomer(); + + if (null !== $customer = $this->securityContext->getCustomerUser()) { + + $payPalCustomer = PaypalCustomerQuery::create()->findOneById($customer->getId()); + + } + + return $payPalCustomer; + } + + /** + * @param $refreshToken + * @return OpenIdTokeninfo + * @throws \Exception + */ + public function generateAccessTokenFromRefreshToken($refreshToken) + { + try { + $tokenInfo = new OpenIdTokeninfo(); + $tokenInfo = $tokenInfo->createFromRefreshToken(['refresh_token' => $refreshToken], PayPalBaseService::getApiContext()); + + return $tokenInfo; + } catch (\Exception $ex) { + PayPalLoggerService::log($ex->getMessage(), [], Logger::ERROR); + throw $ex; + } + } + + /** + * @param $refreshToken + * @return OpenIdUserinfo + * @throws \Exception + */ + public function getUserInfoWithRefreshToken($refreshToken) + { + try { + $tokenInfo = $this->generateAccessTokenFromRefreshToken($refreshToken); + + return $this->getUserInfo($tokenInfo->getAccessToken()); + } catch (\Exception $ex) { + PayPalLoggerService::log($ex->getMessage(), [], Logger::ERROR); + throw $ex; + } + } + + /** + * @return string + */ + public function getUrlToRefreshToken() + { + //Get Authorization URL returns the redirect URL that could be used to get user's consent + $redirectUrl = OpenIdSession::getAuthorizationUrl( + 'http://25b3ee89.ngrok.io/', + [ + 'openid', + 'profile', + 'address', + 'email', + 'phone', + 'https://uri.paypal.com/services/paypalattributes', + 'https://uri.paypal.com/services/expresscheckout', + 'https://uri.paypal.com/services/invoicing' + ], + null, + null, + null, + PayPalBaseService::getApiContext() + ); + + return $redirectUrl; + } +} diff --git a/domokits/local/modules/PayPal/Service/PayPalLoggerService.php b/domokits/local/modules/PayPal/Service/PayPalLoggerService.php new file mode 100644 index 0000000..3a3556b --- /dev/null +++ b/domokits/local/modules/PayPal/Service/PayPalLoggerService.php @@ -0,0 +1,128 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Service; + +use Datetime; +use Monolog\Logger; +use MySQLHandler\MySQLHandler; +use PayPal\Model\Map\PaypalLogTableMap; +use PayPal\Model\PaypalLogQuery; +use PayPal\PayPal; +use Propel\Runtime\Exception\PropelException; +use Propel\Runtime\Propel; +use Thelia\Install\Database; + +/** + * Class PayPalLoggerService + * @package PayPal\Service + */ +class PayPalLoggerService +{ + /** + * @param $message + * @param array $params + * @param int $level + * @throws PropelException + */ + public static function log($message, array $params = [], int $level = Logger::DEBUG) + { + $staticParams = self::getStaticParams(); + + $logger = new Logger(PayPal::getModuleCode()); + + //Create MysqlHandler + $mySQLHandler = new MyOwnSQLHandler( + Propel::getConnection()->getWrappedConnection(), + PaypalLogTableMap::TABLE_NAME, + array_keys($staticParams), + $level + ); + + $logger->pushHandler($mySQLHandler); + + //Now you can use the logger, and further attach additional information + switch ($level) { + case Logger::INFO: + $logger->addRecord(LOG_INFO,$message, array_merge($staticParams, $params)); + break; + + case Logger::NOTICE: + $logger->addRecord(LOG_NOTICE,$message, array_merge($staticParams, $params)); + break; + + case Logger::WARNING: + $logger->addRecord(LOG_WARNING,$message, array_merge($staticParams, $params)); + break; + + case Logger::ERROR: + $logger->addRecord(LOG_ERR,$message, array_merge($staticParams, $params)); + break; + + case Logger::CRITICAL: + $logger->addRecord(LOG_CRIT,$message, array_merge($staticParams, $params)); + break; + + case Logger::ALERT: + $logger->addRecord(LOG_ALERT,$message, array_merge($staticParams, $params)); + break; + + case Logger::EMERGENCY: + $logger->addRecord(LOG_EMERG,$message, array_merge($staticParams, $params)); + break; + + default: + $logger->addRecord(LOG_DEBUG,$message, array_merge($staticParams, $params)); + break; + } + + } + + /** + * @return array + * @throws \Propel\Runtime\Exception\PropelException + */ + public static function getStaticParams() + { + $psr3Fields = ['channel', 'level', 'message', 'time']; + $payPalLogFields = PaypalLogTableMap::getFieldNames(PaypalLogTableMap::TYPE_FIELDNAME); + $readableDate = new Datetime(); + + $staticParams = []; + foreach ($payPalLogFields as $fieldName) { + + // Do not interpret psr3 fields + if (in_array($fieldName, $psr3Fields)) { + continue; + } + + if (in_array($fieldName, ['created_at', 'updated_at'])) { + $staticParams[$fieldName] = $readableDate->format('Y-m-d H:i:s'); + } else { + $staticParams[$fieldName] = null; + } + } + + return $staticParams; + } +} diff --git a/domokits/local/modules/PayPal/Service/PayPalPaymentService.php b/domokits/local/modules/PayPal/Service/PayPalPaymentService.php new file mode 100644 index 0000000..90dfdc3 --- /dev/null +++ b/domokits/local/modules/PayPal/Service/PayPalPaymentService.php @@ -0,0 +1,417 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace PayPal\Service; + +use Exception; +use Monolog\Logger; +use PayPal\Api\Amount; +use PayPal\Api\CreditCard; +use PayPal\Api\CreditCardToken; +use PayPal\Api\Details; +use PayPal\Api\FundingInstrument; +use PayPal\Api\FuturePayment; +use PayPal\Api\OpenIdTokeninfo; +use PayPal\Api\Payer; +use PayPal\Api\Payment; +use PayPal\Api\PaymentExecution; +use PayPal\Api\RedirectUrls; +use PayPal\Api\Transaction; +use PayPal\Exception\PayPalConnectionException; +use PayPal\PayPal; +use PayPal\Service\Base\PayPalBaseService; +use Propel\Runtime\Exception\PropelException; +use Thelia\Core\Translation\Translator; +use Thelia\Model\Cart; +use Thelia\Model\Currency; +use Thelia\Model\CurrencyQuery; +use Thelia\Model\Order; +use Thelia\Tools\URL; + + +/** + * Class PayPalPaymentService + * @package PayPal\Service + */ +class PayPalPaymentService extends PayPalBaseService +{ + /** + * Create a payment using a previously obtained + * credit card id. The corresponding credit + * card is used as the funding instrument. + * + * @param Order $order + * @param bool $future + * @param string|null $creditCardId + * @param string|null $description + * @return Payment + * @throws PropelException|PayPalConnectionException + */ + public function makePayment(Order $order, ?string $creditCardId = null, ?string $description = null, bool $future = false) + { + $payPalOrderEvent = $this->generatePayPalOrder($order); + + if (null !== $creditCardId) { + $creditCardToken = new CreditCardToken(); + $creditCardToken->setCreditCardId($creditCardId); + + $fundingInstrument = new FundingInstrument(); + $fundingInstrument->setCreditCardToken($creditCardToken); + + $payer = self::generatePayer(PayPal::PAYPAL_METHOD_CREDIT_CARD, [$fundingInstrument]); + } else { + $payer = self::generatePayer(); + } + + // Specify the payment amount. + if (null === $currency = CurrencyQuery::create()->findOneById($order->getCurrencyId())) { + $currency = Currency::getDefaultCurrency(); + } + + $amount = $this->generateAmount($order, $currency); + + $transaction = $this->generateTransaction($amount, $description); + + $payment = $this->generatePayment($order, $payer, $transaction, $future); + + $this->updatePayPalOrder($payPalOrderEvent->getPayPalOrder(), $payment->getState(), $payment->getId()); + + return $payment; + } + + /** + * @throws PayPalConnectionException + * @throws PropelException + */ + public function makePaymentFromCart(Cart $cart, $description = null, $future = false, $fromCartView = true) + { + $payer = self::generatePayer(); + + // Specify the payment amount. + if (null === $currency = CurrencyQuery::create()->findOneById($cart->getCurrencyId())) { + $currency = Currency::getDefaultCurrency(); + } + + $amount = $this->generateAmountFromCart($cart, $currency); + + $transaction = $this->generateTransaction($amount, $description); + + $payment = $this->generatePaymentFromCart($cart, $payer, $transaction, $future, $fromCartView); + + return $payment; + } + + /** + * Completes the payment once buyer approval has been + * obtained. Used only when the payment method is 'paypal' + * + * @param string $paymentId id of a previously created + * payment that has its payment method set to 'paypal' + * and has been approved by the buyer. + * + * @param string $payerId PayerId as returned by PayPal post + * buyer approval. + * + * @return Payment + */ + public function executePayment(string $paymentId, string $payerId, Details $details = null) + { + $payment = $this->getPaymentDetails($paymentId); + $paymentExecution = new PaymentExecution(); + $paymentExecution->setPayerId($payerId); + + if (null !== $details) { + $amount = new Amount(); + $totalDetails = (float)$details->getShipping() + (float)$details->getTax() + (float)$details->getSubtotal(); + $amount + ->setCurrency('EUR') + ->setTotal($totalDetails) + ->setDetails($details) + ; + + $transaction = new Transaction(); + $transaction->setAmount($amount); + + $paymentExecution->addTransaction($transaction); + } + + $payment = $payment->execute($paymentExecution, self::getApiContext()); + + return $payment; + } + + public function createDetails($shipping = 0, $shippingTax = 0, $subTotal = 0) + { + $details = new Details(); + $details + ->setShipping($shipping) + ->setTax($shippingTax) + ->setSubtotal($subTotal) + ; + + return $details; + } + + /** + * Retrieves the payment information based on PaymentID from Paypal APIs + * + * @param $paymentId + * + * @return Payment + */ + public function getPaymentDetails($paymentId) + { + $payment = Payment::get($paymentId, self::getApiContext()); + + return $payment; + } + + /** + * @param $authorizationCode + * @return OpenIdTokeninfo + * @throws PayPalConnectionException + */ + public function generateAccessToken($authorizationCode) + { + try { + // Obtain Authorization Code from Code, Client ID and Client Secret + $accessToken = OpenIdTokeninfo::createFromAuthorizationCode( + ['code' => $authorizationCode], + null, + null, + self::getApiContext() + ); + + return $accessToken; + } catch (PayPalConnectionException $ex) { + PayPalLoggerService::log($ex->getMessage(), [], Logger::ERROR); + throw $ex; + } + } + + /** + * @param $type + * @param $number + * @param $expireMonth + * @param $expireYear + * @param $cvv2 + * @return string + * @throws Exception + */ + public function getPayPalCreditCardId($type, $number, $expireMonth, $expireYear, $cvv2) + { + try { + $card = new CreditCard(); + $card->setType($type); + $card->setNumber((int)$number); + $card->setExpireMonth((int)$expireMonth); + $card->setExpireYear((int)$expireYear); + $card->setCvv2($cvv2); + + $card->create(self::getApiContext()); + + return $card->getId(); + } catch (Exception $e) { + PayPalLoggerService::log($e->getMessage(), [], Logger::ERROR); + throw new Exception(Translator::getInstance()->trans('Credit card is invalid', [], PayPal::DOMAIN_NAME)); + } + } + + /** + * @param Order $order + * @param Payer $payer + * @param Transaction $transaction + * @param bool $future + * @return FuturePayment|Payment + * @throws PayPalConnectionException + * @throws Exception + */ + public function generatePayment(Order $order, Payer $payer, Transaction $transaction, $future = false) + { + if ($future) { + $payment = new FuturePayment(); + $payment->setIntent('authorize'); + } else { + $payment = new Payment(); + $payment->setIntent('sale'); + } + + $payment + ->setRedirectUrls($this->getRedirectUrls($order)) + ->setPayer($payer) + ->setTransactions([$transaction]) + ; + + $clientMetadataId = '123123456'; + + try { + + if ($future) { + + //$authorizationCode = self::getAuthorizationCode(); + $refreshToken = $this->getRefreshToken(); + //$refreshToken = FuturePayment::getRefreshToken($this->getAuthorizationCode(), self::getApiContext()); + $payment->updateAccessToken($refreshToken, self::getApiContext()); + $payment->create(self::getApiContext(), $clientMetadataId); + + } else { + $payment->create(self::getApiContext()); + } + + return $payment; + + } catch (PayPalConnectionException $e) { + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } catch (Exception $e) { + PayPalLoggerService::log( + $e->getMessage(), + [ + 'customer_id' => $order->getCustomerId(), + 'order_id' => $order->getId() + ], + Logger::CRITICAL + ); + throw $e; + } + } + + /** + * @param Cart $cart + * @param Payer $payer + * @param Transaction $transaction + * @param bool $future + * @param bool $fromCartView + * @return FuturePayment|Payment + * @throws PayPalConnectionException + * @throws Exception + */ + public function generatePaymentFromCart(Cart $cart, Payer $payer, Transaction $transaction, bool $future = false, bool $fromCartView = true) + { + if ($future) { + $payment = new FuturePayment(); + $payment->setIntent('authorize'); + } else { + $payment = new Payment(); + $payment->setIntent('sale'); + } + + if ($fromCartView) { + $payment->setRedirectUrls($this->getRedirectCartUrls($cart)); + } else { + $payment->setRedirectUrls($this->getRedirectInvoiceUrls($cart)); + } + $payment + ->setPayer($payer) + ->setTransactions([$transaction]) + ; + + $clientMetadataId = '123123456'; + + try { + + if ($future) { + + //$authorizationCode = self::getAuthorizationCode(); + $refreshToken = $this->getRefreshToken(); + //$refreshToken = FuturePayment::getRefreshToken($this->getAuthorizationCode(), self::getApiContext()); + $payment->updateAccessToken($refreshToken, self::getApiContext()); + $payment->create(self::getApiContext(), $clientMetadataId); + + } else { + $payment->create(self::getApiContext()); + } + + return $payment; + + } catch (PayPalConnectionException $e) { + $message = sprintf('url : %s. data : %s. message : %s', $e->getUrl(), $e->getData(), $e->getMessage()); + PayPalLoggerService::log( + $message, + [], + Logger::CRITICAL + ); + throw $e; + } catch (Exception $e) { + PayPalLoggerService::log( + $e->getMessage(), + [], + Logger::CRITICAL + ); + throw $e; + } + } + + /** + * @param Order $order + * @return RedirectUrls + */ + public function getRedirectUrls(Order $order) + { + $redirectUrls = new RedirectUrls(); + $urlOk = URL::getInstance()->absoluteUrl('/module/paypal/ok/' . $order->getId()); + $urlCancel = URL::getInstance()->absoluteUrl('/module/paypal/cancel/' . $order->getId()); + $redirectUrls->setReturnUrl($urlOk); + $redirectUrls->setCancelUrl($urlCancel); + + return $redirectUrls; + } + + /** + * @param Cart $cart + * @return RedirectUrls + */ + public function getRedirectCartUrls(Cart $cart) + { + $redirectUrls = new RedirectUrls(); + $urlOk = URL::getInstance()->absoluteUrl('/module/paypal/express/checkout/ok/' . $cart->getId()); + $urlCancel = URL::getInstance()->absoluteUrl('/module/paypal/express/checkout/ko/' . $cart->getId()); + $redirectUrls->setReturnUrl($urlOk); + $redirectUrls->setCancelUrl($urlCancel); + + return $redirectUrls; + } + + /** + * @param Cart $cart + * @return RedirectUrls + */ + public function getRedirectInvoiceUrls(Cart $cart) + { + $redirectUrls = new RedirectUrls(); + $urlOk = URL::getInstance()->absoluteUrl('/module/paypal/invoice/express/checkout/ok/' . $cart->getId()); + $urlCancel = URL::getInstance()->absoluteUrl('/module/paypal/invoice/express/checkout/ko/' . $cart->getId()); + $redirectUrls->setReturnUrl($urlOk); + $redirectUrls->setCancelUrl($urlCancel); + + return $redirectUrls; + } +} diff --git a/domokits/local/modules/PayPal/composer.json b/domokits/local/modules/PayPal/composer.json new file mode 100644 index 0000000..1201cc9 --- /dev/null +++ b/domokits/local/modules/PayPal/composer.json @@ -0,0 +1,14 @@ +{ + "name": "thelia/paypal-module", + "description": "PayPal module for Thelia ecommerce solution ", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "cqfdev/paypal-rest-api-sdk-php": "1.*", + "wazaari/monolog-mysql": "1.0.5" + }, + "extra": { + "installer-name": "PayPal" + } +} diff --git a/domokits/local/modules/PayPal/images/logo.png b/domokits/local/modules/PayPal/images/logo.png new file mode 100644 index 0000000..1fe1879 Binary files /dev/null and b/domokits/local/modules/PayPal/images/logo.png differ diff --git a/domokits/local/modules/PayPal/images/payment_classic.png b/domokits/local/modules/PayPal/images/payment_classic.png new file mode 100644 index 0000000..8fcb8b0 Binary files /dev/null and b/domokits/local/modules/PayPal/images/payment_classic.png differ diff --git a/domokits/local/modules/PayPal/images/payment_classic_incontext.png b/domokits/local/modules/PayPal/images/payment_classic_incontext.png new file mode 100644 index 0000000..3681705 Binary files /dev/null and b/domokits/local/modules/PayPal/images/payment_classic_incontext.png differ diff --git a/domokits/local/modules/PayPal/images/payment_credit_card.png b/domokits/local/modules/PayPal/images/payment_credit_card.png new file mode 100644 index 0000000..142d268 Binary files /dev/null and b/domokits/local/modules/PayPal/images/payment_credit_card.png differ diff --git a/domokits/local/modules/PayPal/images/payment_express_checkout.png b/domokits/local/modules/PayPal/images/payment_express_checkout.png new file mode 100644 index 0000000..e9f4237 Binary files /dev/null and b/domokits/local/modules/PayPal/images/payment_express_checkout.png differ diff --git a/domokits/local/modules/PayPal/images/payment_recursive.png b/domokits/local/modules/PayPal/images/payment_recursive.png new file mode 100644 index 0000000..2f62d0a Binary files /dev/null and b/domokits/local/modules/PayPal/images/payment_recursive.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_agreement.jpeg b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_agreement.jpeg new file mode 100644 index 0000000..eaa3af2 Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_agreement.jpeg differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf1.png b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf1.png new file mode 100644 index 0000000..7904991 Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf1.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf2.png b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf2.png new file mode 100644 index 0000000..47dad7a Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf2.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf3.png b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf3.png new file mode 100644 index 0000000..e7546de Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_conf3.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_live_button.png b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_live_button.png new file mode 100644 index 0000000..e39588e Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_live_button.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_webhook.png b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_webhook.png new file mode 100644 index 0000000..419d33c Binary files /dev/null and b/domokits/local/modules/PayPal/templates/backOffice/default/assets/paypal_webhook.png differ diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/form/create-or-update-planified-payment-form.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/form/create-or-update-planified-payment-form.html new file mode 100644 index 0000000..454fa4f --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/form/create-or-update-planified-payment-form.html @@ -0,0 +1,38 @@ +{render_form_field field="locale"} +
+
+ {intl l="Global informations of this planified payment" d="paypal.bo.default"} +
+
+
+
+ {render_form_field field="title"} +
+
+
+
+ {render_form_field field="description"} +
+
+
+
+ {render_form_field field="frequency_interval"} +
+
+ {render_form_field field="frequency"} +
+
+ {render_form_field field="cycle"} +
+
+ +
+
+ {render_form_field field="min_amount"} +
+
+ {render_form_field field="max_amount"} +
+
+
+
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row-js.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row-js.html new file mode 100644 index 0000000..c4fef9f --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row-js.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row.html new file mode 100644 index 0000000..7d7acd7 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/includes/paypal-log-row.html @@ -0,0 +1,20 @@ +getHook()} style="cursor: pointer;"{/if}> + + {format_date date=$CREATE_DATE} + + {if $log->getHook()} + {$log->getHook()} ({intl l="See webhook details" d="paypal.bo.default"}) + + {else} + {$log->getMessage() nofilter} + {/if} + + {$log->getCustomerId()} + getOrderId()}">{$log->getOrderId()} + + {$log->getHook()} + + {intl l="critical_{$log->getLevel()}" d="paypal.bo.default"} + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/menu/menu.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/menu/menu.html new file mode 100644 index 0000000..213691f --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/menu/menu.html @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/module-configuration.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/module-configuration.html new file mode 100644 index 0000000..781815a --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/module-configuration.html @@ -0,0 +1,236 @@ +
+
+
+ + {include file="paypal/menu/menu.html" selectedMenu="general"} + +
+ +
+ + {form name="paypal_form_configure"} + + {if $form_error|default:null} +
{$form_error_message}
+ {elseif $general_error|default:null} +
+ {$general_error} +
+ {/if} + + + {form_hidden_fields form=$form} +
+
+ {include file = "includes/inner-form-toolbar.html" + hide_flags = true + page_url = "{url path='/admin/module/Paypal'}" + close_url = "{url path='/admin/modules'}" + } +
+
+
+
+
+

+ 1 + {intl l="SandBox configuration" d="paypal.bo.default"} + +

+
+
+
+ Payment configuration +
+
+
+
+ + {custom_render_form_field form=$form field="sandbox"} + + {$label} + {/custom_render_form_field} + + {render_form_field form=$form field="sandbox_login" value=$sandbox_login|default:null} + {render_form_field form=$form field="sandbox_password" value=$sandbox_password|default:null} + {render_form_field form=$form field="sandbox_merchant_id" value=$sandbox_merchant_id|default:null} + + {render_form_field form=$form field="allowed_ip_list" value={$allowed_ip_list|default:null}} +
+
+
+
+

+  {intl l="Help" d="paypal.bo.default"} : {intl l="Configuration" d="paypal.bo.default"} +

+ - {intl l="Log In on developer.paypal.com" d="paypal.bo.default"}
+ - {intl l="Create REST API apps here" d="paypal.bo.default"}
+ - {intl l="Click on Create App" d="paypal.bo.default"}
+ - {intl l="Fill the fields : App Name & Sandbox developer account" d="paypal.bo.default"}
+ - {intl l="Click on Create App" d="paypal.bo.default"}
+ - {intl l="Copy & Paste the Client ID in the form below" d="paypal.bo.default"}
+ - {intl l="Copy & Paste the Client SECRET in the form below" d="paypal.bo.default"}
+ - {intl l="In SANDBOX WEBHOOKS" d="paypal.bo.default"} :
+ -     {intl l="Add Webhook" d="paypal.bo.default"}
+ -         {url path="/module/paypal/webhook/all/events"}
+ -         {intl l="Check 'All events'" d="paypal.bo.default"}
+ - {intl l="In SANDBOX APP SETTINGS" d="paypal.bo.default"} :
+ -     {intl l="Return URL" d="paypal.bo.default"}
+ -         {navigate to="index"}
+ -         {url path="/module/paypal/login/ok"}
+ -         {url path="/module/paypal/agreement/ok"}
+ -         {url path="/module/paypal/agreement/ko"}
+ -
+ - {intl l="Check" d="paypal.bo.default"} {intl l="Accept payments" d="paypal.bo.default"} 
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Future Payments" d="paypal.bo.default"}
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Billing agreements" d="paypal.bo.default"}
+ - {intl l="Check" d="paypal.bo.default"} {intl l="Invoicing" d="paypal.bo.default"} 
+ - {intl l="Check" d="paypal.bo.default"} {intl l="Payouts" d="paypal.bo.default"} 
+ - {intl l="Check" d="paypal.bo.default"} {intl l="PayPal Here" d="paypal.bo.default"} 
+ - {intl l="Check" d="paypal.bo.default"} {intl l="Log In with PayPal" d="paypal.bo.default"} 
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Personal Information" d="paypal.bo.default"}
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Address Information" d="paypal.bo.default"}
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Account Information" d="paypal.bo.default"}
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Use Seamless Checkout" d="paypal.bo.default"}
+ -     {intl l="Check" d="paypal.bo.default"} {intl l="Allow the customers who haven't yet confirmed their email address with PayPal, to log in to your app" d="paypal.bo.default"}
+ - {intl l="That's it !" d="paypal.bo.default"}
+
+
+
+
+
+

+ 2 + {intl l="Production configuration" d="paypal.bo.default"} +

+
+
+
+ Payment configuration +
+
+
+
+ {render_form_field form=$form field="login" value=$login|default:null} + {render_form_field form=$form field="password" value=$password|default:null} + {render_form_field form=$form field="merchant_id" value=$merchant_id|default:null} +
+
+
+
+

+  {intl l="Help" d="paypal.bo.default"} : {intl l="Configuration" d="paypal.bo.default"} +

+ - {intl l="In your PayPal page API configuration" d="paypal.bo.default"}
+ - {intl l="Click on the Live Button" d="paypal.bo.default"}
+ Payment configuration
+ - {intl l="And configure it like the SandBox" d="paypal.bo.default"}
+
+
+
+
+

+ 3 + {intl l="Payment configuration" d="paypal.bo.default"} +

+ +
+
+ Payment configuration +
+
+ {custom_render_form_field form=$form field="method_paypal"} + + {$label} + {/custom_render_form_field} + + {custom_render_form_field form=$form field="method_paypal_with_in_context"} + + {$label} + {/custom_render_form_field} + + {custom_render_form_field form=$form field="method_express_checkout"} + + {$label} + {/custom_render_form_field} + + {custom_render_form_field form=$form field="method_credit_card"} + + {$label} + {/custom_render_form_field} + +
+ + {intl d='paypal.bo.default' l='This method works only with PayPal PRO UK account. Please contact PayPal to upgrade your account if you need this service. For more informations, go here'} +
+ + {custom_render_form_field form=$form field="method_planified_payment"} + + {$label} + {/custom_render_form_field} + +
+ + {intl d='paypal.bo.default' l='This method use PayPal webhooks and works only in HTTPS !'} +
+ +
+ + {intl d='paypal.bo.default' l='You can add some planified payment here.' url={url path="/admin/module/paypal/configure/planified"}} +
+ + {custom_render_form_field form=$form field="send_confirmation_message_only_if_paid"} + + {$label} + {/custom_render_form_field} + + {custom_render_form_field form=$form field="send_payment_confirmation_message"} + + {$label} + {/custom_render_form_field} + + {custom_render_form_field form=$form field="send_recursive_message"} + + {$label} + {/custom_render_form_field} + +
+ + {intl d='paypal.bo.default' l='You can edit the payment confirmation email sent to the customer after a successful payment.' url={url path="/admin/configuration/messages"}} +
+ + {custom_render_form_field form=$form field="minimum_amount"} +
+ + {currency attr='symbol'} +
+ {/custom_render_form_field} + + {custom_render_form_field form=$form field="maximum_amount"} +
+ + {currency attr='symbol'} +
+ {/custom_render_form_field} + + {render_form_field form=$form field="cart_item_count" value=$cart_item_count} +
+
+
+
+
+
+ {include file = "includes/inner-form-toolbar.html" + hide_flags = true + page_url = "{url path='/admin/module/Paypal'}" + close_url = "{url path='/admin/modules'}" + } +
+
+ + {/form} +
+
+
+
+
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/order-edit-js.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/order-edit-js.html new file mode 100644 index 0000000..3a1e2f3 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/order-edit-js.html @@ -0,0 +1 @@ +{include file = "paypal/includes/paypal-log-row-js.html"} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/payment-information.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/payment-information.html new file mode 100644 index 0000000..52fda46 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/payment-information.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + {loop name="paypal_log" type="paypal_log" backend_context=true order_id=$order_id order="date-reverse"} + {include file = "paypal/includes/paypal-log-row.html"} + {/loop} + +
{intl l='Date' d="paypal.bo.default"}{intl l='Details' d="paypal.bo.default"}{intl l="Customer ID" d="paypal.bo.default"}{intl l="Order ID" d="paypal.bo.default"}{intl l="Webhook" d="paypal.bo.default"}{intl l="Level" d="paypal.bo.default"}
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/paypal-log.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/paypal-log.html new file mode 100644 index 0000000..3285c43 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/paypal-log.html @@ -0,0 +1,105 @@ +{extends file="admin-layout.tpl"} + +{block name="main-content"} + +
+
+
+ + {include file="paypal/menu/menu.html" selectedMenu="log"} + +
+ +
+
+
+ +

+ 1 + {intl l="Automatic PayPal logs" d="paypal.bo.default"} +

+ +
+
+

+  {intl l="Help" d="paypal.bo.default"} : +

+
+ {intl l="This is where we log all the transactions made with PayPal. PayPal webhooks also automatically feed these logs." d="paypal.bo.default"} +
{intl l="This informations can be found directly in concerned order details." d="paypal.bo.default"} +
+
+
+
+ Payment configuration +
+
+
+ + + {$page = $smarty.get.page|default:1} + {$limit = $smarty.get.limit|default:100} + +
+
+
+ + + + + + + + + + + + + + {loop name="paypal_log" type="paypal_log" backend_context=true order="date-reverse" page=$page limit=$limit} + {include file = "paypal/includes/paypal-log-row.html"} + {/loop} + + + + + + +
{intl l='Date' d="paypal.bo.default"}{intl l='Details' d="paypal.bo.default"}{intl l="Customer ID" d="paypal.bo.default"}{intl l="Order ID" d="paypal.bo.default"}{intl l="Webhook" d="paypal.bo.default"}{intl l="Level" d="paypal.bo.default"}
+ {include + file = "includes/pagination.html" + loop_ref = "paypal_log" + max_page_count = $limit + page_url = {url path="/admin/module/paypal/configure/log"} + } +
+
+
+
+
+
+
+
+
+
+
+{/block} + +{block name="javascript-initialization"} + {include file = "paypal/includes/paypal-log-row-js.html"} +{/block} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment-edit.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment-edit.html new file mode 100644 index 0000000..9ecd9fa --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment-edit.html @@ -0,0 +1,86 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} +{$admin_current_location = 'module'} +{/block} + +{block name="page-title"}{intl l='Edit planified payment' d="paypal.bo.default"}{/block} + +{block name="main-content"} +
+
+ {loop name="paypal_planified_payment" type="paypal_planified_payment" id=$planifiedPaymentId backend_context=true} + + + +
+
+ +
+
+ {intl l='Edit planified payment %title' d="paypal.bo.default" title=$planifiedPayment->getTitle()} +
+
+ + + +
+ +
+ +
+ + {form name="paypal_planified_payment_update_form"} +
getId()}" {form_enctype} class="clearfix"> + + {include file="includes/inner-form-toolbar.html" hide_flags=true close_url={url path="/admin/module/paypal/configure/planified"}} + + + + {* Be sure to get the planified payment ID, even if the form could not be validated *} + + + {form_hidden_fields} + + {render_form_field field="id"} + + {render_form_field field="success_url" value={url path="/admin/module/paypal/configure/planified"}} + + {if $form_error} +
{$form_error_message}
+ {/if} + +
+ {include file = "paypal/form/create-or-update-planified-payment-form.html"} +
+ + {include + file="includes/inner-form-toolbar.html" + hide_submit_buttons = false + hide_flags = true + + close_url={url path="/admin/module/paypal/configure/planified"} + } + + {intl l='Planified payment created on %date_create. Last modification: %date_change' d="paypal.bo.default" date_create={format_date date=$CREATE_DATE} date_change={format_date date=$UPDATE_DATE} } +
+ {/form} +
+
+
+
+
+ {/loop} +
+
+{/block} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment.html b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment.html new file mode 100644 index 0000000..2bf98c4 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/backOffice/default/paypal/planified-payment.html @@ -0,0 +1,185 @@ +{extends file="admin-layout.tpl"} + +{block name="main-content"} + +
+
+
+ + {include file="paypal/menu/menu.html" selectedMenu="planifiedPayment"} + +
+ +
+
+
+

+ 1 + {intl l="Planified payment configuration" d="paypal.bo.default"} +

+ +
+
+

+  {intl l="Help" d="paypal.bo.default"} : +

+
+ {intl l="This feature uses PayPal's Billing Plan and Agreement. It allows debiting a client recursively directly from PayPal." d="paypal.bo.default"} +
{intl l="These planned payments will appear in step 4 of the purchase tunnel when selecting the payment method." d="paypal.bo.default"} +
+
+
+
+ Payment configuration +
+
+
+ +
+
+
+ + + + + + + + + + + + + + + {loop name="paypal_planified_payment" type="paypal_planified_payment" backend_context=true order=$order} + + + + + + + + {/loop} + {elseloop rel="paypal_planified_payment"} + + + + {/elseloop} + +
+ {intl l="List of planified payments" d="paypal.bo.default"} + + + +
+ {intl l='Title' d="paypal.bo.default"} + + {intl l='Details' d="paypal.bo.default"} +
+ {$planifiedPayment->getTitle()} ;
+ {$planifiedPayment->getDescription()} +
+ {intl l='Frequency interval' d="paypal.bo.default"} : {$planifiedPayment->getFrequencyInterval()}
+ {intl l='Frequency' d="paypal.bo.default"} : {$planifiedPayment->getFrequency()}
+ {intl l='Cycle' d="paypal.bo.default"} : {$planifiedPayment->getCycle()}
+ {intl l='Min amount' d="paypal.bo.default"} : {if $planifiedPayment->getMinAmount() > 0}{format_money number=$planifiedPayment->getMinAmount()}{else}{intl l='None' d="paypal.bo.default"}{/if}
+ {intl l='Max amount' d="paypal.bo.default"} : {if $planifiedPayment->getMaxAmount() > 0}{format_money number=$planifiedPayment->getMaxAmount()}{else}{intl l='None' d="paypal.bo.default"}{/if}
+
+ +
+
+ {intl l="No planified payment has been created yet. Click the + button to create one." d="paypal.bo.default"} +
+
+
+
+
+
+
+
+
+
+
+
+ + {* Adding a new planified payment *} + {form name="paypal_planified_payment_create_form"} + + {* Capture the dialog body, to pass it to the generic dialog *} + {capture "creation_dialog"} + {form_hidden_fields} + + {include file = "paypal/form/create-or-update-planified-payment-form.html"} + + {render_form_field field="success_url" value={url path='/admin/module/paypal/configure/planified'}} + {/capture} + + {include + file = "includes/generic-create-dialog.html" + + dialog_id = "creation_dialog" + dialog_title = {intl l="Create a new planified payment" d="paypal.bo.default"} + dialog_body = {$smarty.capture.creation_dialog nofilter} + + dialog_ok_label = {intl l="Create this planified payment" d="paypal.bo.default"} + + form_action = {url path='/admin/module/paypal/configure/planified/create'} + form_enctype = {form_enctype} + form_error_message = $form_error_message + } + {/form} + + {* Delete confirmation dialog *} + {capture "delete_dialog"} + + {/capture} + + {include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "delete_dialog" + dialog_title = {intl l="Delete planified payment" d="paypal.bo.default"} + dialog_message = {intl l="Do you really want to delete this planified payment ?" d="paypal.bo.default"} + + form_action = {token_url path='/admin/module/paypal/configure/planified/create/delete'} + form_content = {$smarty.capture.delete_dialog nofilter} + } +{/block} + +{block name="javascript-initialization"} + +{/block} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.html b/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.html new file mode 100644 index 0000000..43c199b --- /dev/null +++ b/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.html @@ -0,0 +1,23 @@ +{extends file="email-layout.tpl"} + +{* Do not provide a "Open in browser" link *} +{block name="browser"}{/block} +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}{intl d='paypal.mail.default' l="Payment of your order %ref" ref={$order_ref}}{/block} + +{* Title *} +{block name="email-title"}{intl d='paypal.mail.default' l="The payment of your order %ref is confirmed" ref={$order_ref}}{/block} + +{* Content *} +{block name="email-content"} +

+ + {intl l="View this order in your account at %shop_name" shop_name={config key="store_name"}} + +

+

{intl d='paypal.email.default' l='Thank you again for your purchase.'}

+

{intl d='paypal.email.default' l='The %store_name team.' store_name={config key="store_name"}}

+{/block} diff --git a/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.txt b/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.txt new file mode 100644 index 0000000..491fe8b --- /dev/null +++ b/domokits/local/modules/PayPal/templates/email/default/paypal-payment-confirmation.txt @@ -0,0 +1,9 @@ +{intl d='paypal.email.default' l='Dear customer'}, + +{intl d='paypal.email.default' l='This is a confirmation of the payment of your order %ref via Paypal on our shop.' ref=$order_ref} + +{intl d='paypal.email.default' l='Your invoice is now available in your customer account at %url.'} url={config key="url_site"}} + +{intl d='paypal.email.default' l='Thank you again for your purchase.'} + +{intl d='paypal.email.default' l='The %store_name team.' store_name={config key="store_name"}} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.html b/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.html new file mode 100644 index 0000000..43c199b --- /dev/null +++ b/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.html @@ -0,0 +1,23 @@ +{extends file="email-layout.tpl"} + +{* Do not provide a "Open in browser" link *} +{block name="browser"}{/block} +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}{intl d='paypal.mail.default' l="Payment of your order %ref" ref={$order_ref}}{/block} + +{* Title *} +{block name="email-title"}{intl d='paypal.mail.default' l="The payment of your order %ref is confirmed" ref={$order_ref}}{/block} + +{* Content *} +{block name="email-content"} +

+ + {intl l="View this order in your account at %shop_name" shop_name={config key="store_name"}} + +

+

{intl d='paypal.email.default' l='Thank you again for your purchase.'}

+

{intl d='paypal.email.default' l='The %store_name team.' store_name={config key="store_name"}}

+{/block} diff --git a/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.txt b/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.txt new file mode 100644 index 0000000..491fe8b --- /dev/null +++ b/domokits/local/modules/PayPal/templates/email/default/paypal-recursive-payment-confirmation.txt @@ -0,0 +1,9 @@ +{intl d='paypal.email.default' l='Dear customer'}, + +{intl d='paypal.email.default' l='This is a confirmation of the payment of your order %ref via Paypal on our shop.' ref=$order_ref} + +{intl d='paypal.email.default' l='Your invoice is now available in your customer account at %url.'} url={config key="url_site"}} + +{intl d='paypal.email.default' l='Thank you again for your purchase.'} + +{intl d='paypal.email.default' l='The %store_name team.' store_name={config key="store_name"}} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/assets/cards-logo.jpg b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/cards-logo.jpg new file mode 100644 index 0000000..962e7d3 Binary files /dev/null and b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/cards-logo.jpg differ diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/assets/ntimes-cards-logo.png b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/ntimes-cards-logo.png new file mode 100644 index 0000000..efef888 Binary files /dev/null and b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/ntimes-cards-logo.png differ diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/assets/paypal-logo.png b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/paypal-logo.png new file mode 100644 index 0000000..aab877a Binary files /dev/null and b/domokits/local/modules/PayPal/templates/frontOffice/default/assets/paypal-logo.png differ diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/cart-bottom.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/cart-bottom.html new file mode 100644 index 0000000..7c8ef99 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/cart-bottom.html @@ -0,0 +1,11 @@ +
+ + diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-credit-card.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-credit-card.html new file mode 100644 index 0000000..edfd5bb --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-credit-card.html @@ -0,0 +1,85 @@ +
+ label}checked{/if}/> + + CB / VISA / Mastercard + + +
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-paypal.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-paypal.html new file mode 100644 index 0000000..9ef809a --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-paypal.html @@ -0,0 +1,17 @@ +
+ {if $method_paypal_with_in_context} + label}checked{/if}/> + + {else} + label}checked{/if}/> + + {/if} + + +
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-planified-payment.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-planified-payment.html new file mode 100644 index 0000000..dccbed5 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/form/extra-planified-payment.html @@ -0,0 +1,26 @@ +{assign var="methodName" value=$name} +{form_field field='paypal_planified_payment'} + {if count($choices) > 0} +
+ label}checked{/if}/> + + +
+ {/if} +{/form_field} + diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/login-bottom.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/login-bottom.html new file mode 100644 index 0000000..5710a61 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/login-bottom.html @@ -0,0 +1,16 @@ +
+ + + +
\ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom-js.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom-js.html new file mode 100644 index 0000000..cc14715 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom-js.html @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom.html new file mode 100644 index 0000000..00d5b4c --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-delivery-bottom.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-bottom.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-bottom.html new file mode 100644 index 0000000..e6b444a --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-bottom.html @@ -0,0 +1,11 @@ + + + diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-js.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-js.html new file mode 100644 index 0000000..98a4a49 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-js.html @@ -0,0 +1,58 @@ + \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-payment-extra.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-payment-extra.html new file mode 100644 index 0000000..3e394be --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-invoice-payment-extra.html @@ -0,0 +1,21 @@ +{form name="thelia.order.payment"} + +{/form} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-placed-additional-payment-info.html b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-placed-additional-payment-info.html new file mode 100644 index 0000000..9ce0fb2 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/frontOffice/default/paypal/order-placed-additional-payment-info.html @@ -0,0 +1,6 @@ +{loop type="paypal_order" name="paypal_order" id=$placed_order_id limit=1} + {if $paypal_order->getPlanifiedCycle() > 0} + {intl l="Planified payment" d="paypal.fo.default"} :  + {intl l="Payment in %x times every %frequency_interval %frequency" x=$paypal_order->getPlanifiedCycle() frequency_interval=$paypal_order->getPlanifiedFrequencyInterval() frequency="{intl l=$paypal_order->getPlanifiedFrequency() d="paypal.fo.default"}" d="paypal.fo.default"} + {/if} +{/loop} \ No newline at end of file diff --git a/domokits/local/modules/PayPal/templates/pdf/default/paypal/after-payment-module.html b/domokits/local/modules/PayPal/templates/pdf/default/paypal/after-payment-module.html new file mode 100644 index 0000000..d08b5e8 --- /dev/null +++ b/domokits/local/modules/PayPal/templates/pdf/default/paypal/after-payment-module.html @@ -0,0 +1,6 @@ +{loop type="paypal_order" name="paypal_order" id=$order limit=1} + {if $paypal_order->getPlanifiedCycle() > 0} + {intl l="Planified payment" d="paypal.pdf.default"} :  + {intl l="Payment in %x times every %frequency_interval %frequency" x=$paypal_order->getPlanifiedCycle() frequency_interval=$paypal_order->getPlanifiedFrequencyInterval() frequency="{intl l=$paypal_order->getPlanifiedFrequency() d="paypal.pdf.default"}" d="paypal.pdf.default"} + {/if} +{/loop} \ No newline at end of file diff --git a/domokits/local/modules/ProductLoopAttributeFilter/CHANGELOG.md b/domokits/local/modules/ProductLoopAttributeFilter/CHANGELOG.md new file mode 100644 index 0000000..db9d280 --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 + +- First version \ No newline at end of file diff --git a/domokits/local/modules/ProductLoopAttributeFilter/Config/config.xml b/domokits/local/modules/ProductLoopAttributeFilter/Config/config.xml new file mode 100644 index 0000000..ea7c54c --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/Config/config.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/domokits/local/modules/ProductLoopAttributeFilter/Config/module.xml b/domokits/local/modules/ProductLoopAttributeFilter/Config/module.xml new file mode 100644 index 0000000..cb44301 --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/Config/module.xml @@ -0,0 +1,43 @@ + + + ProductLoopAttributeFilter\ProductLoopAttributeFilter + + Automatically generated module - please update module.xml file + + + + Module généré automatiquement - éditez le fichier module.xml + + + + + en_US + fr_FR + + 2.5.0 + + + Gilles Bourgeat + gbourgeat@openstudio.fr + + + classic + + 2.5.0 + beta + 0 + 0 + diff --git a/domokits/local/modules/ProductLoopAttributeFilter/LICENSE b/domokits/local/modules/ProductLoopAttributeFilter/LICENSE new file mode 100644 index 0000000..ab60297 --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +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/domokits/local/modules/ProductLoopAttributeFilter/Listener/LoopProductListener.php b/domokits/local/modules/ProductLoopAttributeFilter/Listener/LoopProductListener.php new file mode 100644 index 0000000..43f2b8f --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/Listener/LoopProductListener.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ProductLoopAttributeFilter\Listener; + +use Propel\Runtime\ActiveQuery\Criteria; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Core\Event\Loop\LoopExtendsArgDefinitionsEvent; +use Thelia\Core\Event\Loop\LoopExtendsBuildModelCriteriaEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Model\ProductQuery; + +class LoopProductListener implements EventSubscriberInterface +{ + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::getLoopExtendsEvent( + TheliaEvents::LOOP_EXTENDS_ARG_DEFINITIONS, + 'product' + ) => ['productArgDefinitions', 128], + TheliaEvents::getLoopExtendsEvent( + TheliaEvents::LOOP_EXTENDS_BUILD_MODEL_CRITERIA, + 'product' + ) => ['productBuildModelCriteria', 128], + ]; + } + + public function productArgDefinitions(LoopExtendsArgDefinitionsEvent $event): void + { + $argument = $event->getArgumentCollection(); + + $argument->addArgument(Argument::createBooleanTypeArgument('attribute_extend', false)); + + $argument->addArgument(Argument::createIntListTypeArgument('attribute_availability', null)); + + $argument->addArgument(Argument::createIntTypeArgument('attribute_min_stock', null)); + } + + public function productBuildModelCriteria(LoopExtendsBuildModelCriteriaEvent $event): void + { + if ($event->getLoop()->getAttributeExtend()) { + if (null !== $attributeAvailability = $event->getLoop()->getAttributeAvailability()) { + $this->manageAttributeAvailability($event, $attributeAvailability); + } + } + } + + protected function manageAttributeAvailability(LoopExtendsBuildModelCriteriaEvent $event, array $attributeAvailability): void + { + /** @var ProductQuery $query */ + $query = $event->getModelCriteria(); + + $useProductSaleElementsQuery = $query + ->useProductSaleElementsQuery('pse_attribute_extend', Criteria::INNER_JOIN); + + if (null !== $minStock = $event->getLoop()->getAttributeMinStock()) { + $useProductSaleElementsQuery->filterByQuantity($minStock, Criteria::GREATER_EQUAL); + } + + $useProductSaleElementsQuery->useAttributeCombinationQuery('attribute_extend', Criteria::INNER_JOIN) + ->filterByAttributeAvId($attributeAvailability, Criteria::IN) + ->endUse() + ->endUse(); + } +} diff --git a/domokits/local/modules/ProductLoopAttributeFilter/ProductLoopAttributeFilter.php b/domokits/local/modules/ProductLoopAttributeFilter/ProductLoopAttributeFilter.php new file mode 100644 index 0000000..e718c2e --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/ProductLoopAttributeFilter.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* Copyright (c) OpenStudio */ +/* email : dev@thelia.net */ +/* web : http://www.thelia.net */ + +/* For the full copyright and license information, please view the LICENSE.txt */ +/* file that was distributed with this source code. */ + +namespace ProductLoopAttributeFilter; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; + +class ProductLoopAttributeFilter extends BaseModule +{ + /** @var string */ + public const DOMAIN_NAME = 'productloopattributefilter'; + + /* + * You may now override BaseModuleInterface methods, such as: + * install, destroy, preActivation, postActivation, preDeactivation, postDeactivation + * + * Have fun ! + */ + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/ProductLoopAttributeFilter/Readme.md b/domokits/local/modules/ProductLoopAttributeFilter/Readme.md new file mode 100644 index 0000000..93830f9 --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/Readme.md @@ -0,0 +1,43 @@ +# Product Loop Attribute Filter + +Adds the possibility to filter by attribute on the product loop + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is ProductLoopAttributeFilter. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/product-loop-attribute-filter-module:~1.0.0 +``` + +## Loop + +### Loop Product + +### New input arguments + +|Argument |Type |Default value |Description | +|--- |--- |--- |--- | +|**attribute_extend** | boolean | false | make true for activate the filter | +|**attribute_availability** | int[] | | list of ids | +|**attribute_min_stock** | int | 0 | Minimum quantity | + +### Output arguments + +http://doc.thelia.net/en/documentation/loop/product.html + +### Exemple + +```smarty + +{loop attribute_extend=true attribute_availability="64" attribute_min_stock=1 name="product" type="product" visible="*"} + +{/loop} +``` diff --git a/domokits/local/modules/ProductLoopAttributeFilter/composer.json b/domokits/local/modules/ProductLoopAttributeFilter/composer.json new file mode 100644 index 0000000..f6c59dc --- /dev/null +++ b/domokits/local/modules/ProductLoopAttributeFilter/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/product-loop-attribute-filter-module", + "license": "MIT", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "ProductLoopAttributeFilter" + } +} \ No newline at end of file diff --git a/domokits/local/modules/ReCaptcha/Action/ReCaptchaAction.php b/domokits/local/modules/ReCaptcha/Action/ReCaptchaAction.php new file mode 100644 index 0000000..80cf08e --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Action/ReCaptchaAction.php @@ -0,0 +1,56 @@ +request = $requestStack->getCurrentRequest(); + } + + public function checkCaptcha(ReCaptchaCheckEvent $event) + { + $requestUrl = "https://www.google.com/recaptcha/api/siteverify"; + + $secretKey = ReCaptcha::getConfigValue('secret_key'); + $minScore = ReCaptcha::getConfigValue('min_score', 0.3); + $requestUrl .= "?secret=$secretKey"; + + $captchaResponse = $event->getCaptchaResponse(); + if (null == $captchaResponse) { + $captchaResponse = $this->request->request->get('g-recaptcha-response'); + } + + $requestUrl .= "&response=$captchaResponse"; + + $remoteIp = $event->getRemoteIp(); + if (null == $remoteIp) { + $remoteIp = $this->request->server->get('REMOTE_ADDR'); + } + + $requestUrl .= "&remoteip=$remoteIp"; + + $result = json_decode(file_get_contents($requestUrl), true); + if ($result['success'] == true && (!array_key_exists('score', $result) || $result['score'] > $minScore)) { + $event->setHuman(true); + } + } + + public static function getSubscribedEvents() + { + return [ + ReCaptchaEvents::CHECK_CAPTCHA_EVENT => ['checkCaptcha', 128], + ]; + } +} diff --git a/domokits/local/modules/ReCaptcha/Config/config.xml b/domokits/local/modules/ReCaptcha/Config/config.xml new file mode 100644 index 0000000..749b7cd --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Config/config.xml @@ -0,0 +1,42 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/ReCaptcha/Config/module.xml b/domokits/local/modules/ReCaptcha/Config/module.xml new file mode 100644 index 0000000..fc87649 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Config/module.xml @@ -0,0 +1,41 @@ + + + ReCaptcha\ReCaptcha + + ReCaptcha + + + + ReCaptcha + + + + + en_US + fr_FR + + 3.0.1 + + + Vincent Lopes-Vicente + vlopes@openstudio.fr + + + classic + + 2.5.0 + other + diff --git a/domokits/local/modules/ReCaptcha/Config/routing.xml b/domokits/local/modules/ReCaptcha/Config/routing.xml new file mode 100644 index 0000000..adcae39 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Config/routing.xml @@ -0,0 +1,15 @@ + + + + + + ReCaptcha\Controller\ConfigurationController::viewAction + + + + ReCaptcha\Controller\ConfigurationController::saveAction + + + diff --git a/domokits/local/modules/ReCaptcha/Config/schema.xml b/domokits/local/modules/ReCaptcha/Config/schema.xml new file mode 100644 index 0000000..fdd67dd --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Config/schema.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/domokits/local/modules/ReCaptcha/Controller/ConfigurationController.php b/domokits/local/modules/ReCaptcha/Controller/ConfigurationController.php new file mode 100644 index 0000000..ad5e06d --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Controller/ConfigurationController.php @@ -0,0 +1,52 @@ +render( + "recaptcha/configuration" + ); + } + + public function saveAction() + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'ReCaptcha', AccessManager::VIEW)) { + return $response; + } + + $form = $this->createForm(ConfigurationForm::getName()); + + try { + $data = $this->validateForm($form)->getData(); + + ReCaptcha::setConfigValue('site_key', $data['site_key']); + ReCaptcha::setConfigValue('secret_key', $data['secret_key']); + ReCaptcha::setConfigValue('min_score', $data['min_score']); + ReCaptcha::setConfigValue('captcha_style', $data['captcha_style']); + + } catch (\Exception $e) { + $this->setupFormErrorContext( + Translator::getInstance()->trans( + "Error", + [], + ReCaptcha::DOMAIN_NAME + ), + $e->getMessage(), + $form + ); + return $this->viewAction(); + } + + return $this->generateSuccessRedirect($form); + } +} diff --git a/domokits/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php b/domokits/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php new file mode 100644 index 0000000..42b30dd --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Event/ReCaptchaCheckEvent.php @@ -0,0 +1,80 @@ +captchaResponse = $captchaResponse; + } + + if (null !== $remoteIp) { + $this->remoteIp = $remoteIp; + } + } + + /** + * @return null + */ + public function getCaptchaResponse() + { + return $this->captchaResponse; + } + + /** + * @param null $captchaResponse + * @return ReCaptchaCheckEvent + */ + public function setCaptchaResponse($captchaResponse) + { + $this->captchaResponse = $captchaResponse; + return $this; + } + + /** + * @return null + */ + public function getRemoteIp() + { + return $this->remoteIp; + } + + /** + * @param null $remoteIp + * @return ReCaptchaCheckEvent + */ + public function setRemoteIp($remoteIp) + { + $this->remoteIp = $remoteIp; + return $this; + } + + /** + * @return bool + */ + public function isHuman() + { + return $this->human; + } + + /** + * @param bool $human + * @return ReCaptchaCheckEvent + */ + public function setHuman($human) + { + $this->human = $human; + return $this; + } +} diff --git a/domokits/local/modules/ReCaptcha/Event/ReCaptchaEvents.php b/domokits/local/modules/ReCaptcha/Event/ReCaptchaEvents.php new file mode 100644 index 0000000..2e8b054 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Event/ReCaptchaEvents.php @@ -0,0 +1,8 @@ +formBuilder + ->add( + "site_key", + TextType::class, + [ + "data" => ReCaptcha::getConfigValue("site_key"), + "label"=>Translator::getInstance()->trans("Site key", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "site_key"], + "required" => true + ] + ) + ->add( + "secret_key", + TextType::class, + [ + "data" => ReCaptcha::getConfigValue("secret_key"), + "label"=>Translator::getInstance()->trans("Secret key", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "secret_key"], + "required" => true + ] + ) + ->add( + "min_score", + NumberType::class, + [ + "data" => ReCaptcha::getConfigValue("min_score"), + "label"=>Translator::getInstance()->trans("Captcha minimum score", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "min_score"], + "required" => true, + "attr" => [ + "min" => 0.1, + "max" => 1, + "step" => 0.1 + ] + ] + ) + ->add( + "captcha_style", + ChoiceType::class, + [ + "data" => ReCaptcha::getConfigValue("captcha_style"), + "label"=>Translator::getInstance()->trans("ReCaptcha style", array(), ReCaptcha::DOMAIN_NAME), + "label_attr" => ["for" => "captcha_style"], + "required" => true, + 'choices' => [ + 'Normal'=>'normal', + 'Compact'=>'compact', + 'Invisible'=>'invisible' + ] + ] + ); + } + + public static function getName() + { + return "recaptcha_configuration_form"; + } +} diff --git a/domokits/local/modules/ReCaptcha/Hook/FrontHook.php b/domokits/local/modules/ReCaptcha/Hook/FrontHook.php new file mode 100644 index 0000000..81e3569 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Hook/FrontHook.php @@ -0,0 +1,59 @@ +getArgument('id')) { + $captchaId = $event->getArgument('id'); + } + + $event->add("
"); + } + + public function loadRecaptcha(HookRenderEvent $event) + { + $siteKey = ReCaptcha::getConfigValue('site_key'); + $captchaStyle = ReCaptcha::getConfigValue('captcha_style'); + + if ($captchaStyle !== 'invisible') { + $event->add($this->render( + 'recaptcha-js.html', + [ + "siteKey" => $siteKey, + "captchaStyle" => $captchaStyle, + ] + )); + + return; + } + + $event->add($this->render( + 'recaptcha-js-invisible.html', + [ + "siteKey" => $siteKey, + "captchaStyle" => $captchaStyle, + ] + )); + } +} diff --git a/domokits/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..ca4e780 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,8 @@ + 'ReCaptcha configuration', + 'ReCaptcha module configuration' => 'ReCaptcha module configuration', + 'These infos are available here : ' => 'Ces infos sont disponibles ici : ', + 'reCAPTCHA access :' => 'reCAPTCHA accés :', +); diff --git a/domokits/local/modules/ReCaptcha/I18n/en_US.php b/domokits/local/modules/ReCaptcha/I18n/en_US.php new file mode 100644 index 0000000..0b4fa14 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/domokits/local/modules/ReCaptcha/I18n/fr_FR.php b/domokits/local/modules/ReCaptcha/I18n/fr_FR.php new file mode 100644 index 0000000..d0fba1e --- /dev/null +++ b/domokits/local/modules/ReCaptcha/I18n/fr_FR.php @@ -0,0 +1,7 @@ + 'Erreur', + 'Secret key' => 'Clé secrète', + 'Site key' => 'Clé du site', +); diff --git a/domokits/local/modules/ReCaptcha/LICENSE b/domokits/local/modules/ReCaptcha/LICENSE new file mode 100644 index 0000000..2152256 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 OpenStudio + +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/domokits/local/modules/ReCaptcha/ReCaptcha.php b/domokits/local/modules/ReCaptcha/ReCaptcha.php new file mode 100644 index 0000000..8c2507e --- /dev/null +++ b/domokits/local/modules/ReCaptcha/ReCaptcha.php @@ -0,0 +1,64 @@ + TemplateDefinition::FRONT_OFFICE, + "code" => "recaptcha.js", + "title" => [ + "en_US" => "reCaptcha js", + "fr_FR" => "Js pour recaptcha", + ], + "block" => false, + "active" => true, + ], + [ + "type" => TemplateDefinition::FRONT_OFFICE, + "code" => "recaptcha.check", + "title" => [ + "en_US" => "reCaptcha check hook", + "fr_FR" => "reCaptcha check hook", + ], + "block" => false, + "active" => true, + ], + ]; + } + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/ReCaptcha/Readme.md b/domokits/local/modules/ReCaptcha/Readme.md new file mode 100644 index 0000000..f479cc7 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/Readme.md @@ -0,0 +1,62 @@ +# Re Captcha + +This module allows you to add easily a reCAPTCHA to your form +## Installation + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/re-captcha-module:~2.0.0 +``` + +## Usage + +Before using this module you have to create google api key here http://www.google.com/recaptcha/admin +next configure your reCAPTCHA access here http://your_site.com`/admin/module/ReCaptcha` with keys you obtained in Google's page +and choose which style of captcha you want : + +- A standard captcha (or a compact version of this one) + + ![Checkbox captcha](https://developers.google.com/recaptcha/images/newCaptchaAnchor.gif) + +- An invisible captcha + + ![Invisible captcha](https://developers.google.com/recaptcha/images/invisible_badge.png) + + +Then you'll need help from a developer to add some hooks in template and dispatch the check events, see details below. + +### Hook + +First if you don't have `{hook name="main.head-top"}` hook in your template you have to put this hook `{hook name="recaptcha.js"}` in the top of your head +Then add this hook `{hook name="recaptcha.check"}` in every form where you want to check if the user is human, +be careful if you want to use the invisible captcha this hook must be placed directly in the form tag like this : +``` + + {hook name="recaptcha.check"} + // End of the form +
+``` + +### Event + +To check in server-side if the captcha is valid you have to dispatch the "CHECK_CAPTCHA_EVENT" like this : +``` +$checkCaptchaEvent = new ReCaptchaCheckEvent(); +$eventDispatcher->dispatch($checkCaptchaEvent, ReCaptchaEvents::CHECK_CAPTCHA_EVENT); +``` + +Then the result of check is available in `$checkCaptchaEvent->isHuman()`as boolean so you can do a test like this : +``` +if ($checkCaptchaEvent->isHuman() == false) { + throw new \Exception('Invalid captcha'); +} +``` + +Don't forget to add this use at the top of your class : +``` +use ReCaptcha\Event\ReCaptchaCheckEvent; +use ReCaptcha\Event\ReCaptchaEvents; +``` diff --git a/domokits/local/modules/ReCaptcha/composer.json b/domokits/local/modules/ReCaptcha/composer.json new file mode 100644 index 0000000..42d9d3a --- /dev/null +++ b/domokits/local/modules/ReCaptcha/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/re-captcha-module", + "description": "This module allows you to add easily a reCAPTCHA to your form", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "ReCaptcha" + } +} \ No newline at end of file diff --git a/domokits/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html b/domokits/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html new file mode 100644 index 0000000..f998bc9 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/templates/backOffice/default/recaptcha/configuration.html @@ -0,0 +1,72 @@ +{extends file="admin-layout.tpl"} + +{block name="after-bootstrap-css"} + +{/block} + +{block name="no-return-functions"} + {$admin_current_location = 'module'} +{/block} + +{block name="page-title"}{intl l='ReCaptcha module configuration' d='recaptcha.bo.default'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}ReCaptcha{/block} + +{block name="main-content"} +
+
+
+

+ {intl l="ReCaptcha configuration" d='recaptcha.bo.default'} +

+
+ {form name="recaptcha_configuration_form"} +
+ {form_hidden_fields form=$form} + + {if $form_error} +
{$form_error_message}
+ {/if} + + {form_field form=$form field='success_url'} + + {/form_field} + +
+
+

{intl l="reCAPTCHA access :" d='recaptcha.bo.default'}

+
+ {render_form_field form=$form field="site_key" value={$data}} +
+
+ {render_form_field form=$form field="secret_key" value={$data}} +
+
+ {render_form_field form=$form field="captcha_style" value={$data}} +
+
+ {render_form_field form=$form field="min_score" value={$data}} +
+
+

{intl l="These infos are available here : " d='recaptcha.bo.default'}http://www.google.com/recaptcha/admin

+
+
+ +
+
+
+
+ {/form} +
+
+{/block} + +{block name="javascript-initialization"} + +{/block} diff --git a/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js-invisible.html b/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js-invisible.html new file mode 100644 index 0000000..4c54a7a --- /dev/null +++ b/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js-invisible.html @@ -0,0 +1,56 @@ + + + + + +{literal} + +{/literal} \ No newline at end of file diff --git a/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html b/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html new file mode 100644 index 0000000..f116da9 --- /dev/null +++ b/domokits/local/modules/ReCaptcha/templates/frontOffice/default/recaptcha-js.html @@ -0,0 +1,23 @@ + + \ No newline at end of file diff --git a/domokits/local/modules/ResetPassword/Command/ResetAllPasswordCommand.php b/domokits/local/modules/ResetPassword/Command/ResetAllPasswordCommand.php new file mode 100644 index 0000000..acc3cee --- /dev/null +++ b/domokits/local/modules/ResetPassword/Command/ResetAllPasswordCommand.php @@ -0,0 +1,86 @@ +resetPasswordService = $resetPasswordService; + } + + protected function configure() + { + $this + ->setName("resetpassword:reset:all:password") + ->setDescription("Reset all password and send reset link") + ->addOption( + 'all', + null, + InputOption::VALUE_NONE, + 'Safeguard, if not set only first 5 customer will be reset' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->initRequest(); + + $customerQuery = CustomerQuery::create(); + + if (true !== $input->getOption('all')) { + $customerQuery->limit(5); + } + + $customers = $customerQuery->find(); + + foreach ($customers as $customer) { + try { + if (!$customer->getPassword()) { + continue; + } + $customerForbiddenPassword = CustomerForbiddenPasswordQuery::create() + ->filterByCustomerId($customer->getId()) + ->filterByPassword($customer->getPassword()) + ->findOne(); + + if (null === $customerForbiddenPassword) { + (new CustomerForbiddenPassword()) + ->setCustomerId($customer->getId()) + ->setPassword($customer->getPassword()) + ->save(); + } + + $this->resetPasswordService->sendResetLinkByEmail( + $customer->getEmail(), + ResetPassword::RESET_ALL_PASSWORD_MESSAGE_NAME, + ['customerId' => $customer->getId()], + -1 + ); + + $customer->erasePassword() + ->save(); + + $output->writeln("Password reset link for customer ".$customer->getId()." sent successfully"); + } catch (\Exception $exception) { + $output->writeln("".$exception->getMessage().""); + } + + } + + return 0; + } +} diff --git a/domokits/local/modules/ResetPassword/Config/TheliaMain.sql b/domokits/local/modules/ResetPassword/Config/TheliaMain.sql new file mode 100755 index 0000000..1713337 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/TheliaMain.sql @@ -0,0 +1,50 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- password_reset_token +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `password_reset_token`; + +CREATE TABLE `password_reset_token` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER NOT NULL, + `token` VARCHAR(255), + `end_of_life` DATETIME, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + INDEX `fi_password_reset_token_customer_id` (`customer_id`), + CONSTRAINT `fk_password_reset_token_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- customer_forbidden_password +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `customer_forbidden_password`; + +CREATE TABLE `customer_forbidden_password` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER NOT NULL, + `password` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`), + INDEX `fi_customer_forbidden_password_customer_id` (`customer_id`), + CONSTRAINT `fk_customer_forbidden_password_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/ResetPassword/Config/config.xml b/domokits/local/modules/ResetPassword/Config/config.xml new file mode 100755 index 0000000..c9f3b36 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/config.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/ResetPassword/Config/module.xml b/domokits/local/modules/ResetPassword/Config/module.xml new file mode 100755 index 0000000..4d3efb1 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/module.xml @@ -0,0 +1,43 @@ + + + ResetPassword\ResetPassword + + Change Thelia lost password behaviour to be a reset link + + + + Remplace le comportement de base Thelia des mot de passes oubliés par un lien de réinitialisation + + + + + en_US + fr_FR + + 1.0.1 + + + + + + + classic + + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/ResetPassword/Config/routing.xml b/domokits/local/modules/ResetPassword/Config/routing.xml new file mode 100755 index 0000000..0c92f13 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/domokits/local/modules/ResetPassword/Config/schema.xml b/domokits/local/modules/ResetPassword/Config/schema.xml new file mode 100755 index 0000000..6b1ea39 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/schema.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + +
+ + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/ResetPassword/Config/sqldb.map b/domokits/local/modules/ResetPassword/Config/sqldb.map new file mode 100644 index 0000000..e69de29 diff --git a/domokits/local/modules/ResetPassword/Config/update/1.0.1.sql b/domokits/local/modules/ResetPassword/Config/update/1.0.1.sql new file mode 100644 index 0000000..e9a1c0c --- /dev/null +++ b/domokits/local/modules/ResetPassword/Config/update/1.0.1.sql @@ -0,0 +1,26 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- customer_forbidden_password +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `customer_forbidden_password`; + +CREATE TABLE `customer_forbidden_password` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `customer_id` INTEGER NOT NULL, + `password` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`), + INDEX `fi_customer_forbidden_password_customer_id` (`customer_id`), + CONSTRAINT `fk_customer_forbidden_password_customer_id` + FOREIGN KEY (`customer_id`) + REFERENCES `customer` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/ResetPassword/Controller/Front/ResetPasswordController.php b/domokits/local/modules/ResetPassword/Controller/Front/ResetPasswordController.php new file mode 100755 index 0000000..3c1fbdd --- /dev/null +++ b/domokits/local/modules/ResetPassword/Controller/Front/ResetPasswordController.php @@ -0,0 +1,52 @@ +render("resetPassword/reset_password"); + } + + /** + * @Route("", name="_action", methods="POST") + */ + public function resetPasswordAction( + ParserContext $parserContext, + ResetPasswordService $resetPasswordService + ) + { + $form = $this->createForm(ResetPasswordForm::class); + + try { + $data = $this->validateForm($form)->getData(); + + $resetPasswordService->checkTokenAndUpdatePassword($data['email'], $data['token'], $data['password']); + + return $this->generateSuccessRedirect($form); + } catch (\Exception $exception) { + $form->setErrorMessage($exception->getMessage()); + + $parserContext + ->addForm($form) + ->setGeneralError($exception->getMessage()); + + return $this->generateErrorRedirect($form); + } + } +} diff --git a/domokits/local/modules/ResetPassword/EventListener/LostPasswordListener.php b/domokits/local/modules/ResetPassword/EventListener/LostPasswordListener.php new file mode 100755 index 0000000..7ca6f0d --- /dev/null +++ b/domokits/local/modules/ResetPassword/EventListener/LostPasswordListener.php @@ -0,0 +1,39 @@ +resetPasswordService = $resetPasswordService; + } + + /** + * @throws \Propel\Runtime\Exception\PropelException + */ + public function sendResetLink(LostPasswordEvent $event): void + { + $this->resetPasswordService->sendResetLinkByEmail($event->getEmail()); + $event->stopPropagation(); + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::LOST_PASSWORD => ['sendResetLink', 256], + ]; + } +} diff --git a/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordAskForm.php b/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordAskForm.php new file mode 100755 index 0000000..bb89eac --- /dev/null +++ b/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordAskForm.php @@ -0,0 +1,19 @@ +formBuilder; + $form + ->add( + 'email', + HiddenType::class + ); + } +} diff --git a/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordForm.php b/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordForm.php new file mode 100755 index 0000000..c8161d6 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Form/Front/ResetPasswordForm.php @@ -0,0 +1,59 @@ +formBuilder; + $form + ->add( + 'email', + HiddenType::class + ) + ->add( + 'token', + HiddenType::class + ) + ->add('password', PasswordType::class, [ + 'constraints' => [ + new Constraints\NotBlank(), + new Constraints\Length(['min' => ConfigQuery::read('password.length', 4)]), + ], + 'label' => Translator::getInstance()->trans('New Password'), + 'label_attr' => [ + 'for' => 'password', + ], + ]) + ->add('password_confirm', PasswordType::class, [ + 'constraints' => [ + new Constraints\NotBlank(), + new Constraints\Length(['min' => ConfigQuery::read('password.length', 4)]), + new Constraints\Callback([$this, 'verifyPasswordField']), + ], + 'label' => Translator::getInstance()->trans('Password confirmation'), + 'label_attr' => [ + 'for' => 'password_confirmation', + ], + ]); + ; + } + + public function verifyPasswordField($value, ExecutionContextInterface $context): void + { + $data = $context->getRoot()->getData(); + + if ($data['password'] != $data['password_confirm']) { + $context->addViolation(Translator::getInstance()->trans('password confirmation is not the same as password field')); + } + } +} diff --git a/domokits/local/modules/ResetPassword/I18n/email/default/fr_FR.php b/domokits/local/modules/ResetPassword/I18n/email/default/fr_FR.php new file mode 100644 index 0000000..70bd876 --- /dev/null +++ b/domokits/local/modules/ResetPassword/I18n/email/default/fr_FR.php @@ -0,0 +1,16 @@ + 'Pour des raisons de sécurité, nous vous invitons à renouveler votre mot de passe pour %store_name via le lien suivant : ', + 'Have a nice day' => 'Bonne journée.', + 'Hello %name,' => 'Bonjour %name,', + 'Hello,' => 'Bonjour,', + 'If you don\'t requested a new password, please ignore this message.' => 'Si vous n\'avez pas demandé de nouveau mot de passe, merci d\'ignorer ce message.', + 'Please click here to create a new password.' => ' Veuillez Cliquez ici pour créer un nouveau mot de passe', + 'Please click here to define a new password: %url . You will be prompted to enter a new password.' => 'Veuillez cliquer ici pour définir un nouveau mot de passe : %url . Vous serez invité à saisir un nouveau mot de passe. ', + 'Reset you password for %store' => 'Réinitialisez votre mot de passe pour %store', + 'Thank you and have a nice day.' => 'Merci et bonne journée.', + 'You can also paste the URL below in you browser\'s address bar : ' => 'Vous pouvez également coller l\'URL ci-dessous dans la barre d\'adresse de votre navigateur : ', + 'You have requested a new password for your account at %store_name' => 'Vous avez demandé la réinitialistaion de votre mot de passe sur %store_name', + 'Your password reset link for %store' => 'Votre lien de réinitialisation de mot de passe pour %store', +); diff --git a/domokits/local/modules/ResetPassword/I18n/en_US.php b/domokits/local/modules/ResetPassword/I18n/en_US.php new file mode 100755 index 0000000..d391ee9 --- /dev/null +++ b/domokits/local/modules/ResetPassword/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // 'an english string' => 'The displayed english string', +]; diff --git a/domokits/local/modules/ResetPassword/I18n/fr_FR.php b/domokits/local/modules/ResetPassword/I18n/fr_FR.php new file mode 100755 index 0000000..3839979 --- /dev/null +++ b/domokits/local/modules/ResetPassword/I18n/fr_FR.php @@ -0,0 +1,15 @@ + 'Impossible de générer un jeton pour un client qui n\'existe pas. ', + 'Mail for resetting all passwords' => 'Mail pour la réinitialisation de tous les mots de passe', + 'New Password' => 'Nouveau mot de passe', + 'Password confirmation' => 'Confirmation du mot de passe ', + 'Password reset link' => 'Lien de réinitialisation du mot de passe ', + 'Please use a different password than the previous ones.' => 'Veuillez utiliser un mot de passe différent des précédents.', + 'Reset you password' => 'Réinitialiser votre mot de passe ', + 'This token has expired' => 'Ce jeton a expiré ', + 'This token is invalid or doesn\'t match your email' => 'Ce jeton n\'est pas valide ou ne correspond pas à votre adresse e-mail. ', + 'Your password reset link' => 'Votre lien de réinitialisation de mot de passe ', + 'password confirmation is not the same as password field' => 'la confirmation du mot de passe n\'est pas identique au mot de passe ', +); diff --git a/domokits/local/modules/ResetPassword/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/ResetPassword/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..db85d4a --- /dev/null +++ b/domokits/local/modules/ResetPassword/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,7 @@ + 'Choisissez votre nouveau mot de passe', + 'Reset password' => 'Réinitialisation du mot de passe', + 'missing or invalid data' => 'Données manquantes ou invalides', +); diff --git a/domokits/local/modules/ResetPassword/Model/CustomerForbiddenPassword.php b/domokits/local/modules/ResetPassword/Model/CustomerForbiddenPassword.php new file mode 100644 index 0000000..e2f7f0c --- /dev/null +++ b/domokits/local/modules/ResetPassword/Model/CustomerForbiddenPassword.php @@ -0,0 +1,19 @@ +getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, array(__DIR__ . '/Config/TheliaMain.sql')); + + $this->setConfigValue('is_initialized', true); + } + + $this->generateEmailMessage(); + } + + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + $finder = Finder::create() + ->name('*.sql') + ->depth(0) + ->sortByName() + ->in(__DIR__ . DS . 'Config' . DS . 'update'); + + $database = new Database($con); + + /** @var \SplFileInfo $file */ + foreach ($finder as $file) { + if (version_compare($currentVersion, $file->getBasename('.sql'), '<')) { + $database->insertSql(null, [$file->getPathname()]); + } + } + + $this->generateEmailMessage(); + } + + /** + * Defines how services are loaded in your modules + * + * @param ServicesConfigurator $servicesConfigurator + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } + + public static function getTokenLength() + { + return self::getConfigValue(self::TOKEN_LENGTH_CONFIG_KEY, 32); + } + + public static function getTokenTimeToLive() + { + return self::getConfigValue(self::TOKEN_TIME_TO_LIVE_KEY, 86400); + } + + protected function generateEmailMessage(): void + { + // In case translator has not been instancied + try { + Translator::getInstance(); + } catch (\Exception $e) { + new Translator($this->getContainer()->get('request_stack')); + } + + if (null === MessageQuery::create()->findOneByName(self::RESET_PASSWORD_MESSAGE_NAME)) { + $message = new Message(); + $message + ->setName(self::RESET_PASSWORD_MESSAGE_NAME) + ->setHtmlTemplateFileName(self::RESET_PASSWORD_MESSAGE_NAME . '.html') + ->setHtmlLayoutFileName('') + ->setTextTemplateFileName(self::RESET_PASSWORD_MESSAGE_NAME . '.txt') + ->setTextLayoutFileName('') + ->setSecured(0); + + $languages = LangQuery::create()->find(); + + foreach ($languages as $language) { + $locale = $language->getLocale(); + + $message->setLocale($locale); + + $message->setSubject( + Translator::getInstance()->trans('Your password reset link', [], ResetPassword::DOMAIN_NAME, $locale) + ); + $message->setTitle( + Translator::getInstance()->trans('Password reset link', [],ResetPassword::DOMAIN_NAME, $locale) + ); + } + + $message->save(); + } + + if (null === MessageQuery::create()->findOneByName(self::RESET_ALL_PASSWORD_MESSAGE_NAME)) { + $message = new Message(); + $message + ->setName(self::RESET_ALL_PASSWORD_MESSAGE_NAME) + ->setHtmlTemplateFileName(self::RESET_ALL_PASSWORD_MESSAGE_NAME . '.html') + ->setHtmlLayoutFileName('') + ->setSecured(0); + + $languages = LangQuery::create()->find(); + + foreach ($languages as $language) { + $locale = $language->getLocale(); + + $message->setLocale($locale); + + $message->setSubject( + Translator::getInstance()->trans('Reset you password', [], ResetPassword::DOMAIN_NAME, $locale) + ); + $message->setTitle( + Translator::getInstance()->trans('Mail for resetting all passwords', [],ResetPassword::DOMAIN_NAME, $locale) + ); + } + + $message->save(); + } + } +} diff --git a/domokits/local/modules/ResetPassword/Service/ResetPasswordService.php b/domokits/local/modules/ResetPassword/Service/ResetPasswordService.php new file mode 100755 index 0000000..ad9c465 --- /dev/null +++ b/domokits/local/modules/ResetPassword/Service/ResetPasswordService.php @@ -0,0 +1,136 @@ +mailer = $mailer; + } + + public function sendResetLinkByEmail( + $email, + $messageCode = null, + $additionalParameters = [], + $tokenTimeToLive = null + ) + { + $customer = CustomerQuery::create() + ->filterByEmail($email) + ->findOne(); + + if (null === $customer) { + throw new \Exception(Translator::getInstance()->trans("Can't generate a token for a customer that doesn't exist.", [], ResetPassword::DOMAIN_NAME)); + } + + if (null === $messageCode) { + $messageCode = ResetPassword::RESET_PASSWORD_MESSAGE_NAME; + } + + $tokenLink = $this->generateResetTokenLink($customer, $tokenTimeToLive); + $this->mailer->sendEmailToCustomer( + $messageCode, + $customer, + array_merge( + [ + 'customerId' => $customer->getId(), + 'tokenLink' => $tokenLink, + 'tokenTimeToLive' => ResetPassword::getTokenTimeToLive() + ], + $additionalParameters + ) + ); + } + + public function generateResetTokenLink(Customer $customer, $tokenTimeToLive = null) + { + $token = bin2hex(random_bytes(ResetPassword::getTokenLength())); + + if (null === $tokenTimeToLive) { + $tokenTimeToLive = ResetPassword::getTokenTimeToLive(); + } + + $endOfLife = $tokenTimeToLive > -1 ? (new \DateTime())->add((new \DateInterval("PT".ResetPassword::getTokenTimeToLive()."S"))) : null; + + $passwordResetToken = (new PasswordResetToken()) + ->setCustomerId($customer->getId()) + ->setToken($token) + ->setEndOfLife($endOfLife); + + $passwordResetToken->save(); + + return URL::getInstance()->absoluteUrl("/reset_password")."?token=$token&email=".urlencode($customer->getEmail()); + } + + public function checkTokenAndUpdatePassword($email, $token, $newPassword) + { + $customer = CustomerQuery::create() + ->filterByEmail($email) + ->findOne(); + + if (null === $customer) { + throw new \Exception(Translator::getInstance()->trans("This token is invalid or doesn't match your email", [], ResetPassword::DOMAIN_NAME)); + } + + $tokenModel = $this->checkToken($token, null, $customer); + + $passwordHash = password_hash($newPassword, \PASSWORD_BCRYPT); + + $forbiddenPassword = CustomerForbiddenPasswordQuery::create() + ->filterById($customer->getId()) + ->filterByPassword($passwordHash) + ->findOne(); + + if (null !== $forbiddenPassword) { + throw new \Exception(Translator::getInstance()->trans("Please use a different password than the previous ones.", [], ResetPassword::DOMAIN_NAME)); + } + + $customer->setPassword($newPassword) + ->save(); + + $tokenModel->delete(); + return $customer; + } + + public function checkToken($token, $email = null, Customer $customer = null): PasswordResetToken + { + if (null === $customer) { + $customer = CustomerQuery::create() + ->filterByEmail($email) + ->findOne(); + } + + if (null === $customer) { + throw new \Exception(Translator::getInstance()->trans("This token is invalid or doesn't match your email", [], ResetPassword::DOMAIN_NAME)); + } + + $passwordResetToken = PasswordResetTokenQuery::create() + ->filterByCustomerId($customer->getId()) + ->filterByToken($token) + ->findOne(); + + if (null === $passwordResetToken) { + throw new \Exception(Translator::getInstance()->trans("This token is invalid or doesn't match your email", [], ResetPassword::DOMAIN_NAME)); + } + + if (null !== $passwordResetToken->getEndOfLife() && (new \DateTime()) > $passwordResetToken->getEndOfLife()) { + throw new \Exception(Translator::getInstance()->trans("This token has expired", [], ResetPassword::DOMAIN_NAME)); + } + + + return $passwordResetToken; + } +} diff --git a/domokits/local/modules/ResetPassword/composer.json b/domokits/local/modules/ResetPassword/composer.json new file mode 100755 index 0000000..cfad46a --- /dev/null +++ b/domokits/local/modules/ResetPassword/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/reset-password-module", + "description": "Change Thelia lost password behaviour to be a reset link", + "license": "LGPL-3.0-or-later", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "ResetPassword" + } +} diff --git a/domokits/local/modules/ResetPassword/templates/email/default/reset_all_password_message.html b/domokits/local/modules/ResetPassword/templates/email/default/reset_all_password_message.html new file mode 100644 index 0000000..3bbcc18 --- /dev/null +++ b/domokits/local/modules/ResetPassword/templates/email/default/reset_all_password_message.html @@ -0,0 +1,30 @@ +{default_translation_domain domain='resetpassword.email.default'} +{extends file="email-layout.tpl"} + +{* Open in browser *} +{block name="browser"}{/block} + +{* No big image header *} +{block name="image-header"}{/block} + +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}{intl l="Reset you password for %store" store={config key="store_name"}}{/block} + +{* Title *} +{block name="email-title"}{/block} + +{* Content *} +{block name="email-content"} + + {loop name="customer" type="customer" id=$customerId current=false limit=1} + {$name=$FIRSTNAME} + {/loop} + + {intl l="Hello %name," name=$name}

+ {intl l="For security reasons we invite you to renew your password for %store_name via the following link: " store_name={config key="store_name"}}.

+ {$tokenLink}

+ {intl l='Thank you and have a nice day.'} +{/block} diff --git a/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.html b/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.html new file mode 100755 index 0000000..493d692 --- /dev/null +++ b/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.html @@ -0,0 +1,30 @@ +{default_translation_domain domain='resetpassword.email.default'} +{extends file="email-layout.tpl"} + +{* Open in browser *} +{block name="browser"}{/block} + +{* No big image header *} +{block name="image-header"}{/block} + +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}{intl l="Your password reset link for %store" store={config key="store_name"}}{/block} + +{* Title *} +{block name="email-title"}{/block} + +{* Content *} +{block name="email-content"} + {intl l="Hello,"}

+ {intl l="You have requested a new password for your account at %store_name" store_name={config key="store_name"}}.

+ {intl l='Please click here to create a new password.' url={$tokenLink}}

+ {intl l="You can also paste the URL below in you browser's address bar : "}

+ {$tokenLink} +

+

+ {intl l='If you don\'t requested a new password, please ignore this message.'}.

+ {intl l='Have a nice day'} +{/block} diff --git a/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.txt b/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.txt new file mode 100755 index 0000000..28cd534 --- /dev/null +++ b/domokits/local/modules/ResetPassword/templates/email/default/reset_password_message.txt @@ -0,0 +1,10 @@ +{default_translation_domain domain='resetpassword.email.default'} +{intl l="Hello,"} + +{intl l="You have requested a new password for your account at %store_name" store_name={config key="store_name"}}. + +{intl l="Please click here to define a new password: %url . You will be prompted to enter a new password." url={$tokenLink}} + +{intl l='If you don\'t requested a new password, please ignore this message.'}. + +{intl l='Have a nice day'} diff --git a/domokits/local/modules/ResetPassword/templates/frontOffice/default/resetPassword/reset_password.html b/domokits/local/modules/ResetPassword/templates/frontOffice/default/resetPassword/reset_password.html new file mode 100755 index 0000000..f5f1872 --- /dev/null +++ b/domokits/local/modules/ResetPassword/templates/frontOffice/default/resetPassword/reset_password.html @@ -0,0 +1,87 @@ +{extends file="layout.tpl"} + +{* Body Class *} +{block name="body-class"}page-login{/block} + +{* Breadcrumb *} +{block name='no-return-functions' append} + {$breadcrumbs = [ + ['title' => {intl l="Reset password" d="resetpassword.fo.default"}, 'url'=>{url path="/reset_password"}] + ]} +{/block} + + +{block name="main-content"} + + {* This page should not replace the current previous URL *} + {set_previous_url ignore_current="1"} + +
+
+

{intl l="Reset password" d="resetpassword.fo.default"}

+ {form name="resetpassword_form_front_reset_password_form"} +
+ {if $form_error}
{$form_error_message}
{/if} + + {form_field field='success_url'} + {* the url the user is redirected to on login success *} + {/form_field} + + {form_field field='error_url'} + + {/form_field} + + {form_field field='error_message'} + {* the url the user is redirected to on login success *} + {/form_field} + {form_hidden_fields} +
+ {form_field field="email"} + + {/form_field} + {form_field field="token"} + + {/form_field} + +
+
+ {intl l="Choose your new password" d="resetpassword.fo.default"} +
+ +
+ {form_field field="password"} +
+ +
+ + {if $error } + {$message} + {assign var="error_focus" value="true"} + {/if} +
+
+ {/form_field} + + {form_field field="password_confirm"} +
+ +
+ + {if $error } + {$message} + {assign var="error_focus" value="true"} + {/if} +
+
+ {/form_field} +
+
+
+
+ +
+
+ {/form} +
+
+{/block} diff --git a/domokits/local/modules/RewriteUrl/.github/workflows/release.yml b/domokits/local/modules/RewriteUrl/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit-js.html b/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit-js.html new file mode 100755 index 0000000..9469412 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit-js.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module-js.html" viewName='brand' altViewName='brand'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit.html b/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit.html new file mode 100755 index 0000000..6bdb055 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/brand-edit.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module.html" viewName='brand' altViewName='brand'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit-js.html b/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit-js.html new file mode 100755 index 0000000..c33264b --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit-js.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module-js.html" viewName='category' altViewName='categories'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit.html b/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit.html new file mode 100755 index 0000000..7d74e12 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/category-edit.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module.html" viewName='category' altViewName='categories'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit-js.html b/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit-js.html new file mode 100755 index 0000000..a07f810 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit-js.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module-js.html" viewName='content' altViewName='content'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit.html b/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit.html new file mode 100755 index 0000000..818780e --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/content-edit.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module.html" viewName='content' altViewName='content'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit-js.html b/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit-js.html new file mode 100755 index 0000000..ca64507 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit-js.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module-js.html" viewName='folder' altViewName='folders'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit.html b/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit.html new file mode 100755 index 0000000..4eb674a --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/folder-edit.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module.html" viewName='folder' altViewName='folders'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit-js.html b/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit-js.html new file mode 100755 index 0000000..241a6a7 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit-js.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module-js.html" viewName='product' altViewName='products'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit.html b/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit.html new file mode 100755 index 0000000..e91997b --- /dev/null +++ b/domokits/local/modules/RewriteUrl/AdminIncludes/product-edit.html @@ -0,0 +1 @@ +{include file="RewriteUrl/tab-module.html" viewName='product' altViewName='products'} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/Config/config.xml b/domokits/local/modules/RewriteUrl/Config/config.xml new file mode 100755 index 0000000..8f495b2 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/RewriteUrl/Config/module.xml b/domokits/local/modules/RewriteUrl/Config/module.xml new file mode 100755 index 0000000..be32ed4 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/module.xml @@ -0,0 +1,18 @@ + + + RewriteUrl\RewriteUrl + + Manage rewrite url + + + Gérer la réécriture d'url + + 2.1.8 + + Vincent Lopes, Gilles Bourgeat, Tom Pradat + vlopes@openstudio.fr, gbourgeat@openstudio.fr, tpradat@openstudio.fr + + classic + 2.5.0 + prod + diff --git a/domokits/local/modules/RewriteUrl/Config/routing.xml b/domokits/local/modules/RewriteUrl/Config/routing.xml new file mode 100755 index 0000000..e7ad916 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/routing.xml @@ -0,0 +1,51 @@ + + + + + + RewriteUrl\Controller\Admin\ModuleConfigController::getDatatableRules + + + RewriteUrl\Controller\Admin\ModuleConfigController::setRewritingEnableAction + + + RewriteUrl\Controller\Admin\ModuleConfigController::addRuleAction + + + RewriteUrl\Controller\Admin\ModuleConfigController::updateRuleAction + + + RewriteUrl\Controller\Admin\ModuleConfigController::removeRuleAction + + + RewriteUrl\Controller\Admin\ModuleConfigController::moveRulePositionAction + + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::deleteAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::searchAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::existAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::addAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::setDefaultAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::reassignAction + + + RewriteUrl\Controller\Admin\RewriteUrlAdminController::changeRedirectTypeAction + + + RewriteUrl\Controller\Admin\NotRewritenUrlsAdminController::defaultAction + + + diff --git a/domokits/local/modules/RewriteUrl/Config/schema.xml b/domokits/local/modules/RewriteUrl/Config/schema.xml new file mode 100644 index 0000000..83f3a36 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/schema.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + +
+ + +
diff --git a/domokits/local/modules/RewriteUrl/Config/thelia.sql b/domokits/local/modules/RewriteUrl/Config/thelia.sql new file mode 100644 index 0000000..5dcfd32 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/thelia.sql @@ -0,0 +1,62 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- rewriting_redirect_type +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `rewriting_redirect_type`; + +CREATE TABLE `rewriting_redirect_type` +( + `id` INTEGER NOT NULL, + `httpcode` INTEGER, + PRIMARY KEY (`id`), + CONSTRAINT `rewriting_redirect_type_FK_1` + FOREIGN KEY (`id`) + REFERENCES `rewriting_url` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- rewriteurl_rule +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `rewriteurl_rule`; + +CREATE TABLE `rewriteurl_rule` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `rule_type` VARCHAR(64) NOT NULL, + `value` VARCHAR(255), + `only404` TINYINT(1) NOT NULL, + `redirect_url` VARCHAR(255) NOT NULL, + `position` INTEGER(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- rewriteurl_rule_param +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `rewriteurl_rule_param`; + +CREATE TABLE `rewriteurl_rule_param` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `id_rule` INTEGER NOT NULL, + `param_name` VARCHAR(255) NOT NULL, + `param_condition` VARCHAR(64) NOT NULL, + `param_value` VARCHAR(255), + PRIMARY KEY (`id`), + INDEX `rewriteurl_rule_rule_param_FI_id` (`id_rule`), + CONSTRAINT `rewriteurl_rule_rule_param_FK_id` + FOREIGN KEY (`id_rule`) + REFERENCES `rewriteurl_rule` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/RewriteUrl/Config/update/1.4.8.sql b/domokits/local/modules/RewriteUrl/Config/update/1.4.8.sql new file mode 100644 index 0000000..f5cf4ec --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/update/1.4.8.sql @@ -0,0 +1,22 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- rewriting_redirect_type +-- --------------------------------------------------------------------- + +CREATE TABLE IF NOT EXISTS `rewriting_redirect_type` +( + `id` INTEGER NOT NULL, + `httpcode` INTEGER, + PRIMARY KEY (`id`), + CONSTRAINT `rewriting_redirect_type_FK_1` + FOREIGN KEY (`id`) + REFERENCES `rewriting_url` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/Config/update/1.5.0.sql b/domokits/local/modules/RewriteUrl/Config/update/1.5.0.sql new file mode 100644 index 0000000..00778a8 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Config/update/1.5.0.sql @@ -0,0 +1,45 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- rewriteurl_rule +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `rewriteurl_rule`; + +CREATE TABLE `rewriteurl_rule` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `rule_type` VARCHAR(64) NOT NULL, + `value` VARCHAR(255), + `only404` TINYINT(1) NOT NULL, + `redirect_url` VARCHAR(255) NOT NULL, + `position` INTEGER(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- rewriteurl_rule_param +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `rewriteurl_rule_param`; + +CREATE TABLE `rewriteurl_rule_param` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `id_rule` INTEGER NOT NULL, + `param_name` VARCHAR(255) NOT NULL, + `param_condition` VARCHAR(64) NOT NULL, + `param_value` VARCHAR(255), + PRIMARY KEY (`id`), + INDEX `rewriteurl_rule_rule_param_FI_id` (`id_rule`), + CONSTRAINT `rewriteurl_rule_rule_param_FK_id` + FOREIGN KEY (`id_rule`) + REFERENCES `rewriteurl_rule` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/Controller/Admin/ModuleConfigController.php b/domokits/local/modules/RewriteUrl/Controller/Admin/ModuleConfigController.php new file mode 100644 index 0000000..9f25b7d --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Controller/Admin/ModuleConfigController.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RewriteUrl\Controller\Admin; + +use Propel\Runtime\ActiveQuery\Criteria; +use RewriteUrl\Model\RewriteurlRule; +use RewriteUrl\Model\RewriteurlRuleParam; +use RewriteUrl\Model\RewriteurlRuleQuery; +use RewriteUrl\RewriteUrl; +use Thelia\Controller\Admin\BaseAdminController; +use Thelia\Core\HttpFoundation\JsonResponse; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\HttpFoundation\Response; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Core\Translation\Translator; +use Thelia\Exception\TheliaProcessException; +use Thelia\Model\ConfigQuery; + +class ModuleConfigController extends BaseAdminController +{ + public function viewConfigAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], 'RewriteUrl', AccessManager::VIEW)) { + return $response; + } + + $isRewritingEnabled = ConfigQuery::isRewritingEnable(); + + return $this->render( + 'RewriteUrl/module-configuration', + [ + 'isRewritingEnabled' => $isRewritingEnabled, + ] + ); + } + + public function getDatatableRules(Request $request) + { + $requestSearchValue = $request->get('search') ? '%' . $request->get('search')['value'] . '%' : ''; + $recordsTotal = RewriteurlRuleQuery::create()->count(); + $search = RewriteurlRuleQuery::create(); + if ('' !== $requestSearchValue) { + $search + ->filterByValue($requestSearchValue, Criteria::LIKE) + ->_or() + ->filterByRedirectUrl($requestSearchValue); + } + + $recordsFiltered = $search->count(); + + $orderColumn = $request->get('order')[0]['column']; + $orderDirection = $request->get('order')[0]['dir']; + switch ($orderColumn) { + case '0': + $search->orderByRuleType($orderDirection); + break; + case '1': + $search->orderByValue($orderDirection); + break; + case '2': + $search->orderByOnly404($orderDirection); + break; + case '3': + $search->orderByRedirectUrl($orderDirection); + break; + case '4': + $search->orderByPosition($orderDirection); + break; + default: + $search->orderByPosition(); + break; + } + + $search + ->offset($request->get('start')) + ->limit($request->get('length')); + $searchArray = $search->find()->toArray(); + + $resultsArray = []; + foreach ($searchArray as $row) { + $id = $row['Id']; + $isRegexSelected = $row['RuleType'] === RewriteurlRule::TYPE_REGEX ? 'selected' : ''; + $isParamsSelected = $row['RuleType'] === RewriteurlRule::TYPE_GET_PARAMS ? 'selected' : ''; + $isRegexParamsSelected = $row['RuleType'] === RewriteurlRule::TYPE_REGEX_GET_PARAMS ? 'selected' : ''; + $isTextParamsSelected = $row['RuleType'] === RewriteurlRule::TYPE_TEXT ? 'selected' : ''; + $isOnly404Checked = $row['Only404'] ? 'checked' : ''; + $rewriteUrlRuleParams = RewriteurlRuleQuery::create()->findPk($row['Id'])->getRewriteUrlParamCollection(); + $resultsArray[] = [ + 'Id' => $row['Id'], + 'RuleType' => '', + 'Value' => $this->renderRaw( + 'RewriteUrl/tab-value-render', + [ + 'ID' => $row['Id'], + 'REWRITE_URL_PARAMS' => $rewriteUrlRuleParams, + 'VALUE' => $row['Value'], + ] + ), + 'Only404' => '', + 'RedirectUrl' => '
+ +
', + 'Position' => ' + ' . $row['Position'] . ' + ', + 'Actions' => ' + +', + ]; + } + + return new JsonResponse([ + 'draw' => $request->get('draw'), + 'recordsTotal' => $recordsTotal, + 'recordsFiltered' => $recordsFiltered, + 'data' => $resultsArray, + ]); + } + + public function setRewritingEnableAction(Request $request): Response + { + $isRewritingEnable = $request->get('rewriting_enable', null); + + if ($isRewritingEnable !== null) { + ConfigQuery::write('rewriting_enable', $isRewritingEnable ? 1 : 0); + + return $this->jsonResponse(json_encode(['state' => 'Success']), 200); + } + + return $this->jsonResponse(Translator::getInstance()->trans( + 'Unable to change the configuration variable.', + [], + RewriteUrl::MODULE_DOMAIN + ), 500); + } + + public function addRuleAction(Request $request) + { + try { + $rule = new RewriteurlRule(); + + $this->fillRuleObjectFields($rule, $request); + } catch (\Exception $ex) { + return $this->jsonResponse($ex->getMessage(), 500); + } + + return $this->jsonResponse(json_encode(['state' => 'Success']), 200); + } + + public function updateRuleAction(Request $request) + { + try { + $rule = RewriteurlRuleQuery::create()->findOneById($request->get('id')); + + if ($rule === null) { + throw new \Exception(Translator::getInstance()->trans( + 'Unable to find rule to update.', + [], + RewriteUrl::MODULE_DOMAIN + )); + } + + $this->fillRuleObjectFields($rule, $request); + } catch (\Exception $ex) { + return $this->jsonResponse($ex->getMessage(), 500); + } + + return $this->jsonResponse(json_encode(['state' => 'Success']), 200); + } + + public function removeRuleAction(Request $request) + { + try { + $rule = RewriteurlRuleQuery::create()->findOneById($request->get('id')); + + if ($rule === null) { + throw new \Exception(Translator::getInstance()->trans( + 'Unable to find rule to remove.', + [], + RewriteUrl::MODULE_DOMAIN + )); + } + + $rule->delete(); + } catch (\Exception $ex) { + return $this->jsonResponse($ex->getMessage(), 500); + } + + return $this->jsonResponse(json_encode(['state' => 'Success']), 200); + } + + public function moveRulePositionAction(Request $request) + { + try { + $rule = RewriteurlRuleQuery::create()->findOneById($request->get('id')); + + if ($rule === null) { + throw new \Exception(Translator::getInstance()->trans( + 'Unable to find rule to change position.', + [], + RewriteUrl::MODULE_DOMAIN + )); + } + + $type = $request->get('type', null); + + if ($type === 'up') { + $rule->movePositionUp(); + } elseif ($type === 'down') { + $rule->movePositionDown(); + } elseif ($type === 'absolute') { + $position = $request->get('position', null); + if (!empty($position)) { + $rule->changeAbsolutePosition($position); + } + } + } catch (\Exception $ex) { + return $this->jsonResponse($ex->getMessage(), 500); + } + + return $this->jsonResponse(json_encode(['state' => 'Success']), 200); + } + + /** + * @throws \Propel\Runtime\Exception\PropelException + */ + protected function fillRuleObjectFields(RewriteurlRule $rule, Request $request): void + { + $ruleType = $request->get('ruleType', null); + + $isParamRule = $ruleType === RewriteurlRule::TYPE_GET_PARAMS || $ruleType === RewriteurlRule::TYPE_REGEX_GET_PARAMS; + $isRegexRule = $ruleType === RewriteurlRule::TYPE_REGEX || $ruleType === RewriteurlRule::TYPE_REGEX_GET_PARAMS; + $isTextRule = $ruleType === RewriteurlRule::TYPE_TEXT; + + if (!($isParamRule || $isRegexRule || $isTextRule)) { + throw new TheliaProcessException(Translator::getInstance()->trans('Unknown rule type.', [], RewriteUrl::MODULE_DOMAIN)); + } + + if ($isTextRule && !$textValue = $request->get('textValue', null)) { + throw new TheliaProcessException(Translator::getInstance()->trans('Text value cannot be empty.', [], RewriteUrl::MODULE_DOMAIN)); + } + + $regexValue = $request->get('value', null); + + if ($isRegexRule && empty($regexValue)) { + throw new TheliaProcessException(Translator::getInstance()->trans('Regex value cannot be empty.', [], RewriteUrl::MODULE_DOMAIN)); + } + + $redirectUrl = $request->get('redirectUrl', null); + + if (empty($redirectUrl)) { + throw new TheliaProcessException(Translator::getInstance()->trans('Redirect url cannot be empty.', [], RewriteUrl::MODULE_DOMAIN)); + } + + $value = $isTextRule ? $textValue : $regexValue; + + $paramRuleArray = []; + + if ($isParamRule) { + $paramRuleArray = $request->get('paramRules', null); + if (empty($paramRuleArray)) { + throw new TheliaProcessException(Translator::getInstance()->trans('At least one GET parameter is required.', [], RewriteUrl::MODULE_DOMAIN)); + } + } + + $rule->setRuleType($ruleType) + ->setValue($value) + ->setOnly404($request->get('only404', 1)) + ->setRedirectUrl($redirectUrl); + + if (empty($rule->getPosition())) { + $rule->setPosition($rule->getNextPosition()); + } + + $rule->deleteAllRelatedParam(); + + $rule->save(); + + if ($isParamRule) { + foreach ($paramRuleArray as $paramRule) { + if (!\array_key_exists('paramName', $paramRule) || empty($paramRule['paramName'])) { + throw new TheliaProcessException(Translator::getInstance()->trans( + 'Param name is empty.', + [], + RewriteUrl::MODULE_DOMAIN + )); + } + if (!\array_key_exists('condition', $paramRule) || empty($paramRule['condition'])) { + throw new TheliaProcessException(Translator::getInstance()->trans( + 'Param condition is empty.', + [], + RewriteUrl::MODULE_DOMAIN + )); + } + + (new RewriteurlRuleParam()) + ->setParamName($paramRule['paramName']) + ->setParamCondition($paramRule['condition']) + ->setParamValue($paramRule['paramValue']) + ->setIdRule($rule->getId()) + ->save(); + } + } + } +} diff --git a/domokits/local/modules/RewriteUrl/Controller/Admin/NotRewritenUrlsAdminController.php b/domokits/local/modules/RewriteUrl/Controller/Admin/NotRewritenUrlsAdminController.php new file mode 100644 index 0000000..4b6a80a --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Controller/Admin/NotRewritenUrlsAdminController.php @@ -0,0 +1,39 @@ + + */ +class NotRewritenUrlsAdminController extends AdminController +{ + public function defaultAction(Request $request) + { + return $this->render( + 'list-notrewritenurls', + array( + 'current_tab' => $request->get('current_tab', 'categories'), + 'page_category' => $request->get('page_category', 1), + 'page_product' => $request->get('page_product', 1), + 'page_brand' => $request->get('page_brand', 1), + 'page_folder' => $request->get('page_folder', 1), + 'page_content' => $request->get('page_content', 1), + ) + ); + } +} diff --git a/domokits/local/modules/RewriteUrl/Controller/Admin/RewriteUrlAdminController.php b/domokits/local/modules/RewriteUrl/Controller/Admin/RewriteUrlAdminController.php new file mode 100755 index 0000000..15c65b5 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Controller/Admin/RewriteUrlAdminController.php @@ -0,0 +1,480 @@ + + * @author Gilles Bourgeat + */ +class RewriteUrlAdminController extends BaseAdminController +{ + /** @var array */ + private $correspondence = array( + 'brand' => 'brand', + 'category' => 'categories', + 'content' => 'content', + 'folder' => 'folders', + 'product' => 'products' + ); + + /** + * @return mixed + */ + public function deleteAction( + Request $request, + EventDispatcherInterface $dispatcher + ) { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'RewriteUrl', AccessManager::DELETE)) { + return $response; + } + + $urlId = $request->request->get('id_url'); + $rewritingUrl = RewritingUrlQuery::create()->findOneById($urlId); + + if ($rewritingUrl !== null) { + $event = new RewriteUrlEvent($rewritingUrl); + $dispatcher->dispatch($event,RewriteUrlEvents::REWRITEURL_DELETE); + } + + return $this->generateRedirectFromRoute( + 'admin.'.$this->correspondence[$rewritingUrl->getView()].'.update', + [ + $rewritingUrl->getView().'_id'=>$rewritingUrl->getViewId(), + 'current_tab' => 'modules' + ], + [ + $rewritingUrl->getView().'_id'=>$rewritingUrl->getViewId() + ] + ); + } + + /** + * @return mixed + */ + public function addAction(EventDispatcherInterface $dispatcher, ParserContext $parserContext) + { + $message = null; + $exception = null; + + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'RewriteUrl', AccessManager::CREATE)) { + return $response; + } + + $addForm = $this->createForm(AddUrlForm::getName()); + + try { + $form = $this->validateForm($addForm); + $data = $form->getData(); + + $findExist = RewritingUrlQuery::create()->findOneByUrl(($data['url'])); + + if ($findExist !== null && !in_array($findExist->getView(), RewriteUrl::getUnknownSources()) && $findExist->getView() !== '') { + $url = $this->generateUrlByRewritingUrl($findExist); + + throw new \Exception( + Translator::getInstance()->trans( + "This url is already used here %url.", + ['%url' => '' . $url . ''], + RewriteUrl::MODULE_DOMAIN + ) + ); + } + + $rewriting = $findExist !== null ? $findExist : new RewritingUrlOverride(); + $rewriting->setUrl($data['url']) + ->setView($data['view']) + ->setViewId($data['view-id']) + ->setViewLocale($data['locale']); + + $rewriteDefault = RewritingUrlQuery::create() + ->filterByView($rewriting->getView()) + ->filterByViewId($rewriting->getViewId()) + ->filterByViewLocale($rewriting->getViewLocale()) + ->findOneByRedirected(null); + + $redirectType = null; + + if ($data['default'] == 1) { + $rewriting->setRedirected(null); + } else { + if ($rewriteDefault !== null) { + $rewriting->setRedirected($rewriteDefault->getId()); + $redirectType = RewritingRedirectTypeQuery::create() + ->filterById($rewriting->getId()) + ->findOneOrCreate(); + $redirectType->setHttpcode($data['httpcode']); + } else { + $rewriting->setRedirected(null); + } + } + + $dispatcher->dispatch( + new RewriteUrlEvent($rewriting, $redirectType), + RewriteUrlEvents::REWRITEURL_ADD + ); + + return $this->generateSuccessRedirect($addForm); + } catch (FormValidationException $exception) { + $message = $this->createStandardFormValidationErrorMessage($exception); + } catch (\Exception $exception) { + $message = $exception->getMessage(); + } + + if ($message !== null && $exception !== null) { + Tlog::getInstance()->error(sprintf("Error during order delivery process : %s. Exception was %s", $message, $exception->getMessage())); + + $addForm->setErrorMessage($message); + + $parserContext + ->addForm($addForm) + ->setGeneralError($message); + } + + return $this->generateSuccessRedirect($addForm); + } + + /** + * @return mixed + */ + public function setDefaultAction(Request $request, EventDispatcherInterface $dispatcher) + { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'RewriteUrl', AccessManager::UPDATE)) { + return $response; + } + + $urlId = $request->request->get('id_url'); + $rewritingUrl = RewritingUrlQuery::create()->findOneById($urlId); + + if ($rewritingUrl !== null) { + $event = new RewriteUrlEvent($rewritingUrl); + $dispatcher->dispatch($event,RewriteUrlEvents::REWRITEURL_SET_DEFAULT); + } + + return $this->generateRedirectFromRoute( + 'admin.'.$this->correspondence[$rewritingUrl->getView()].'.update', + [ + $rewritingUrl->getView().'_id'=>$rewritingUrl->getViewId(), + 'current_tab' => 'modules' + ], + [ + $rewritingUrl->getView().'_id'=>$rewritingUrl->getViewId() + ] + ); + } + + + public function changeRedirectTypeAction(Request $request, EventDispatcherInterface $dispatcher) + { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'RewriteUrl', AccessManager::UPDATE)) { + return $response; + } + + $urlId = $request->get('id_url'); + $httpcode = $request->get('httpcode'); + $rewritingUrl = RewritingUrlQuery::create()->findOneById($urlId); + + if ($rewritingUrl !== null) { + $rewritingRedirectType = RewritingRedirectTypeQuery::create() + ->filterById($urlId) + ->findOneOrCreate(); + $rewritingRedirectType->setHttpcode($httpcode); + + $event = new RewriteUrlEvent($rewritingUrl, $rewritingRedirectType); + $dispatcher->dispatch($event,RewriteUrlEvents::REWRITEURL_UPDATE); + } + + return new Response("", Response::HTTP_OK); + } + + /** + * @return mixed + */ + public function reassignAction(EventDispatcherInterface $dispatcher, ParserContext $parserContext) + { + if (null !== $response = $this->checkAuth(AdminResources::MODULE, 'RewriteUrl', AccessManager::UPDATE)) { + return $response; + } + + $reassignForm = $this->createForm(ReassignForm::getName()); + + try { + $form = $this->validateForm($reassignForm); + $data = $form->getData(); + + $all = $data['all']; + $newRewrite = explode('::', $data['select-reassign']); + $rewriteId = $data['rewrite-id']; + $newView = $newRewrite[1]; + $newViewId = $newRewrite[0]; + + if ($all === 1) { + self::allReassign($dispatcher, $rewriteId, $newView, $newViewId); + } else { + self::simpleReassign($dispatcher, $rewriteId, $newView, $newViewId); + } + + return $this->generateSuccessRedirect($reassignForm); + } catch (FormValidationException $e) { + $message = $this->createStandardFormValidationErrorMessage($e); + } catch (\Exception $e) { + $message = $e->getMessage(); + } + + $reassignForm->setErrorMessage($message); + + $parserContext + ->addForm($reassignForm) + ->setGeneralError($message) + ; + + return $this->generateSuccessRedirect($reassignForm); + } + + /** + * @return mixed|\Thelia\Core\HttpFoundation\Response + */ + public function existAction(Request $request) + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'RewriteUrl', AccessManager::VIEW)) { + return $response; + } + + $search = $request->query->get('q'); + + $rewritingUrl = RewritingUrlQuery::create() + ->filterByView(RewriteUrl::getUnknownSources(), Criteria::NOT_IN) + ->findOneByUrl($search); + + if ($rewritingUrl !== null) { + if (!in_array($rewritingUrl->getView(), $this->correspondence)) { + return $this->jsonResponse(json_encode(false)); + } + + $rewritingUrlArray = ["reassignUrl" => $this->generateUrlByRewritingUrl($rewritingUrl)]; + + return $this->jsonResponse(json_encode($rewritingUrlArray)); + } else { + return $this->jsonResponse(json_encode(false)); + } + } + + /** + * @return mixed|\Thelia\Core\HttpFoundation\Response + * @throws \Propel\Runtime\Exception\PropelException + */ + public function searchAction(Request $request) + { + if (null !== $response = $this->checkAuth(array(AdminResources::MODULE), 'RewriteUrl', AccessManager::VIEW)) { + return $response; + } + + $search = '%'.$request->query->get('q').'%'; + + $resultArray = array(); + + $categoriesI18n = CategoryI18nQuery::create()->filterByTitle($search, Criteria::LIKE)->limit(10); + $contentsI18n = ContentI18nQuery::create()->filterByTitle($search, Criteria::LIKE)->limit(10); + $foldersI18n = FolderI18nQuery::create()->filterByTitle($search, Criteria::LIKE)->limit(10); + $brandsI18n = BrandI18nQuery::create()->filterByTitle($search, Criteria::LIKE)->limit(10); + + $productsI18n = ProductI18nQuery::create()->filterByTitle($search, Criteria::LIKE)->limit(10); + $productsRef = ProductQuery::create()->filterByRef($search, Criteria::LIKE)->limit(10); + + /** @var \Thelia\Model\CategoryI18n $categoryI18n */ + foreach ($categoriesI18n as $categoryI18n) { + $category = $categoryI18n->getCategory(); + $resultArray['category'][$category->getId()] = $categoryI18n->getTitle(); + } + + /** @var \Thelia\Model\ContentI18n $contentI18n */ + foreach ($contentsI18n as $contentI18n) { + $content = $contentI18n->getContent(); + $resultArray['content'][$content->getId()] = $contentI18n->getTitle(); + } + + /** @var \Thelia\Model\FolderI18n $folderI18n */ + foreach ($foldersI18n as $folderI18n) { + $folder = $folderI18n->getFolder(); + $resultArray['folder'][$folder->getId()] = $folderI18n->getTitle(); + } + + /** @var \Thelia\Model\BrandI18n $brandI18n */ + foreach ($brandsI18n as $brandI18n) { + $brand = $brandI18n->getBrand(); + $resultArray['brand'][$brand->getId()] = $brandI18n->getTitle(); + } + + /** @var \Thelia\Model\ProductI18n $productI18n */ + foreach ($productsI18n as $productI18n) { + $product = $productI18n->getProduct(); + $resultArray['product'][$product->getId()] = $product->getRef().' : '.$productI18n->getTitle(); + } + + /** @var \Thelia\Model\Product $product */ + foreach ($productsRef as $product) { + $productI18n = ProductI18nQuery::create()->filterByProduct($product)->findOne(); + $resultArray['product'][$product->getId()] = $productI18n->getTitle(); + } + + return $this->jsonResponse(json_encode($resultArray)); + } + + /** + * @param int $rewriteId + * @param string $newView + * @param int $newViewId + */ + protected function allReassign( + EventDispatcherInterface $dispatcher, + $rewriteId, + $newView, + $newViewId + ) { + $origin = RewritingUrlQuery::create()->findOneById($rewriteId); + + $rewrites = RewritingUrlQuery::create() + ->filterByView($origin->getView()) + ->filterByViewId($origin->getViewId()) + ->find(); + + /** @var RewritingUrl $rewrite */ + foreach ($rewrites as $rewrite) { + $destination = RewritingUrlQuery::create() + ->filterByView($newView) + ->filterByViewId($newViewId) + ->filterByViewLocale($rewrite->getViewLocale()) + ->filterByRedirected(null) + ->findOne(); + + $rewrite + ->setView($newView) + ->setViewId($newViewId) + ->setRedirected(($destination === null) ? null : $destination->getId()); + + $dispatcher->dispatch( + new RewriteUrlEvent($rewrite), + RewriteUrlEvents::REWRITEURL_UPDATE + ); + } + } + + /** + * @param int $rewriteId + * @param string $newView + * @param int $newViewId + */ + protected function simpleReassign( + EventDispatcherInterface $dispatcher, + $rewriteId, + $newView, + $newViewId + ) { + $rewrite = RewritingUrlQuery::create()->findOneById($rewriteId); + + // add new default url + if (null !== $newDefault = RewritingUrlQuery::create()->findOneByRedirected($rewrite->getId())) { + $dispatcher->dispatch( + new RewriteUrlEvent( + $newDefault->setRedirected(null) + ), + RewriteUrlEvents::REWRITEURL_UPDATE + ); + } + + //Update urls who redirected to updated URL + if (null !== $isRedirection = RewritingUrlQuery::create()->findByRedirected($rewrite->getId())) { + /** @var \Thelia\Model\RewritingUrl $redirected */ + foreach ($isRedirection as $redirected) { + $dispatcher->dispatch( + new RewriteUrlEvent( + $redirected->setRedirected( + ($newDefault !== null) ? $newDefault->getId() : $rewrite->getRedirected() + ) + ), + RewriteUrlEvents::REWRITEURL_UPDATE + ); + } + } + + $rewrite->setView($newView) + ->setViewId($newViewId); + + //Check if default url already exist for the view with the locale + $rewriteDefault = RewritingUrlQuery::create() + ->filterByView($newView) + ->filterByViewId($newViewId) + ->filterByViewLocale($rewrite->getViewLocale()) + ->findOneByRedirected(null); + + if ($rewriteDefault !== null) { + $rewrite->setRedirected($rewriteDefault->getId()); + } else { + $rewrite->setRedirected(null); + } + + $event = new RewriteUrlEvent($rewrite); + $dispatcher->dispatch($event,RewriteUrlEvents::REWRITEURL_UPDATE); + } + + /** + * @param RewritingUrl $rewritingUrl + * @return null|string url + */ + protected function generateUrlByRewritingUrl(RewritingUrl $rewritingUrl) + { + if (isset($this->correspondence[$rewritingUrl->getView()])) { + $route = $this->getRoute( + "admin.".$this->correspondence[$rewritingUrl->getView()] . ".update", + [$rewritingUrl->getView().'_id' => $rewritingUrl->getViewId()] + ); + return URL::getInstance()->absoluteUrl( + $route, + [$rewritingUrl->getView().'_id' => $rewritingUrl->getViewId(), 'current_tab' => 'modules'] + ); + } + + return null; + } +} diff --git a/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvent.php b/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvent.php new file mode 100755 index 0000000..c5e9543 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvent.php @@ -0,0 +1,211 @@ + + * @author Gilles Bourgeat + */ +class RewriteUrlEvent extends ActionEvent +{ + /** @var int|null */ + protected $id; + + /** @var string */ + protected $url; + + /** @var string */ + protected $view; + + /** @var string */ + protected $viewLocale; + + /** @var int|null */ + protected $redirected; + + /** @var RewritingUrl */ + private $rewritingUrl; + + /** @var RewritingRedirectType */ + private $redirectType; + + /** + * @param RewritingUrl $rewritingUrl + * @param RewritingRedirectType $redirectType + */ + public function __construct(RewritingUrl $rewritingUrl, RewritingRedirectType $redirectType = null) + { + $this->id = $rewritingUrl->getId(); + $this->url = $rewritingUrl->getUrl(); + $this->view = $rewritingUrl->getView(); + $this->viewLocale = $rewritingUrl->getViewLocale(); + $this->redirected = $rewritingUrl->getRedirected(); + $this->rewritingUrl = $rewritingUrl; + $this->redirectType = $redirectType; + } + + /** + * @return RewritingUrl + */ + public function getRewritingUrl() + { + return $this->rewritingUrl; + } + + /** + * @return RewritingRedirectType + */ + public function getRedirectType() + { + return $this->redirectType; + } + + /** + * @param int $id|null + * @return $this + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @param array $parameters + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param boolean $propagationStopped + * @return $this + */ + public function setPropagationStopped($propagationStopped) + { + $this->propagationStopped = boolval($propagationStopped); + + return $this; + } + + /** + * @return boolean + */ + public function getPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * @param null|int $redirected + * @return $this + */ + public function setRedirected($redirected) + { + $this->redirected = $redirected; + + return $this; + } + + /** + * @return null|int + */ + public function getRedirected() + { + return $this->redirected; + } + + /** + * @param string $url + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * @return null|int + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param $view + * @return $this + */ + public function setView($view) + { + $this->view = $view; + + return $this; + } + + /** + * @return null + */ + public function getView() + { + return $this->view; + } + + /** + * @param string $view_locale + * @return $this + */ + public function setViewLocale($view_locale) + { + $this->viewLocale = $view_locale; + + return $this; + } + + /** + * @return null + */ + public function getViewLocale() + { + return $this->viewLocale; + } +} diff --git a/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvents.php b/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvents.php new file mode 100755 index 0000000..d009f8a --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Event/RewriteUrlEvents.php @@ -0,0 +1,26 @@ + + */ +class RewriteUrlEvents +{ + const REWRITEURL_ADD = 'rewriteurl.action.add'; + const REWRITEURL_DELETE = "rewriteurl.action.delete"; + const REWRITEURL_UPDATE = "rewriteurl.action.update"; + const REWRITEURL_SET_DEFAULT = "rewriteurl.action.setdefault"; +} diff --git a/domokits/local/modules/RewriteUrl/EventListeners/KernelExceptionListener.php b/domokits/local/modules/RewriteUrl/EventListeners/KernelExceptionListener.php new file mode 100644 index 0000000..af160d9 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/EventListeners/KernelExceptionListener.php @@ -0,0 +1,69 @@ +requestStack = $requestStack; + } + + + public static function getSubscribedEvents() + { + return [ + KernelEvents::EXCEPTION => ['onKernelHttpNotFoundException', 300], + ]; + } + + public function onKernelHttpNotFoundException(ExceptionEvent $event) + { + if ($event->getThrowable() instanceof NotFoundHttpException) { + $urlTool = URL::getInstance(); + + $request = $this->requestStack->getCurrentRequest(); + $pathInfo = $request instanceof TheliaRequest ? $request->getRealPathInfo() : $request->getPathInfo(); + + // Check RewriteUrl text rules + $textRule = RewriteurlRuleQuery::create() + ->filterByOnly404(0) + ->filterByValue(ltrim($pathInfo, '/')) + ->filterByRuleType('text') + ->orderByPosition() + ->findOne(); + + if ($textRule) { + $event->setThrowable(new RedirectException($urlTool->absoluteUrl($textRule->getRedirectUrl()), 301)); + } + + $ruleCollection = RewriteurlRuleQuery::create() + ->filterByOnly404(1) + ->orderByPosition() + ->find(); + + /** @var RewriteurlRule $rule */ + foreach ($ruleCollection as $rule) { + if ($rule->isMatching($pathInfo, $request->query->all())) { + $event->setThrowable(new RedirectException($urlTool->absoluteUrl($rule->getRedirectUrl()), 301)); + return; + } + } + } + } +} diff --git a/domokits/local/modules/RewriteUrl/EventListeners/RewriteUrlListener.php b/domokits/local/modules/RewriteUrl/EventListeners/RewriteUrlListener.php new file mode 100755 index 0000000..818a588 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/EventListeners/RewriteUrlListener.php @@ -0,0 +1,161 @@ + + */ +class RewriteUrlListener implements EventSubscriberInterface +{ + protected $dispatcher; + + /** + * RewriteUrlListener constructor. + * @param EventDispatcherInterface $dispatcher + */ + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + RewriteUrlEvents::REWRITEURL_DELETE =>['deleteRewrite'], + RewriteUrlEvents::REWRITEURL_UPDATE =>['updateRewrite'], + RewriteUrlEvents::REWRITEURL_ADD =>['addRewrite'], + RewriteUrlEvents::REWRITEURL_SET_DEFAULT =>['setDefaultRewrite'] + ]; + } + + /** + * @param RewriteUrlEvent $event + */ + public function deleteRewrite(RewriteUrlEvent $event) + { + $rewritingUrl = $event->getRewritingUrl(); + + $newDefault = null; + + // test if default url + if ($event->getRewritingUrl()->getRedirected() === null) { + // add new default url + if (null !== $newDefault = RewritingUrlQuery::create()->findOneByRedirected($rewritingUrl->getId())) { + $this->dispatcher->dispatch( + new RewriteUrlEvent( + $newDefault->setRedirected(null) + ), + RewriteUrlEvents::REWRITEURL_UPDATE + ); + } + } + + $isRedirection = RewritingUrlQuery::create()->findByRedirected($rewritingUrl->getId()); + + //Update urls who redirected to deleted URL + /** @var \Thelia\Model\RewritingUrl $redirected */ + foreach ($isRedirection as $redirected) { + $this->dispatcher->dispatch( + new RewriteUrlEvent( + $redirected->setRedirected( + ($newDefault !== null) ? $newDefault->getId() : $rewritingUrl->getRedirected() + ) + ), + RewriteUrlEvents::REWRITEURL_UPDATE + ); + } + + $rewritingUrl->delete(); + } + + /** + * @param RewriteUrlEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function addRewrite(RewriteUrlEvent $event) + { + $rewritingUrl = $event->getRewritingUrl(); + $rewritingUrl->save(); + + if ($rewritingUrl->getRedirected() === null) { + //check if the new redirect is set to default if yes redirect all to the new one + RewritingUrlQuery::create() + ->filterByView($rewritingUrl->getView()) + ->filterByViewId($rewritingUrl->getViewId()) + ->filterByViewLocale($rewritingUrl->getViewLocale()) + ->update(array( + "Redirected" => $rewritingUrl->getId() + )); + + //Re set new url to default + $rewritingDefault = RewritingUrlQuery::create()->findOneById($rewritingUrl->getId()); + $rewritingDefault->setRedirected(null); + $rewritingDefault->save(); + } else { + $redirectType = $event->getRedirectType(); + if ($redirectType !== null) { + $redirectType->setId($rewritingUrl->getId()); + $redirectType->save(); + } + } + } + + /** + * @param RewriteUrlEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function setDefaultRewrite(RewriteUrlEvent $event) + { + $rewritingUrl = $event->getRewritingUrl(); + + //redirect all url to the new one + RewritingUrlQuery::create() + ->filterByView($rewritingUrl->getView()) + ->filterByViewId($rewritingUrl->getViewId()) + ->filterByViewLocale($rewritingUrl->getViewLocale()) + ->update(array( + "Redirected" => $rewritingUrl->getId() + )); + + //Re set new url to default + $rewritingUrl->setRedirected(null); + $rewritingUrl->save(); + } + + /** + * @param RewriteUrlEvent $event + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + */ + public function updateRewrite(RewriteUrlEvent $event) + { + $event->getRewritingUrl()->save(); + $redirectType = $event->getRedirectType(); + if ($redirectType !== null) { + $redirectType->save(); + } + } +} diff --git a/domokits/local/modules/RewriteUrl/Form/AddUrlForm.php b/domokits/local/modules/RewriteUrl/Form/AddUrlForm.php new file mode 100755 index 0000000..0eef997 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Form/AddUrlForm.php @@ -0,0 +1,87 @@ + + */ +class AddUrlForm extends BaseForm +{ + /** + * @return string + */ + public static function getName() + { + return "rewriteurl_add_form"; + } + + public function buildForm() + { + $this->formBuilder + ->add( + 'view', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'view-id', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'url', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'default', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'locale', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'httpcode', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ; + } +} diff --git a/domokits/local/modules/RewriteUrl/Form/ReassignForm.php b/domokits/local/modules/RewriteUrl/Form/ReassignForm.php new file mode 100755 index 0000000..1d2b2e6 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Form/ReassignForm.php @@ -0,0 +1,62 @@ + + */ +class ReassignForm extends BaseForm +{ + /** + * @return string + */ + public static function getName() + { + return "rewriteurl_reassign_form"; + } + + protected function buildForm() + { + $this->formBuilder + ->add( + 'rewrite-id', + IntegerType::class, + array( + 'required' => true + ) + ) + ->add( + 'select-reassign', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ) + ->add( + 'all', + IntegerType::class, + array( + 'required' => true + ) + ) + ; + } +} diff --git a/domokits/local/modules/RewriteUrl/Form/SetDefaultForm.php b/domokits/local/modules/RewriteUrl/Form/SetDefaultForm.php new file mode 100755 index 0000000..b548f9c --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Form/SetDefaultForm.php @@ -0,0 +1,46 @@ + + */ +class SetDefaultForm extends BaseForm +{ + /** + * @return string + */ + public static function getName() + { + return "rewriteurl_setdefault_form"; + } + + protected function buildForm() + { + $this->formBuilder + ->add( + 'rewrite-id', + TextType::class, + array( + 'constraints' => array(new NotBlank()), + 'required' => true + ) + ); + } +} diff --git a/domokits/local/modules/RewriteUrl/Hook/ConfigurationHook.php b/domokits/local/modules/RewriteUrl/Hook/ConfigurationHook.php new file mode 100644 index 0000000..6bffeb0 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Hook/ConfigurationHook.php @@ -0,0 +1,72 @@ + + */ +class ConfigurationHook extends BaseHook +{ + public function onModuleConfiguration(HookRenderEvent $event) + { + $event->add( + $this->render( + 'RewriteUrl/module-configuration.html', + [ + "isRewritingEnabled" => ConfigQuery::isRewritingEnable() + ] + ) + ); + } + + public function onModuleConfigurationJavascript(HookRenderEvent $event) + { + $event->add( + $this->render( + 'RewriteUrl/module-configuration-js.html', + [ + "isRewritingEnabled" => ConfigQuery::isRewritingEnable() + ] + ) + ); + } + + public function onConfigurationCatalogTop(HookRenderEvent $event) + { + $event->add($this->render( + 'configuration-catalog.html' + )); + } + + public function onMainTopMenuTools(HookRenderBlockEvent $event) + { + $event->add( + [ + 'id' => 'tools_menu_rewriteutl', + 'class' => '', + 'url' => URL::getInstance()->absoluteUrl('/admin/module/RewriteUrl'), + 'title' => $this->trans('Global URL Rewriting', [], RewriteUrl::MODULE_DOMAIN), + ] + ); + } +} diff --git a/domokits/local/modules/RewriteUrl/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/RewriteUrl/I18n/backOffice/default/fr_FR.php new file mode 100755 index 0000000..14aca64 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,86 @@ + '404 uniquement', + 'Actions' => 'Actions', + 'All your brands have rewriten urls' => 'Toutes vos marques ont des urls réécrites', + 'All your categories have rewriten urls' => 'Toutes vos rubriques ont des urls réécrites', + 'All your contents have rewriten urls' => 'Tous vos contenus ont des urls réécrites', + 'All your folders have rewriten urls' => 'Tous vos dossiers ont des urls réécrites', + 'All your products have rewriten urls' => 'Tous vos produits ont des urls réécrites', + 'Brand' => 'Marque', + 'Brands' => 'Marques', + 'Categories' => 'Rubriques', + 'Category' => 'Catégorie', + 'Change this category' => 'Modifier cette rubrique', + 'Change this content' => 'Modifier ce contenu', + 'Change this folder' => 'Modifier ce dossier', + 'Change this product' => 'Modifier ce produit', + 'Close' => 'Fermer', + 'Condition' => 'Condition', + 'Configuration' => 'Configuration', + 'Contents' => 'Contenus', + 'Currently enabled rules' => 'Règles activées', + 'Default' => 'Url par défaut', + 'Default Url' => 'URL par défaut', + 'Default url' => 'Url par défaut', + 'Delete Url' => 'Supprimer une Url', + 'Delete this redirect' => 'Supprimer cette redirection', + 'Do you really want to delete the url %html ?' => 'Voulez-vous vraiment supprimer l\'url %html ?', + 'Do you really want to set the url %html as default ?' => 'Définir %html comme URL part défaut ?', + 'Edit information in %lng' => 'Modifier l\'information en %lng', + 'Enable URL rewriting' => 'Activer la réécriture d\'URL', + 'Enter a URL with a leading \'/\' and no domain name. Ex : for \'www.mysite.com/one/two\', enter \'/one/two\'.' => 'Saisissez une URL commençant par un \'/\' et sans nom de domaine. Ex : pour \'www.monsite.com/un/deux\', saisissez \'/un/deux\'.', + 'Enter new rule position' => 'Saisir une nouvelle position de règle', + 'Error this url already exist you can reassign by follow this ' => 'Erreur cette url existe déjà, vous pouvez la réassigner en suivant ce ', + 'Folder' => 'Dossier', + 'Folders' => 'Dossiers', + 'For questions or bug reporting, thank you to use %url.' => 'Pour toutes questions ou déclaration de bug, merci d\'utiliser %url', + 'For the regex, your input is compare to URL without the domain name and without any GET params. Ex : for \'www.mysite.com/one/two?three=four\', your regex will be compare to \'/one/two\'.' => 'Pour une regex, votre expression régulière sera comparée aux URLs sans nom de domaine et sans paramètres GET. Ex : pour \'www.monsite.com/un/deux?trois=quatre\', votre regex sera comparée à \'/un/deux\'.', + 'GET params' => 'Paramètres GET', + 'Global URL Rewriting' => 'Ré-écritures URL globales', + 'Home' => 'Accueil', + 'ID' => 'ID', + 'List of not rewriten urls' => 'Liste des urls non réécrites', + 'Name' => 'Nom', + 'New url' => 'Nouvelle url', + 'No redirected url.' => 'Pas de redirection', + 'No results found for your search.' => 'Aucun resultat trouvé pour votre recherche.', + 'Not rewriten urls' => 'Urls non réécrites', + 'Permanent redirect' => 'Redirection permanente', + 'Please wait ...' => 'Veuillez patienter ...', + 'Position' => 'Position', + 'Product' => 'Produit', + 'Products' => 'Produits', + 'Reassign all urls' => 'Ré-attribuer toutes les urls', + 'Reassign the url' => 'Ré-attribuer l\'url', + 'Reassign this redirect' => 'Réassigner cette redirection', + 'Redirect all urls' => 'Rediriger toutes les urls', + 'Redirect all urls on a (category, product, folder, content, brand).' => 'Rediriger toutes les urls sur un/une (catégorie, produit, dossier, contenu, marque).', + 'Redirect the url %html on :' => 'Rediriger l\'url %html sur :', + 'Redirect to' => 'Rediriger vers', + 'Redirect to default' => 'Rediriger sur l\'url par défaut', + 'Redirected' => 'Redirigé vers', + 'Reference' => 'Référence', + 'Regex' => 'Regex', + 'Regex and GET params' => 'Expr. régulière + Param. GET', + 'Rule management' => 'Gestion des règles de redirection', + 'Rule type' => 'Type de règle', + 'Search' => 'Rechercher', + 'Set the config variable \'rewriting_enable\'' => 'Définit la valeur de la variable \'rewriting_enable\'.', + 'Set this redirect to default' => 'Mettre cette url par défaut', + 'Temporary redirect' => 'Redirection temporaire', + 'This action is irreversible after confirmation.' => 'Cette action est irréversible après confirmation.', + 'Title, Ref ...' => 'Titre, Ref ...', + 'Type' => 'Type', + 'Url' => 'Url', + 'Url redirected' => 'Url de redirection', + 'Validate' => 'Valider', + 'View locale' => 'Langue', + 'content' => 'Contenu', + 'exists' => 'existe', + 'is empty' => 'est vide', + 'is missing' => 'est manquante', + 'is not empty' => 'n\'est pas vide', + 'link' => 'lien', +); diff --git a/domokits/local/modules/RewriteUrl/I18n/fr_FR.php b/domokits/local/modules/RewriteUrl/I18n/fr_FR.php new file mode 100644 index 0000000..f9a53ca --- /dev/null +++ b/domokits/local/modules/RewriteUrl/I18n/fr_FR.php @@ -0,0 +1,19 @@ + 'Au moins un paramètre GET est requis.', + 'Get Params' => 'Paramètres GET', + 'Global URL Rewriting' => 'Ré-écritures URL globales', + 'Param condition is empty.' => 'Une des conditions d\'un paramètre GET est vide.', + 'Param name is empty.' => 'Le nom d\'un paramètre GET est vide.', + 'Redirect url cannot be empty.' => 'L URL redirigée ne peut pas être vide.', + 'Regex' => 'Expression régulière', + 'Regex and Get Params' => 'Expr. régulière + Param. GET', + 'Regex value cannot be empty.' => 'La valeur de l\'expression régulière ne peut pas être vide.', + 'This url is already used here %url.' => 'L URL est déjà utilisée ici : %url', + 'Unable to change the configuration variable.' => 'Impossible de changer la variable de configuration.', + 'Unable to find rule to change position.' => 'Impossible de trouver la règle pour le changement de position.', + 'Unable to find rule to remove.' => 'La règle à supprimer n a pas pu être trouvée.', + 'Unable to find rule to update.' => 'La règle à modifier n a pas pu être trouvée.', + 'Unknown rule type.' => 'Type de règle inconnu.', +); diff --git a/domokits/local/modules/RewriteUrl/LICENSE.txt b/domokits/local/modules/RewriteUrl/LICENSE.txt new file mode 100755 index 0000000..65c5ca8 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/domokits/local/modules/RewriteUrl/Loop/NotRewritenUrlCategoryLoop.php b/domokits/local/modules/RewriteUrl/Loop/NotRewritenUrlCategoryLoop.php new file mode 100644 index 0000000..c86160c --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Loop/NotRewritenUrlCategoryLoop.php @@ -0,0 +1,106 @@ + + * @author Gilles Bourgeat + * + * @method string getView() + */ +class NotRewritenUrlCategoryLoop extends BaseI18nLoop implements PropelSearchLoopInterface +{ + protected static $cacheRewritingUrl = []; + + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createEnumListTypeArgument( + 'view', + ['product', 'category', 'folder', 'content', 'brand'] + ) + ); + } + + public function buildModelCriteria() + { + $view = $this->getView()[0]; + + $rewritingUrlQuery = RewritingUrlQuery::create(); + + $class = 'Thelia\Model\\' . ucfirst($view) . 'Query'; + /** @var CategoryQuery|ProductQuery|FolderQuery|ContentQuery|BrandQuery $objectQuery */ + $objectQuery = $class::create(); + + $localeSearch = LangQuery::create()->findByIdOrLocale($this->getLang()); + + $rewritingUrlQuery->filterByView($view)->filterByViewLocale( + $localeSearch !== null ? $localeSearch->getLocale() : 'en_US' + ); + + if (!isset(static::$cacheRewritingUrl[$view])) { + static::$cacheRewritingUrl[$view] = $rewritingUrlQuery + ->select([RewritingUrlTableMap::VIEW_ID]) + ->groupBy(RewritingUrlTableMap::VIEW_ID) + ->find(); + } + + $query = $objectQuery + ->filterById(static::$cacheRewritingUrl[$view]->getData(), Criteria::NOT_IN); + + /* manage translations */ + $this->configureI18nProcessing($query, ['TITLE']); + + return $query; + } + + public function parseResults(LoopResult $loopResult) + { + /** @var Category|Product|Folder|Content|Brand $category */ + foreach ($loopResult->getResultDataCollection() as $category) { + $loopResultRow = (new LoopResultRow($category)) + ->set('ID', $category->getId()) + ->set('NAME', $category->getVirtualColumn('i18n_TITLE')); + + if (property_exists($category, 'ref')) { + $loopResultRow->set('REF', $category->getRef()); + } + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} diff --git a/domokits/local/modules/RewriteUrl/Loop/RewriteUrlLoop.php b/domokits/local/modules/RewriteUrl/Loop/RewriteUrlLoop.php new file mode 100755 index 0000000..1760d2b --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Loop/RewriteUrlLoop.php @@ -0,0 +1,104 @@ + + * @author Gilles Bourgeat + */ +class RewriteUrlLoop extends BaseLoop implements PropelSearchLoopInterface +{ + /** + * @return ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('id'), + Argument::createAnyTypeArgument('view'), + Argument::createIntTypeArgument('view_id'), + Argument::createIntTypeArgument('redirect') + ); + } + + /** + * @return \Thelia\Model\RewritingUrlQuery + */ + public function buildModelCriteria() + { + $search = RewritingUrlQuery::create(); + + if (null !== $id = $this->getId()) { + $search->filterById($id); + } + + if (null !== $view_id = $this->getViewId()) { + $search->filterByViewId($view_id)->filterByView($this->getView())->find(); + } + + $redirect = $this->getRedirect(); + if ($redirect == 1) { + $search->filterByRedirected(null, Criteria::NOT_EQUAL)->find(); + } elseif ($redirect == 0) { + $search->filterByRedirected(null, Criteria::EQUAL)->find(); + } + + return $search; + } + + /** + * @param LoopResult $loopResult + * @return LoopResult + */ + public function parseResults(LoopResult $loopResult) + { + $redirectTypeSearch = RewritingRedirectTypeQuery::create(); + foreach ($loopResult->getResultDataCollection() as $rewriteURl) { + $loopResultRow = (new LoopResultRow($rewriteURl)) + ->set('ID_URL', $rewriteURl->getID()) + ->set('URL', $rewriteURl->getUrl()) + ->set('VIEW', $rewriteURl->getView()) + ->set('VIEW_LOCALE', $rewriteURl->getViewLocale()) + ->set('REDIRECTED', $rewriteURl->getRedirected()) + ->set('VIEW_ID', $rewriteURl->getViewId()); + + + if ($rewriteURl->getRedirected()) { + $redirectType = $redirectTypeSearch->findPk($rewriteURl->getID()); + if ($redirectType == null) { + $httpcode = RewritingRedirectType::DEFAULT_REDIRECT_TYPE; + } else { + $httpcode = $redirectType->getHttpcode(); + } + $loopResultRow->set('HTTPCODE', $httpcode); + } + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} diff --git a/domokits/local/modules/RewriteUrl/Loop/RewriteUrlRuleLoop.php b/domokits/local/modules/RewriteUrl/Loop/RewriteUrlRuleLoop.php new file mode 100644 index 0000000..023b852 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Loop/RewriteUrlRuleLoop.php @@ -0,0 +1,83 @@ +getId()){ + $search->filterById($id); + } + + if (null !== $ruleType = $this->getRuleType()){ + $search->filterByRuleType($ruleType); + } + + if (null !== $value = $this->getValue()){ + $search->filterByValue($value); + } + + if (null !== $redirectUrl = $this->getRedirectUrl()){ + $search->filterByRedirectUrl($redirectUrl); + } + + return $search->orderByPosition(); + } + + public function parseResults(LoopResult $loopResult) + { + /** @var RewriteurlRule $rewriteUrlRule */ + foreach ($loopResult->getResultDataCollection() as $rewriteUrlRule){ + $loopResultRow = (new LoopResultRow($rewriteUrlRule)) + ->set('ID', $rewriteUrlRule->getId()) + ->set('RULE_TYPE', $rewriteUrlRule->getRuleType()) + ->set('VALUE', $rewriteUrlRule->getValue()) + ->set('ONLY404', $rewriteUrlRule->getOnly404()) + ->set('REDIRECT_URL', $rewriteUrlRule->getRedirectUrl()) + ->set('POSITION', $rewriteUrlRule->getPosition()) + ->set('REWRITE_URL_PARAMS', $rewriteUrlRule->getRewriteUrlParamCollection()); + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/Model/RewriteurlRule.php b/domokits/local/modules/RewriteUrl/Model/RewriteurlRule.php new file mode 100644 index 0000000..12f3c6d --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Model/RewriteurlRule.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace RewriteUrl\Model; + +use RewriteUrl\Model\Base\RewriteurlRule as BaseRewriteurlRule; +use Thelia\Log\Tlog; +use Thelia\Model\Tools\PositionManagementTrait; + +class RewriteurlRule extends BaseRewriteurlRule +{ + use PositionManagementTrait; + + /** @var string */ + public const TYPE_REGEX = 'regex'; + + /** @var string */ + public const TYPE_GET_PARAMS = 'params'; + + /** @var string */ + public const TYPE_REGEX_GET_PARAMS = 'regex-params'; + + /** @var string */ + public const TYPE_TEXT = 'text'; + + protected $rewriteUrlParamCollection = null; + + /** + * @return \Propel\Runtime\Collection\ObjectCollection|RewriteurlRuleParam[] + */ + public function getRewriteUrlParamCollection() + { + if ($this->rewriteUrlParamCollection === null) { + $this->rewriteUrlParamCollection = RewriteurlRuleParamQuery::create()->filterByIdRule($this->getId())->find(); + } + + return $this->rewriteUrlParamCollection; + } + + protected function isMatchingPath(string $url): bool + { + if (!empty($this->getValue())) { + try { + $match = @preg_match('/' . $this->getValue() . '/', $url); + + if (false === $match) { + Tlog::getInstance()->error('Invalid pattern: ' . $this->getValue()); + } + + return (bool)$match; + } catch (\Exception $ex) { + Tlog::getInstance()->error('Failed to match rule : ' . $ex->getMessage()); + } + } + + return false; + } + + protected function isMatchingGetParams(array $getParamArray): bool + { + if ($this->getRewriteUrlParamCollection()->count() === 0) { + return false; + } + + foreach ($this->getRewriteUrlParamCollection() as $rewriteUrlParam) { + if (!$rewriteUrlParam->isMatching($getParamArray)) { + return false; + } + } + + return true; + } + + public function isMatching(string $url, array $getParamArray): bool + { + if ($this->getRuleType() === self::TYPE_REGEX) { + return $this->isMatchingPath($url); + } + + if ($this->getRuleType() === self::TYPE_GET_PARAMS) { + return $this->isMatchingGetParams($getParamArray); + } + + if ($this->getRuleType() === self::TYPE_REGEX_GET_PARAMS) { + return $this->isMatchingPath($url) && $this->isMatchingGetParams($getParamArray); + } + + return false; + } + + public function deleteAllRelatedParam(): void + { + RewriteurlRuleParamQuery::create()->filterByIdRule($this->getId())->find()->delete(); + } +} diff --git a/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParam.php b/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParam.php new file mode 100644 index 0000000..fe37b75 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParam.php @@ -0,0 +1,53 @@ +getParamName(), $getParamArray)) { + $value = $getParamArray[$this->getParamName()]; + if (empty($value)) { + if ($this->getParamCondition() === self::PARAM_CONDITION_EMPTY) { + return true; + } + } else { + if ($this->getParamCondition() === self::PARAM_CONDITION_NOT_EMPTY) { + return true; + } + } + + if ($value == $this->getParamValue()) { + if ($this->getParamCondition() === self::PARAM_CONDITION_EQUALS) { + return true; + } + } else { + if ($this->getParamCondition() === self::PARAM_CONDITION_NOT_EQUALS) { + return true; + } + } + + if ($this->getParamCondition() === self::PARAM_CONDITION_EXISTS) { + return true; + } + + } else { + if ($this->getParamCondition() === self::PARAM_CONDITION_MISSING) { + return true; + } + } + + return false; + } +} diff --git a/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParamQuery.php b/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParamQuery.php new file mode 100644 index 0000000..96d4e10 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Model/RewriteurlRuleParamQuery.php @@ -0,0 +1,21 @@ + + */ +class RewritingUrlOverride extends RewritingUrl +{ + /** + * disable the Thelia behavior + * + * @param ConnectionInterface $con + */ + public function postInsert(ConnectionInterface $con = null): void + { + } +} diff --git a/domokits/local/modules/RewriteUrl/Readme.md b/domokits/local/modules/RewriteUrl/Readme.md new file mode 100755 index 0000000..87ae24c --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Readme.md @@ -0,0 +1,59 @@ +# Rewrite Url + +This module allows you to create general redirection rules for your website. You can also manage rewritten urls for products, categories, folders, contents, brands. + +* Allows you to redirect a url to another based on a regular expression or matching GET parameters +* Allows you to reassign a url to another (product, category, folder, content, brand) +* Allows you to reassign all urls to another (product, category, folder, content, brand) +* Allows you to reassign a default url to your (product, category, folder, content, brand) +* Allows you to display the list of not rewriten urls (product, category, folder, content, brand) + +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/thelia-modules/RewriteUrl/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/thelia-modules/RewriteUrl/?branch=master) +[![License](https://poser.pugx.org/thelia/rewrite-url-module/license)](https://packagist.org/packages/thelia/rewrite-url-module) +[![Latest Stable Version](https://poser.pugx.org/thelia/rewrite-url-module/v/stable)](https://packagist.org/packages/thelia/rewrite-url-module) + +#### [See the changelog](https://github.com/thelia-modules/RewriteUrl/blob/master/CHANGELOG.md) + +## Compatibility + +Thelia > 2.1 + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is RewriteUrl. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/rewrite-url-module:~1.5.8 +``` + +## Usage + +BackOffice : +- in Product edit tab modules +- in Folder edit tab modules +- in Content edit tab modules +- in Brand edit tab modules +- in Category edit tab modules +- in Configuration list not rewriten urls +- in the module configuration page + +## Screenshot + +#### In "Modules" tab + +![RewriteUrl](https://github.com/thelia-modules/RewriteUrl/blob/master/screenshot/screenshot-1.jpeg) + +#### In Thelia configuration + +![RewriteUrl](https://github.com/thelia-modules/RewriteUrl/blob/master/screenshot/screenshot-2.jpeg) + +#### In the module configuration + +![RewriteUrl](https://github.com/thelia-modules/RewriteUrl/blob/master/screenshot/screenshot-3.png) \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/RewriteUrl.php b/domokits/local/modules/RewriteUrl/RewriteUrl.php new file mode 100755 index 0000000..b70d3e6 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/RewriteUrl.php @@ -0,0 +1,130 @@ + + * @author Gilles Bourgeat + */ +class RewriteUrl extends BaseModule +{ + /** @var string */ + const MODULE_DOMAIN = "rewriteurl"; + + /** @var string */ + const MODULE_NAME = "rewriteurl"; + + /* @var string */ + const UPDATE_PATH = __DIR__ . DS . 'Config' . DS . 'update'; + + /** @static null|array */ + static protected $unknownSources; + + + public function preActivation(ConnectionInterface $con = null) + { + if (!$this->getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, array(__DIR__ . '/Config/thelia.sql')); + + $this->setConfigValue('is_initialized', true); + } + + return true; + } + + /** + * @param string $currentVersion + * @param string $newVersion + * @param ConnectionInterface $con + * @throws \Exception + * @throws \Propel\Runtime\Exception\PropelException + * @since 1.2.3 + */ + public function update($currentVersion, $newVersion, ConnectionInterface $con = null): void + { + $finder = (new Finder())->files()->name('#.*?\.sql#')->sortByName()->in(self::UPDATE_PATH); + + if ($finder->count() === 0) { + return; + } + + $database = new Database($con); + + /** @var \Symfony\Component\Finder\SplFileInfo $updateSQLFile */ + foreach ($finder as $updateSQLFile) { + if (version_compare($currentVersion, str_replace('.sql', '', $updateSQLFile->getFilename()), '<')) { + $database->insertSql(null, [$updateSQLFile->getPathname()]); + } + } + + /* + * Fix for urls that redirect on itself + */ + $urls = RewritingUrlQuery::create() + ->where(RewritingUrlTableMap::ID . " = " . RewritingUrlTableMap::REDIRECTED) + ->find(); + + /** @var RewritingUrl $url */ + foreach ($urls as $url) { + $parent = RewritingUrlQuery::create() + ->filterByView($url->getView()) + ->filterByViewId($url->getViewId()) + ->filterByViewLocale($url->getViewLocale()) + ->filterByRedirected(null) + ->findOne(); + + $url->setRedirected(($parent === null) ? null : $parent->getId())->save(); + } + } + + /** + * @return array|null + */ + public static function getUnknownSources() + { + if (static::$unknownSources === null) { + static::$unknownSources = []; + if (null !== $config = ConfigQuery::read('obsolete_rewriten_url_view', null)) { + static::$unknownSources[] = $config; + } + } + return static::$unknownSources; + } + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode() . '\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()) . '/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/RewriteUrl/Service/RewritingRouterFirst.php b/domokits/local/modules/RewriteUrl/Service/RewritingRouterFirst.php new file mode 100644 index 0000000..d03c1d4 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Service/RewritingRouterFirst.php @@ -0,0 +1,150 @@ +getRealPathInfo() : $request->getPathInfo(); + + // Check RewriteUrl text rules + $textRule = RewriteurlRuleQuery::create() + ->filterByOnly404(0) + ->filterByValue(ltrim($pathInfo, '/')) + ->filterByRuleType('text') + ->orderByPosition() + ->findOne(); + + if ($textRule) { + $this->redirect($urlTool->absoluteUrl($textRule->getRedirectUrl()), 301); + } + + // Check RewriteUrl rules + $ruleCollection = RewriteurlRuleQuery::create() + ->filterByOnly404(0) + ->orderByPosition() + ->find(); + + /** @var RewriteurlRule $rule */ + foreach ($ruleCollection as $rule) { + if ($rule->isMatching($pathInfo, $request->query->all())) { + $this->redirect($urlTool->absoluteUrl($rule->getRedirectUrl()), 301); + } + } + + try { + $rewrittenUrlData = $urlTool->resolve($pathInfo); + } catch (UrlRewritingException $e) { + switch ($e->getCode()) { + case UrlRewritingException::URL_NOT_FOUND: + throw new ResourceNotFoundException(); + break; + default: + throw $e; + } + } + + // If we have a "lang" parameter, whe have to check if the found URL has the proper locale + // If it's not the case, find the rewritten URL with the requested locale, and redirect to it. + if (null == !$requestedLocale = $request->get('lang')) { + if (null !== $requestedLang = LangQuery::create()->findOneByLocale($requestedLocale)) { + if ($requestedLang->getLocale() != $rewrittenUrlData->locale) { + $localizedUrl = $urlTool->retrieve( + $rewrittenUrlData->view, + $rewrittenUrlData->viewId, + $requestedLang->getLocale() + )->toString(); + + $this->redirect($urlTool->absoluteUrl($localizedUrl), 301); + } + } + } + + /* is the URL redirected ? */ + if (null !== $rewrittenUrlData->redirectedToUrl) { + $redirect = RewritingUrlQuery::create() + ->filterByView($rewrittenUrlData->view) + ->filterByViewId($rewrittenUrlData->viewId) + ->filterByViewLocale($rewrittenUrlData->locale) + ->filterByRedirected(null, Criteria::ISNULL) + ->findOne(); + + // Differences with the base class for handling 301 or 302 redirection + $redirectType = $this->fetchRewritingRedirectTypeFromUrl($rewrittenUrlData->rewrittenUrl); + + if ($redirectType == null) { + $httpRedirectCode = RewritingRedirectType::DEFAULT_REDIRECT_TYPE; + } else { + $httpRedirectCode = $redirectType->getHttpcode(); + } + // End of differences + + $this->redirect($urlTool->absoluteUrl($redirect->getUrl()), $httpRedirectCode); + } + + /* define GET arguments in request */ + + if (null !== $rewrittenUrlData->view) { + $request->attributes->set('_view', $rewrittenUrlData->view); + if (null !== $rewrittenUrlData->viewId) { + $request->query->set($rewrittenUrlData->view . '_id', $rewrittenUrlData->viewId); + } + } + + if (null !== $rewrittenUrlData->locale) { + $this->manageLocale($rewrittenUrlData, $request); + } + + + foreach ($rewrittenUrlData->otherParameters as $parameter => $value) { + $request->query->set($parameter, $value); + } + + return array( + '_controller' => 'Thelia\\Controller\\Front\\DefaultController::noAction', + '_route' => 'rewrite', + '_rewritten' => true, + ); + } + throw new ResourceNotFoundException(); + } + + /** + * @param $url + * @return RewritingRedirectType + */ + public function fetchRewritingRedirectTypeFromUrl($url) + { + return RewritingRedirectTypeQuery::create() + ->joinRewritingUrl() + ->useRewritingUrlQuery() + ->filterByUrl($url) + ->endUse() + ->findOne(); + } +} diff --git a/domokits/local/modules/RewriteUrl/Service/RewritingRouterLast.php b/domokits/local/modules/RewriteUrl/Service/RewritingRouterLast.php new file mode 100644 index 0000000..a273553 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/Service/RewritingRouterLast.php @@ -0,0 +1,54 @@ +getRealPathInfo() : $request->getPathInfo(); + + // Check RewriteUrl text rules + $textRule = RewriteurlRuleQuery::create() + ->filterByOnly404(1) + ->filterByValue(ltrim($pathInfo, '/')) + ->filterByRuleType('text') + ->orderByPosition() + ->findOne(); + + if ($textRule) { + $this->redirect($urlTool->absoluteUrl($textRule->getRedirectUrl()), 301); + } + + $ruleCollection = RewriteurlRuleQuery::create() + ->filterByOnly404(1) + ->orderByPosition() + ->find(); + + /** @var RewriteurlRule $rule */ + foreach ($ruleCollection as $rule) { + if ($rule->isMatching($pathInfo, $request->query->all())) { + $this->redirect($urlTool->absoluteUrl($rule->getRedirectUrl()), 301); + } + } + } + throw new ResourceNotFoundException(); + } +} diff --git a/domokits/local/modules/RewriteUrl/composer.json b/domokits/local/modules/RewriteUrl/composer.json new file mode 100755 index 0000000..304d74b --- /dev/null +++ b/domokits/local/modules/RewriteUrl/composer.json @@ -0,0 +1,25 @@ +{ + "name": "thelia/rewrite-url-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "RewriteUrl" + }, + "authors": [ + { + "name": "Vincent Lopes", + "email": "vlopes@openstudio.fr", + "homepage": "http://thelia.net", + "role": "Developer" + }, + { + "name": "Gilles Bourgeat", + "email": "gbourgeat@openstudio.fr", + "homepage": "http://thelia.net", + "role": "Developer" + } + ] +} \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/logo.jpg b/domokits/local/modules/RewriteUrl/logo.jpg new file mode 100644 index 0000000..74847c0 Binary files /dev/null and b/domokits/local/modules/RewriteUrl/logo.jpg differ diff --git a/domokits/local/modules/RewriteUrl/screenshot/screenshot-1.jpeg b/domokits/local/modules/RewriteUrl/screenshot/screenshot-1.jpeg new file mode 100644 index 0000000..544098d Binary files /dev/null and b/domokits/local/modules/RewriteUrl/screenshot/screenshot-1.jpeg differ diff --git a/domokits/local/modules/RewriteUrl/screenshot/screenshot-2.jpeg b/domokits/local/modules/RewriteUrl/screenshot/screenshot-2.jpeg new file mode 100644 index 0000000..1810e07 Binary files /dev/null and b/domokits/local/modules/RewriteUrl/screenshot/screenshot-2.jpeg differ diff --git a/domokits/local/modules/RewriteUrl/screenshot/screenshot-3.png b/domokits/local/modules/RewriteUrl/screenshot/screenshot-3.png new file mode 100644 index 0000000..4619f59 Binary files /dev/null and b/domokits/local/modules/RewriteUrl/screenshot/screenshot-3.png differ diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration-js.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration-js.html new file mode 100644 index 0000000..4edec76 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration-js.html @@ -0,0 +1,425 @@ +{default_translation_domain domain='googleshoppingxml.bo.default'} + +{javascripts file="assets/js/bootstrap-switch/bootstrap-switch.js"} + +{/javascripts} + +{javascripts file='assets/js/bootstrap-editable/bootstrap-editable.js'} + +{/javascripts} + + + + + + diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration.html new file mode 100644 index 0000000..34e54a6 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/module-configuration.html @@ -0,0 +1,140 @@ +{$d='rewriteurl.bo.default'} +
+
+
+
+
+ {intl d=$d l="Global URL Rewriting"} +
+
+ +
+
+
+
+
+ +
+ +
+
+ {intl d=$d l="Set the config variable 'rewriting_enable'"} +
+
+ +
+
+

+ {intl d=$d l="Rule management"} +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ / + + / +
+
+ / + + +
+
+
+ + + + + + +

{intl d=$d l="Currently enabled rules"}


+ + + + + + + + + + + + +
+
+
+
+
+
+
+ + +
+ +{include file="RewriteUrl/module-configuration-js.html"} + diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module-js.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module-js.html new file mode 100755 index 0000000..a9404ff --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module-js.html @@ -0,0 +1,188 @@ + \ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module.html new file mode 100755 index 0000000..b88b92a --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-module.html @@ -0,0 +1,327 @@ +{* +/*************************************************************************************/ +/* This file is part of the RewriteUrl module for Thelia. */ +/* */ +/* Copyright (c) OpenStudio */ +/* email : dev@thelia.net */ +/* web : http://www.thelia.net */ +/* */ +/* For the full copyright and license information, please view the LICENSE.txt */ +/* file that was distributed with this source code. */ +/*************************************************************************************/ +*} + +
+
+
+
+ + + + {ifloop rel="rewriteUrl"} + + + + + + + + + + + {loop type="rewrite_url" name="rewriteUrl" view_id="{$ID}" view="{$viewName}" redirect="0"} + + + + + + + {/loop} + + {/ifloop} +
+ {intl l='Default url' d="rewriteurl.bo.default"} + +
#{intl l='View locale' d="rewriteurl.bo.default"}{intl l='Url' d="rewriteurl.bo.default"}{intl l='Actions' d="rewriteurl.bo.default"}
+ {$ID_URL} + + {$code = "_"|explode:$VIEW_LOCALE} + {$VIEW_LOCALE} + + {$URL} + + + +
+
+
+ + + + {ifloop rel="rewriteUrl"} + + + + + + + + + + + + + {loop type="rewrite_url" name="rewriteUrl" view_id="{$ID}" view="{$viewName}" redirect="1"} + + + + + + + + + {/loop} + + {/ifloop} + {elseloop rel="rewriteUrl"} + + + + + + {/elseloop} +
+ {intl l='Url redirected' d="rewriteurl.bo.default"} +
#{intl l='View locale' d="rewriteurl.bo.default"}{intl l='Url' d="rewriteurl.bo.default"}{intl l='Redirected' d="rewriteurl.bo.default"}{intl l='Type' d="rewriteurl.bo.default"}{intl l='Actions' d="rewriteurl.bo.default"}
+ {$ID_URL} + + {$code = "_"|explode:$VIEW_LOCALE} + {$VIEW_LOCALE} + + {$URL} + + {$REDIRECTED} + + + +
+ + + +
+
+
+ {intl l="No redirected url." d="rewriteurl.bo.default"} +
+
+
+ {intl l="New url" d="rewriteurl.bo.default"} +
+ {form name="rewriteurl.add.form"} +
+ + {form_hidden_fields form=$form} + + {if $form_error} +
{$form_error_message nofilter}
+ {/if} + +
+ + {form_field form=$form field='success_url'} + + {/form_field} + + {form_field form=$form field="view"} + + {/form_field} + + {form_field form=$form field="view-id"} + + {/form_field} + +
+
+
+ {form_field form=$form field="locale"} + + {/form_field} +
+
+
+
+ {form_field form=$form field="url"} +
+ {$url_language|default:{config key="url_site"}}/ + +
+ {/form_field} +
+
+
+
+ {form_field form=$form field="default"} + + {/form_field} +
+
+
+
+ {form_field form=$form field="httpcode"} + + {/form_field} +
+
+
+ +
+
+
+ {/form} +
+
+ + {loop type="rewrite_url" name="rewriteUrl" view_id=$ID view=$viewName limit=1} +
+

+ {intl l='Redirect all urls' d="rewriteurl.bo.default"} +

+
+
+
+ {intl l="Redirect all urls on a (category, product, folder, content, brand)." d="rewriteurl.bo.default"} +
+ {intl l="This action is irreversible after confirmation." d="rewriteurl.bo.default"} +
+ +
+
+
+ {/loop} +
+ + + + {* -- Delete RewriteUrl confirmation dialog ----------------------------------- *} + + {capture "rewrite_delete_dialog"} + + {/capture} + + {include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "rewrite_delete_dialog" + dialog_title = {intl l="Delete Url" d="rewriteurl.bo.default"} + dialog_message = {intl l="Do you really want to delete the url %html ?" d="rewriteurl.bo.default" html=""} + + form_action = {url path='/admin/module/rewriteurl/delete'} + form_content = {$smarty.capture.rewrite_delete_dialog nofilter} + } + + + {* -- Default RewriteUrl confirmation dialog ----------------------------------- *} + + {capture "rewrite_default_dialog"} + + {/capture} + + {include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "rewrite_default_dialog" + dialog_title = {intl l="Default Url" d="rewriteurl.bo.default"} + dialog_message = {intl l="Do you really want to set the url %html as default ?" d="rewriteurl.bo.default" html=""} + + form_action = {url path='/admin/module/rewriteurl/setdefault'} + form_content = {$smarty.capture.rewrite_default_dialog nofilter} + } + +
\ No newline at end of file diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-value-render.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-value-render.html new file mode 100644 index 0000000..dcce3e7 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/RewriteUrl/tab-value-render.html @@ -0,0 +1,44 @@ +
+ / + + / +
+ +
+ / + + +
+ +
+ {foreach from=$REWRITE_URL_PARAMS item=ruleParam} +
+
+
+ +
+ +
+ +
+ +
+ +
+ + + + + + +
+
+ {/foreach} +
diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/configuration-catalog.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/configuration-catalog.html new file mode 100644 index 0000000..164b010 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/configuration-catalog.html @@ -0,0 +1,6 @@ +{loop type="auth" name="can_change" role="ADMIN" module="RewriteUrl" access="VIEW"} + + {intl l='Not rewriten urls' d="rewriteurl.bo.default"} + + +{/loop} diff --git a/domokits/local/modules/RewriteUrl/templates/backOffice/default/list-notrewritenurls.html b/domokits/local/modules/RewriteUrl/templates/backOffice/default/list-notrewritenurls.html new file mode 100644 index 0000000..2029b34 --- /dev/null +++ b/domokits/local/modules/RewriteUrl/templates/backOffice/default/list-notrewritenurls.html @@ -0,0 +1,425 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} + {$admin_current_location = 'configuration'} +{/block} + +{block name="page-title"}{intl l='List of not rewriten urls' d="rewriteurl.bo.default"}{/block} + +{block name="main-content"} +
+
+ + + +
+
+
+
+ {intl l="List of not rewriten urls" d="rewriteurl.bo.default"} +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+ {ifloop rel="list-notrewritenurls-category"} +
+ + + + + + + + + + + + {loop type="list-notrewritenurls" name="list-notrewritenurls-category" page=$page_category backend_context="on" lang=$edit_language_id view="category" limit="10"} + + + + + + {/loop} + + + + + + +
+ {intl l='Categories' d="rewriteurl.bo.default"} +
+ {intl l='ID' d="rewriteurl.bo.default"} + + {intl l='Name' d="rewriteurl.bo.default"} + + {intl l='Actions' d="rewriteurl.bo.default"} +
+ {$ID} + + {$NAME} + + + + +
+ {include + file = "includes/pagination.html" + + loop_ref = "list-notrewritenurls-category" + max_page_count = 10 + page_param_name = "page_category" + page_url = {url path="/admin/list-notrewritenurls" current_tab='categories'} + } + +
+
+ {/ifloop} + {elseloop rel="list-notrewritenurls-category"} +
+
+ {intl l="All your categories have rewriten urls" d="rewriteurl.bo.default"} +
+ {/elseloop} +
+
+ {ifloop rel="list-notrewritenurls-product"} +
+ + + + + + + + + + + + + {loop type="list-notrewritenurls" name="list-notrewritenurls-product" lang=$edit_language_id view='product' page=$page_product limit="10" backend_context="on"} + + + + + + + {/loop} + + + + + + +
+ {intl l='Products' d="rewriteurl.bo.default"} +
+ {intl l='ID' d="rewriteurl.bo.default"} + + {intl l='Name' d="rewriteurl.bo.default"} + + {intl l='Reference' d="rewriteurl.bo.default"} + + {intl l='Actions' d="rewriteurl.bo.default"} +
+ {$ID} + + {$NAME} + + {$REF} + + + + +
+ {include + file = "includes/pagination.html" + + loop_ref = "list-notrewritenurls-product" + max_page_count = 10 + page_param_name = "page_product" + page_url = {url path="/admin/list-notrewritenurls" current_tab='products'} + } + +
+
+ {/ifloop} + {elseloop rel="list-notrewritenurls-product"} +
+
+ {intl l="All your products have rewriten urls" d="rewriteurl.bo.default"} +
+ {/elseloop} +
+
+ {ifloop rel="list-notrewritenurls-brand"} +
+ + + + + + + + + + + + {loop type="list-notrewritenurls" name="list-notrewritenurls-brand" lang=$edit_language_id view='brand' page=$page_brand limit="10" backend_context="on"} + + + + + + {/loop} + + + + + + +
+ {intl l='Brands' d="rewriteurl.bo.default"} +
+ {intl l='ID' d="rewriteurl.bo.default"} + + {intl l='Name' d="rewriteurl.bo.default"} + + {intl l='Actions' d="rewriteurl.bo.default"} +
+ {$ID} + + {$NAME} + + + + +
+ {include + file = "includes/pagination.html" + + loop_ref = "list-notrewritenurls-brand" + max_page_count = 10 + page_param_name = "page_brand" + page_url = {url path="/admin/list-notrewritenurls" current_tab='brands'} + } +
+
+ {/ifloop} + {elseloop rel="list-notrewritenurls-brand"} +
+
+ {intl l="All your brands have rewriten urls" d="rewriteurl.bo.default"} +
+ {/elseloop} +
+
+ {ifloop rel="list-notrewritenurls-folder"} +
+ + + + + + + + + + + + {loop type="list-notrewritenurls" name="list-notrewritenurls-folder" lang=$edit_language_id view='folder' page=$page_folder limit="10" backend_context="on"} + + + + + + {/loop} + + + + + + +
+ {intl l='Folders' d="rewriteurl.bo.default"} +
+ {intl l='ID' d="rewriteurl.bo.default"} + + {intl l='Name' d="rewriteurl.bo.default"} + + {intl l='Actions' d="rewriteurl.bo.default"} +
+ {$ID} + + {$NAME} + + + + +
+ {include + file = "includes/pagination.html" + + loop_ref = "list-notrewritenurls-folder" + max_page_count = 10 + page_param_name = "page_folder" + page_url = {url path="/admin/list-notrewritenurls" current_tab='folders'} + } +
+
+ {/ifloop} + {elseloop rel="list-notrewritenurls-folder"} +
+
+ {intl l="All your folders have rewriten urls" d="rewriteurl.bo.default"} +
+ {/elseloop} +
+
+ {ifloop rel="list-notrewritenurls-content"} +
+ + + + + + + + + + + + {loop type="list-notrewritenurls" name="list-notrewritenurls-content" lang=$edit_language_id view='content' page=$page_content limit="10" backend_context="on"} + + + + + + {/loop} + + + + + + +
+ {intl l='Contents' d="rewriteurl.bo.default"} +
+ {intl l='ID' d="rewriteurl.bo.default"} + + {intl l='Name' d="rewriteurl.bo.default"} + + {intl l="Actions" d="rewriteurl.bo.default"} +
+ {$ID} + + {$NAME} + + + + +
+ {include + file = "includes/pagination.html" + + loop_ref = "list-notrewritenurls-content" + max_page_count = 10 + page_param_name = "page_content" + page_url = {url path="/admin/list-notrewritenurls" current_tab='contents'} + } +
+
+ {/ifloop} + {elseloop rel="list-notrewritenurls-content"} +
+
+ {intl l="All your contents have rewriten urls" d="rewriteurl.bo.default"} +
+ {/elseloop} +
+
+
+
+
+
+
+
+{/block} + +{block name="javascript-initialization"} + +{/block} \ No newline at end of file diff --git a/domokits/local/modules/ShortCode/Config/TheliaMain.sql b/domokits/local/modules/ShortCode/Config/TheliaMain.sql new file mode 100644 index 0000000..37f0982 --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/TheliaMain.sql @@ -0,0 +1,25 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- short_code +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `short_code`; + +CREATE TABLE `short_code` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `tag` VARCHAR(55) NOT NULL, + `event` VARCHAR(255) NOT NULL, + `active` TINYINT, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + UNIQUE INDEX `short_code_U_1` (`tag`) +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/ShortCode/Config/config.xml b/domokits/local/modules/ShortCode/Config/config.xml new file mode 100644 index 0000000..8f50daf --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/config.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/ShortCode/Config/module.xml b/domokits/local/modules/ShortCode/Config/module.xml new file mode 100644 index 0000000..a75ed8d --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/module.xml @@ -0,0 +1,43 @@ + + + ShortCode\ShortCode + + Add short codes Wordpress'ShortCode syntax + + + + Ajoute les short codes avec la syntax wordpress + + + + + en_US + fr_FR + + 2.0.0 + + + Vincent Lopes-Vicente + vlopes@openstudio.fr + + + classic + + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/ShortCode/Config/routing.xml b/domokits/local/modules/ShortCode/Config/routing.xml new file mode 100644 index 0000000..480ce41 --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/domokits/local/modules/ShortCode/Config/schema.xml b/domokits/local/modules/ShortCode/Config/schema.xml new file mode 100644 index 0000000..ddd0c15 --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/schema.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + +
+ +
diff --git a/domokits/local/modules/ShortCode/Config/sqldb.map b/domokits/local/modules/ShortCode/Config/sqldb.map new file mode 100644 index 0000000..63a93ba --- /dev/null +++ b/domokits/local/modules/ShortCode/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +thelia.sql=thelia diff --git a/domokits/local/modules/ShortCode/Event/ShortCodeEvent.php b/domokits/local/modules/ShortCode/Event/ShortCodeEvent.php new file mode 100644 index 0000000..d3fb3ff --- /dev/null +++ b/domokits/local/modules/ShortCode/Event/ShortCodeEvent.php @@ -0,0 +1,60 @@ +content = $content; + $this->attributes = $attributes; + $this->result = $content; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @return string + */ + public function getResult() + { + return $this->result; + } + + /** + * @param string $result + */ + public function setResult($result) + { + $this->result = $result; + } +} \ No newline at end of file diff --git a/domokits/local/modules/ShortCode/EventListener/ResponseListener.php b/domokits/local/modules/ShortCode/EventListener/ResponseListener.php new file mode 100644 index 0000000..502aaf4 --- /dev/null +++ b/domokits/local/modules/ShortCode/EventListener/ResponseListener.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ShortCode\EventListener; + +use Maiorano\Shortcodes\Library\SimpleShortcode; +use Maiorano\Shortcodes\Manager\ShortcodeManager; +use ShortCode\Event\ShortCodeEvent; +use ShortCode\Model\ShortCode; +use ShortCode\Model\ShortCodeQuery; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\ResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Thelia\Log\Tlog; + +class ResponseListener implements EventSubscriberInterface +{ + /** @var EventDispatcherInterface */ + protected $eventDispatcher; + + public function __construct( + EventDispatcherInterface $eventDispatcher + ) { + $this->eventDispatcher = $eventDispatcher; + } + + public static function getSubscribedEvents() + { + return [ + KernelEvents::RESPONSE => [['dispatchShortCodeEvents', 64]], + ]; + } + + public function dispatchShortCodeEvents(ResponseEvent $event): void + { + if ($event->getRequest()->get('disable_shortcode', 0) == 1) { + return; + } + + $response = $event->getResponse(); + + if ( + $response instanceof BinaryFileResponse + || $response instanceof StreamedResponse + || $response instanceof RedirectResponse + || $response instanceof JsonResponse + ) { + return; + } + + $dispatcher = $this->eventDispatcher; + + $simpleShortCodes = []; + + $shortCodes = ShortCodeQuery::create() + ->filterByActive(1) + ->find(); + + /** @var ShortCode $shortCode */ + foreach ($shortCodes as $shortCode) { + $simpleShortCodes[$shortCode->getTag()] = new SimpleShortcode($shortCode->getTag(), null, function ($content, $attributes) use ($shortCode, $dispatcher) { + $shortCodeEvent = new ShortCodeEvent($content, $attributes); + $dispatcher->dispatch($shortCodeEvent, $shortCode->getEvent()); + + return $shortCodeEvent->getResult(); + }); + } + + $manager = new ShortcodeManager($simpleShortCodes); + + $content = $response->getContent(); + + try { + $content = $manager->doShortCode($content, null, true); + } catch (\Exception $exception) { + Tlog::getInstance()->error($exception->getMessage()); + } + + $response->setContent($content); + } +} diff --git a/domokits/local/modules/ShortCode/I18n/en_US.php b/domokits/local/modules/ShortCode/I18n/en_US.php new file mode 100755 index 0000000..0b4fa14 --- /dev/null +++ b/domokits/local/modules/ShortCode/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/domokits/local/modules/ShortCode/I18n/fr_FR.php b/domokits/local/modules/ShortCode/I18n/fr_FR.php new file mode 100755 index 0000000..3708624 --- /dev/null +++ b/domokits/local/modules/ShortCode/I18n/fr_FR.php @@ -0,0 +1,4 @@ + 'La traduction française de la chaine', +); diff --git a/domokits/local/modules/ShortCode/LICENCE b/domokits/local/modules/ShortCode/LICENCE new file mode 100644 index 0000000..3312f1f --- /dev/null +++ b/domokits/local/modules/ShortCode/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 + +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. \ No newline at end of file diff --git a/domokits/local/modules/ShortCode/Model/ShortCode.php b/domokits/local/modules/ShortCode/Model/ShortCode.php new file mode 100644 index 0000000..4541fe4 --- /dev/null +++ b/domokits/local/modules/ShortCode/Model/ShortCode.php @@ -0,0 +1,10 @@ +/local/modules/``` directory and be sure that the name of the module is ShortCode. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/shortcode-module:~1.0 +``` + +## Usage + +This module https://github.com/thelia-modules/ShortCodeMeta is a good example of how to use ShortCode. + +### 1. Register your short codes + You can do this at the post activation of your module. The best way to do this is to use the module method : `ShortCode::createNewShortCodeIfNotExist` + the first parameter is the name you will use to call the short code in your templates (like this `[my_shortcode_name], second parameter is the event dispatched when the short code is detected. + +### 2. Add short codes to your templates + The short codes syntax is as follows: + ``` + [shortcode] - No content, no attributes + [shortcode]My Content[/shortcode] - Short code with content + [shortcode attribute=value foo=bar] - Short code with attributes + [shortcode attribute=value foo=bar]My Content[/shortcode] - Short code with content and attributes + ``` + +### 3. Listen events associated to your short codes + When a short code is detected in response a `ShortCodeEvent` is dispatched with the event name given at the creation. + So if you want replace your short code by something you have to listen this event. + In this event you have 3 properties : + - `content` (string) The content between your tags it will be `My Content` for the example above. + - `attributes` (array) The array of all attributes passed to your short code `['attribute'=>'value', 'foo'=>'bar']` for the example above. + - `result` (string) Your short code will be replaced by this value in response (equal to content by default) \ No newline at end of file diff --git a/domokits/local/modules/ShortCode/ShortCode.php b/domokits/local/modules/ShortCode/ShortCode.php new file mode 100644 index 0000000..029d9f0 --- /dev/null +++ b/domokits/local/modules/ShortCode/ShortCode.php @@ -0,0 +1,89 @@ +getConfigValue('is_initialized', false)) { + $database = new Database($con); + + $database->insertSql(null, array(__DIR__ . '/Config/TheliaMain.sql')); + + $this->setConfigValue('is_initialized', true); + } + + return true; + } + + /** + * Create a new ShortCode + * @param string $shortCodeName the name for call the ShortCode in template + * @param string $eventName the name of the event dispatched when shortcode is in template + * @throws PropelException + */ + public static function createNewShortCodeIfNotExist($shortCodeName, $eventName) + { + if (null === ShortCodeQuery::create()->findOneByTag($shortCodeName)) { + $shortCode = new \ShortCode\Model\ShortCode(); + $shortCode->setTag($shortCodeName) + ->setEvent($eventName) + ->setActive(1) + ->save(); + } + } + + /** + * Active a ShortCode by his name + * @param string $shortCodeName the name for call the ShortCode in template + * @throws PropelException + */ + public static function activateShortCode($shortCodeName) + { + $shortCode = ShortCodeQuery::create() + ->filterByTag($shortCodeName) + ->findOne(); + + if (null !== $shortCode) { + $shortCode->setActive(1) + ->save(); + } + } + + /** + * Deactive a ShortCode by his name + * @param string $shortCodeName the name for call the ShortCode in template + * @throws PropelException + */ + public static function deactivateShortCode($shortCodeName) + { + $shortCode = ShortCodeQuery::create() + ->filterByTag($shortCodeName) + ->findOne(); + + if (null !== $shortCode) { + $shortCode->setActive(0) + ->save(); + } + } +} diff --git a/domokits/local/modules/ShortCode/composer.json b/domokits/local/modules/ShortCode/composer.json new file mode 100644 index 0000000..9f3e354 --- /dev/null +++ b/domokits/local/modules/ShortCode/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/short-code-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "maiorano84/shortcodes": "v2.0.0-beta" + }, + "extra": { + "installer-name": "ShortCode" + } +} \ No newline at end of file diff --git a/domokits/local/modules/ShortCodeMeta/Config/config.xml b/domokits/local/modules/ShortCodeMeta/Config/config.xml new file mode 100644 index 0000000..69c89e8 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/Config/config.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/ShortCodeMeta/Config/module.xml b/domokits/local/modules/ShortCodeMeta/Config/module.xml new file mode 100644 index 0000000..60ca399 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/Config/module.xml @@ -0,0 +1,40 @@ + + + ShortCodeMeta\ShortCodeMeta + + Add meta to header by short code + + + + Ajoute des méta-données au header par les short codes + + + + + en_US + fr_FR + + 2.0.0 + + + Vincent Lopes-Vicente + vlopes@openstudio.fr + + + classic + + + ShortCode + + + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/ShortCodeMeta/Config/routing.xml b/domokits/local/modules/ShortCodeMeta/Config/routing.xml new file mode 100644 index 0000000..f1bc5a9 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/domokits/local/modules/ShortCodeMeta/Config/schema.xml b/domokits/local/modules/ShortCodeMeta/Config/schema.xml new file mode 100644 index 0000000..99ef826 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/Config/schema.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/domokits/local/modules/ShortCodeMeta/EventListener/ShortCodeListener.php b/domokits/local/modules/ShortCodeMeta/EventListener/ShortCodeListener.php new file mode 100644 index 0000000..524fb69 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/EventListener/ShortCodeListener.php @@ -0,0 +1,45 @@ + [['checkEmptyPage', 128]], + ShortCodeMeta::PAGINATION_META_SHORT_CODE => [['addPaginationMeta', 128]], + ]; + } + + public function addPaginationMeta(ShortCodeEvent $event) + { + $attributes = $event->getAttributes(); + + if (!isset($attributes['rel'])) { + return; + } + + $rel = $attributes['rel']; + + $staticVariableName = strtoupper($attributes['rel']).'_PAGE_URL'; + + if (null !== $url = ShortCodeMeta::$$staticVariableName) { + $event->setResult(''); + } + } + + public function checkEmptyPage(ShortCodeEvent $event) + { + if (ShortCodeMeta::$IS_EMPTY_PAGE === true) { + $event->setResult(''); + } + } + +} \ No newline at end of file diff --git a/domokits/local/modules/ShortCodeMeta/I18n/en_US.php b/domokits/local/modules/ShortCodeMeta/I18n/en_US.php new file mode 100755 index 0000000..0b4fa14 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/I18n/en_US.php @@ -0,0 +1,4 @@ + 'The displayed english string', +); diff --git a/domokits/local/modules/ShortCodeMeta/I18n/fr_FR.php b/domokits/local/modules/ShortCodeMeta/I18n/fr_FR.php new file mode 100755 index 0000000..3708624 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/I18n/fr_FR.php @@ -0,0 +1,4 @@ + 'La traduction française de la chaine', +); diff --git a/domokits/local/modules/ShortCodeMeta/LICENSE b/domokits/local/modules/ShortCodeMeta/LICENSE new file mode 100644 index 0000000..2152256 --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 OpenStudio + +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/domokits/local/modules/ShortCodeMeta/Readme.md b/domokits/local/modules/ShortCodeMeta/Readme.md new file mode 100644 index 0000000..d5df0ea --- /dev/null +++ b/domokits/local/modules/ShortCodeMeta/Readme.md @@ -0,0 +1,54 @@ +# Short Code Meta + +ShortCodeMeta allow you to add meat in head for +- Empty page (`noindex, nofollow`) +- Pagination link + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is ShortCodeMeta. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/short-code-meta-module:~1.0 +``` + +## Usage + +This module use ShortCode (https://github.com/thelia-modules/ShortCode) to add metas in head after smarty has completly build the page. +The short codes are automatically added in templates with the hook `main.head-bottom` so be sure you have this hook in your template layout. + +### Empty pages + +To add a meta `noindex, nofollow` to an empty page you have to inform the module with the smarty tag `{set_empty_page_meta}` +All the page where this tag is present will have a noindex meta. +For example if you use a loop product in your category page you can add this tag in your elseloop like this : +``` + {ifloop rel="product_list"} +
+ {loop type="product" name="product_list" category=$category_id} +

{$TITLE} + {/loop} +

+ {/ifloop} + {elseloop rel="product_list"} + {set_empty_page_meta} + {/elseloop} +``` +With this, all categories page without products will not be indexed by robots. + + +### Pagination meta link + +To add a ` + + + + + + + + + diff --git a/domokits/local/modules/SmartyRedirection/Config/module.xml b/domokits/local/modules/SmartyRedirection/Config/module.xml new file mode 100644 index 0000000..0fd9df3 --- /dev/null +++ b/domokits/local/modules/SmartyRedirection/Config/module.xml @@ -0,0 +1,26 @@ + + + SmartyRedirection\SmartyRedirection + + Redirection function for templates + + + Fonction de redirection pour les templates + + + en_US + fr_FR + + 2.0.0 + + + Benjamin Perche + bperche9@gmail.com + + + classic + 2.5.0 + prod + diff --git a/domokits/local/modules/SmartyRedirection/LICENSE.txt b/domokits/local/modules/SmartyRedirection/LICENSE.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/domokits/local/modules/SmartyRedirection/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/domokits/local/modules/SmartyRedirection/Readme.md b/domokits/local/modules/SmartyRedirection/Readme.md new file mode 100644 index 0000000..ca6b070 --- /dev/null +++ b/domokits/local/modules/SmartyRedirection/Readme.md @@ -0,0 +1,39 @@ +# Smarty Redirection + +This module adds a smarty function to redirect the user directly from a template + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is SmartyRedirection. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/smarty-redirection-module ~1.0.0 +``` + +## Usage + +You can use the ```{redirect }``` function like ```{url }```. +Only one parameter is specific to this function: ```status```. + +If this parameter isn't given, its value is 302. Otherwise, you can set it to 301 to define a permanent redirection in a template. + +## Example + +```smarty +{if ! $foo} + {redirect path="/anywhere"} +{/if} +``` + +```smarty +{if ! $foo} + {redirect path="/anywhere" status=301} +{/if} +``` diff --git a/domokits/local/modules/SmartyRedirection/Smarty/Plugins/Redirect.php b/domokits/local/modules/SmartyRedirection/Smarty/Plugins/Redirect.php new file mode 100644 index 0000000..36e286e --- /dev/null +++ b/domokits/local/modules/SmartyRedirection/Smarty/Plugins/Redirect.php @@ -0,0 +1,62 @@ + + */ +class Redirect extends AbstractSmartyPlugin +{ + const DEFAULT_REDIRECTION_STATUS = 302; + /** + * @var UrlGenerator + */ + protected $urlGeneratorPlugin; + + public function __construct(UrlGenerator $urlGenerator) + { + $this->urlGeneratorPlugin = $urlGenerator; + } + + /** + * @param $params + */ + public function throwRedirect($params) + { + $status = static::DEFAULT_REDIRECTION_STATUS; + + if (isset($params["status"])) { + $status = (int) $params["status"]; + unset ($params["status"]); + } + + throw new RedirectException($this->urlGeneratorPlugin->generateUrlFunction($params, $foo), $status); + } + + /** + * @return array of SmartyPluginDescriptor + */ + public function getPluginDescriptors() + { + return array( + new SmartyPluginDescriptor("function", "redirect", $this, "throwRedirect"), + ); + } +} diff --git a/domokits/local/modules/SmartyRedirection/SmartyRedirection.php b/domokits/local/modules/SmartyRedirection/SmartyRedirection.php new file mode 100644 index 0000000..23218ca --- /dev/null +++ b/domokits/local/modules/SmartyRedirection/SmartyRedirection.php @@ -0,0 +1,21 @@ + + + + + + + + + + + +
+ + + diff --git a/domokits/local/modules/StoreSeo/Config/module.xml b/domokits/local/modules/StoreSeo/Config/module.xml new file mode 100755 index 0000000..10fa3c4 --- /dev/null +++ b/domokits/local/modules/StoreSeo/Config/module.xml @@ -0,0 +1,26 @@ + + + StoreSeo\StoreSeo + + Manage translations for your store main SEO meta + + + Gère les traductions des principales metas SEO de votre boutique + + + en_US + fr_FR + + 2.0.1 + + + Etienne Perriere + eperriere@openstudio.fr + + + classic + 2.5.0 + other + diff --git a/domokits/local/modules/StoreSeo/Config/routing.xml b/domokits/local/modules/StoreSeo/Config/routing.xml new file mode 100755 index 0000000..b2ce795 --- /dev/null +++ b/domokits/local/modules/StoreSeo/Config/routing.xml @@ -0,0 +1,6 @@ + + + + diff --git a/domokits/local/modules/StoreSeo/Controller/StoreSeoConfigController.php b/domokits/local/modules/StoreSeo/Controller/StoreSeoConfigController.php new file mode 100755 index 0000000..990414f --- /dev/null +++ b/domokits/local/modules/StoreSeo/Controller/StoreSeoConfigController.php @@ -0,0 +1,97 @@ + + */ +class StoreSeoConfigController extends BaseAdminController +{ + /** + * @Route("/admin/module/StoreSeo", name="storeseo_configuration_default", methods="GET") + */ + public function defaultAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ["storeseo"], AccessManager::VIEW)) { + return $response; + } + + // Get current edition language locale + $locale = $this->getCurrentEditionLocale(); + + $form = $this->createForm( + "storeseo_form_config", + FormType::class, + [ + 'title' => StoreSeo::getConfigValue('title', null, $locale), + 'description' => StoreSeo::getConfigValue('description', null, $locale), + 'keywords' => StoreSeo::getConfigValue('keywords', null, $locale) + ] + ); + + $this->getParserContext()->addForm($form); + + return $this->render("storeseo-configuration"); + } + + /** + * @Route("/admin/module/StoreSeo", name="storeseo_configuration_save", methods="POST") + */ + public function saveAction() + { + if (null !== $response = $this->checkAuth([AdminResources::MODULE], ["storeseo"], AccessManager::UPDATE)) { + return $response; + } + + $baseForm = $this->createForm("storeseo_form_config"); + + $errorMessage = null; + + // Get current edition language locale + $locale = $this->getCurrentEditionLocale(); + + try { + $form = $this->validateForm($baseForm); + $data = $form->getData(); + + // Save data + StoreSeo::setConfigValue('title', $data["title"], $locale); + StoreSeo::setConfigValue('description', $data["description"], $locale); + StoreSeo::setConfigValue('keywords', $data["keywords"], $locale); + + } catch (FormValidationException $ex) { + // Invalid data entered + $errorMessage = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $errorMessage = $this->getTranslator()->trans('Sorry, an error occurred: %err', ['%err' => $ex->getMessage()], StoreSeo::DOMAIN_NAME, $locale); + } + + if (null !== $errorMessage) { + // Mark the form as with error + $baseForm->setErrorMessage($errorMessage); + + // Send the form and the error to the parser + $this->getParserContext() + ->addForm($baseForm) + ->setGeneralError($errorMessage) + ; + } else { + $this->getParserContext() + ->set("success", true) + ; + } + + return $this->defaultAction(); + } +} diff --git a/domokits/local/modules/StoreSeo/Form/StoreSeoForm.php b/domokits/local/modules/StoreSeo/Form/StoreSeoForm.php new file mode 100755 index 0000000..1c66d32 --- /dev/null +++ b/domokits/local/modules/StoreSeo/Form/StoreSeoForm.php @@ -0,0 +1,38 @@ +formBuilder + ->add( + 'title', + TextType::class, + ['label' => $this->translator->trans('Store name', [], 'storeseo.fo.default')] + ) + ->add( + 'description', + TextType::class, + ['label' => $this->translator->trans('Store description', [], 'storeseo.fo.default')] + ) + ->add( + 'keywords', + TextType::class, + ['label' => $this->translator->trans('Keywords', [], 'storeseo.fo.default')] + ) + ; + } +} diff --git a/domokits/local/modules/StoreSeo/Hook/StoreSeoHook.php b/domokits/local/modules/StoreSeo/Hook/StoreSeoHook.php new file mode 100755 index 0000000..4abb755 --- /dev/null +++ b/domokits/local/modules/StoreSeo/Hook/StoreSeoHook.php @@ -0,0 +1,19 @@ + + */ +class StoreSeoHook extends BaseHook +{ + public function onModuleConfig(HookRenderEvent $event) + { + $event->add($this->render('storeseo-configuration.html')); + } +} \ No newline at end of file diff --git a/domokits/local/modules/StoreSeo/I18n/backOffice/default/en_US.php b/domokits/local/modules/StoreSeo/I18n/backOffice/default/en_US.php new file mode 100755 index 0000000..78ab426 --- /dev/null +++ b/domokits/local/modules/StoreSeo/I18n/backOffice/default/en_US.php @@ -0,0 +1,9 @@ + 'Configuration correctly saved', + 'Configure StoreSEO' => 'Configure StoreSEO', + 'Home' => 'Home', + 'Modules' => 'Modules', + 'StoreSeo configuration' => 'StoreSEO configuration', +); diff --git a/domokits/local/modules/StoreSeo/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/StoreSeo/I18n/backOffice/default/fr_FR.php new file mode 100755 index 0000000..ca86d96 --- /dev/null +++ b/domokits/local/modules/StoreSeo/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,9 @@ + 'Configuration correctement sauvegardée', + 'Configure StoreSEO' => 'Configurer StoreSEO', + 'Home' => 'Accueil', + 'Modules' => 'Modules', + 'StoreSeo configuration' => 'Configuration de StoreSEO', +); diff --git a/domokits/local/modules/StoreSeo/I18n/en_US.php b/domokits/local/modules/StoreSeo/I18n/en_US.php new file mode 100755 index 0000000..cc80e0e --- /dev/null +++ b/domokits/local/modules/StoreSeo/I18n/en_US.php @@ -0,0 +1,8 @@ + 'Keywords', + 'Sorry, an error occurred: %err' => 'Sorry, an error occurred: %err', + 'Store description' => 'Store description', + 'Store name' => 'Store name', +); diff --git a/domokits/local/modules/StoreSeo/I18n/fr_FR.php b/domokits/local/modules/StoreSeo/I18n/fr_FR.php new file mode 100755 index 0000000..cef43d6 --- /dev/null +++ b/domokits/local/modules/StoreSeo/I18n/fr_FR.php @@ -0,0 +1,8 @@ + 'Mots-clés', + 'Sorry, an error occurred: %err' => 'Désolé, une erreur s\'est produite : %err', + 'Store description' => 'Description de la boutique', + 'Store name' => 'Nom de la boutique', +); diff --git a/domokits/local/modules/StoreSeo/Readme.md b/domokits/local/modules/StoreSeo/Readme.md new file mode 100755 index 0000000..46e696c --- /dev/null +++ b/domokits/local/modules/StoreSeo/Readme.md @@ -0,0 +1,56 @@ +# Store Seo + +Manage translation for your store SEO meta. + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is StoreSeo. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require thelia/store-seo-module:~1.2.0 +``` + +## Usage + +Once activated, click on the configuration button of the module. + +Then, select one of your store available language and fill inputs with your store title, description and keywords. Save and do it for each of your language. + +They will be used on pages with no SEO meta configured. + +## Integration + +Open the layout.tpl file of your template. + +Check the ```Define some stuff for Smarty``` section at the top of the file, and be sur that you have this assignation: + +``` +{assign var="lang_locale" value={lang attr="locale"}} +``` + + +Add this line in the `````` section, before the `````` tag : + +``` +{store_seo_meta locale=$lang_locale} +``` + + +Also add this in the ```{block name="meta"}```, after the ```<meta name="description">``` tag : + +``` +{if $page_keywords} + <meta name="keywords" content="{$page_keywords}"> +{else} + <meta name="keywords" content="{$default_keywords}"> +{/if} +``` + +Finally, be sure that you have no ```{$page_title = {config key="store_name"}}``` declaration in your other template files. diff --git a/domokits/local/modules/StoreSeo/Smarty/Plugins/StoreSeoPlugin.php b/domokits/local/modules/StoreSeo/Smarty/Plugins/StoreSeoPlugin.php new file mode 100755 index 0000000..5293726 --- /dev/null +++ b/domokits/local/modules/StoreSeo/Smarty/Plugins/StoreSeoPlugin.php @@ -0,0 +1,48 @@ +<?php + +namespace StoreSeo\Smarty\Plugins; + +use StoreSeo\StoreSeo; +use Thelia\Model\ConfigQuery; +use TheliaSmarty\Template\AbstractSmartyPlugin; +use TheliaSmarty\Template\SmartyPluginDescriptor; + +/** + * Class StoreSeoPlugin + * @package StoreSeo\Smarty\Plugins + * @author Etienne Perriere <eperriere@openstudio.fr> + */ +class StoreSeoPlugin extends AbstractSmartyPlugin +{ + + /** + * @return SmartyPluginDescriptor[] an array of SmartyPluginDescriptor + */ + public function getPluginDescriptors() + { + return [ + new SmartyPluginDescriptor("function", "store_seo_meta", $this, "changeSeoMeta") + ]; + } + + /** + * Assign meta title, description and keyword for the template + * + * @param array $params + * @param \Smarty $smarty + */ + public function changeSeoMeta($params, &$smarty) + { + // Get language and moduleConfig + $locale = $params['locale']; + + // Get store title + $smarty->assign("store_name", StoreSeo::getConfigValue('title', null, $locale)); + + // Get store description + $smarty->assign("store_description", StoreSeo::getConfigValue('description', null, $locale)); + + // Get store keywords + $smarty->assign("default_keywords", StoreSeo::getConfigValue('keywords', null, $locale)); + } +} \ No newline at end of file diff --git a/domokits/local/modules/StoreSeo/StoreSeo.php b/domokits/local/modules/StoreSeo/StoreSeo.php new file mode 100755 index 0000000..a60000d --- /dev/null +++ b/domokits/local/modules/StoreSeo/StoreSeo.php @@ -0,0 +1,42 @@ +<?php +/*************************************************************************************/ +/* This file is part of the Thelia package. */ +/* */ +/* Copyright (c) OpenStudio */ +/* email : dev@thelia.net */ +/* web : http://www.thelia.net */ +/* */ +/* For the full copyright and license information, please view the LICENSE.txt */ +/* file that was distributed with this source code. */ +/*************************************************************************************/ + +namespace StoreSeo; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; + +/** + * Class StoreSeo + * @package StoreSeo + * @author Etienne Perriere <eperriere@openstudio.fr> + */ +class StoreSeo extends BaseModule +{ + /** @var string */ + const DOMAIN_NAME = 'storeseo'; + + /* + * You may now override BaseModuleInterface methods, such as: + * install, destroy, preActivation, postActivation, preDeactivation, postDeactivation + * + * Have fun ! + */ + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/StoreSeo/composer.json b/domokits/local/modules/StoreSeo/composer.json new file mode 100755 index 0000000..89142ec --- /dev/null +++ b/domokits/local/modules/StoreSeo/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/store-seo-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "StoreSeo" + } +} diff --git a/domokits/local/modules/StoreSeo/templates/backOffice/default/storeseo-configuration.html b/domokits/local/modules/StoreSeo/templates/backOffice/default/storeseo-configuration.html new file mode 100755 index 0000000..309df8c --- /dev/null +++ b/domokits/local/modules/StoreSeo/templates/backOffice/default/storeseo-configuration.html @@ -0,0 +1,98 @@ +{extends file="admin-layout.tpl"} + +{block name="no-return-functions"} +{$admin_current_location = 'modules'} +{/block} + +{block name="page-title"}{intl d="storeseo.bo.default" l='StoreSeo configuration'}{/block} + +{block name="check-resource"}admin.module{/block} +{block name="check-access"}view{/block} +{block name="check-module"}StoreSeo{/block} + +{block name="main-content"} +<div class="container" id="wrapper"> + + <ul class="breadcrumb"> + <li><a href="{url path='/admin'}">{intl l="Home" d="storeseo.bo.default"}</a></li> + <li><a href="{url path='/admin/modules'}">{intl l="Modules" d="storeseo.bo.default"}</a></li> + <li>{intl l="StoreSeo configuration" d="storeseo.bo.default"}</li> + </ul> + + <div class="general-block-decorator"> + + <div class="title title-without-tabs"> + {intl l="Configure StoreSEO" d="storeseo.bo.default"} + </div> + + <div class="row"> + <div class="col-md-12"> + {if $success|default:false} + <div class="alert alert-success"> + {intl l="Configuration correctly saved" d="storeseo.bo.default"} + </div> + {/if} + + <div class="form-container"> + {form name='storeseo_form_config'} + <form method="post" action="{url path='/admin/module/StoreSeo'}"> + + {form_hidden_fields form=$form} + + {include "includes/inner-form-toolbar.html" close_url={url path='/admin/modules'}} + <br/> + + {form_field form=$form field="title"} + <div class="form-group {if $error}has-error{/if}"> + <label class="control-label"> + {$label} + + {form_error form=$form field="title"} + <br /> + <span class="error">{$message|default:null}</span> + {/form_error} + </label> + + <input type="text" class="form-control" name="{$name}" value="{$value}" /> + </div> + {/form_field} + + {form_field form=$form field="description"} + <div class="form-group {if $error}has-error{/if}"> + <label class="control-label"> + {$label} + + {form_error form=$form field="description"} + <br /> + <span class="error">{$message|default:null}</span> + {/form_error} + </label> + + <input type="text" class="form-control" name="{$name}" value="{$value}" /> + </div> + {/form_field} + + {form_field form=$form field="keywords"} + <div class="form-group {if $error}has-error{/if}"> + <label class="control-label"> + {$label} + + {form_error form=$form field="keywords"} + <br /> + <span class="error">{$message|default:null}</span> + {/form_error} + </label> + + <input type="text" class="form-control" name="{$name}" value="{$value}" /> + </div> + {/form_field} + + </form> + {/form} + </div> + </div> + </div> + + </div> +</div> +{/block} diff --git a/domokits/local/modules/StripePayment/Classes/StripePaymentException.php b/domokits/local/modules/StripePayment/Classes/StripePaymentException.php new file mode 100644 index 0000000..633ef04 --- /dev/null +++ b/domokits/local/modules/StripePayment/Classes/StripePaymentException.php @@ -0,0 +1,13 @@ +<?php + +namespace StripePayment\Classes; + +/** + * Class StripePaymentException + * @package StripePayment\Classes + * @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr> + */ +class StripePaymentException extends \Exception +{ + +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/Classes/StripePaymentLog.php b/domokits/local/modules/StripePayment/Classes/StripePaymentLog.php new file mode 100644 index 0000000..f89d5dd --- /dev/null +++ b/domokits/local/modules/StripePayment/Classes/StripePaymentLog.php @@ -0,0 +1,60 @@ +<?php + +namespace StripePayment\Classes; + +use Thelia\Log\Tlog; + +/** + * Class StripePaymentLog + * @package StripePayment\Classes + * @author Etienne Perriere - OpenStudio <eperriere@openstudio.fr> + */ +class StripePaymentLog +{ + const EMERGENCY = 'EMERGENCY'; + const ALERT = 'ALERT'; + const CRITICAL = 'CRITICAL'; + const ERROR = 'ERROR'; + const WARNING = 'WARNING'; + const NOTICE = 'NOTICE'; + const INFO = 'INFO'; + const DEBUG = 'DEBUG'; + const LOGCLASS = "\\Thelia\\Log\\Destination\\TlogDestinationFile"; + + /** @var Tlog $log */ + protected $log; + + /** + * Log a message + * + * @param string $message Message + * @param string $severity EMERGENCY|ALERT|CRITICAL|ERROR|WARNING|NOTICE|INFO|DEBUG + * @param string $category Category + */ + public function logText($message, $severity = 'ALERT', $category = 'stripe') + { + $this->setTLogStripe(); + $msg = "$category.$severity: $message"; + $this->log->info($msg); + // Back to previous state + $this->getBackToPreviousState(); + } + + /** + * @return Tlog + */ + protected function setTLogStripe() + { + /* + * Write Log + */ + $this->log = Tlog::getInstance(); + $this->log->setDestinations(self::LOGCLASS); + $this->log->setConfig(self::LOGCLASS, 0, THELIA_ROOT . "log" . DS . "log-stripe.txt"); + } + + protected function getBackToPreviousState() + { + $this->log->setDestinations("\\Thelia\\Log\\Destination\\TlogDestinationRotatingFile"); + } +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/Config/config.xml b/domokits/local/modules/StripePayment/Config/config.xml new file mode 100644 index 0000000..571db36 --- /dev/null +++ b/domokits/local/modules/StripePayment/Config/config.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<config xmlns="http://thelia.net/schema/dic/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://thelia.net/schema/dic/config http://thelia.net/schema/dic/config/thelia-1.0.xsd"> + <forms> + <form name="stripepayment_config_form" class="StripePayment\Form\StripePaymentConfigForm"/> + </forms> + <hooks> + <hook id="stripepayment.hook" class="StripePayment\Hook\StripePaymentHook" scope="request"> + <argument id="request" type="service"/> + <argument type="service" id="thelia.taxEngine"/> + <tag name="hook.event_listener" event="order-invoice.payment-extra" type="front" method="includeStripe"/> + <tag name="hook.event_listener" event="order-invoice.after-javascript-include" type="front" method="declareStripeOnClickEvent"/> + <tag name="hook.event_listener" event="main.after-javascript-include" type="front" method="includeStripeJsV3"/> + <tag name="hook.event_listener" event="main.head-bottom" type="front" method="onMainHeadBottom"/> + <tag name="hook.event_listener" event="module.configuration" type="back" templates="render:stripepayment-configuration.html"/> + </hook> + </hooks> + <!-- + <services> + <service id="stripepayment.cart.event_listener" class="StripePayment\EventListeners\CartEventListener" scope="request"> + <argument id="request" type="service"/> + <argument type="service" id="event_dispatcher" /> + <argument type="service" id="thelia.taxEngine"/> + <tag name="kernel.event_subscriber"/> + </service> + </services> + --> +</config> diff --git a/domokits/local/modules/StripePayment/Config/module.xml b/domokits/local/modules/StripePayment/Config/module.xml new file mode 100644 index 0000000..e28c711 --- /dev/null +++ b/domokits/local/modules/StripePayment/Config/module.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module xmlns="http://thelia.net/schema/dic/module" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://thelia.net/schema/dic/module http://thelia.net/schema/dic/module/module-2_1.xsd"> + <fullnamespace>StripePayment\StripePayment</fullnamespace> + <descriptive locale="en_US"> + <title>Stripe + + + Stripe + + + en_US + fr_FR + + 3.0.1 + + Etienne Perriere + eperriere@openstudio.fr + + payment + 2.5.0 + other + diff --git a/domokits/local/modules/StripePayment/Config/routing.xml b/domokits/local/modules/StripePayment/Config/routing.xml new file mode 100644 index 0000000..7b1c007 --- /dev/null +++ b/domokits/local/modules/StripePayment/Config/routing.xml @@ -0,0 +1,30 @@ + + + + diff --git a/domokits/local/modules/StripePayment/Config/schema.xml b/domokits/local/modules/StripePayment/Config/schema.xml new file mode 100644 index 0000000..5385763 --- /dev/null +++ b/domokits/local/modules/StripePayment/Config/schema.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/domokits/local/modules/StripePayment/Controller/StripePaymentConfigController.php b/domokits/local/modules/StripePayment/Controller/StripePaymentConfigController.php new file mode 100644 index 0000000..62a5b7a --- /dev/null +++ b/domokits/local/modules/StripePayment/Controller/StripePaymentConfigController.php @@ -0,0 +1,75 @@ +checkAuth([AdminResources::MODULE], ["stripepayment"], AccessManager::UPDATE)) { + return $response; + } + + $baseForm = $this->createForm(StripePaymentConfigForm::getName()); + + $errorMessage = null; + + try { + $form = $this->validateForm($baseForm); + $data = $form->getData(); + StripePayment::setConfigValue(StripePayment::ENABLED, is_bool($data["enabled"]) ? (int) ($data["enabled"]) : $data["enabled"]); + StripePayment::setConfigValue(StripePayment::STRIPE_ELEMENT, is_bool($data["stripe_element"]) ? (int) ($data["stripe_element"]) : $data["stripe_element"]); + StripePayment::setConfigValue(StripePayment::ONE_CLICK_PAYMENT, is_bool($data["one_click_payment"]) ? (int) ($data["one_click_payment"]) : $data["one_click_payment"]); + StripePayment::setConfigValue(StripePayment::SECRET_KEY, is_bool($data["secret_key"]) ? (int) ($data["secret_key"]) : $data["secret_key"]); + StripePayment::setConfigValue(StripePayment::PUBLISHABLE_KEY, is_bool($data["publishable_key"]) ? (int) ($data["publishable_key"]) : $data["publishable_key"]); + StripePayment::setConfigValue(StripePayment::WEBHOOKS_KEY, is_bool($data["webhooks_key"]) ? (int) ($data["webhooks_key"]) : $data["webhooks_key"]); + StripePayment::setConfigValue(StripePayment::SECURE_URL, is_bool($data["secure_url"]) ? (int) ($data["secure_url"]) : $data["secure_url"]); + } catch (FormValidationException $ex) { + // Invalid data entered + $errorMessage = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $errorMessage = Translator::getInstance()->trans('Sorry, an error occurred: %err', ['%err' => $ex->getMessage()], "", StripePayment::MESSAGE_DOMAIN); + } + + if (null !== $errorMessage) { + // Mark the form as with error + $baseForm->setErrorMessage($errorMessage); + + // Send the form and the error to the parser + $context + ->addForm($baseForm) + ->setGeneralError($errorMessage) + ; + } else { + $context + ->set("success", true) + ; + } + + return $this->generateRedirect(URL::getInstance()->absoluteUrl('/admin/module/StripePayment')); + } +} diff --git a/domokits/local/modules/StripePayment/Controller/StripePaymentController.php b/domokits/local/modules/StripePayment/Controller/StripePaymentController.php new file mode 100644 index 0000000..ec099ad --- /dev/null +++ b/domokits/local/modules/StripePayment/Controller/StripePaymentController.php @@ -0,0 +1,24 @@ + + */ +class StripePaymentController extends BasePaymentModuleController +{ + /** + * Return a module identifier used to calculate the name of the log file, + * and in the log messages. + * + * @return string the module code + */ + protected function getModuleCode() + { + return 'StripePayment'; + } +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/Controller/StripeWebHooksController.php b/domokits/local/modules/StripePayment/Controller/StripeWebHooksController.php new file mode 100644 index 0000000..2e0deb6 --- /dev/null +++ b/domokits/local/modules/StripePayment/Controller/StripeWebHooksController.php @@ -0,0 +1,149 @@ +logText(serialize($event)); + + // Handle the event + switch ($event->type) { + case 'checkout.session.completed': + /** @var Session $sessionCompleted */ + $sessionCompleted = $event->data->object; + $this->handleSessionCompleted($sessionCompleted, $dispatcher); + break; + case 'payment_intent.succeeded': + // Needed to wait for order to be created (Stripe is faster than Thelia) + sleep(5); + /** @var Session $sessionCompleted */ + $paymentId = $event->data->object->id; + $this->handlePaymentIntentSuccess($paymentId, $dispatcher); + break; + case 'payment_intent.payment_failed': + // Needed to wait for order to be created (Stripe is faster than Thelia) + sleep(5); + /** @var Session $sessionCompleted */ + $paymentId = $event->data->object->id; + $this->handlePaymentIntentFail($paymentId, $dispatcher); + break; + default: + // Unexpected event type + (new StripePaymentLog())->logText('Unexpected event type'); + + return new Response('Unexpected event type', 400); + } + + return new Response('Success', 200); + } catch (\UnexpectedValueException $e) { + // Invalid payload + (new StripePaymentLog())->logText($e->getMessage()); + return new Response('Invalid payload', 400); + } catch (SignatureVerification $e) { + return new Response($e->getMessage(), 400); + } catch (\Exception $e) { + return new Response($e->getMessage(), 404); + } + } + + return new Response('Bad request', 400); + } + + protected function handleSessionCompleted(Session $sessionCompleted, EventDispatcherInterface $dispatcher) + { + $order = OrderQuery::create() + ->findOneByRef($sessionCompleted->client_reference_id); + + if (null === $order) { + throw new \Exception("Order with reference $sessionCompleted->client_reference_id not found"); + } + + $this->setOrderToPaid($order, $dispatcher); + } + + protected function handlePaymentIntentSuccess($paymentId, EventDispatcherInterface $dispatcher) + { + $order = OrderQuery::create() + ->findOneByTransactionRef($paymentId); + + if (null === $order) { + throw new \Exception("Order with transaction ref $paymentId not found"); + } + + $this->setOrderToPaid($order, $dispatcher); + } + + protected function handlePaymentIntentFail($paymentId, EventDispatcherInterface $dispatcher) + { + $order = OrderQuery::create() + ->findOneByTransactionRef($paymentId); + + if (null === $order) { + throw new \Exception("Order with transaction ref $paymentId not found"); + } + + $this->setOrderToCanceled($order, $dispatcher); + } + + protected function setOrderToPaid($order, EventDispatcherInterface $dispatcher) + { + $paidStatusId = OrderStatusQuery::create() + ->filterByCode('paid') + ->select('ID') + ->findOne(); + + $event = new OrderEvent($order); + $event->setStatus($paidStatusId); + $dispatcher->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + } + + protected function setOrderToCanceled($order, EventDispatcherInterface $dispatcher) + { + $canceledStatusId = OrderStatusQuery::create() + ->filterByCode('canceled') + ->select('ID') + ->findOne(); + + $event = new OrderEvent($order); + $event->setStatus($canceledStatusId); + $dispatcher->dispatch($event, TheliaEvents::ORDER_UPDATE_STATUS); + } +} diff --git a/domokits/local/modules/StripePayment/EventListeners/CartEventListener.php b/domokits/local/modules/StripePayment/EventListeners/CartEventListener.php new file mode 100644 index 0000000..3b27f14 --- /dev/null +++ b/domokits/local/modules/StripePayment/EventListeners/CartEventListener.php @@ -0,0 +1,212 @@ +request = $requestStack->getCurrentRequest(); + $this->dispatcher = $dispatcher; + $this->taxEngine = $taxEngine; + } + + public static function getSubscribedEvents() + { + $events = [ + TheliaEvents::CART_RESTORE_CURRENT => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_CREATE_NEW => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_ADDITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_DELETEITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_UPDATEITEM => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CART_CLEAR => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::CHANGE_DEFAULT_CURRENCY => ["createOrUpdatePaymentIntent", 64], + TheliaEvents::ORDER_SET_POSTAGE => [ "createOrUpdatePaymentIntent", 64 ] + ]; + + return $events; + } + + public function createOrUpdatePaymentIntent(ActionEvent $event) + { + $secretKey = StripePayment::getConfigValue('secret_key'); + Stripe::setApiKey($secretKey); + + /** @var Session $session */ + $session = $this->request->getSession(); + + $paymentIntentValues = $this->getPaymentIntentValues($event); + + if (false === $paymentIntentValues) { + return; + } + + if ( + $session->has(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY) + && + null !== $paymentId = $session->get(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY) + ) + { + + $payment = PaymentIntent::update( + $paymentId, + $paymentIntentValues + ); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, $payment->client_secret); + + return; + } + try { + /** @var PaymentIntent $payment */ + $payment = PaymentIntent::create($paymentIntentValues); + + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, $payment->id); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, $payment->client_secret); + } catch (\Exception $exception){ + Tlog::getInstance()->addAlert($exception->getMessage()); + } + + return; + } + + + protected function getPaymentIntentValues(ActionEvent $event) + { + /** @var Session $session */ + $session = $this->request->getSession(); + $currency = $session->getCurrency(); + + $data = $this->getCartAndOrderFromEvent($event); + + if (false === $data) { + return false; + } + + /** @var Cart $cart */ + $cart = $data['cart']; + + /** @var Order $order */ + $order = $data['order']; + + $postageAmount = floatval($order->getPostage()); + + $country = $this->taxEngine->getDeliveryCountry(); + + $cartAmount = floatval($cart->getTaxedAmount($country)); + + $totalAmount = ($postageAmount + $cartAmount) * 100; + + if (!$totalAmount > 0) { + return false; + } + + $values = [ + 'amount' => intval(round($totalAmount)), + 'currency' => strtolower($currency->getCode()) + ]; + + if (null !== $stripeCustomerId = $this->getStripeCustomerId($session)) { + $values['customer'] = $stripeCustomerId; + } + + return $values; + } + + protected function getStripeCustomerId(Session $session) + { + if (null === $session->getCustomerUser()) { + return null; + } + + if (!$session->has(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY)) { + /** @var Customer $customer */ + $customer = $session->getCustomerUser(); + $email = $customer->getEmail(); + + $stripeCustomer = \Stripe\Customer::create([ + 'email' => $email + ]); + + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, $stripeCustomer->id); + } + + return $session->get(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY); + } + + protected function getCartAndOrderFromEvent(ActionEvent $event) + { + /** @var Session $session */ + $session = $this->request->getSession(); + + if ($event instanceof CartRestoreEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CartCreateEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CartEvent) { + return [ + 'cart' => $event->getCart(), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof CurrencyChangeEvent) { + return [ + 'cart' => $session->getSessionCart($this->dispatcher), + 'order' => $session->getOrder() + ]; + } + + if ($event instanceof OrderEvent) { + return [ + 'cart' => $session->getSessionCart($this->dispatcher), + 'order' => $event->getOrder() + ]; + } + + return false; + } +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/EventListeners/SendConfirmationEmailListener.php b/domokits/local/modules/StripePayment/EventListeners/SendConfirmationEmailListener.php new file mode 100644 index 0000000..2a89ebc --- /dev/null +++ b/domokits/local/modules/StripePayment/EventListeners/SendConfirmationEmailListener.php @@ -0,0 +1,75 @@ +parser = $parser; + $this->mailer = $mailer; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @return MailerFactory + */ + public function getMailer() + { + return $this->mailer; + } + + public function updateOrderStatus(OrderEvent $event) + { + $stripe = new StripePayment(); + + if ($event->getOrder()->isPaid() && $stripe->isPaymentModuleFor($event->getOrder())) { + $this->eventDispatcher->dispatch($event, TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL); + $this->eventDispatcher->dispatch($event, TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL); + } + } + + + public function cancelOrderConfirmationEmail(OrderEvent $event) + { + $stripe = new StripePayment(); + + if ($stripe->isPaymentModuleFor($event->getOrder()) && !$event->getOrder()->isPaid()) { + $event->stopPropagation(); + } + } + + public static function getSubscribedEvents() + { + return array( + TheliaEvents::ORDER_UPDATE_STATUS => array("updateOrderStatus", 128), + TheliaEvents::ORDER_SEND_NOTIFICATION_EMAIL => array("cancelOrderConfirmationEmail", 150), + TheliaEvents::ORDER_SEND_CONFIRMATION_EMAIL => array("cancelOrderConfirmationEmail", 150) + ); + } + +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php b/domokits/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php new file mode 100644 index 0000000..0b83966 --- /dev/null +++ b/domokits/local/modules/StripePayment/Form/Base/StripePaymentConfigForm.php @@ -0,0 +1,208 @@ +formBuilder attribute : + * + * $this->formBuilder->add("name", "text") + * ->add("email", "email", array( + * "attr" => array( + * "class" => "field" + * ), + * "label" => "email", + * "constraints" => array( + * new \Symfony\Component\Validator\Constraints\NotBlank() + * ) + * ) + * ) + * ->add('age', 'integer'); + * + * @return null + */ + protected function buildForm() + { + $translationKeys = $this->getTranslationKeys(); + $fieldsIdKeys = $this->getFieldsIdKeys(); + + $this->addEnabledField($translationKeys, $fieldsIdKeys); + $this->addStripeElementField($translationKeys, $fieldsIdKeys); + $this->addOneClickPaymentField($translationKeys, $fieldsIdKeys); + $this->addSecretKeyField($translationKeys, $fieldsIdKeys); + $this->addPublishableKeyField($translationKeys, $fieldsIdKeys); + $this->addWebhooksKeyField($translationKeys, $fieldsIdKeys); + $this->addSecureUrlField($translationKeys, $fieldsIdKeys); + } + + protected function addEnabledField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("enabled", CheckboxType::class, array( + "label" => $this->readKey("enabled", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("enabled", $fieldsIdKeys), + "help" => $this->readKey("help.enabled", $translationKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePayment::ENABLED, false), + )) + ; + } + + protected function addStripeElementField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("stripe_element", CheckboxType::class, array( + "label" => $this->readKey("stripe_element", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("stripeelementch", $fieldsIdKeys), + "help" => $this->readKey("help.stripe_element", $translationKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePayment::STRIPE_ELEMENT, false), + )) + ; + } + + protected function addOneClickPaymentField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("one_click_payment", CheckboxType::class, array( + "label" => $this->readKey("one_click_payment", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("one_click_payment", $fieldsIdKeys) + ], + "required" => false, + "constraints" => array( + ), + "value" => StripePayment::getConfigValue(StripePayment::ONE_CLICK_PAYMENT, false), + )) + ; + } + + protected function addSecretKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("secret_key", TextType::class, array( + "label" => $this->readKey("secret_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("secret_key", $fieldsIdKeys), + "help" => $this->readKey("help.secret_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePayment::SECRET_KEY), + )) + ; + } + + protected function addPublishableKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("publishable_key", TextType::class, array( + "label" => $this->readKey("publishable_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("publishable_key", $fieldsIdKeys), + "help" => $this->readKey("help.publishable_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePayment::PUBLISHABLE_KEY), + )) + ; + } + + protected function addWebhooksKeyField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("webhooks_key", TextType::class, array( + "label" => $this->readKey("webhooks_key", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("webhooks_key", $fieldsIdKeys), + "help" => $this->readKey("help.webhooks_key", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePayment::WEBHOOKS_KEY), + )) + ; + } + protected function addSecureUrlField(array $translationKeys, array $fieldsIdKeys) + { + $this->formBuilder + ->add("secure_url", TextType::class, array( + "label" => $this->readKey("secure_url", $translationKeys), + "label_attr" => [ + "for" => $this->readKey("secure_url", $fieldsIdKeys), + "help" => $this->readKey("help.secure_url", $translationKeys) + ], + "required" => true, + "constraints" => array( + new NotBlank(), + ), + "data" => StripePayment::getConfigValue(StripePayment::SECURE_URL), + )) + ; + } + + public static function getName() + { + return static::FORM_NAME; + } + + public function readKey($key, array $keys, $default = '') + { + if (isset($keys[$key])) { + return $keys[$key]; + } + + return $default; + } + + public function getTranslationKeys() + { + return array(); + } + + public function getFieldsIdKeys() + { + return array( + "enabled" => "enabled", + "secret_key" => "secret_key", + "publishable_key" => "publishable_key", + "webhooks_key" => "webhooks_key", + "secure_url" => "secure_url" + ); + } +} diff --git a/domokits/local/modules/StripePayment/Form/StripePaymentConfigForm.php b/domokits/local/modules/StripePayment/Form/StripePaymentConfigForm.php new file mode 100644 index 0000000..979ce2e --- /dev/null +++ b/domokits/local/modules/StripePayment/Form/StripePaymentConfigForm.php @@ -0,0 +1,33 @@ + $this->translator->trans("Activate payment with stripe ?", [], StripePayment::MESSAGE_DOMAIN), + "stripe_element" => $this->translator->trans("Activate Element ?", [], StripePayment::MESSAGE_DOMAIN), + "one_click_payment" => $this->translator->trans("Activate one click payment ?", [], StripePayment::MESSAGE_DOMAIN), + "secret_key" => $this->translator->trans("Your secret key", [], StripePayment::MESSAGE_DOMAIN), + "publishable_key" => $this->translator->trans("Your publishable key (test or live)", [], StripePayment::MESSAGE_DOMAIN), + "webhooks_key" => $this->translator->trans("Your webhooks key", [], StripePayment::MESSAGE_DOMAIN), + "secure_url" => $this->translator->trans("Your chain of char for secure return webhook", [], StripePayment::MESSAGE_DOMAIN), + "help.enabled" => $this->translator->trans("Do you want to activate Stripe Payment", [], StripePayment::MESSAGE_DOMAIN), + "help.stripe_element" => $this->translator->trans("Element is the embedded and customizable payment form", [], StripePayment::MESSAGE_DOMAIN), + "help.secret_key" => $this->translator->trans("You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys", [], StripePayment::MESSAGE_DOMAIN), + ); + } +} diff --git a/domokits/local/modules/StripePayment/Hook/StripePaymentHook.php b/domokits/local/modules/StripePayment/Hook/StripePaymentHook.php new file mode 100644 index 0000000..b854d6f --- /dev/null +++ b/domokits/local/modules/StripePayment/Hook/StripePaymentHook.php @@ -0,0 +1,74 @@ + + */ +class StripePaymentHook extends BaseHook +{ + protected $request; + + protected $taxEngine; + + public function __construct(Request $request, TaxEngine $taxEngine) + { + $this->request = $request; + $this->taxEngine = $taxEngine; + } + + public function includeStripe(HookRenderEvent $event) + { + if(StripePayment::getConfigValue('stripe_element')){ + $publicKey = StripePayment::getConfigValue('publishable_key'); + $clientSecret = $this->request->getSession()->get(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY); + $currency = strtolower($this->request->getSession()->getCurrency()->getCode()); + $country = $this->taxEngine->getDeliveryCountry()->getIsoalpha2(); + $event->add($this->render( + 'assets/js/stripe-js.html', + [ + 'stripe_module_id' => $this->getModule()->getModuleId(), + 'public_key' => $publicKey, + 'oneClickPayment' => StripePayment::getConfigValue(StripePayment::ONE_CLICK_PAYMENT, false), + 'clientSecret' => $clientSecret, + 'currency' => $currency, + 'country' => $country + ] + )); + } + } + + public function declareStripeOnClickEvent(HookRenderEvent $event) + { + if(StripePayment::getConfigValue('stripe_element')){ + $publicKey = StripePayment::getConfigValue('publishable_key'); + $event->add($this->render( + 'assets/js/order-invoice-after-js-include.html', + [ + 'stripe_module_id' => $this->getModule()->getModuleId(), + 'public_key' => $publicKey + ] + )); + } + } + + public function includeStripeJsV3(HookRenderEvent $event) + { + $event->add(''); + } + + public function onMainHeadBottom(HookRenderEvent $event) + { + $content = $this->addCSS('assets/css/styles.css'); + $event->add($content); + } +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/I18n/backOffice/default/en_US.php b/domokits/local/modules/StripePayment/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..115c80f --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/backOffice/default/en_US.php @@ -0,0 +1,12 @@ + 'Configuration correctly saved', + 'Configure stripepayment' => 'Configure stripepayment', + 'Home' => 'Home', + 'Modules' => 'Modules', + 'StripePayment configuration' => 'StripePayment configuration', + 'The configuration value enabled' => 'The configuration value enabled', + 'The configuration value publishable_key' => 'The configuration value publishable_key', + 'The configuration value secret_key' => 'The configuration value secret_key', +); diff --git a/domokits/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..7fc731d --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,17 @@ + 'Configuration correctement sauvegardée', + 'Configure stripepayment' => 'Configurer Stripe', + 'Home' => 'Accueil', + 'Modules' => 'Modules', + 'StripePayment configuration' => 'Configuration du module Stripe', + 'The configuration value enabled' => 'La valeur de configuration "enabled"', + 'The configuration value publishable_key' => 'La valeur de configuration "publishable_key"', + 'The configuration value secret_key' => 'La valeur de configuration "secret_key"', + 'The configuration value secure_url' => 'La valeur de configuration secure_url', + 'The configuration value webhooks_key whsec_...' => 'La valeur de configuration webhooks_key whsec_...', + 'Webhooks endpoint url' => 'URL d\'endpoint WebHook', + 'Webhooks event to activate' => 'Événements WebHook à activer', + 'active stripe element' => 'Activer Stripe Element', +); diff --git a/domokits/local/modules/StripePayment/I18n/email/default/en_US.php b/domokits/local/modules/StripePayment/I18n/email/default/en_US.php new file mode 100644 index 0000000..a3bd1cb --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/email/default/en_US.php @@ -0,0 +1,13 @@ + 'Dear customer,', + 'Payment is confirmed for your order' => 'Payment is confirmed for your order', + 'Reference %ref' => 'Reference: %ref', + 'Thank you again for your purchase.' => 'Thank you again for your purchase!', + 'Thank you for your order!' => 'Thank you for your order!', + 'The %name team.' => 'The %name team.', + 'This is a confirmation of the payment of your order %order on %name.' => 'This is a confirmation of the payment of your order %order on %name.', + 'Your invoice is now available in your customer account on' => 'Your invoice is now available in your customer account on', + 'Your invoice is now available in your customer account on %site' => 'Your invoice is now available in your customer account on %site.', +); diff --git a/domokits/local/modules/StripePayment/I18n/email/default/fr_FR.php b/domokits/local/modules/StripePayment/I18n/email/default/fr_FR.php new file mode 100644 index 0000000..2b922e5 --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/email/default/fr_FR.php @@ -0,0 +1,13 @@ + 'Cher client,', + 'Payment is confirmed for your order' => 'Le paiement de votre commande est confirmé', + 'Reference %ref' => 'Référence de commande : %ref', + 'Thank you again for your purchase.' => 'Merci encore pour cet achat !', + 'Thank you for your order!' => 'Merci pour votre commande !', + 'The %name team.' => 'L\'équipe %name.', + 'This is a confirmation of the payment of your order %order on %name.' => 'Ce message confirme le paiement de votre commande n° %order sur %name.', + 'Your invoice is now available in your customer account on' => 'Votre facture est maintenant disponible sur votre compte sur ', + 'Your invoice is now available in your customer account on %site' => 'Votre facture est maintenant disponible sur votre compte sur %site. ', +); diff --git a/domokits/local/modules/StripePayment/I18n/en_US.php b/domokits/local/modules/StripePayment/I18n/en_US.php new file mode 100644 index 0000000..88eb9c9 --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/en_US.php @@ -0,0 +1,21 @@ + 'Activated ?', + 'An error occurred during payment.' => 'An error occurred during payment.', + 'An error occurred with Stripe.' => 'An error occurred with Stripe.', + 'Authentication with Stripe failed. Please contact administrators.' => 'Authentication with Stripe failed. Please contact administrators.', + 'Do you want to activate Stripe Payment' => 'Do you want to activate Stripe Payment', + 'Invalid parameters were supplied to Stripe.' => 'Invalid parameters were supplied to Stripe.', + 'Network communication failed.' => 'Network communication failed.', + 'Payment confirmation of your order {$order_ref} on %store_name' => 'Payment confirmation of your order {$order_ref} on %store_name', + 'Payment confirmation on %store_name' => 'Payment confirmation on %store_name', + 'Sorry, an error occurred: %err' => 'Sorry, an error occurred: %err', + 'Stripe library isn\'t installed' => 'Stripe library isn\'t installed', + 'The payment mean does not have the same amount as your cart. Please reload and try again.' => 'The payment mean does not have the same amount as your cart. Please reload and try again.', + 'Too many requests too quickly.' => 'Too many requests too quickly.', + 'You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys' => 'You can see all your keys in your Stripe dashboard. Also note that you have to place your test and your live API keys', + 'Your card has been declined.' => 'Your card has been declined.', + 'Your publishable key (test or live)' => 'Your publishable key (test or live)', + 'Your secret key' => 'Your secret key', +); diff --git a/domokits/local/modules/StripePayment/I18n/fr_FR.php b/domokits/local/modules/StripePayment/I18n/fr_FR.php new file mode 100644 index 0000000..c265567 --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/fr_FR.php @@ -0,0 +1,29 @@ + 'Activer Element ?', + 'Activate payment with stripe ?' => 'Activer le paiement avec Stripe ?', + 'An error occurred during payment.' => 'Une erreur est survenue lors du paiement.', + 'An error occurred with Stripe.' => 'Une erreur est survenue lors du paiement avec Stripe.', + 'Authentication with Stripe failed. Please contact administrators.' => 'Des paramètres invalides ont été envoyés à Stripe.', + 'Discount' => 'Remise', + 'Do you want to activate Stripe Payment' => 'Voulez-vous activer Stripe ?', + 'Element is the embedded and customizable payment form' => 'Element est un formulaire intégrer et personlisable.', + 'Invalid parameters were supplied to Stripe.' => 'Une erreur liée au réseau empêche la communication avec Stripe.', + 'Network communication failed.' => 'L\'authentification auprès de Stripe a échoué. Merci de contacter les administrateurs du site.', + 'Payment confirmation for Stripe Payment' => 'Confirmation de paiement par Stripe', + 'Payment confirmation of your order {$order_ref} on {$store_name}' => 'Confirmation de paiement pour votre commande {$order_ref} sur {$store_name}', + 'Sorry, an error occurred: %err' => 'Désolé, une erreur est survenue : %err', + 'Stripe library is missing.' => 'La libraire Stripe est manquante', + 'Stripe version is greater than max version (< %version). Current version: %curVersion.' => 'La version de la libraire Stripe est plus haute qua la version maximum ( < %version). Version actuelle %curVersion.', + 'Stripe version is lower than min version (%version). Current version: %curVersion.' => 'La version de la libraire Stripe est plus basse qua la version minimum ( > %version). Version actuelle %curVersion.', + 'The payment mean does not have the same amount as your cart. Please reload and try again.' => 'Le moyen de paiement n\'indique pas le même montant que votre panier. Rechargez la pager et réessayez.', + 'Too many requests too quickly.' => 'Trop de requêtes en peu de temps.', + 'Total' => 'Total', + 'You can see all your keys in your Stripe dashboard. Also note that you can place your test or your live API keys' => 'Vos clés sont disponibles dans votre compte sur la plateforme d\'administration Stripe. Veuillez noter que vous devez renseigner ici vos clés publique et privée.', + 'Your card has been declined.' => 'Votre carte bancaire a été refusée.', + 'Your chain of char for secure return webhook' => 'Une chaine de caractère pour sécuriser le retour des Webhooks', + 'Your publishable key (test or live)' => 'Votre clé publique', + 'Your secret key' => 'Votre clé secrète', + 'Your webhooks key' => 'Votre clé Webhooks ?', +); diff --git a/domokits/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php new file mode 100644 index 0000000..855d244 --- /dev/null +++ b/domokits/local/modules/StripePayment/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,6 @@ + 'Ou entrer les détails de votre carte de crédit', + 'Quick pay' => 'Paiement rapide', +); diff --git a/domokits/local/modules/StripePayment/LICENSE.txt b/domokits/local/modules/StripePayment/LICENSE.txt new file mode 100644 index 0000000..65c5ca8 --- /dev/null +++ b/domokits/local/modules/StripePayment/LICENSE.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/domokits/local/modules/StripePayment/Readme.md b/domokits/local/modules/StripePayment/Readme.md new file mode 100644 index 0000000..a6856f9 --- /dev/null +++ b/domokits/local/modules/StripePayment/Readme.md @@ -0,0 +1,43 @@ +# Stripe + +Thelia payment module for [Stripe](http://stripe.com). + +You need a subscription to Stripe payment solution to use this module. + +## Installation + +Either you install StripePayment manually or via composer, the presence of Stripe API files is checked when you try to activate the module. +If the API files are absent, you can't use Stripe. +Be aware that API files are set into the core/vendor folder. + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is StripePayment. +* Install the Stripe PHP library : + * add "stripe/stripe-php" to your composer.json file with command : `composer require stripe/stripe-php:"6.*"` + * or download the library from and install it in your `core/vendor` directory +* Activate it in your Thelia administration panel + + +### Composer + +Add it in your main thelia composer.json file: + +``` +composer require thelia/stripe-payment-module ~2.0.0 +``` + +### Configuration + +Enter your Stripe keys (*secret* and *public*) available on your [Stripe dashboard](https://dashboard.stripe.com/). + +Put your Stripe account in live mode. + +Then activate the Stripe in the module configuration panel. + +Activate the webhooks in stripe dashboard with the url specified in Thelia Back-office Stripe configuration, +and add events listed in Thelia Back-office Stripe configuration. + +### Logs + +Stripe error logs are stored in a specific file located in the log folder. diff --git a/domokits/local/modules/StripePayment/Resource/images/module/stripe.png b/domokits/local/modules/StripePayment/Resource/images/module/stripe.png new file mode 100644 index 0000000..20b7de7 Binary files /dev/null and b/domokits/local/modules/StripePayment/Resource/images/module/stripe.png differ diff --git a/domokits/local/modules/StripePayment/StripePayment.php b/domokits/local/modules/StripePayment/StripePayment.php new file mode 100644 index 0000000..1b22d60 --- /dev/null +++ b/domokits/local/modules/StripePayment/StripePayment.php @@ -0,0 +1,529 @@ + + */ +class StripePayment extends AbstractPaymentModule +{ + const MESSAGE_DOMAIN = "stripepayment"; + const CONFIRMATION_MESSAGE_NAME = "stripe_confirm_payment"; + + const PAYMENT_INTENT_ID_SESSION_KEY = 'payment_intent_id'; + const PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY = 'payment_intent_customer_id'; + const PAYMENT_INTENT_SECRET_SESSION_KEY = 'payment_intent_secret'; + + const ENABLED = "enabled"; + const STRIPE_ELEMENT = "stripe_element"; + const ONE_CLICK_PAYMENT = "one_click_payment"; + const SECRET_KEY = "secret_key"; + const PUBLISHABLE_KEY = "publishable_key"; + const WEBHOOKS_KEY = "webhooks_key"; + const SECURE_URL = "secure_url"; + + public function preActivation(ConnectionInterface $con = null) + { + // Check if Stripe API is present + try { + $this->checkApi(); + } catch (\Exception $ex) { + throw $ex; + } + + return true; + } + + public function postActivation(ConnectionInterface $con = null): void + { + // Module image + $moduleModel = $this->getModuleModel(); + + if (! $moduleModel->isModuleImageDeployed($con)) { + $this->deployImageFolder($moduleModel, sprintf('%s'.DS.'Resource'.DS.'images'.DS.'module', __DIR__), $con); + } + + $this->createMailMessage(); + } + + public function createMailMessage() + { + // Create payment confirmation message from templates, if not already defined + if (null === MessageQuery::create()->findOneByName(self::CONFIRMATION_MESSAGE_NAME)) { + + $languages = LangQuery::create()->find(); + + $message = new Message(); + $message + ->setName(self::CONFIRMATION_MESSAGE_NAME) + ->setHtmlTemplateFileName(self::CONFIRMATION_MESSAGE_NAME.'.html') + ->setTextTemplateFileName(self::CONFIRMATION_MESSAGE_NAME.'.txt') + ; + + foreach ($languages as $language) { + /** @var Lang $language */ + $locale = $language->getLocale(); + $message + ->setLocale($locale) + ->setTitle( + Translator::getInstance()->trans( + "Payment confirmation for Stripe Payment", + [], + self::MESSAGE_DOMAIN, + $locale + ) + ) + ->setSubject( + Translator::getInstance()->trans( + 'Payment confirmation of your order {$order_ref} on {$store_name}', + [], + self::MESSAGE_DOMAIN, + $locale + ) + ) + ; + } + + $message->save(); + } + } + + public function checkApi() + { + try { + $ReflectedClass = new \ReflectionClass('Stripe\Stripe'); + } catch (\Exception $ex) { + throw new \Exception( + Translator::getInstance()->trans( + "Stripe library is missing.", + [], + self::MESSAGE_DOMAIN + ) + ); + } + } + + /** + * + * Method used by payment gateway. + * + * If this method return a \Thelia\Core\HttpFoundation\Response instance, this response is send to the + * browser. + * + * In many cases, it's necessary to send a form to the payment gateway. On your response you can return this form already + * completed, ready to be sent + * + * @param \Thelia\Model\Order $order processed order + * @return null|\Thelia\Core\HttpFoundation\Response + */ + public function pay(Order $order) + { + if (!$this->isValidPayment()) { + throw new Exception("Your connection is not secured. Check that 'https' is present at the beginning of the site's address."); + } + + return $this->doPay($order); + } + + protected function doPay(Order $order) + { + $session = $this->getRequest()->getSession(); + + try { + + if(StripePayment::getConfigValue('stripe_element')){ + $order->setTransactionRef($session->get(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY)) + ->save(); + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, null); + + return; + } + + $session->set(StripePayment::PAYMENT_INTENT_ID_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_SECRET_SESSION_KEY, null); + $session->set(StripePayment::PAYMENT_INTENT_CUSTOMER_ID_SESSION_KEY, null); + + // Create the session on Stripe's servers - this will charge the user's order and save session id into order transaction reference + return $this->createStripeSession($order); + } catch(\Stripe\Exception\CardException $e) { + // The card has been declined + // FIXME Translate message here + $logMessage = sprintf( + 'Error paying order %d with Stripe. Card declined. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Your card has been declined.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Exception\RateLimitException $e) { + // Too many requests made to the API too quickly + $logMessage = sprintf( + 'Error paying order %d with Stripe. Too many requests. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Too many requests too quickly.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Exception\InvalidRequestException $e) { + // Invalid parameters were supplied to Stripe's API + $logMessage = sprintf( + 'Error paying order %d with Stripe. Invalid parameters. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Invalid parameters were supplied to Stripe.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Exception\AuthenticationException $e) { + // Authentication with Stripe's API failed + // (maybe you changed API keys recently) + $logMessage = sprintf( + 'Error paying order %d with Stripe. Authentication failed: API key changed? Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Authentication with Stripe failed. Please contact administrators.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Exception\ApiConnectionException $e) { + // Network communication with Stripe failed + $logMessage = sprintf( + 'Error paying order %d with Stripe. Network communication failed. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'Network communication failed.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (\Stripe\Exception\ApiErrorException $e) { + // Display a very generic error to the user + $logMessage = sprintf( + 'Error paying order %d with Stripe. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'An error occurred with Stripe.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } catch (StripePaymentException $e) { + // Amount shown to the user by Stripe & order amount are not equal + $logMessage = sprintf( + 'Error paying order %d with Stripe. Amounts are different. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = $e->getMessage(); + } catch (\Exception $e) { + // Something else happened, completely unrelated to Stripe + $logMessage = sprintf( + 'Error paying order %d with Stripe but maybe unrelated with it. Message: %s', + $order->getId(), + $e->getMessage() + ); + + $userMessage = Translator::getInstance() + ->trans( + 'An error occurred during payment.', + [], + StripePayment::MESSAGE_DOMAIN + ); + } + + if ($logMessage !== NULL) { + (new StripePaymentLog())->logText($logMessage); + + return new RedirectResponse( + URL::getInstance()->absoluteUrl("/order/failed/".$order->getId()."/".$userMessage) + ); + } + + return new Response(); + } + + public function createStripeSession(OrderModel $order) + { + /* Impossible d'ajouter une ligne spécifique pour la remise, cette partie est mise de côté en attendant que stripe ajoute cette possibilité + + $lineItems = $this->prepareLineItems($order); + + */ + + $currency = $order->getCurrency(); + + if (null === $currency) { + $currency = $this->getRequest()->getSession()->getCurrency(); + } + + $lineItems[] = [ + 'name'=> Translator::getInstance()->trans('Total', [], StripePayment::MESSAGE_DOMAIN ), + 'quantity'=> 1, + 'currency' => strtolower($currency->getCode()), + 'amount' => round($order->getTotalAmount(), 2) * 100 + ]; + + if(empty($lineItems)){ + throw new \Exception("Sorry, your cart is empty. There's nothing to pay."); + } + + $stripe = new \Stripe\StripeClient(StripePayment::getConfigValue('secret_key')); + + $session = $stripe->checkout->sessions->create([ + 'customer_email' => $order->getCustomer()->getEmail(), + 'client_reference_id' => $order->getRef(), + 'payment_method_types' => ['card'], + 'line_items' => $lineItems, + 'mode' => 'payment', + 'success_url' => URL::getInstance()->absoluteUrl('/order/placed/' . $order->getId()), + 'cancel_url' => URL::getInstance()->absoluteUrl('/order/failed/' . $order->getId() . '/error'), + ]); + + $order->setTransactionRef($session->payment_intent)->save(); + + /** @var ParserInterface $parser */ + $parser = $this->getContainer()->get("thelia.parser"); + + $parser->setTemplateDefinition( + $parser->getTemplateHelper()->getActiveFrontTemplate(), + true + ); + + $renderedTemplate = $parser->render( + "stripe-paiement.html", + [ + 'checkout_session_id' => $session->id, + 'public_key' => StripePayment::getConfigValue('publishable_key') + ] + ); + + return new Response($renderedTemplate); + } + + /** + * + * This method is call on Payment loop. + * + * If you return true, the payment method will be display + * If you return false, the payment method will not be display + * + * @return boolean + */ + public function isValidPayment() + { + $secretKey = self::getConfigValue(self::SECRET_KEY); + return ( (($this->isDevEnvironment() || $this->isSslEnabled()) && self::getConfigValue('enabled')) && $secretKey && $this->getCurrentOrderTotalAmount() > 0); + } + + /** + * Return true if the current environment is in Dev mode + * + * @return bool + */ + protected function isDevEnvironment() + { + return 'dev' === $this->getContainer()->getParameter('kernel.environment'); + } + + /** + * return true if SSL is enabled + * + * @return bool + */ + protected function isSslEnabled() + { + return $this->getRequest()->isSecure(); + } + + public function checkOrderAmount(OrderModel $order, $stripeAmount) + { + $orderAmount = $order->getTotalAmount() * 100; + + if (strval($stripeAmount) != strval($orderAmount)) { + throw new StripePaymentException(Translator::getInstance() + ->trans( + 'The payment mean does not have the same amount as your cart. Please reload and try again.', + [], + StripePayment::MESSAGE_DOMAIN + ) + ); + } + } + + protected function prepareLineItems(Order $order, $currency) + { + $stripeAmount = 0; + $lineItems = []; + + $baseSourceFilePath = ConfigQuery::read('images_library_path'); + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR . 'media' . DS . 'images'; + } else { + $baseSourceFilePath = THELIA_ROOT . $baseSourceFilePath; + } + if(null !== $orderProducts = OrderProductQuery::create()->filterByOrderId($order->getId())->joinOrderProductTax('opt', Criteria::LEFT_JOIN)->withColumn('SUM(`opt`.AMOUNT)', 'TOTAL_TAX')->withColumn('SUM(`opt`.PROMO_AMOUNT)', 'TOTAL_PROMO_TAX')->groupById()->find()){ + foreach ($orderProducts as $orderProduct) { + $description=''; + if(null !== $orderProductAttributeCombinations = OrderProductAttributeCombinationQuery::create()->filterByOrderProductId($orderProduct->getId())->find()){ + foreach ($orderProductAttributeCombinations as $orderProductAttributeCombination) { + if($description) $description .= ', '; + $description .= $orderProductAttributeCombination->getAttributeTitle() . ' ' . $orderProductAttributeCombination->getAttributeAvTitle(); + } + } + $images=array(); + if(null !== $product = ProductQuery::create()->filterByRef($orderProduct->getProductRef())->findOne()){ + if(null !== $productImages = ProductImageQuery::create()->filterByProductId($product->getId())->filterByVisible(1)->orderBy('position')->find()){ + foreach ($productImages as $productImage) { + // Put source image file path + $sourceFilePath = sprintf( + '%s/%s/%s', + $baseSourceFilePath, + 'product', + $productImage->getFile() + ); + + // Create image processing event + $event = new ImageEvent(); + $event->setSourceFilepath($sourceFilePath); + $event->setCacheSubdirectory('product'); + $width=100; + try { + // Dispatch image processing event + $event->setWidth($width); + $order->getDispatcher()->dispatch(TheliaEvents::IMAGE_PROCESS, $event); + $images[]=$event->getFileUrl(); + } catch (\Exception $ex) { + // Ignore the result and log an error + Tlog::getInstance()->addError(sprintf("Failed to process image in image loop: %s", $ex->getMessage())); + } + } + } + } + if($orderProduct->getWasInPromo()){ + $amount = (float) $orderProduct->getPromoPrice() + (float) $orderProduct->getVirtualColumn('TOTAL_PROMO_TAX'); + }else{ + $amount = (float) $orderProduct->getPrice() + (float) $orderProduct->getVirtualColumn('TOTAL_TAX'); + } + + $stripeAmount += $amount * $orderProduct->getQuantity() * 100; + $lineItems[] = [ + 'name' => $orderProduct->getTitle(), + 'description' => $description, + 'images' => $images, + 'amount' => $amount*100, + 'currency' => $currency, + 'quantity' => $orderProduct->getQuantity(), + ]; + } + } + if ($order->getPostage()){ + if (null !== $module = ModuleQuery::create()->findPk($order->getDeliveryModuleId())){ + $locale = $this->getRequest()->getLocale(); + if ($locale === 'en') { + $locale = 'en_US'; + } + $module->setLocale($locale); + + if (!$module->getTitle()) { + $module->setLocale('fr_FR'); + } + $lineItems[] = ['name'=> $module->getTitle(), 'description' => $module->getChapo(), 'quantity'=> 1, 'currency' => $currency, 'amount' => ($order->getPostage()*100)]; + $stripeAmount += $order->getPostage() * 100; + } + } + + if($order->getDiscount() > 0){ + $description=null; + if(null !== $orderCoupons = OrderCouponQuery::create()->filterByOrderId($order->getId())->find()){ + foreach($orderCoupons as $orderCoupon){ + if($description)$description .= ', '; + $description .= $orderCoupon->getTitle(); + } + } + $lineItems[] = ['name'=> Translator::getInstance()->trans('Discount', [], StripePayment::MESSAGE_DOMAIN ), 'description' => $description, 'quantity'=> 1, 'currency' => $currency, 'amount' => -($order->getDiscount()*100)]; + $stripeAmount -= $order->getDiscount() * 100; + } + + $this->checkOrderAmount($order, $stripeAmount); + + return $lineItems; + } + + + /** + * if you want, you can manage stock in your module instead of order process. + * Return false to decrease the stock when order status switch to pay + * + * @return bool + */ + public function manageStockOnCreation() + { + return false; + } + + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR . ucfirst(self::getModuleCode()). "/I18n/*"]) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/StripePayment/composer.json b/domokits/local/modules/StripePayment/composer.json new file mode 100644 index 0000000..6341e7d --- /dev/null +++ b/domokits/local/modules/StripePayment/composer.json @@ -0,0 +1,12 @@ +{ + "name": "thelia/stripe-payment-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1", + "stripe/stripe-php": "^v7.100" + }, + "extra": { + "installer-name": "StripePayment" + } +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html b/domokits/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html new file mode 100644 index 0000000..87a1794 --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/backOffice/default/stripepayment-configuration.html @@ -0,0 +1,174 @@ +
+
+
+ {intl l="Configure stripepayment" d="stripepayment.bo.default"} +
+ +
+
+ {if $success|default:null} +
+ {intl l="Configuration correctly saved" d="stripepayment.bo.default"} +
+ {/if} + + {form name="stripepayment_config_form"} + + {include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'}} +
+ + {form_field form=$form field="success_url"} + + {/form_field} + + {form_hidden_fields form=$form} + + {form_field form=$form field="enabled"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="stripe_element"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="one_click_payment"} +
+ + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + {form_field form=$form field="secret_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help nofilter} + {/if} +
+ {/form_field} + {form_field form=$form field="publishable_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="webhooks_key"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + + {form_field form=$form field="secure_url"} +
+ + + + {if ! empty($label_attr.help)} + {$label_attr.help} + {/if} +
+ {/form_field} + +
+ + +
+ +
+ +
    +
  • payment_intent.payment_failed
  • +
  • payment_intent.succeeded
  • +
  • checkout.session.completed
  • +
+
+ {include "includes/inner-form-toolbar.html" hide_flags = 1 close_url={url path='/admin/modules'} page_bottom=1} + + {/form} +
+
+
+
diff --git a/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html b/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html new file mode 100644 index 0000000..b57d755 --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.html @@ -0,0 +1,34 @@ +{extends file="email-layout.tpl"} + +{* Open in browser *} +{block name="browser"}{/block} + +{* No big image header *} +{block name="image-header"}{/block} + +{* No pre-header *} +{block name="pre-header"}{/block} + +{* Subject *} +{block name="email-subject"}Payment confirmation {$store_name}{/block} + +{* Title *} +{block name="email-title"}{/block} + +{* Content *} +{block name="email-content"} +

{$store_name}

+ +

{intl l="Payment is confirmed for your order" d="stripepayment.email.default"}

+ +

{intl l="Reference %ref" ref={$order_ref} d="stripepayment.email.default"}

+ +

+ {intl l="Your invoice is now available in your customer account on" d="stripepayment.email.default"} + {$store_name}. +

+ +

{intl l="Thank you for your order!" d="stripepayment.email.default"}

+ +

{intl l="The %name team." name={$store_name} d="stripepayment.email.default"}

+{/block} diff --git a/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt b/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt new file mode 100644 index 0000000..1e3c866 --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/email/default/stripe_confirm_payment.txt @@ -0,0 +1,9 @@ +{intl l="Dear customer," d="stripepayment.email.default"} + +{intl l="This is a confirmation of the payment of your order %order on %name." order={$order_ref} name={$store_name} d="stripepayment.email.default"} + +{intl l="Your invoice is now available in your customer account on %site" site={$store_url} d="stripepayment.email.default"} + +{intl l="Thank you again for your purchase." d="stripepayment.email.default"} + +{intl l="The %name team." name={$store_name} d="stripepayment.email.default"} diff --git a/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css new file mode 100644 index 0000000..87bff2d --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/css/styles.css @@ -0,0 +1,61 @@ +/** + * The CSS shown here will not be introduced in the Quickstart guide, but shows + * how you can use CSS to style your Element's container. + */ +.payment { + margin-bottom: 20px; +} +.stripe-payment { + width: 80%; + margin: auto; + text-align: center; +} +.stripe-payment .payment{ + background-color: #f5f5f5; + border-radius: 5px; +} +.stripe-payment .payment-label { + font-size: 20px; + font-weight: 500; +} +#payment-request-button { + margin-top: 10px; +} +#card-element { + box-sizing: border-box; + + height: 40px; + + padding: 10px 12px; + + border: 1px solid transparent; + border-radius: 4px; + background-color: white; + + box-shadow: 0 1px 3px 0 #e6ebf1; + -webkit-transition: box-shadow 150ms ease; + transition: box-shadow 150ms ease; +} + +#card-element--focus { + box-shadow: 0 1px 3px 0 #cfd7df; +} + +#card-element--invalid { + border-color: #fa755a; +} + +#card-element--webkit-autofill { + background-color: #fefde5 !important; +} +#card-errors, #payment-request-errors { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; + border-radius: .25rem; + padding: .75rem 1.25rem; + text-align: center; +} +#card-errors.hidden, #payment-request-errors.hidden { + display: none; +} \ No newline at end of file diff --git a/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html new file mode 100644 index 0000000..58040e7 --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/order-invoice-after-js-include.html @@ -0,0 +1,166 @@ + diff --git a/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html new file mode 100644 index 0000000..720ee8e --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/frontOffice/default/assets/js/stripe-js.html @@ -0,0 +1,29 @@ + + +
+ {if $oneClickPayment} +
+ {intl l="Quick pay" d="stripepayment.fo.default"} +
+ +
+ +
+ + {/if} +
+ {if $oneClickPayment} + {intl l="Or enter card details" d="stripepayment.fo.default"} + {/if} +
+ +
+
+ +
diff --git a/domokits/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html b/domokits/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html new file mode 100644 index 0000000..1654133 --- /dev/null +++ b/domokits/local/modules/StripePayment/templates/frontOffice/default/stripe-paiement.html @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/domokits/local/modules/TheliaBlocks/.github/workflows/npm-publish.yml b/domokits/local/modules/TheliaBlocks/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..e7a8905 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/.github/workflows/npm-publish.yml @@ -0,0 +1,24 @@ +# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created +# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages + +name: Node.js Package + +on: + release: + types: [created] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 14 + registry-url: https://registry.npmjs.org/ + - run: cd templates/backOffice/default/app && npm ci + - run: cd templates/backOffice/default/app && npm run build + - run: cd templates/backOffice/default/app && npm publish --access public + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + diff --git a/domokits/local/modules/TheliaBlocks/.github/workflows/release.yml b/domokits/local/modules/TheliaBlocks/.github/workflows/release.yml new file mode 100644 index 0000000..91f2a25 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/TheliaBlocks/.gitignore b/domokits/local/modules/TheliaBlocks/.gitignore new file mode 100755 index 0000000..80e5be4 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store + diff --git a/domokits/local/modules/TheliaBlocks/.husky/pre-commit b/domokits/local/modules/TheliaBlocks/.husky/pre-commit new file mode 100755 index 0000000..a9f6a01 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npm run build diff --git a/domokits/local/modules/TheliaBlocks/Command/ValidateJsonContent.php b/domokits/local/modules/TheliaBlocks/Command/ValidateJsonContent.php new file mode 100644 index 0000000..7b9614f --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Command/ValidateJsonContent.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Command; + +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Thelia\Command\ContainerAwareCommand; +use TheliaBlocks\Model\BlockGroupI18nQuery; + +class ValidateJsonContent extends ContainerAwareCommand +{ + public function __construct() + { + parent::__construct(); + } + + protected function configure(): void + { + $this + ->setName('thelia_blocks:blocks:validate') + ->setDescription('Validate & update json_content structure'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $jsonContents = BlockGroupI18nQuery::create()->find(); + + $output->writeln('============================================================= '); + $output->writeln('Thelia blocks json_content validation started'); + $progressBar = new ProgressBar($output, $jsonContents->count()); + $progressBar->start(); + + foreach ($jsonContents as $jsonContent) { + $content = $jsonContent->getJsonContent(); + + $decodedJson = json_decode($content, true); + + foreach ($decodedJson as $key => $value) { + /* if ($value['type']['id'] === 'blockAccordion') { + $oldData = $decodedJson[$key]['data']; + + $group = array_map(function ($item) { + if (isset($item['group'])) { + $item['content'] = []; + $item['content'][] = $item['group']; + + unset($item['group']); + + $item['content'][0]['data'] = ['content' => [$item['content'][0]['data']]]; + } + + return $item; + }, $oldData); + + $decodedJson[$key]['data'] = [ + 'title' => '', + 'group' => $group, + ]; + } */ + + if ($value['type']['id'] === "blockAccordion") { + $accordionTitle = $decodedJson[$key]['data']['group']['title']; + $accordionContent = $decodedJson[$key]['data']['group']['group']; + + $decodedJson[$key]['data'] = [ + 'title' => $accordionTitle, + 'group' => $accordionContent, + ]; + } + + if ($value['type']['id'] === 'blockGroup') { + $oldData = $decodedJson[$key]['data']; + + $itemsFromData = array_map(function ($item) { + return $item; + }, $oldData); + + $decodedJson[$key]['data'] = [ + 'content' => [$itemsFromData], + ]; + } + + if ($value['type']['id'] === 'multiColumns') { + $oldData = $decodedJson[$key]['data']; + + $itemsFromGroupsInCol = array_map(function ($item) { + if (isset($item['group'])) { + $item = $item['group']['data']; + } + + foreach ($item as $index => $column){ + if (null === $column['type']['id'] || empty($column['type']['id'])) { + unset($item[$index]); + } + } + + return $item; + }, $oldData); + + $oldData = $itemsFromGroupsInCol; + + $decodedJson[$key]['title'] = [ + 'default' => \count($itemsFromGroupsInCol) . ' Columns', + 'fr' => \count($itemsFromGroupsInCol) . ' Colonnes', + 'en' => \count($itemsFromGroupsInCol) . ' Columns', + 'es' => \count($itemsFromGroupsInCol) . ' Columnas', + 'it' => \count($itemsFromGroupsInCol) . ' Colonne', + ]; + + $decodedJson[$key]['data'] = $oldData; + } + } + + $jsonContent->setJsonContent(json_encode($decodedJson)); + + $jsonContent->save(); + + $progressBar->advance(); + } + + $progressBar->finish(); + $output->writeln(''); + $output->writeln('Thelia blocks json_content validation ended'); + $output->writeln('============================================================= '); + + return 1; + } +} diff --git a/domokits/local/modules/TheliaBlocks/Config/TheliaMain.sql b/domokits/local/modules/TheliaBlocks/Config/TheliaMain.sql new file mode 100644 index 0000000..91cdbc5 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/TheliaMain.sql @@ -0,0 +1,64 @@ + +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +-- --------------------------------------------------------------------- +-- block_group +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `block_group`; + +CREATE TABLE `block_group` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `slug` VARCHAR(50), + `visible` TINYINT DEFAULT 0 NOT NULL, + `created_at` DATETIME, + `updated_at` DATETIME, + PRIMARY KEY (`id`), + UNIQUE INDEX `slug_unique` (`slug`) +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- item_block_group +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `item_block_group`; + +CREATE TABLE `item_block_group` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `item_type` VARCHAR(255), + `item_id` INTEGER, + `block_group_id` INTEGER, + PRIMARY KEY (`id`), + INDEX `fi_item_block_group_block_group_id` (`block_group_id`), + CONSTRAINT `fk_item_block_group_block_group_id` + FOREIGN KEY (`block_group_id`) + REFERENCES `block_group` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + +-- --------------------------------------------------------------------- +-- block_group_i18n +-- --------------------------------------------------------------------- + +DROP TABLE IF EXISTS `block_group_i18n`; + +CREATE TABLE `block_group_i18n` +( + `id` INTEGER NOT NULL, + `locale` VARCHAR(5) DEFAULT 'en_US' NOT NULL, + `title` VARCHAR(255) NOT NULL, + `json_content` TEXT, + PRIMARY KEY (`id`,`locale`), + CONSTRAINT `block_group_i18n_fk_d94d4c` + FOREIGN KEY (`id`) + REFERENCES `block_group` (`id`) + ON DELETE CASCADE +) ENGINE=InnoDB; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/TheliaBlocks/Config/config.xml b/domokits/local/modules/TheliaBlocks/Config/config.xml new file mode 100755 index 0000000..eb30e5e --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/config.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/TheliaBlocks/Config/module.xml b/domokits/local/modules/TheliaBlocks/Config/module.xml new file mode 100755 index 0000000..4dc156e --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/module.xml @@ -0,0 +1,40 @@ + + + TheliaBlocks\TheliaBlocks + + Assemble blocks and display them wherever you want + + + + Assemblez des blocs et afficher les où vous voulez + + + + + en_US + fr_FR + + 2.1.15 + + + + + + + classic + + OpenApi + ShortCode + TheliaLibrary + + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/TheliaBlocks/Config/routing.xml b/domokits/local/modules/TheliaBlocks/Config/routing.xml new file mode 100755 index 0000000..fc772b7 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/routing.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/domokits/local/modules/TheliaBlocks/Config/schema.xml b/domokits/local/modules/TheliaBlocks/Config/schema.xml new file mode 100755 index 0000000..c14f2fe --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/schema.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + +
+ + + + + + + + +
+
diff --git a/domokits/local/modules/TheliaBlocks/Config/sqldb.map b/domokits/local/modules/TheliaBlocks/Config/sqldb.map new file mode 100755 index 0000000..a58fd16 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/sqldb.map @@ -0,0 +1,2 @@ +# Sqlfile -> Database map +TheliaMain.sql=TheliaMain diff --git a/domokits/local/modules/TheliaBlocks/Config/update/1.0.1.sql b/domokits/local/modules/TheliaBlocks/Config/update/1.0.1.sql new file mode 100644 index 0000000..213be37 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/update/1.0.1.sql @@ -0,0 +1,24 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +DROP TABLE IF EXISTS `item_block_group`; + +CREATE TABLE `item_block_group` +( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `item_type` VARCHAR(255), + `item_id` INTEGER, + `block_group_id` INTEGER, + PRIMARY KEY (`id`), + INDEX `fi_item_block_group_block_group_id` (`block_group_id`), + CONSTRAINT `fk_item_block_group_block_group_id` + FOREIGN KEY (`block_group_id`) + REFERENCES `block_group` (`id`) + ON UPDATE RESTRICT + ON DELETE CASCADE +) ENGINE=InnoDB; + + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/TheliaBlocks/Config/update/2.1.9.sql b/domokits/local/modules/TheliaBlocks/Config/update/2.1.9.sql new file mode 100644 index 0000000..c7d54c5 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Config/update/2.1.9.sql @@ -0,0 +1,9 @@ +# This is a fix for InnoDB in MySQL >= 4.1.x +# It "suspends judgement" for fkey relationships until are tables are set. +SET FOREIGN_KEY_CHECKS = 0; + +ALTER TABLE `block_group_i18n` + MODIFY `json_content` MEDIUMTEXT; + +# This restores the fkey checks, after having unset them earlier +SET FOREIGN_KEY_CHECKS = 1; diff --git a/domokits/local/modules/TheliaBlocks/Controller/Admin/BlockGroupController.php b/domokits/local/modules/TheliaBlocks/Controller/Admin/BlockGroupController.php new file mode 100644 index 0000000..975c84f --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Controller/Admin/BlockGroupController.php @@ -0,0 +1,269 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Controller\Admin; + +use OpenApi\Annotations as OA; +use OpenApi\Controller\Admin\BaseAdminOpenApiController; +use OpenApi\Model\Api\ModelFactory; +use OpenApi\Service\OpenApiService; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Core\HttpFoundation\JsonResponse; +use Thelia\Core\HttpFoundation\Request; +use TheliaBlocks\Model\BlockGroup; +use TheliaBlocks\Model\BlockGroupI18n; +use TheliaBlocks\Model\BlockGroupI18nQuery; +use TheliaBlocks\Model\BlockGroupQuery; +use TheliaBlocks\Model\ItemBlockGroup; +use TheliaBlocks\Model\ItemBlockGroupQuery; + +/** + * @Route("/open_api/block_group", name="block_group") + */ +class BlockGroupController extends BaseAdminOpenApiController +{ + /** + * @Route("", name="add_block_group", methods="POST") + * + * @OA\Post( + * path="/block_group", + * tags={"block group"}, + * summary="Add a new group of block", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * @OA\Property( + * property="blockGroup", + * ref="#/components/schemas/BlockGroup" + * ), + * @OA\Property( + * property="itemBlockGroup", + * ref="#/components/schemas/ItemBlockGroup" + * ), + * @OA\Property( + * property="locale", + * default="en_US", + * type="string" + * ), + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function createBlockGroup( + Request $request, + ModelFactory $modelFactory + ) { + $data = json_decode($request->getContent(), true); + /** @var \TheliaBlocks\Model\Api\BlockGroup $openApiBlockGroup */ + $openApiBlockGroup = $modelFactory->buildModel('BlockGroup', $data['blockGroup']); + $openApiBlockGroup->validate(self::GROUP_CREATE); + + if (!isset($data['locale'])) { + $data['locale'] = $request->getSession()->getAdminLang()->getLocale(); + } + + /** @var BlockGroup $blockGroup */ + $blockGroup = $openApiBlockGroup->toTheliaModel($data['locale']); + $blockGroup->save(); + + if (isset($data['itemBlockGroup'])) { + $this->assignBlockGroupToItem($modelFactory, $data['itemBlockGroup'], $blockGroup->getId()); + } + + $blockGroup->clearItemBlockGroups(); + + return OpenApiService::jsonResponse( + $modelFactory->buildModel('BlockGroup', $blockGroup) + ); + } + + /** + * @Route("", name="update_block_group", methods="PATCH") + * + * @OA\Patch( + * path="/block_group", + * tags={"block group"}, + * summary="Update a block group", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * @OA\Property( + * property="blockGroup", + * ref="#/components/schemas/BlockGroup" + * ), + * @OA\Property( + * property="itemBlockGroup", + * ref="#/components/schemas/ItemBlockGroup" + * ), + * @OA\Property( + * property="locale", + * type="string", + * default="en_US" + * ), + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function updateBlockGroup( + Request $request, + ModelFactory $modelFactory + ) { + $data = json_decode($request->getContent(), true); + /** @var \TheliaBlocks\Model\Api\BlockGroup $openApiBlockGroup */ + $openApiBlockGroup = $modelFactory->buildModel('BlockGroup', $data['blockGroup']); + $openApiBlockGroup->validate(self::GROUP_UPDATE); + + if (!isset($data['locale'])) { + $data['locale'] = $request->getSession()->getAdminLang()->getLocale(); + } + + /** @var BlockGroup $blockGroup */ + $blockGroup = $openApiBlockGroup->toTheliaModel($data['locale']); + $blockGroup->save(); + + if (isset($data['itemBlockGroup'])) { + $this->assignBlockGroupToItem($modelFactory, $data['itemBlockGroup'], $blockGroup->getId()); + } + $blockGroup->clearItemBlockGroups(); + + return OpenApiService::jsonResponse($modelFactory->buildModel('BlockGroup', $blockGroup)); + } + + /** + * @Route("/{blockGroupId}", name="delete_block_group", methods="DELETE", requirements={"blockGroupId"="\d+"}) + * + * @OA\Delete( + * path="/block_group/{blockGroupId}", + * tags={"block group"}, + * summary="Delete a block group", + * @OA\Parameter( + * name="blockGroupId", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="204", + * description="Success" + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function deleteBlockGroup( + $blockGroupId + ) { + $blockGroup = BlockGroupQuery::create() + ->filterById($blockGroupId) + ->findOne(); + + $blockGroup->delete(); + + return new JsonResponse('Success', 204); + } + + /** + * @Route("/duplicate/{blockGroupId}", name="duplicate_block_group", methods="POST", requirements={"blockGroupId"="\d+"}) + * + * @OA\Post( + * path="/block_group/duplicate/{blockGroupId}", + * tags={"block group"}, + * summary="Duplicate a group of block", + * @OA\Parameter( + * name="blockGroupId", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function duplicateBlockGroup( + $blockGroupId + ) { + $propelBlockGroup = BlockGroupQuery::create()->filterById($blockGroupId)->findOne(); + + if (null === $propelBlockGroup) { + return OpenApiService::jsonResponse(null, 404); + } + + $newBlockGroup = $propelBlockGroup->copy(); + $newBlockGroup->save(); + $newBlockId = $newBlockGroup->getId(); + + $blockGroupI18ns = BlockGroupI18nQuery::create()->filterById($blockGroupId)->find(); + + array_map(function (BlockGroupI18n $blockI18n) use ($newBlockId): void { + $newBlockI18n = $blockI18n->copy(); + $newBlockI18n->setId($newBlockId)->save(); + }, iterator_to_array($blockGroupI18ns)); + + return OpenApiService::jsonResponse($newBlockGroup->getId()); + } + + protected function assignBlockGroupToItem(ModelFactory $modelFactory, $itemBlockGroup, $blockGroupId) + { + $openApiItemBlockGroup = $modelFactory->buildModel('ItemBlockGroup', $itemBlockGroup); + /** @var ItemBlockGroup $itemBlockGroup */ + $itemBlockGroup = $openApiItemBlockGroup->toTheliaModel(); + $itemBlockGroup->setBlockGroupId($blockGroupId); + + // todo allow multiple block for an item + $oldItemBlockGroup = ItemBlockGroupQuery::create() + ->filterByItemType($itemBlockGroup->getItemType()) + ->filterByItemId($itemBlockGroup->getItemId()) + ->findOne(); + if (null !== $oldItemBlockGroup) { + $oldItemBlockGroup->delete(); + } + + $itemBlockGroup->save(); + + return $itemBlockGroup; + } +} diff --git a/domokits/local/modules/TheliaBlocks/Controller/Admin/ConfigurationController.php b/domokits/local/modules/TheliaBlocks/Controller/Admin/ConfigurationController.php new file mode 100644 index 0000000..55a6a79 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Controller/Admin/ConfigurationController.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Controller\Admin; + +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Controller\Admin\BaseAdminController; +use TheliaBlocks\Service\JsonBlockService; +use TheliaBlocks\TheliaBlocks; + +/** + * Class ConfigurationController. + * + * @author Damien Foulhoux + */ + +/** + * @Route("/admin/TheliaBlocks", name="thelia_blocks") + */ +class ConfigurationController extends BaseAdminController +{ + /** @var JsonBlockService */ + private $jsonBlockService; + + public function __construct(JsonBlockService $jsonBlockService) + { + TheliaBlocks::$pageNeedTheliaBlockAssets = true; + $this->jsonBlockService = $jsonBlockService; + } + + /** + * @Route("", name="_list", methods="GET") + */ + public function reactAppListingAction() + { + return $this->render('thelia-blocks-configuration'); + } + + /** + * @Route("/new", name="_new", methods="GET") + */ + public function reactAppNewAction() + { + return $this->render('thelia-blocks-new-configuration'); + } + + /** + * @Route("/{blockGroupId}", name="_edit", methods="GET", requirements={"blockGroupId"="\d+"}) + */ + public function reactAppEditAction($blockGroupId) + { + return $this->render('thelia-blocks-item-configuration', [ + 'groupId' => $blockGroupId, + ]); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Controller/Admin/ItemBlockGroupController.php b/domokits/local/modules/TheliaBlocks/Controller/Admin/ItemBlockGroupController.php new file mode 100644 index 0000000..ae036e3 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Controller/Admin/ItemBlockGroupController.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Controller\Admin; + +use OpenApi\Annotations as OA; +use OpenApi\Controller\Admin\BaseAdminOpenApiController; +use OpenApi\Model\Api\ModelFactory; +use OpenApi\Service\OpenApiService; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Core\HttpFoundation\JsonResponse; +use Thelia\Core\HttpFoundation\Request; +use TheliaBlocks\Model\BlockGroup; +use TheliaBlocks\Model\ItemBlockGroup; +use TheliaBlocks\Model\ItemBlockGroupQuery; + +/** + * @Route("/open_api/item_block_group", name="item_block_group") + */ +class ItemBlockGroupController extends BaseAdminOpenApiController +{ + /** + * @Route("", name="_create", methods="POST") + * + * @OA\Post( + * path="/item_block_group", + * tags={"item block group", "block group"}, + * summary="Add a relation between an item and a block group", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * @OA\Property( + * property="itemBlockGroup", + * ref="#/components/schemas/ItemBlockGroup" + * ) + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function createItemBlockGroup( + Request $request, + ModelFactory $modelFactory + ) { + $data = json_decode($request->getContent(), true); + $openApiItemBlockGroup = $modelFactory->buildModel('ItemBlockGroup', $data['itemBlockGroup']); + /** @var ItemBlockGroup $itemBlockGroup */ + $itemBlockGroup = $openApiItemBlockGroup->toTheliaModel(); + + // todo allow multiple block for an item + $oldItemBlockGroup = ItemBlockGroupQuery::create() + ->filterByItemType($itemBlockGroup->getItemType()) + ->filterByItemId($itemBlockGroup->getItemId()) + ->findOne(); + if (null !== $oldItemBlockGroup) { + $oldItemBlockGroup->delete(); + } + + $itemBlockGroup->save(); + + $theliaBlock = $modelFactory->buildModel('BlockGroup', $itemBlockGroup->getBlockGroup(), $request->get('locale')); + + return OpenApiService::jsonResponse($theliaBlock, 200); + } + + /** + * @Route("/{itemBlockGroupId}", name="_delete", methods="DELETE", requirements={"itemBlockGroupId"="\d+"}) + * + * @OA\Delete( + * path="/item_block_group/{itemBlockGroupId}", + * tags={"item block group", "block group"}, + * summary="Delete a relation between an item and a block group", + * @OA\Parameter( + * name="itemBlockGroupId", + * in="path", + * required=true, + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Response( + * response="204", + * description="Success" + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function deleteItemBlockGroup( + $itemBlockGroupId + ) { + $itemBlockGroup = ItemBlockGroupQuery::create() + ->filterById($itemBlockGroupId) + ->findOne(); + + if (null !== $itemBlockGroup) { + $itemBlockGroup->delete(); + } + + return new JsonResponse('Success', 204); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Controller/Front/BlockGroupController.php b/domokits/local/modules/TheliaBlocks/Controller/Front/BlockGroupController.php new file mode 100644 index 0000000..6bfb79d --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Controller/Front/BlockGroupController.php @@ -0,0 +1,271 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Controller\Front; + +use OpenApi\Annotations as OA; +use OpenApi\Controller\Front\BaseFrontOpenApiController; +use OpenApi\Model\Api\ModelFactory; +use OpenApi\Service\OpenApiService; +use Propel\Runtime\ActiveQuery\Criteria; +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Model\Lang; +use TheliaBlocks\Model\Api\BlockGroup; +use TheliaBlocks\Model\BlockGroupI18nQuery; +use TheliaBlocks\Model\BlockGroupQuery; + +/** + * @Route("/open_api/block_group", name="block_group") + */ +class BlockGroupController extends BaseFrontOpenApiController +{ + /** + * @Route("", name="_get", methods="GET") + * + * @OA\Get( + * path="/block_group", + * tags={"block group"}, + * summary="Get a block group", + * @OA\Parameter( + * name="id", + * in="query", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Parameter( + * name="slug", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Parameter( + * name="visible", + * in="query", + * @OA\Schema( + * type="boolean", + * default="true" + * ) + * ), + * @OA\Parameter( + * name="locale", + * in="query", + * description="Current locale by default", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function getBlockGroup( + Request $request, + ModelFactory $modelFactory + ) { + $blockGroupQuery = BlockGroupQuery::create(); + + if (null !== $id = $request->get('id')) { + $blockGroupQuery->filterById($id); + } + + if (null !== $slug = $request->get('slug')) { + $blockGroupQuery->filterBySlug($slug); + } + + if ($request->get('visible') !== null) { + $visible = (bool) json_decode(strtolower($request->get('visible'))); + $blockGroupQuery->filterByVisible($visible); + } + + $propelBlockGroup = $blockGroupQuery->findOne(); + + if (null === $propelBlockGroup) { + return OpenApiService::jsonResponse(null, 404); + } + + /** @var BlockGroup $blockGroup */ + $blockGroup = $modelFactory->buildModel('BlockGroup', $propelBlockGroup, $request->get('locale')); + + if (null !== $blockGroup && empty($blockGroup->getJsonContent())) { + $requestLocale = $request->get('locale'); + + if (!in_array($requestLocale, $blockGroup->getLocales())) { + // Copy default locale JSON content + $defaultLocale = Lang::getDefaultLanguage()->getLocale(); + + $copyLocale = $blockGroup->getLocales()[0]; + + if (in_array($defaultLocale, $blockGroup->getLocales())) { + $copyLocale = $defaultLocale; + } + + if ( + null !== $copyGroup = BlockGroupI18nQuery::create() + ->filterById($blockGroup->getId()) + ->filterByLocale($copyLocale) + ->findOne() + ) { + $blockGroup->setJsonContent($copyGroup->getJsonContent()); + } + } + } + + return OpenApiService::jsonResponse($blockGroup); + } + + /** + * @Route("/list", name="_get_list", methods="GET") + * + * @OA\Get( + * path="/block_group/list", + * tags={"block group"}, + * summary="Get list of block groups", + * @OA\Parameter( + * name="limit", + * in="query", + * @OA\Schema( + * type="integer" + * ) + * ), + * @OA\Parameter( + * name="offset", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Parameter( + * name="itemType", + * description="the type of an item linked to the block group", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Parameter( + * name="title", + * in="query", + * @OA\Schema( + * type="string" + * ), + * ), + * @OA\Parameter( + * name="itemId", + * description="the id of an item linked to the block group (itemType has too be defined too)", + * in="query", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Parameter( + * name="visible", + * in="query", + * @OA\Schema( + * type="boolean", + * default="true" + * ) + * ), + * @OA\Parameter( + * name="locale", + * in="query", + * description="Current locale by default", + * @OA\Schema( + * type="string" + * ) + * ), + * @OA\Response( + * response="200", + * description="Success", + * @OA\JsonContent(ref="#/components/schemas/BlockGroup") + * ), + * @OA\Response( + * response="400", + * description="Bad request", + * @OA\JsonContent(ref="#/components/schemas/Error") + * ) + * ) + */ + public function getBlockGroups( + Request $request, + ModelFactory $modelFactory + ) { + $blockGroupQuery = BlockGroupQuery::create(); + + if (null !== $limit = $request->get('limit')) { + $blockGroupQuery->limit($limit); + } + + if (null !== $offset = $request->get('offset')) { + $blockGroupQuery->offset($offset); + } + + if (null !== $title = $request->get('title')) { + $blockGroupQuery + ->useBlockGroupI18nQuery() + ->filterByTitle('%' . $title . '%', Criteria::LIKE) + ->endUse(); + } + + if (null !== $itemType = $request->get('itemType')) { + $itemBlockGroupQuery = $blockGroupQuery->useItemBlockGroupQuery() + ->filterByItemType($itemType); + + if (null !== $itemId = $request->get('itemId')) { + $itemBlockGroupQuery->filterByItemId($itemId); + } + + $itemBlockGroupQuery->endUse(); + } + + if ($request->get('visible') !== null) { + $visible = (bool) json_decode(strtolower($request->get('visible'))); + $blockGroupQuery->filterByVisible($visible); + } + + $order = $request->get('order'); + + switch ($order) { + case 'id': + $blockGroupQuery->orderById(Criteria::ASC); + break; + case 'id_reverse': + $blockGroupQuery->orderById(Criteria::DESC); + break; + default: + $blockGroupQuery->orderById(Criteria::DESC); + } + + $propelTheliaBlocks = $blockGroupQuery->find(); + + if (empty($propelTheliaBlocks)) { + return OpenApiService::jsonResponse([], 404); + } + + $theliaBlocks = array_map( + fn ($propelBlockGroup) => $modelFactory->buildModel('BlockGroup', $propelBlockGroup, $request->get('locale')), + iterator_to_array($propelTheliaBlocks) + ); + + return OpenApiService::jsonResponse($theliaBlocks); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Controller/Front/PreviewGroupController.php b/domokits/local/modules/TheliaBlocks/Controller/Front/PreviewGroupController.php new file mode 100644 index 0000000..b7220bf --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Controller/Front/PreviewGroupController.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Controller\Front; + +/* + * This file is part of the Thelia package. + * http://www.thelia.net + * + * (c) OpenStudio + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\Routing\Annotation\Route; +use Thelia\Controller\Front\BaseFrontController; +use Thelia\Core\HttpFoundation\Request; + +/** + * @Route("/TheliaBlocks", name="thelia_blocks_front") + */ +class PreviewGroupController extends BaseFrontController +{ + /** + * @Route("/preview", name="_preview", methods="POST") + */ + public function previewBlockGroup(Request $request) + { + return $this->render('thelia-blocks-preview', [ + 'json' => $request->get('json'), + ]); + } +} diff --git a/domokits/local/modules/TheliaBlocks/EventListeners/ShortCodeListener.php b/domokits/local/modules/TheliaBlocks/EventListeners/ShortCodeListener.php new file mode 100644 index 0000000..feac7c7 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/EventListeners/ShortCodeListener.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\EventListeners; + +use ShortCode\Event\ShortCodeEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Template\ParserInterface; +use Thelia\Core\Template\TheliaTemplateHelper; +use Thelia\Log\Tlog; +use Thelia\Model\CategoryQuery; +use Thelia\Model\ContentQuery; +use Thelia\Model\FolderQuery; +use Thelia\Model\ProductQuery; +use TheliaBlocks\Model\BlockGroupQuery; +use TheliaBlocks\TheliaBlocks; + +class ShortCodeListener implements EventSubscriberInterface +{ + /** @var ParserInterface */ + protected $parser; + + /** @var Request */ + protected $request; + + /** @var TheliaTemplateHelper */ + private $templateHelper; + + public function __construct(RequestStack $requestStack, ParserInterface $parser, TheliaTemplateHelper $templateHelper) + { + $this->request = $requestStack->getCurrentRequest(); + $this->parser = $parser; + $this->templateHelper = $templateHelper; + } + + public static function getSubscribedEvents() + { + return [ + TheliaBlocks::BLOCK_GROUP_SHORT_CODE => [['blockGroupShortCode']], + TheliaBlocks::ADMIN_CSS_SHORTCODE => [['addBlockGroupCss']], + TheliaBlocks::ADMIN_JS_SHORTCODE => [['addBlockGroupJs']], + TheliaBlocks::PRODUCT_LINK => [['renderProductLink']], + TheliaBlocks::CATEGORY_LINK => [['renderCategoryLink']], + TheliaBlocks::FOLDER_LINK => [['renderFolderLink']], + TheliaBlocks::CONTENT_LINK => [['renderContentLink']], + ]; + } + + public function addBlockGroupCss(ShortCodeEvent $event): void + { + if (TheliaBlocks::$pageNeedTheliaBlockAssets) { + $event->setResult( + $this->parser->render('thelia-blocks-css.html') + ); + } + } + + public function addBlockGroupJs(ShortCodeEvent $event): void + { + if (TheliaBlocks::$pageNeedTheliaBlockAssets) { + $event->setResult( + $this->parser->render('thelia-blocks-js.html') + ); + } + } + + public function blockGroupShortCode(ShortCodeEvent $event): void + { + $attributes = $event->getAttributes(); + + if (!isset($attributes['slug'])) { + return; + } + + $blockGroupSlug = $attributes['slug']; + $blockGroup = BlockGroupQuery::create() + ->filterBySlug($blockGroupSlug) + ->findOne(); + + if (null === $blockGroup) { + Tlog::getInstance()->warning("Block group with slug $blockGroupSlug not found"); + + return; + } + + $lang = $this->request->getSession()->getLang(); + $blockGroup->setLocale($lang->getLocale()); + + $blocks = json_decode($blockGroup->getJsonContent(), true); + + if (!\is_array($blocks)) { + return; + } + + // Todo return the raw value of block group + $raw = isset($attributes['raw']) && (bool) json_decode(strtolower($attributes['raw'])) === true; + + if ($raw) { + $event->setResult($this->parser->render('blocks/rawBlockGroup.html', compact('blocks'))); + + return; + } + + $blockRenders = []; + foreach ($blocks as $block) { + $blockRenders[] = $this->parser->render('blocks'.DS.$block['type']['id'].'.html', $block); + } + + $event->setResult(implode(' ', $blockRenders)); + } + + public function renderProductLink(ShortCodeEvent $event): void + { + $event->setResult($this->generateLink(ProductQuery::create(), $event)); + } + + public function renderCategoryLink(ShortCodeEvent $event): void + { + $event->setResult($this->generateLink(CategoryQuery::create(), $event)); + } + + public function renderContentLink(ShortCodeEvent $event): void + { + $event->setResult($this->generateLink(ContentQuery::create(), $event)); + } + + public function renderFolderLink(ShortCodeEvent $event): void + { + $event->setResult($this->generateLink(FolderQuery::create(), $event)); + } + + public function generateLink(CategoryQuery|ProductQuery|ContentQuery|FolderQuery $query, ShortCodeEvent $event) + { + $attributes = $event->getAttributes(); + $id = $attributes['id']; + $locale = $this->request->getSession()->getLang()->getLocale(); + + $item = $query + ->filterById($id) + ->findOne(); + + $url = $item->getUrl($locale); + $title = $attributes['title'] ?? $item->getTitle(); + $link = $this->renderLinkTemplate($url, $title); + + return $link; + } + + private function renderLinkTemplate(string $url, string $title) + { + return ''.$title.''; + } +} diff --git a/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksBackHook.php b/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksBackHook.php new file mode 100644 index 0000000..664b57c --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksBackHook.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Hook; + +use Thelia\Core\Event\Hook\HookRenderBlockEvent; +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Tools\URL; +use TheliaBlocks\Model\BlockGroupQuery; +use TheliaBlocks\TheliaBlocks; + +class TheliaBlocksBackHook extends BaseHook +{ + public function onProductTab(HookRenderBlockEvent $event): void + { + $this->addTheliaBlocksConfigurationTab($event, 'product'); + } + + public function onCategoryTab(HookRenderBlockEvent $event): void + { + $this->addTheliaBlocksConfigurationTab($event, 'category'); + } + + public function onBrandTab(HookRenderBlockEvent $event): void + { + $this->addTheliaBlocksConfigurationTab($event, 'brand', $event->getArgument('brand_id')); + } + + public function onContentTab(HookRenderBlockEvent $event): void + { + $this->addTheliaBlocksConfigurationTab($event, 'content'); + } + + public function onFolderTab(HookRenderBlockEvent $event): void + { + $this->addTheliaBlocksConfigurationTab($event, 'folder'); + } + + public function onBlockItemConfiguration(HookRenderEvent $event): void + { + $itemId = $event->getArgument('itemId'); + $itemType = $event->getArgument('itemType'); + $groupId = $event->getArgument('groupId'); + + $event->add($this->getConfigurationRender($itemType, $itemId, $groupId)); + } + + public function onMainCss(HookRenderEvent $event): void + { + $event->add('[block_group_admin_css]'); + } + + public function onMainJs(HookRenderEvent $event): void + { + $event->add('[block_group_admin_js]'); + } + + protected function addTheliaBlocksConfigurationTab(HookRenderBlockEvent $event, $itemType, $itemId = null): void + { + if (null === $itemId) { + $itemId = $event->getArgument('id'); + } + $groupId = $event->getArgument('groupId'); + + $event->add( + [ + 'id' => 'theliablocks_item_details', + 'title' => $this->trans('Blocs de contenus', [], TheliaBlocks::DOMAIN_NAME), + 'content' => $this->getConfigurationRender($itemType, $itemId, $groupId), + ] + ); + } + + private function getConfigurationRender($itemType, $itemId, $groupId = null) + { + TheliaBlocks::$pageNeedTheliaBlockAssets = true; + + $search = BlockGroupQuery::create(); + $search->useItemBlockGroupQuery() + ->filterByItemType($itemType) + ->filterByItemId($itemId) + ->endUse(); + + if ($groupId) { + $search->filterById($groupId); + } + + $group = $search->findOne(); + + return $this->render( + 'item-configuration.html', + [ + 'itemId' => $itemId, + 'itemType' => $itemType, + 'groupId' => $group?->getId(), + ] + ); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksMenuHook.php b/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksMenuHook.php new file mode 100644 index 0000000..9bbcdb8 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Hook/TheliaBlocksMenuHook.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +class TheliaBlocksMenuHook extends BaseHook +{ + public function onMainInTopMenuItems(HookRenderEvent $event): void + { + $event->add( + $this->render('hook-in-top-menu-item.html', $event->getTemplateVars()) + ); + } +} diff --git a/domokits/local/modules/TheliaBlocks/I18n/en_US.php b/domokits/local/modules/TheliaBlocks/I18n/en_US.php new file mode 100755 index 0000000..d391ee9 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // 'an english string' => 'The displayed english string', +]; diff --git a/domokits/local/modules/TheliaBlocks/I18n/fr_FR.php b/domokits/local/modules/TheliaBlocks/I18n/fr_FR.php new file mode 100755 index 0000000..ff066ca --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // 'an english string' => 'La traduction française de la chaine', +]; diff --git a/domokits/local/modules/TheliaBlocks/Loop/BlockGroup.php b/domokits/local/modules/TheliaBlocks/Loop/BlockGroup.php new file mode 100644 index 0000000..7dfdc93 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Loop/BlockGroup.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Loop; + +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Core\Template\Element\BaseI18nLoop; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Core\Template\Element\LoopResultRow; +use Thelia\Core\Template\Element\PropelSearchLoopInterface; +use Thelia\Core\Template\Loop\Argument\Argument; +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Type\BooleanOrBothType; +use TheliaBlocks\Model\BlockGroupQuery; + +/** + * Class BlockGroup. + * + * @method int[] getId() + * @method string[] getSlug() + * @method string getItemType() + * @method int getItemId() + * @method bool|string getVisible() + */ +class BlockGroup extends BaseI18nLoop implements PropelSearchLoopInterface +{ + /** + * {@inheritdoc} + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntListTypeArgument('id'), + Argument::createAlphaNumStringListTypeArgument('slug'), + Argument::createAlphaNumStringTypeArgument('item_type'), + Argument::createIntTypeArgument('item_id'), + Argument::createBooleanOrBothTypeArgument('visible', 1) + ); + } + + /** + * this method returns a Propel ModelCriteria. + * + * @return \Propel\Runtime\ActiveQuery\ModelCriteria + */ + public function buildModelCriteria() + { + $search = BlockGroupQuery::create(); + + $this->configureI18nProcessing($search, ['TITLE', 'JSON_CONTENT']); + + $id = $this->getId(); + + if (null !== $id) { + $search->filterById($id, Criteria::IN); + } + + $slug = $this->getSlug(); + + if (null !== $slug) { + $search->filterBySlug($slug, Criteria::IN); + } + + $itemType = $this->getItemType(); + $itemId = $this->getItemId(); + + if (null !== $itemType && null !== $itemId) { + $search->useItemBlockGroupQuery() + ->filterByItemType($itemType) + ->filterByItemId($itemId) + ->endUse(); + } + + $visible = $this->getVisible(); + + if ($visible !== BooleanOrBothType::ANY) { + $search->filterByVisible($visible ? 1 : 0); + } + + return $search; + } + + public function parseResults(LoopResult $loopResult) + { + /** @var \TheliaBlocks\Model\BlockGroup $entry */ + foreach ($loopResult->getResultDataCollection() as $entry) { + + if (!$entry->getVirtualColumn('i18n_JSON_CONTENT')) { + continue; + } + + $htmlRender = $this->container->get('theliablocks.json.block')->renderJsonBlocks($entry->getVirtualColumn('i18n_JSON_CONTENT')); + + $content = json_decode($entry->getVirtualColumn('i18n_JSON_CONTENT'), true); + + $row = new LoopResultRow($entry); + $row + ->set('ID', $entry->getId()) + ->set('SLUG', $entry->getSlug()) + ->set('VISIBLE', $entry->getVisible()) + ->set('TITLE', $entry->getVirtualColumn('i18n_TITLE')) + ->set('CONTENT', $content) + ->set('RENDER', $htmlRender) + ; + + $this->addOutputFields($row, $entry); + + $loopResult->addRow($row); + } + + return $loopResult; + } +} diff --git a/domokits/local/modules/TheliaBlocks/Model/Api/BlockGroup.php b/domokits/local/modules/TheliaBlocks/Model/Api/BlockGroup.php new file mode 100755 index 0000000..5ab495e --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/Api/BlockGroup.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model\Api; + +use OpenApi\Annotations as OA; +use OpenApi\Constraint; +use OpenApi\Exception\OpenApiException; +use OpenApi\Model\Api\BaseApiModel; +use OpenApi\Model\Api\Error; +use OpenApi\OpenApi; +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Core\Translation\Translator; +use Thelia\Model\LangQuery; +use TheliaBlocks\Model\BlockGroupI18nQuery; +use TheliaBlocks\Model\BlockGroupQuery; + +/** + * Class BlockGroup. + * + * @OA\Schema( + * schema="BlockGroup", + * title="BlockGroup", + * ) + */ +class BlockGroup extends BaseApiModel +{ + /** + * @var int + * @OA\Property( + * type="integer", + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $id; + + /** + * @var bool + * @OA\Property( + * type="boolean", + * ) + */ + protected $visible; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $title; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $slug = null; + + /** + * @var array + * @OA\Property( + * readOnly=true, + * type="array", + * @OA\Items( + * ref="#/components/schemas/ItemBlockGroup" + * ) + * ) + */ + protected $itemBlockGroups = []; + + /** + * @var string + * @OA\Property( + * description="All blocks json encoded", + * type="string" + * ) + */ + protected $jsonContent; + + /** + * @var array + * @OA\Property( + * type="array", + * @OA\Items() + * ) + */ + protected $locales; + + /** + * @param $groups + * + * @throws OpenApiException + * + * @return BlockGroup + */ + public function validate($groups, $recursively = true) + { + parent::validate($groups, $recursively); + + $violations = []; + + if (null !== $this->getSlug()) { + $sameSlugQuery = BlockGroupQuery::create() + ->filterBySlug($this->getSlug()); + + if (null !== $this->getId()) { + $sameSlugQuery->filterById($this->getId(), Criteria::NOT_EQUAL); + } + + if (null !== $sameSlugQuery->findOne()) { + $violations[] = $this->modelFactory->buildModel( + 'SchemaViolation', + [ + 'key' => 'slug', + 'error' => Translator::getInstance()->trans('Slug must be unique', [], OpenApi::DOMAIN_NAME), + ] + ); + } + } + + if (!empty($violations)) { + /** @var Error $error */ + $error = $this->modelFactory->buildModel( + 'Error', + ['title' => Translator::getInstance()->trans('Invalid data', [], OpenApi::DOMAIN_NAME)] + ); + + $error->setSchemaViolations($violations); + + throw new OpenApiException($error); + } + + return $this; + } + + /** + * @return int + */ + public function getId(): ?int + { + return $this->id; + } + + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + public function setVisible(bool $visible): self + { + $this->visible = $visible; + + return $this; + } + + /** + * @return string + */ + public function getTitle(): ?string + { + return $this->title; + } + + /** + * @param string $title + */ + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } + + /** + * @return string + */ + public function getSlug(): ?string + { + return $this->slug; + } + + /** + * @param string $slug + */ + public function setSlug(?string $slug): self + { + $this->slug = $slug; + + return $this; + } + + /** + * @return string + */ + public function getJsonContent(): ?string + { + return $this->jsonContent; + } + + public function setJsonContent(?string $jsonContent): self + { + $this->jsonContent = $jsonContent; + + return $this; + } + + public function getItemBlockGroups(): array + { + return $this->itemBlockGroups; + } + + public function setItemBlockGroups(array $itemBlockGroups): self + { + $this->itemBlockGroups = $itemBlockGroups; + + return $this; + } + + public function getLocales(): array|null + { + return $this->locales; + } + + public function setLocales(array $locales): self + { + $this->locales = $locales; + + return $this; + } + + protected function getTheliaModel($propelModelName = null) + { + return parent::getTheliaModel(\TheliaBlocks\Model\BlockGroup::class); + } + + public function createFromTheliaModel($theliaModel, $locale = null): void + { + parent::createFromTheliaModel($theliaModel, $locale); + + $locales = array_map( + function ($item) { + return LangQuery::create()->findOneByLocale($item['Locale'])->getLocale(); + }, + BlockGroupI18nQuery::create() + ->filterById($this->getId()) + ->find() + ->toArray() + ); + + $this->setLocales($locales); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Model/Api/ItemBlockGroup.php b/domokits/local/modules/TheliaBlocks/Model/Api/ItemBlockGroup.php new file mode 100644 index 0000000..4a7df18 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/Api/ItemBlockGroup.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model\Api; + +use OpenApi\Annotations as OA; +use OpenApi\Constraint; +use OpenApi\Model\Api\BaseApiModel; +use Thelia\Tools\URL; +use TheliaMain\PropelResolver; + +/** + * Class ItemBlockGroup. + * + * @OA\Schema( + * schema="ItemBlockGroup", + * title="ItemBlockGroup", + * ) + */ +class ItemBlockGroup extends BaseApiModel +{ + /** + * @var int + * @OA\Property( + * type="integer", + * ) + */ + protected $id; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $itemType; + + /** + * @var int + * @OA\Property( + * type="integer", + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $itemId; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $itemTitle; + + /** + * @var string + * @OA\Property( + * type="string", + * ) + */ + protected $itemUrl; + + /** + * @var int + * @OA\Property( + * type="integer", + * ) + * @Constraint\NotBlank(groups={"read"}) + */ + protected $blockGroupId; + + /** + * @return int + */ + public function getId(): ?int + { + return $this->id; + } + + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } + + public function getItemType(): string + { + return $this->itemType; + } + + public function setItemType(string $itemType): self + { + $this->itemType = $itemType; + + return $this; + } + + public function getItemId(): int + { + return $this->itemId; + } + + public function setItemId(int $itemId): self + { + $this->itemId = $itemId; + + return $this; + } + + /** + * @return int + */ + public function getBlockGroupId(): ?int + { + return $this->blockGroupId; + } + + public function setBlockGroupId(int $blockGroupId): self + { + $this->blockGroupId = $blockGroupId; + + return $this; + } + + public function getItemTitle(): string + { + return $this->itemTitle; + } + + public function setItemTitle(string $itemTitle): self + { + $this->itemTitle = $itemTitle; + + return $this; + } + + public function getItemUrl(): string + { + return $this->itemUrl; + } + + public function setItemUrl(string $itemUrl): self + { + $this->itemUrl = $itemUrl; + + return $this; + } + + protected function getTheliaModel($propelModelName = null) + { + return parent::getTheliaModel(\TheliaBlocks\Model\ItemBlockGroup::class); + } + + public function createFromTheliaModel($theliaModel, $locale = null) + { + parent::createFromTheliaModel($theliaModel, $locale); + + if (!$this->getItemType()) { + return $this; + } + + try { + $tableMapClass = PropelResolver::getTableMapByTableName($this->getItemType()); + + if (!$tableMapClass) { + return $this; + } + + $tableMap = new $tableMapClass(); + $queryClass = $tableMap->getClassName().'Query'; + + if (!class_exists($queryClass)) { + return $this; + } + + $query = $queryClass::create(); + + if (null === $query) { + return $this; + } + + $item = $query->findOneById($this->getItemId()); + + if ($item === null) { + return $this; + } + + if (method_exists($item, 'getTitle')) { + $this->setItemTitle($item->getTitle()); + } + + switch ($this->getItemType()) { + case 'product': + $this->setItemUrl(URL::getInstance()->absoluteUrl("/admin/products/update?product_id={$this->getItemId()}")); + break; + case 'category': + $this->setItemUrl(URL::getInstance()->absoluteUrl("/admin/categories/update?category_id={$this->getItemId()}")); + break; + case 'content': + $this->setItemUrl(URL::getInstance()->absoluteUrl("/admin/content/update/{$this->getItemId()}")); + break; + case 'brand': + $this->setItemUrl(URL::getInstance()->absoluteUrl("/admin/brands/update/{$this->getItemId()}")); + break; + case 'folder': + $this->setItemUrl(URL::getInstance()->absoluteUrl("/admin/folders/update/{$this->getItemId()}")); + break; + default: + if (method_exists($item, 'getUrl')) { + $this->setItemUrl($item->getUrl()); + } + break; + } + } catch (\Throwable $th) { + // throw $th; + } + } +} diff --git a/domokits/local/modules/TheliaBlocks/Model/BlockGroup.php b/domokits/local/modules/TheliaBlocks/Model/BlockGroup.php new file mode 100644 index 0000000..76aea66 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/BlockGroup.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use Propel\Runtime\ActiveQuery\Criteria; +use Propel\Runtime\Connection\ConnectionInterface; +use TheliaBlocks\Model\Base\BlockGroup as BaseBlockGroup; + +/** + * Skeleton subclass for representing a row from the 'block_group' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class BlockGroup extends BaseBlockGroup +{ + public const SLUG_MAX_LENGTH = 45; + + /** + * Code to be run before inserting to database. + * + * @param ConnectionInterface $con + * + * @return bool + */ + public function preSave(ConnectionInterface $con = null) + { + if (null === $this->getSlug()) { + $this->setSlug($this->slugify($this->getTitle())); + } + + $this->setSlug($this->findUnusedSlug(substr($this->getSlug(), 0, self::SLUG_MAX_LENGTH))); + + return parent::preInsert($con); + } + + protected function findUnusedSlug($baseSlug, $iteration = 0) + { + $iteratedSlug = $baseSlug; + if ($iteration !== 0) { + $iterationSuffix = '_'.$iteration; + $iteratedSlug = substr($baseSlug, 0, self::SLUG_MAX_LENGTH - mb_strlen($iterationSuffix)).$iterationSuffix; + } + + $alreadyExistForAnotherGroupQuery = BlockGroupQuery::create()->filterBySlug($iteratedSlug); + if (null !== $this->getId()) { + $alreadyExistForAnotherGroupQuery->filterById($this->getId(), Criteria::NOT_EQUAL); + } + + if (null === $alreadyExistForAnotherGroupQuery->findOne()) { + return $iteratedSlug; + } + + ++$iteration; + + return $this->findUnusedSlug($baseSlug, $iteration); + } + + protected function slugify($text) + { + $table = [ + 'Š' => 'S', 'š' => 's', 'Đ' => 'Dj', 'đ' => 'dj', 'Ž' => 'Z', 'ž' => 'z', 'Č' => 'C', 'č' => 'c', 'Ć' => 'C', 'ć' => 'c', + 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'A', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', + 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', + 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ý' => 'Y', 'Þ' => 'B', 'ß' => 'Ss', + 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'a', 'ç' => 'c', 'è' => 'e', 'é' => 'e', + 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'o', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', + 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ý' => 'y', 'ý' => 'y', 'þ' => 'b', + 'ÿ' => 'y', 'Ŕ' => 'R', 'ŕ' => 'r', '/' => '_', ' ' => '_', + ]; + + // -- Remove duplicated spaces + $text = preg_replace(['/\s{2,}/', '/[\t\n]/'], '_', $text); + + $text = preg_replace('~[^\pL\d]+~u', '_', $text); + // -- Returns the slug + return trim(strtolower(strtr($text, $table)), '_'); + } +} diff --git a/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18n.php b/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18n.php new file mode 100644 index 0000000..fc54129 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18n.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use TheliaBlocks\Model\Base\BlockGroupI18n as BaseBlockGroupI18n; + +/** + * Skeleton subclass for representing a row from the 'block_group_i18n' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class BlockGroupI18n extends BaseBlockGroupI18n +{ +} diff --git a/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18nQuery.php b/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18nQuery.php new file mode 100644 index 0000000..a5fafe8 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/BlockGroupI18nQuery.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use TheliaBlocks\Model\Base\BlockGroupI18nQuery as BaseBlockGroupI18nQuery; + +/** + * Skeleton subclass for performing query and update operations on the 'block_group_i18n' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class BlockGroupI18nQuery extends BaseBlockGroupI18nQuery +{ +} diff --git a/domokits/local/modules/TheliaBlocks/Model/BlockGroupQuery.php b/domokits/local/modules/TheliaBlocks/Model/BlockGroupQuery.php new file mode 100644 index 0000000..24fa313 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/BlockGroupQuery.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use TheliaBlocks\Model\Base\BlockGroupQuery as BaseBlockGroupQuery; + +/** + * Skeleton subclass for performing query and update operations on the 'block_group' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class BlockGroupQuery extends BaseBlockGroupQuery +{ +} diff --git a/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroup.php b/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroup.php new file mode 100644 index 0000000..5124d09 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroup.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use TheliaBlocks\Model\Base\ItemBlockGroup as BaseItemBlockGroup; + +/** + * Skeleton subclass for representing a row from the 'item_block_group' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class ItemBlockGroup extends BaseItemBlockGroup +{ +} diff --git a/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroupQuery.php b/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroupQuery.php new file mode 100644 index 0000000..c98a8d8 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Model/ItemBlockGroupQuery.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace TheliaBlocks\Model; + +use TheliaBlocks\Model\Base\ItemBlockGroupQuery as BaseItemBlockGroupQuery; + +/** + * Skeleton subclass for performing query and update operations on the 'item_block_group' table. + * + * You should add additional methods to this class to meet the + * application requirements. This class will only be generated as + * long as it does not already exist in the output directory. + */ +class ItemBlockGroupQuery extends BaseItemBlockGroupQuery +{ +} diff --git a/domokits/local/modules/TheliaBlocks/Readme.md b/domokits/local/modules/TheliaBlocks/Readme.md new file mode 100755 index 0000000..903d8e0 --- /dev/null +++ b/domokits/local/modules/TheliaBlocks/Readme.md @@ -0,0 +1,306 @@ +# Création d'un plugin pour Thelia Blocks + +## Exemple : Création d'un plugin de citation + +### Introduction + +Ce plugin devra pouvoir afficher un champ pour indiquer le nom de l'auteur, et un second champ permettant d'insérer la citation en question. + +Dans cet exemple, nous allons créer le plugin depuis un module Thelia. +Si vous ne connaissez pas encore le fonctionnement des modules Thelia, nous vous conseillons vivement d'aller lire la [documentation officielle sur les modules Thelia](https://doc.thelia.net/en/documentation/modules/index.html). + +### Architecture du module + +Lors de cet exemple, nous utiliserons une architecture bien spécifique. +Vous êtes évidemment libre de structurer votre module comme vous le souhaitez. + +``` +. +├── ... +├── local/modules/ModuleCitation +│ ├── Config/ +│ │ ├── module.xml +│ │ └── config.xml +│ ├── Hook/ +│ │ └── BackHook.php +│ └── templates/ +│ │ ├── frontOffice/default/blocks/ +│ │ │ ├── blockCitation.html +│ │ │ └── ... +│ │ └── backOffice/default/ +│ │ │ ├── src/ +│ │ │ │ └── Citation.jsx +│ │ │ ├── tsup.config.js +│ │ │ └── index.js +│ ├── package.json +│ └── ModuleCitation.php +└── ... +``` + +### Installation des dépendances : + +```bash +npm install react tsup @openstudio/blocks-editor +``` + +### 1 - Création du composant + +Commençons par créer un fichier `Citation.jsx` et par définir les données initiales du plugin : + +```js +// ./templates/backOffice/default/src/Citation.jsx + +const initialData = { + author: "", + quote: "", +}; +``` + +Ensuite, nous allons pouvoir écrire le composant React permettant de visualiser le plugin dans l'éditeur de Thelia Blocks. + +:warning: Attention : un plugin Thelia Blocks prends toujours deux `props` : + +| Prop | Type | Description | +| :--------- | :--------- | :--------------------------------------------------------- | +| `data` | `any` | Objet contenant les données du plugin | +| `onUpdate` | `Function` | Fonction permettant de mettre à jour les données du plugin | + +Exemple : + +```jsx +// ./templates/backOffice/default/src/Citation.jsx + +const BlockQuoteComponent = ({ data, onUpdate }) => { + return ( +
+
+ + onUpdate({ ...data, author: e.target.value })} + /> +
+
+ + '; + break; + case 'view': + if (isset($_GET['type'])) { + $_SESSION['RF']['view_type'] = $_GET['type']; + } else { + response(trans('view type number missing').AddErrorLocation())->send(); + exit; + } + break; + case 'filter': + if (isset($_GET['type'])) { + if (isset($remember_text_filter) && $remember_text_filter) { + $_SESSION['RF']['filter'] = $_GET['type']; + } + } else { + response(trans('view type number missing').AddErrorLocation())->send(); + exit; + } + break; + case 'sort': + if (isset($_GET['sort_by'])) { + $_SESSION['RF']['sort_by'] = $_GET['sort_by']; + } + + if (isset($_GET['descending'])) { + $_SESSION['RF']['descending'] = $_GET['descending']; + } + break; + case 'image_size': // not used + $pos = strpos($_POST['path'], $upload_dir); + if ($pos !== false) { + $info = getimagesize(substr_replace($_POST['path'], $current_path, $pos, strlen($upload_dir))); + response($info)->send(); + exit; + } + break; + case 'save_img': + $info = pathinfo($_POST['name']); + + if ( + strpos($_POST['path'], '/') === 0 + || strpos($_POST['path'], '../') !== false + || strpos($_POST['path'], '..\\') !== false + || strpos($_POST['path'], './') === 0 + || (strpos($_POST['url'], 'http://s3.amazonaws.com/feather') !== 0 && strpos($_POST['url'], 'https://s3.amazonaws.com/feather') !== 0) + || $_POST['name'] != fix_filename($_POST['name'], $config) + || !in_array(strtolower($info['extension']), ['jpg', 'jpeg', 'png', 'svg']) + ) { + response(trans('wrong data').AddErrorLocation())->send(); + exit; + } + $image_data = get_file_by_url($_POST['url']); + if ($image_data === false) { + response(trans('Aviary_No_Save').AddErrorLocation())->send(); + exit; + } + + if (!checkresultingsize(strlen($image_data))) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + if ($ftp) { + $temp = tempnam('/tmp', 'RF'); + unlink($temp); + $temp .= '.'.substr(strrchr($_POST['url'], '.'), 1); + file_put_contents($temp, $image_data); + + $ftp->put($ftp_base_folder.$upload_dir.$_POST['path'].$_POST['name'], $temp, \FTP_BINARY); + + create_img($temp, $temp, 122, 91); + $ftp->put($ftp_base_folder.$ftp_thumbs_dir.$_POST['path'].$_POST['name'], $temp, \FTP_BINARY); + + unlink($temp); + } else { + file_put_contents($current_path.$_POST['path'].$_POST['name'], $image_data); + create_img($current_path.$_POST['path'].$_POST['name'], $thumbs_base_path.$_POST['path'].$_POST['name'], 122, 91); + // TODO something with this function cause its blowing my mind + new_thumbnails_creation( + $current_path.$_POST['path'], + $current_path.$_POST['path'].$_POST['name'], + $_POST['name'], + $current_path, + $relative_image_creation, + $relative_path_from_current_pos, + $relative_image_creation_name_to_prepend, + $relative_image_creation_name_to_append, + $relative_image_creation_width, + $relative_image_creation_height, + $relative_image_creation_option, + $fixed_image_creation, + $fixed_path_from_filemanager, + $fixed_image_creation_name_to_prepend, + $fixed_image_creation_to_append, + $fixed_image_creation_width, + $fixed_image_creation_height, + $fixed_image_creation_option + ); + } + break; + case 'extract': + if (strpos($_POST['path'], '/') === 0 + || strpos($_POST['path'], '../') !== false + || strpos($_POST['path'], '..\\') !== false + || strpos($_POST['path'], './') === 0) { + response(trans('wrong path'.AddErrorLocation()))->send(); + exit; + } + + if ($ftp) { + $path = $ftp_base_url.$upload_dir.$_POST['path']; + $base_folder = $ftp_base_url.$upload_dir.fix_dirname($_POST['path']).'/'; + } else { + $path = $current_path.$_POST['path']; + $base_folder = $current_path.fix_dirname($_POST['path']).'/'; + } + + $info = pathinfo($path); + + if ($ftp) { + $tempDir = tempdir(); + $temp = tempnam($tempDir, 'RF'); + unlink($temp); + $temp .= '.'.$info['extension']; + $handle = fopen($temp, 'w'); + fwrite($handle, file_get_contents($path)); + fclose($handle); + $path = $temp; + $base_folder = $tempDir.'/'; + } + + $info = pathinfo($path); + + switch ($info['extension']) { + case 'zip': + $zip = new ZipArchive(); + if ($zip->open($path) === true) { + // get total size + $sizeTotalFinal = 0; + for ($i = 0; $i < $zip->numFiles; ++$i) { + $aStat = $zip->statIndex($i); + $sizeTotalFinal += $aStat['size']; + } + if (!checkresultingsize($sizeTotalFinal)) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + + // make all the folders + for ($i = 0; $i < $zip->numFiles; ++$i) { + $OnlyFileName = $zip->getNameIndex($i); + $FullFileName = $zip->statIndex($i); + if (substr($FullFileName['name'], -1, 1) == '/') { + create_folder($base_folder.$FullFileName['name']); + } + } + // unzip into the folders + for ($i = 0; $i < $zip->numFiles; ++$i) { + $OnlyFileName = $zip->getNameIndex($i); + $FullFileName = $zip->statIndex($i); + + if (!(substr($FullFileName['name'], -1, 1) == '/')) { + $fileinfo = pathinfo($OnlyFileName); + if (in_array(strtolower($fileinfo['extension']), $ext)) { + copy('zip://'.$path.'#'.$OnlyFileName, $base_folder.$FullFileName['name']); + } + } + } + $zip->close(); + } else { + response(trans('Zip_No_Extract').AddErrorLocation())->send(); + exit; + } + + break; + case 'gz': + // No resulting size pre-control available + $p = new PharData($path); + $p->decompress(); // creates files.tar + + break; + case 'tar': + // No resulting size pre-control available + // unarchive from the tar + $phar = new PharData($path); + $phar->decompressFiles(); + $files = []; + check_files_extensions_on_phar($phar, $files, '', $ext); + $phar->extractTo($base_folder, $files, true); + + break; + default: + response(trans('Zip_Invalid').AddErrorLocation())->send(); + exit; + } + + if ($ftp) { + unlink($path); + $ftp->putAll($base_folder, '/'.$ftp_base_folder.$upload_dir.fix_dirname($_POST['path']), \FTP_BINARY); + deleteDir($base_folder); + } + + break; + case 'media_preview': + if ($ftp) { + $preview_file = $ftp_base_url.$upload_dir.$_GET['file']; + } else { + $preview_file = $current_path.$_GET['file']; + } + $info = pathinfo($preview_file); + ob_start(); + ?> + + + + + + + + + + send(); + exit; + + break; + case 'copy_cut': + if ($_POST['sub_action'] != 'copy' && $_POST['sub_action'] != 'cut') { + response(trans('wrong sub-action').AddErrorLocation())->send(); + exit; + } + + if (strpos($_POST['path'], '../') !== false + || strpos($_POST['path'], './') !== false + || strpos($_POST['path'], '..\\') !== false + || strpos($_POST['path'], '.\\') !== false) { + response(trans('wrong path'.AddErrorLocation()))->send(); + exit; + } + + if (trim($_POST['path']) == '') { + response(trans('no path').AddErrorLocation())->send(); + exit; + } + + $msg_sub_action = ($_POST['sub_action'] == 'copy' ? trans('Copy') : trans('Cut')); + $path = $current_path.$_POST['path']; + + if (is_dir($path)) { + // can't copy/cut dirs + if ($copy_cut_dirs === false) { + response(sprintf(trans('Copy_Cut_Not_Allowed'), $msg_sub_action, trans('Folders')).AddErrorLocation())->send(); + exit; + } + + [$sizeFolderToCopy,$fileNum,$foldersCount] = folder_info($path, false); + // size over limit + if ($copy_cut_max_size !== false && is_int($copy_cut_max_size)) { + if (($copy_cut_max_size * 1024 * 1024) < $sizeFolderToCopy) { + response(sprintf(trans('Copy_Cut_Size_Limit'), $msg_sub_action, $copy_cut_max_size).AddErrorLocation())->send(); + exit; + } + } + + // file count over limit + if ($copy_cut_max_count !== false && is_int($copy_cut_max_count)) { + if ($copy_cut_max_count < $fileNum) { + response(sprintf(trans('Copy_Cut_Count_Limit'), $msg_sub_action, $copy_cut_max_count).AddErrorLocation())->send(); + exit; + } + } + + if (!checkresultingsize($sizeFolderToCopy)) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + } else { + // can't copy/cut files + if ($copy_cut_files === false) { + response(sprintf(trans('Copy_Cut_Not_Allowed'), $msg_sub_action, trans('Files')).AddErrorLocation())->send(); + exit; + } + } + + $_SESSION['RF']['clipboard']['path'] = $_POST['path']; + $_SESSION['RF']['clipboard_action'] = $_POST['sub_action']; + break; + case 'clear_clipboard': + $_SESSION['RF']['clipboard'] = null; + $_SESSION['RF']['clipboard_action'] = null; + break; + case 'chmod': + if ($ftp) { + $path = $ftp_base_url.$upload_dir.$_POST['path']; + if ( + ($_POST['folder'] == 1 && $chmod_dirs === false) + || ($_POST['folder'] == 0 && $chmod_files === false) + || (is_function_callable('chmod') === false)) { + response(sprintf(trans('File_Permission_Not_Allowed'), is_dir($path) ? trans('Folders') : trans('Files'), 403).AddErrorLocation())->send(); + exit; + } + $info = $_POST['permissions']; + } else { + $path = $current_path.$_POST['path']; + if ( + (is_dir($path) && $chmod_dirs === false) + || (is_file($path) && $chmod_files === false) + || (is_function_callable('chmod') === false)) { + response(sprintf(trans('File_Permission_Not_Allowed'), is_dir($path) ? trans('Folders') : trans('Files'), 403).AddErrorLocation())->send(); + exit; + } + + $perms = fileperms($path) & 0777; + + $info = '-'; + + // Owner + $info .= (($perms & 0x0100) ? 'r' : '-'); + $info .= (($perms & 0x0080) ? 'w' : '-'); + $info .= (($perms & 0x0040) ? + (($perms & 0x0800) ? 's' : 'x') : + (($perms & 0x0800) ? 'S' : '-')); + + // Group + $info .= (($perms & 0x0020) ? 'r' : '-'); + $info .= (($perms & 0x0010) ? 'w' : '-'); + $info .= (($perms & 0x0008) ? + (($perms & 0x0400) ? 's' : 'x') : + (($perms & 0x0400) ? 'S' : '-')); + + // World + $info .= (($perms & 0x0004) ? 'r' : '-'); + $info .= (($perms & 0x0002) ? 'w' : '-'); + $info .= (($perms & 0x0001) ? + (($perms & 0x0200) ? 't' : 'x') : + (($perms & 0x0200) ? 'T' : '-')); + } + + $ret = '
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
r  w  x  
'.trans('User').'
'.trans('Group').'
'.trans('All').'
'; + + if (!$ftp && is_dir($path)) { + $ret .= '
'.trans('File_Permission_Recursive').'

+
    +
  • +
  • +
  • +
  • +
+
'; + } + + $ret .= '
'; + + response($ret)->send(); + exit; + + break; + case 'get_lang': + if (!file_exists('lang/languages.php')) { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; + } + + $languages = include 'lang/languages.php'; + if (!isset($languages) || !is_array($languages)) { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; + } + + $curr = $_SESSION['RF']['language']; + + $ret = ''; + + response($ret)->send(); + exit; + + break; + case 'change_lang': + $choosen_lang = (!empty($_POST['choosen_lang'])) ? $_POST['choosen_lang'] : 'en_EN'; + + if (array_key_exists($choosen_lang, $languages)) { + if (!file_exists('lang/'.$choosen_lang.'.php')) { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; + } + $_SESSION['RF']['language'] = $choosen_lang; + } + + break; + case 'cad_preview': + if ($ftp) { + $selected_file = $ftp_base_url.$upload_dir.$_GET['file']; + } else { + $selected_file = $current_path.$_GET['file']; + + if (!file_exists($selected_file)) { + response(trans('File_Not_Found').AddErrorLocation())->send(); + exit; + } + } + if ($ftp) { + $url_file = $selected_file; + } else { + $url_file = $base_url.$upload_dir.str_replace($current_path, '', $_GET['file']); + } + + $cad_url = urlencode($url_file); + $cad_html = ''; + $ret = $cad_html; + response($ret)->send(); + break; + case 'get_file': // preview or edit + $sub_action = $_GET['sub_action']; + $preview_mode = $_GET['preview_mode']; + + if ($sub_action != 'preview' && $sub_action != 'edit') { + response(trans('wrong action').AddErrorLocation())->send(); + exit; + } + + if ($ftp) { + $selected_file = ($sub_action == 'preview' ? $ftp_base_url.$upload_dir.$_GET['file'] : $ftp_base_url.$upload_dir.$_POST['path']); + } else { + $selected_file = ($sub_action == 'preview' ? $current_path.$_GET['file'] : $current_path.$_POST['path']); + + if (!file_exists($selected_file)) { + response(trans('File_Not_Found').AddErrorLocation())->send(); + exit; + } + } + + $info = pathinfo($selected_file); + + if ($preview_mode == 'text') { + $is_allowed = ($sub_action == 'preview' ? $preview_text_files : $edit_text_files); + $allowed_file_exts = ($sub_action == 'preview' ? $previewable_text_file_exts : $editable_text_file_exts); + } elseif ($preview_mode == 'viewerjs') { + $is_allowed = $viewerjs_enabled; + $allowed_file_exts = $viewerjs_file_exts; + } elseif ($preview_mode == 'google') { + $is_allowed = $googledoc_enabled; + $allowed_file_exts = $googledoc_file_exts; + } + + if (!isset($allowed_file_exts) || !is_array($allowed_file_exts)) { + $allowed_file_exts = []; + } + + if (!in_array($info['extension'], $allowed_file_exts) + || !isset($is_allowed) + || $is_allowed === false + || (!$ftp && !is_readable($selected_file)) + ) { + response(sprintf(trans('File_Open_Edit_Not_Allowed'), $sub_action == 'preview' ? strtolower(trans('Open')) : strtolower(trans('Edit'))).AddErrorLocation())->send(); + exit; + } + if ($sub_action == 'preview') { + if ($preview_mode == 'text') { + // get and sanities + $data = file_get_contents($selected_file); + $data = htmlspecialchars(htmlspecialchars_decode($data)); + $ret = ''; + + if (!in_array($info['extension'], $previewable_text_file_exts_no_prettify)) { + $ret .= ''; + $ret .= '
'.$data.'
'; + } else { + $ret .= '
'.$data.'
'; + } + } elseif ($preview_mode == 'google' || $preview_mode == 'viewerjs') { + if ($ftp) { + $url_file = $selected_file; + } else { + $url_file = $base_url.$upload_dir.str_replace($current_path, '', $_GET['file']); + } + + $googledoc_url = urlencode($url_file); + $googledoc_html = ''; + $ret = $googledoc_html; + } + } else { + $data = stripslashes(htmlspecialchars(file_get_contents($selected_file))); + $ret = ''; + } + + response($ret)->send(); + exit; + + break; + default: + response(trans('no action passed').AddErrorLocation())->send(); + exit; + } +} else { + response(trans('no action passed').AddErrorLocation())->send(); + exit; +} +?> diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/.htaccess b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/.htaccess new file mode 100755 index 0000000..14249c5 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/.htaccess @@ -0,0 +1 @@ +Deny from all \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/config.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/config.php new file mode 100755 index 0000000..1306aa6 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/config/config.php @@ -0,0 +1,593 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +mb_internal_encoding('UTF-8'); +mb_http_output('UTF-8'); +mb_language('uni'); +mb_regex_encoding('UTF-8'); +ob_start('mb_output_handler'); +//date_default_timezone_set('Europe/Rome'); + +use Symfony\Component\Filesystem\Filesystem; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Core\Thelia; +use Thelia\Model\ConfigQuery; + +function generateFolder($env): void +{ + $webMediaPath = THELIA_WEB_DIR.'media'; + $webMediaEnvPath = null; + if ($env !== 'prod') { + //Remove separtion between dev and prod in particular environment + $env = str_replace('_dev', '', $env); + $webMediaEnvPath = $webMediaPath.DS.$env; + } + + $fileSystem = new Filesystem(); + + // Create the media directory in the web root , if required + if (null !== $webMediaEnvPath) { + if (false === $fileSystem->exists($webMediaEnvPath)) { + $fileSystem->mkdir($webMediaEnvPath.DS.'upload'); + $fileSystem->mkdir($webMediaEnvPath.DS.'thumbs'); + } + } else { + if (false === $fileSystem->exists($webMediaPath)) { + $fileSystem->mkdir($webMediaPath.DS.'upload'); + $fileSystem->mkdir($webMediaPath.DS.'thumbs'); + } + } +} + +$env = 'prod'; + +if (file_exists(__DIR__.'/../../../../../../../../bootstrap.php')) { + // Symlinked with thelia-project + require_once __DIR__.'/../../../../../../../../bootstrap.php'; +} elseif (file_exists(__DIR__.'/../../../../bootstrap.php')) { + // Hard copy with thelia-project + require_once __DIR__.'/../../../../bootstrap.php'; +} elseif (file_exists(__DIR__.'/../../../../../../../../vendor/autoload.php')) { + // Symlinked with std install + require_once __DIR__.'/../../../../../../../../vendor/autoload.php'; +} elseif (file_exists(__DIR__.'/../../../../vendor/autoload.php')) { + // Hard copy with std install + require_once __DIR__.'/../../../../vendor/autoload.php'; +} + +(new \Symfony\Component\Dotenv\Dotenv())->bootEnv(__DIR__.'/../../../../../../../../.env'); + +$thelia = new App\Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); +$request = Request::createFromGlobals(); +$thelia->boot(); + +/** @var \Symfony\Component\DependencyInjection\ContainerInterface $container */ +$container = $thelia->getContainer(); + +$eventDispatcher = $container->get('event_dispatcher'); +$container->get('thelia.translator'); +$container->get('thelia.url.manager'); +$container->get('request_stack')->push($request); +$event = new \Thelia\Core\Event\SessionEvent(THELIA_CACHE_DIR.$env, false, $env); + +$eventDispatcher->dispatch($event, \Thelia\Core\TheliaKernelEvents::SESSION); +$session = $event->getSession(); +$session->start(); +$request->setSession($session); + +/** @var \Thelia\Core\Security\SecurityContext $securityContext */ +$securityContext = $container->get('thelia.securityContext'); + +// We just check the current user has the ADMIN role. +$isGranted = $securityContext->isGranted(['ADMIN'], [], [], []); + +if (false === $isGranted) { + echo "Sorry, it seems that you're not allowed to use this function. ADMIN role is required."; + + exit; +} + +//------------------------------------------------------------------------------ +// DO NOT COPY THESE VARIABLES IN FOLDERS config.php FILES +//------------------------------------------------------------------------------ + +//********************** +//Path configuration +//********************** + +// In this configuration the media folder is located in the /web directory. + +// base url of site (without final /). if you prefer relative urls leave empty. +$base_url = rtrim(ConfigQuery::getConfiguredShopUrl(), '/'); + +// Argh, url_site is not defined ?! +if (empty($base_url)) { + // A we did not used the router to access this dialog, we cannot use the URL class. Use the good old method. + $base_url = $request->getSchemeAndHttpHost().preg_replace('!/tinymce/filemanager/dialog.php.*$!', '', $_SERVER['REQUEST_URI']); +} + +//Check for backward compatibility +if ($env !== 'prod') { + // path from base_url to base of upload folder for current env (with start and final /) + $upload_dir = '/media/'.$env.'/upload/'; + + // path from base_url to base of upload folder for current env (with start and final /) + $thumbs_dir = '/media/'.$env.'/thumbs/'; + + // path to file manager folder to upload folder for current env (with final /) + $current_path = THELIA_WEB_DIR.'media'.DS.$env.DS.'upload'.DS; + + // path to file manager folder to thumbs folder for current env (with final /) + // WARNING: thumbs folder should not be inside the upload folder + $thumbs_base_path = THELIA_WEB_DIR.'media'.DS.$env.DS.'thumbs'.DS; +} else { + // path from base_url to base of upload folder (with start and final /) + $upload_dir = '/media/upload/'; + + // path from base_url to base of upload folder (with start and final /) + $thumbs_dir = '/media/thumbs/'; + + // path to file manager folder to upload folder (with final /) + $current_path = THELIA_WEB_DIR.'media'.DS.'upload'.DS; + + // path to file manager folder to thumbs folder (with final /) + // WARNING: thumbs folder should not be inside the upload folder + $thumbs_base_path = THELIA_WEB_DIR.'media'.DS.'thumbs'.DS; +} + +generateFolder($env); + +// path from base_url to filemanager folder (with start and final /) +$filemanager_dir = '/tinymce/filemanager/'; + +// Set the language to the back-office current language, if it is available +$current_locale = $request->getSession()->getLang()->getLocale(); + +if (file_exists(__DIR__.DS.'..'.DS.'lang.'.DS.$current_locale.'.php')) { + $default_language = $current_locale; +} else { + $default_language = 'en_EN'; +} + +/* +|-------------------------------------------------------------------------- +| Optional security +|-------------------------------------------------------------------------- +| +| if set to true only those will access RF whose url contains the access key(akey) like: +| +| in tinymce a new parameter added: filemanager_access_key:"myPrivateKey" +| example tinymce config: +| +| tiny init ... +| external_filemanager_path:"../filemanager/", +| filemanager_title:"Filemanager" , +| filemanager_access_key:"myPrivateKey" , +| ... +| +*/ + +define('USE_ACCESS_KEYS', false); // TRUE or FALSE + +/* +|-------------------------------------------------------------------------- +| DON'T COPY THIS VARIABLES IN FOLDERS config.php FILES +|-------------------------------------------------------------------------- +*/ + +define('DEBUG_ERROR_MESSAGE', false); // TRUE or FALSE + +/* +|-------------------------------------------------------------------------- +| Path configuration +|-------------------------------------------------------------------------- +| In this configuration the folder tree is +| root +| |- source <- upload folder +| |- thumbs <- thumbnail folder [must have write permission (755)] +| |- filemanager +| |- js +| | |- tinymce +| | | |- plugins +| | | | |- responsivefilemanager +| | | | | |- plugin.min.js +*/ + +$config = [ + /* + |-------------------------------------------------------------------------- + | DON'T TOUCH (base url (only domain) of site). + |-------------------------------------------------------------------------- + | + | without final / (DON'T TOUCH) + | + */ + 'base_url' => $base_url, + + /* + |-------------------------------------------------------------------------- + | path from base_url to base of upload folder + |-------------------------------------------------------------------------- + | + | with start and final / + | + */ + 'upload_dir' => $upload_dir, + /* + |-------------------------------------------------------------------------- + | relative path from filemanager folder to upload folder + |-------------------------------------------------------------------------- + | + | with final / + | + */ + 'current_path' => $current_path, + + /* + |-------------------------------------------------------------------------- + | relative path from filemanager folder to thumbs folder + |-------------------------------------------------------------------------- + | + | with final / + | DO NOT put inside upload folder + | + */ + 'thumbs_base_path' => $thumbs_base_path, + + /* + |-------------------------------------------------------------------------- + | FTP configuration BETA VERSION + |-------------------------------------------------------------------------- + | + | If you want enable ftp use write these parametres otherwise leave empty + | Remember to set base_url properly to point in the ftp server domain and + | upload dir will be ftp_base_folder + upload_dir so without final / + | + */ + 'ftp_host' => false, + 'ftp_user' => 'user', + 'ftp_pass' => 'pass', + 'ftp_base_folder' => 'base_folder', + 'ftp_base_url' => 'http://site to ftp root', + /* -------------------------------------------------------------------------- + | path from ftp_base_folder to base of thumbs folder with start and final | + |--------------------------------------------------------------------------*/ + 'ftp_thumbs_dir' => '/thumbs/', + 'ftp_ssl' => false, + 'ftp_port' => 21, + + // 'ftp_host' => "s108707.gridserver.com", + // 'ftp_user' => "test@responsivefilemanager.com", + // 'ftp_pass' => "Test.1234", + // 'ftp_base_folder' => "/domains/responsivefilemanager.com/html", + + /* + |-------------------------------------------------------------------------- + | Access keys + |-------------------------------------------------------------------------- + | + | add access keys eg: array('myPrivateKey', 'someoneElseKey'); + | keys should only containt (a-z A-Z 0-9 \ . _ -) characters + | if you are integrating lets say to a cms for admins, i recommend making keys randomized something like this: + | $username = 'Admin'; + | $salt = 'dsflFWR9u2xQa' (a hard coded string) + | $akey = md5($username.$salt); + | DO NOT use 'key' as access key! + | Keys are CASE SENSITIVE! + | + */ + + 'access_keys' => [], + + //-------------------------------------------------------------------------------------------------------- + // YOU CAN COPY AND CHANGE THESE VARIABLES INTO FOLDERS config.php FILES TO CUSTOMIZE EACH FOLDER OPTIONS + //-------------------------------------------------------------------------------------------------------- + + /* + |-------------------------------------------------------------------------- + | Maximum size of all files in source folder + |-------------------------------------------------------------------------- + | + | in Megabytes + | + */ + 'MaxSizeTotal' => false, + + /* + |-------------------------------------------------------------------------- + | Maximum upload size + |-------------------------------------------------------------------------- + | + | in Megabytes + | + */ + 'MaxSizeUpload' => 100, + + /* + |-------------------------------------------------------------------------- + | File and Folder permission + |-------------------------------------------------------------------------- + | + */ + 'fileFolderPermission' => 0755, + + /* + |-------------------------------------------------------------------------- + | default language file name + |-------------------------------------------------------------------------- + */ + 'default_language' => $default_language, + + /* + |-------------------------------------------------------------------------- + | Icon theme + |-------------------------------------------------------------------------- + | + | Default available: ico and ico_dark + | Can be set to custom icon inside filemanager/img + | + */ + 'icon_theme' => 'ico', + + //Show or not total size in filemanager (is possible to greatly increase the calculations) + 'show_total_size' => false, + //Show or not show folder size in list view feature in filemanager (is possible, if there is a large folder, to greatly increase the calculations) + 'show_folder_size' => false, + //Show or not show sorting feature in filemanager + 'show_sorting_bar' => true, + //Show or not show filters button in filemanager + 'show_filter_buttons' => true, + //Show or not language selection feature in filemanager + 'show_language_selection' => true, + //active or deactive the transliteration (mean convert all strange characters in A..Za..z0..9 characters) + 'transliteration' => false, + //convert all spaces on files name and folders name with $replace_with variable + 'convert_spaces' => false, + //convert all spaces on files name and folders name this value + 'replace_with' => '_', + //convert to lowercase the files and folders name + 'lower_case' => false, + + //Add ?484899493349 (time value) to returned images to prevent cache + 'add_time_to_img' => false, + + // -1: There is no lazy loading at all, 0: Always lazy-load images, 0+: The minimum number of the files in a directory + // when lazy loading should be turned on. + 'lazy_loading_file_number_threshold' => -1, + + //******************************************* + //Images limit and resizing configuration + //******************************************* + + // set maximum pixel width and/or maximum pixel height for all images + // If you set a maximum width or height, oversized images are converted to those limits. Images smaller than the limit(s) are unaffected + // if you don't need a limit set both to 0 + 'image_max_width' => 0, + 'image_max_height' => 0, + 'image_max_mode' => 'auto', + /* + # $option: 0 / exact = defined size; + # 1 / portrait = keep aspect set height; + # 2 / landscape = keep aspect set width; + # 3 / auto = auto; + # 4 / crop= resize and crop; + */ + + //Automatic resizing // + // If you set $image_resizing to TRUE the script converts all uploaded images exactly to image_resizing_width x image_resizing_height dimension + // If you set width or height to 0 the script automatically calculates the other dimension + // Is possible that if you upload very big images the script not work to overcome this increase the php configuration of memory and time limit + 'image_resizing' => false, + 'image_resizing_width' => 0, + 'image_resizing_height' => 0, + 'image_resizing_mode' => 'auto', // same as $image_max_mode + 'image_resizing_override' => false, + // If set to TRUE then you can specify bigger images than $image_max_width & height otherwise if image_resizing is + // bigger than $image_max_width or height then it will be converted to those values + + //****************** + // + // WATERMARK IMAGE + // + //Watermark url or false + 'image_watermark' => false, + // Could be a pre-determined position such as: + // tl = top left, + // t = top (middle), + // tr = top right, + // l = left, + // m = middle, + // r = right, + // bl = bottom left, + // b = bottom (middle), + // br = bottom right + // Or, it could be a co-ordinate position such as: 50x100 + 'image_watermark_position' => 'br', + // padding: If using a pre-determined position you can + // adjust the padding from the edges by passing an amount + // in pixels. If using co-ordinates, this value is ignored. + 'image_watermark_padding' => 0, + + //****************** + // Default layout setting + // + // 0 => boxes + // 1 => detailed list (1 column) + // 2 => columns list (multiple columns depending on the width of the page) + // YOU CAN ALSO PASS THIS PARAMETERS USING SESSION VAR => $_SESSION['RF']["VIEW"]= + // + //****************** + 'default_view' => 0, + + //set if the filename is truncated when overflow first row + 'ellipsis_title_after_first_row' => true, + + //************************* + //Permissions configuration + //****************** + 'delete_files' => true, + 'create_folders' => true, + 'delete_folders' => true, + 'upload_files' => true, + 'rename_files' => true, + 'rename_folders' => true, + 'duplicate_files' => true, + 'copy_cut_files' => true, // for copy/cut files + 'copy_cut_dirs' => true, // for copy/cut directories + 'chmod_files' => true, // change file permissions + 'chmod_dirs' => true, // change folder permissions + 'preview_text_files' => true, // eg.: txt, log etc. + 'edit_text_files' => true, // eg.: txt, log etc. + 'create_text_files' => true, // only create files with exts. defined in $editable_text_file_exts + + // you can preview these type of files if $preview_text_files is true + 'previewable_text_file_exts' => ['bsh', 'c', 'css', 'cc', 'cpp', 'cs', 'csh', 'cyc', 'cv', 'htm', 'html', 'java', 'js', 'm', 'mxml', 'perl', 'pl', 'pm', 'py', 'rb', 'sh', 'xhtml', 'xml', 'xsl'], + 'previewable_text_file_exts_no_prettify' => ['txt', 'log'], + + // you can edit these type of files if $edit_text_files is true (only text based files) + // you can create these type of files if $create_text_files is true (only text based files) + // if you want you can add html,css etc. + // but for security reasons it's NOT RECOMMENDED! + 'editable_text_file_exts' => ['txt', 'log', 'xml', 'html', 'css', 'htm', 'js'], + + // Preview with Google Documents + 'googledoc_enabled' => true, + 'googledoc_file_exts' => ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'], + + // Preview with Viewer.js + 'viewerjs_enabled' => true, + 'viewerjs_file_exts' => ['pdf', 'odt', 'odp', 'ods'], + + // defines size limit for paste in MB / operation + // set 'FALSE' for no limit + 'copy_cut_max_size' => 100, + // defines file count limit for paste / operation + // set 'FALSE' for no limit + 'copy_cut_max_count' => 200, + //IF any of these limits reached, operation won't start and generate warning + + //********************** + //Allowed extensions (lowercase insert) + //********************** + 'ext_img' => ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'svg'], //Images + 'ext_file' => ['svg', 'doc', 'docx', 'rtf', 'pdf', 'xls', 'xlsx', 'txt', 'csv', 'html', 'xhtml', 'psd', 'sql', 'log', 'fla', 'xml', 'ade', 'adp', 'mdb', 'accdb', 'ppt', 'pptx', 'odt', 'ots', 'ott', 'odb', 'odg', 'otp', 'otg', 'odf', 'ods', 'odp', 'css', 'ai', 'kmz', 'dwg', 'dxf', 'hpgl', 'plt', 'spl', 'step', 'stp', 'iges', 'igs', 'sat', 'cgm'], //Files + 'ext_video' => ['mov', 'mpeg', 'm4v', 'mp4', 'avi', 'mpg', 'wma', 'flv', 'webm'], //Video + 'ext_music' => ['mp3', 'mpga', 'm4a', 'ac3', 'aiff', 'mid', 'ogg', 'wav'], //Audio + 'ext_misc' => ['zip', 'rar', 'gz', 'tar', 'iso', 'dmg'], //Archives + + /****************** + * AVIARY config + *******************/ + 'aviary_active' => true, + 'aviary_apiKey' => '2444282ef4344e3dacdedc7a78f8877d', + 'aviary_language' => 'en', + 'aviary_theme' => 'light', + 'aviary_tools' => 'all', + 'aviary_maxSize' => '1400', + // Add or modify the Aviary options below as needed - they will be json encoded when added to the configuration so arrays can be utilized as needed + + //The filter and sorter are managed through both javascript and php scripts because if you have a lot of + //file in a folder the javascript script can't sort all or filter all, so the filemanager switch to php script. + //The plugin automatic swich javascript to php when the current folder exceeds the below limit of files number + 'file_number_limit_js' => 500, + + //********************** + // Hidden files and folders + //********************** + // set the names of any folders you want hidden (eg "hidden_folder1", "hidden_folder2" ) Remember all folders with these names will be hidden (you can set any exceptions in config.php files on folders) + 'hidden_folders' => [], + // set the names of any files you want hidden. Remember these names will be hidden in all folders (eg "this_document.pdf", "that_image.jpg" ) + 'hidden_files' => ['config.php'], + + /******************* + * URL upload + *******************/ + 'url_upload' => true, + + /******************* + * JAVA upload + *******************/ + 'java_upload' => true, + 'JAVAMaxSizeUpload' => 200, //Gb + + //************************************ + //Thumbnail for external use creation + //************************************ + + // New image resized creation with fixed path from filemanager folder after uploading (thumbnails in fixed mode) + // If you want create images resized out of upload folder for use with external script you can choose this method, + // You can create also more than one image at a time just simply add a value in the array + // Remember than the image creation respect the folder hierarchy so if you are inside source/test/test1/ the new image will create at + // path_from_filemanager/test/test1/ + // PS if there isn't write permission in your destination folder you must set it + // + 'fixed_image_creation' => false, //activate or not the creation of one or more image resized with fixed path from filemanager folder + 'fixed_path_from_filemanager' => ['../test/', '../test1/'], //fixed path of the image folder from the current position on upload folder + 'fixed_image_creation_name_to_prepend' => ['', 'test_'], //name to prepend on filename + 'fixed_image_creation_to_append' => ['_test', ''], //name to appendon filename + 'fixed_image_creation_width' => [300, 400], //width of image (you can leave empty if you set height) + 'fixed_image_creation_height' => [200, ''], //height of image (you can leave empty if you set width) + /* + # $option: 0 / exact = defined size; + # 1 / portrait = keep aspect set height; + # 2 / landscape = keep aspect set width; + # 3 / auto = auto; + # 4 / crop= resize and crop; + */ + 'fixed_image_creation_option' => ['crop', 'auto'], //set the type of the crop + + // New image resized creation with relative path inside to upload folder after uploading (thumbnails in relative mode) + // With Responsive filemanager you can create automatically resized image inside the upload folder, also more than one at a time + // just simply add a value in the array + // The image creation path is always relative so if i'm inside source/test/test1 and I upload an image, the path start from here + // + 'relative_image_creation' => false, //activate or not the creation of one or more image resized with relative path from upload folder + 'relative_path_from_current_pos' => ['./', './'], //relative path of the image folder from the current position on upload folder + 'relative_image_creation_name_to_prepend' => ['', ''], //name to prepend on filename + 'relative_image_creation_name_to_append' => ['_thumb', '_thumb1'], //name to append on filename + 'relative_image_creation_width' => [300, 400], //width of image (you can leave empty if you set height) + 'relative_image_creation_height' => [200, ''], //height of image (you can leave empty if you set width) + /* + # $option: 0 / exact = defined size; + # 1 / portrait = keep aspect set height; + # 2 / landscape = keep aspect set width; + # 3 / auto = auto; + # 4 / crop= resize and crop; + */ + 'relative_image_creation_option' => ['crop', 'crop'], //set the type of the crop + + // Remember text filter after close filemanager for future session + 'remember_text_filter' => false, +]; + +return array_merge( + $config, + [ + 'MaxSizeUpload' => ((int) (ini_get('post_max_size')) < $config['MaxSizeUpload']) + ? (int) (ini_get('post_max_size')) : $config['MaxSizeUpload'], + 'ext' => array_merge( + $config['ext_img'], + $config['ext_file'], + $config['ext_misc'], + $config['ext_video'], + $config['ext_music'] + ), + // For a list of options see: https://developers.aviary.com/docs/web/setup-guide#constructor-config + 'aviary_defaults_config' => [ + 'apiKey' => $config['aviary_apiKey'], + 'language' => $config['aviary_language'], + 'theme' => $config['aviary_theme'], + 'tools' => $config['aviary_tools'], + 'maxSize' => $config['aviary_maxSize'], + ], + ] +); diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/css/style.css b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/css/style.css new file mode 100644 index 0000000..bdda797 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/css/style.css @@ -0,0 +1,36 @@ +/*! + * Bootstrap v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none}html{-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:#333 dotted thin;outline:-webkit-focus-ring-color auto;outline-offset:-2px}a:active,a:hover{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{max-width:100%;height:auto;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button,input[type=button],input[type=checkbox],input[type=radio],input[type=reset],input[type=submit],label,select{cursor:pointer}input[type=search]{box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}textarea{overflow:auto}@media print{*{text-shadow:none!important;color:#000!important;background:0 0!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}}body{line-height:20px;color:#333;background-color:#fff}a:focus,a:hover{color:#005580;text-decoration:underline}.img-rounded{border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);box-shadow:0 1px 3px rgba(0,0,0,.1)}.img-circle{border-radius:500px}.row{margin-left:-20px}.row:after,.row:before{display:table;content:"";line-height:0}.row:after{clear:both}[class*=span]{float:left;min-height:1px;margin-left:20px}.container,.navbar-fixed-bottom .container,.navbar-fixed-top .container,.navbar-static-top .container,.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%}.row-fluid:after,.row-fluid:before{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*=span]{display:block;width:100%;min-height:30px;box-sizing:border-box;float:left;margin-left:2.12765957%}.row-fluid [class*=span]:first-child{margin-left:0}.row-fluid .controls-row [class*=span]+[class*=span]{margin-left:2.12765957%}.row-fluid .span12{width:100%}.row-fluid .span11{width:91.4893617%}.row-fluid .span10{width:82.9787234%}.row-fluid .span9{width:74.46808511%}.row-fluid .span8{width:65.95744681%}.row-fluid .span7{width:57.44680851%}.row-fluid .span6{width:48.93617021%}.row-fluid .span5{width:40.42553191%}.row-fluid .span4{width:31.91489362%}.row-fluid .span3{width:23.40425532%}.row-fluid .span2{width:14.89361702%}.row-fluid .span1{width:6.38297872%}.row-fluid .offset12{margin-left:104.25531915%}.row-fluid .offset12:first-child{margin-left:102.12765957%}.row-fluid .offset11{margin-left:95.74468085%}.row-fluid .offset11:first-child{margin-left:93.61702128%}.row-fluid .offset10{margin-left:87.23404255%}.row-fluid .offset10:first-child{margin-left:85.10638298%}.row-fluid .offset9{margin-left:78.72340426%}.row-fluid .offset9:first-child{margin-left:76.59574468%}.row-fluid .offset8{margin-left:70.21276596%}.row-fluid .offset8:first-child{margin-left:68.08510638%}.row-fluid .offset7{margin-left:61.70212766%}.row-fluid .offset7:first-child{margin-left:59.57446809%}.row-fluid .offset6{margin-left:53.19148936%}.row-fluid .offset6:first-child{margin-left:51.06382979%}.row-fluid .offset5{margin-left:44.68085106%}.row-fluid .offset5:first-child{margin-left:42.55319149%}.row-fluid .offset4{margin-left:36.17021277%}.row-fluid .offset4:first-child{margin-left:34.04255319%}.row-fluid .offset3{margin-left:27.65957447%}.row-fluid .offset3:first-child{margin-left:25.53191489%}.row-fluid .offset2{margin-left:19.14893617%}.row-fluid .offset2:first-child{margin-left:17.0212766%}.row-fluid .offset1{margin-left:10.63829787%}.row-fluid .offset1:first-child{margin-left:8.5106383%}.row-fluid [class*=span].hide,[class*=span].hide{display:none}.row-fluid [class*=span].pull-right,[class*=span].pull-right{float:right}.container{margin-right:auto;margin-left:auto}.container:after,.container:before{display:table;content:"";line-height:0}.container:after{clear:both}.container-fluid:after,.container-fluid:before{display:table;content:"";line-height:0}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:700}em{font-style:italic}cite{font-style:normal}.muted{color:#999}a.muted:focus,a.muted:hover{color:grey}.text-warning{color:#c09853}a.text-warning:focus,a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:focus,a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:focus,a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:focus,a.text-success:hover{color:#356635}.text-left{text-align:left}.text-right{text-align:right}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:700;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:400;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small,h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ol,ul{padding:0;margin:0 0 10px 25px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}li{line-height:20px}ol.inline,ol.unstyled,ul.inline,ul.unstyled{margin-left:0;list-style:none}ol.inline>li,ul.inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-bottom:20px}dd,dt{line-height:20px}dt{font-weight:700}dd{margin-left:10px}.dl-horizontal:after,.dl-horizontal:before{display:table;content:"";line-height:0}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:17.5px;font-weight:300;line-height:1.25}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}blockquote:after,blockquote:before,q:after,q:before{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8;white-space:nowrap}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px}pre code{padding:0;color:inherit;white-space:pre;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}button,input,label,select,textarea{font-size:14px;font-weight:400;line-height:20px}button,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}.uneditable-input,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;border-radius:4px;vertical-align:middle}.uneditable-input,input,textarea{width:206px}textarea{height:auto}.uneditable-input,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{background-color:#fff;border:1px solid #ccc;box-shadow:inset 0 1px 1px rgba(0,0,0,.075);transition:border linear .2s,box-shadow linear .2s}.uneditable-input:focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,textarea:focus{border-color:rgba(82,168,236,.8);outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(82,168,236,.6)}input[type=checkbox],input[type=radio]{margin:4px 0 0;line-height:normal}input[type=button],input[type=checkbox],input[type=file],input[type=image],input[type=radio],input[type=reset],input[type=submit]{width:auto}input[type=file],select{height:30px;line-height:30px}select{width:220px;border:1px solid #ccc;background-color:#fff}select[multiple],select[size]{height:auto}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus,select:focus{outline:#333 dotted thin;outline:-webkit-focus-ring-color auto;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;background-color:#fcfcfc;border-color:#ccc;box-shadow:inset 0 1px 2px rgba(0,0,0,.025);cursor:not-allowed}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.checkbox,.radio{min-height:20px;padding-left:20px}.checkbox input[type=checkbox],.radio input[type=radio]{float:left;margin-left:-20px}.controls>.checkbox:first-child,.controls>.radio:first-child{padding-top:5px}.checkbox.inline,.radio.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.checkbox.inline+.checkbox.inline,.radio.inline+.radio.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}.row-fluid .uneditable-input[class*=span],.row-fluid input[class*=span],.row-fluid select[class*=span],.row-fluid textarea[class*=span],.uneditable-input[class*=span],input[class*=span],select[class*=span],textarea[class*=span]{float:none;margin-left:0}.input-append .uneditable-input[class*=span],.input-append input[class*=span],.input-prepend .uneditable-input[class*=span],.input-prepend input[class*=span],.row-fluid .input-append [class*=span],.row-fluid .input-prepend [class*=span],.row-fluid .uneditable-input[class*=span],.row-fluid input[class*=span],.row-fluid select[class*=span],.row-fluid textarea[class*=span]{display:inline-block}.uneditable-input,input,textarea{margin-left:0}.controls-row [class*=span]+[class*=span]{margin-left:20px}.uneditable-input.span12,input.span12,textarea.span12{width:926px}.uneditable-input.span11,input.span11,textarea.span11{width:846px}.uneditable-input.span10,input.span10,textarea.span10{width:766px}.uneditable-input.span9,input.span9,textarea.span9{width:686px}.uneditable-input.span8,input.span8,textarea.span8{width:606px}.uneditable-input.span7,input.span7,textarea.span7{width:526px}.uneditable-input.span6,input.span6,textarea.span6{width:446px}.uneditable-input.span5,input.span5,textarea.span5{width:366px}.uneditable-input.span4,input.span4,textarea.span4{width:286px}.uneditable-input.span3,input.span3,textarea.span3{width:206px}.uneditable-input.span2,input.span2,textarea.span2{width:126px}.uneditable-input.span1,input.span1,textarea.span1{width:46px}.controls-row:after,.controls-row:before{display:table;content:"";line-height:0}.controls-row:after{clear:both}.controls-row [class*=span],.row-fluid .controls-row [class*=span]{float:left}.controls-row .checkbox[class*=span],.controls-row .radio[class*=span]{padding-top:5px}input[disabled],input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type=checkbox][disabled],input[type=checkbox][readonly],input[type=radio][disabled],input[type=radio][readonly]{background-color:transparent}.control-group.warning .checkbox,.control-group.warning .control-label,.control-group.warning .help-block,.control-group.warning .help-inline,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #dbc59e}.control-group.warning .input-append .add-on,.control-group.warning .input-prepend .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error .checkbox,.control-group.error .control-label,.control-group.error .help-block,.control-group.error .help-inline,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #d59392}.control-group.error .input-append .add-on,.control-group.error .input-prepend .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success .checkbox,.control-group.success .control-label,.control-group.success .help-block,.control-group.success .help-inline,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7aba7b}.control-group.success .input-append .add-on,.control-group.success .input-prepend .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info .checkbox,.control-group.info .control-label,.control-group.info .help-block,.control-group.info .help-inline,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7ab5d3}.control-group.info .input-append .add-on,.control-group.info .input-prepend .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e9322d;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5}.form-actions:after,.form-actions:before{display:table;content:"";line-height:0}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;vertical-align:middle;padding-left:5px}.input-append,.input-prepend{display:inline-block;margin-bottom:10px;vertical-align:middle;font-size:0;white-space:nowrap}.input-append .dropdown-menu,.input-append .popover,.input-append .uneditable-input,.input-append input,.input-append select,.input-prepend .dropdown-menu,.input-prepend .popover,.input-prepend .uneditable-input,.input-prepend input,.input-prepend select{font-size:14px}.input-append .uneditable-input,.input-append input,.input-append select,.input-prepend .uneditable-input,.input-prepend input,.input-prepend select{position:relative;margin-bottom:0;vertical-align:top;border-radius:0 4px 4px 0}.input-append .uneditable-input:focus,.input-append input:focus,.input-append select:focus,.input-prepend .uneditable-input:focus,.input-prepend input:focus,.input-prepend select:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:400;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-append .btn,.input-append .btn-group>.dropdown-toggle,.input-prepend .add-on,.input-prepend .btn,.input-prepend .btn-group>.dropdown-toggle{vertical-align:top;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-append .uneditable-input,.input-append input,.input-append select,.input-prepend .add-on:first-child,.input-prepend .btn:first-child{border-radius:4px 0 0 4px}.input-append .uneditable-input+.btn-group .btn:last-child,.input-append input+.btn-group .btn:last-child,.input-append select+.btn-group .btn:last-child{border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-prepend.input-append .uneditable-input,.input-prepend.input-append input,.input-prepend.input-append select{border-radius:0}.input-prepend.input-append .uneditable-input+.btn-group .btn,.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn{border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-left:14px;margin-bottom:0;border-radius:15px}.form-search .input-append .search-query{border-radius:14px 0 0 14px}.form-search .input-append .btn,.form-search .input-prepend .search-query{border-radius:0 14px 14px 0}.form-search .input-prepend .btn{border-radius:14px 0 0 14px}.form-horizontal .help-inline,.form-horizontal .input-append,.form-horizontal .input-prepend,.form-horizontal .uneditable-input,.form-horizontal input,.form-horizontal select,.form-horizontal textarea,.form-inline .help-inline,.form-inline .input-append,.form-inline .input-prepend,.form-inline .uneditable-input,.form-inline input,.form-inline select,.form-inline textarea,.form-search .help-inline,.form-search .input-append,.form-search .input-prepend,.form-search .uneditable-input,.form-search input,.form-search select,.form-search textarea{display:inline-block;margin-bottom:0;vertical-align:middle}.form-horizontal .hide,.form-inline .hide,.form-search .hide{display:none}.form-inline .btn-group,.form-inline label,.form-search .btn-group,.form-search label{display:inline-block}.form-inline .input-append,.form-inline .input-prepend,.form-search .input-append,.form-search .input-prepend{margin-bottom:0}.form-inline .checkbox,.form-inline .radio,.form-search .checkbox,.form-search .radio{padding-left:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio],.form-search .checkbox input[type=checkbox],.form-search .radio input[type=radio]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px}.form-horizontal .control-group:after,.form-horizontal .control-group:before{display:table;content:"";line-height:0}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{margin-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal .input-append+.help-block,.form-horizontal .input-prepend+.help-block,.form-horizontal .uneditable-input+.help-block,.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table td,.table th{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:700}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child td,.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child td,.table thead:first-child tr:first-child th{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed td,.table-condensed th{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;border-left:0;border-radius:4px}.table-bordered td,.table-bordered th{border-left:1px solid #ddd}.table-bordered caption+tbody tr:first-child td,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+thead tr:first-child th,.table-bordered tbody:first-child tr:first-child td,.table-bordered tbody:first-child tr:first-child th,.table-bordered thead:first-child tr:first-child th{border-top:0}.table-bordered tbody:first-child tr:first-child>td:first-child,.table-bordered tbody:first-child tr:first-child>th:first-child,.table-bordered thead:first-child tr:first-child>th:first-child{border-top-left-radius:4px}.table-bordered tbody:first-child tr:first-child>td:last-child,.table-bordered tbody:first-child tr:first-child>th:last-child,.table-bordered thead:first-child tr:first-child>th:last-child{border-top-right-radius:4px}.table-bordered tbody:last-child tr:last-child>td:first-child,.table-bordered tbody:last-child tr:last-child>th:first-child,.table-bordered tfoot:last-child tr:last-child>td:first-child,.table-bordered tfoot:last-child tr:last-child>th:first-child,.table-bordered thead:last-child tr:last-child>th:first-child{border-bottom-left-radius:4px}.table-bordered tbody:last-child tr:last-child>td:last-child,.table-bordered tbody:last-child tr:last-child>th:last-child,.table-bordered tfoot:last-child tr:last-child>td:last-child,.table-bordered tfoot:last-child tr:last-child>th:last-child,.table-bordered thead:last-child tr:last-child>th:last-child{border-bottom-right-radius:4px}.table-bordered tfoot+tbody:last-child tr:last-child td:first-child{border-bottom-left-radius:0}.table-bordered tfoot+tbody:last-child tr:last-child td:last-child{border-bottom-right-radius:0}.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered caption+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child{border-top-left-radius:4px}.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered caption+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child{border-top-right-radius:4px}.table-striped tbody>tr:nth-child(odd)>td,.table-striped tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover tbody tr:hover>td,.table-hover tbody tr:hover>th{background-color:#f5f5f5}.row-fluid table td[class*=span],.row-fluid table th[class*=span],table td[class*=span],table th[class*=span]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success>td{background-color:#dff0d8}.table tbody tr.error>td{background-color:#f2dede}.table tbody tr.warning>td{background-color:#fcf8e3}.table tbody tr.info>td{background-color:#d9edf7}.table-hover tbody tr.success:hover>td{background-color:#d0e9c6}.table-hover tbody tr.error:hover>td{background-color:#ebcccc}.table-hover tbody tr.warning:hover>td{background-color:#faf2cc}.table-hover tbody tr.info:hover>td{background-color:#c4e3f3}[class*=" icon-"],[class^=icon-]{display:inline-block;width:14px;height:14px;line-height:14px;vertical-align:text-top;background-image:url(../img/glyphicons-halflings.png);background-position:14px 14px;background-repeat:no-repeat;margin-top:1px}.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-menu>.active>a>[class^=icon-],.dropdown-menu>li>a:focus>[class*=" icon-"],.dropdown-menu>li>a:focus>[class^=icon-],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^=icon-],.dropdown-submenu:focus>a>[class*=" icon-"],.dropdown-submenu:focus>a>[class^=icon-],.dropdown-submenu:hover>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^=icon-],.icon-white,.nav-list>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^=icon-],.nav-pills>.active>a>[class*=" icon-"],.nav-pills>.active>a>[class^=icon-],.navbar-inverse .nav>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^=icon-]{background-image:url(../img/glyphicons-halflings-white.png)}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{background-position:-216px -120px;width:16px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px;width:16px}.icon-folder-open{background-position:-408px -120px;width:16px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropdown,.dropup{position:relative}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 5px 10px rgba(0,0,0,.2);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover,.dropdown-submenu:focus>a,.dropdown-submenu:hover>a{text-decoration:none;color:#fff;background-color:#0081c2;background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#0081c2;background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#999}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:default}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;border-radius:0 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;content:" ";float:right;width:0;height:0;border-color:transparent;border-style:solid;border-width:5px 0 5px 5px;border-left-color:#ccc;margin-top:5px;margin-right:-10px}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-left:20px;padding-right:20px}.typeahead{z-index:1051;margin-top:2px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-large{padding:24px;border-radius:6px}.well-small{padding:9px;border-radius:3px}.fade{opacity:0;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:700;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.btn{display:inline-block;padding:4px 12px;margin-bottom:0;font-size:14px;line-height:20px;text-align:center;vertical-align:middle;cursor:pointer;color:#333;background-color:#f5f5f5;background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);border:1px solid #ccc}.btn.active,.btn.disabled,.btn:active,.btn:focus,.btn:hover,.btn[disabled]{color:#333;background-color:#e6e6e6}.btn:focus,.btn:hover{color:#333;text-decoration:none;background-position:0 -15px;transition:background-position .1s linear}.btn:focus{outline-offset:-2px}.btn.active,.btn:active{background-image:none;outline:0;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn.disabled,.btn[disabled]{cursor:default;background-image:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;border-radius:6px}.btn-large [class*=" icon-"],.btn-large [class^=icon-]{margin-top:4px}.btn-small{padding:2px 10px;font-size:11.9px;border-radius:3px}.btn-small [class*=" icon-"],.btn-small [class^=icon-]{margin-top:0}.btn-mini [class*=" icon-"],.btn-mini [class^=icon-]{margin-top:-1px}.btn-mini{padding:0 6px;font-size:10.5px;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary.active,.btn-primary.disabled,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.btn-primary[disabled]{color:#fff}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#faa732;background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning.active,.btn-warning.disabled,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.btn-warning[disabled]{color:#fff;background-color:#f89406}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#da4f49;background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger.active,.btn-danger.disabled,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.btn-danger[disabled]{color:#fff;background-color:#bd362f}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#5bb75b;background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success.active,.btn-success.disabled,.btn-success:active,.btn-success:focus,.btn-success:hover,.btn-success[disabled]{color:#fff;background-color:#51a351}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#49afcd;background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info.active,.btn-info.disabled,.btn-info:active,.btn-info:focus,.btn-info:hover,.btn-info[disabled]{color:#fff;background-color:#2f96b4}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#363636;background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse.active,.btn-inverse.disabled,.btn-inverse:active,.btn-inverse:focus,.btn-inverse:hover,.btn-inverse[disabled]{color:#fff;background-color:#222}button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;box-shadow:none}.btn-link{border-color:transparent;cursor:pointer;color:#08c;border-radius:0}.btn-link:focus,.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;font-size:0;vertical-align:middle;white-space:nowrap}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{font-size:0;margin-top:10px;margin-bottom:10px}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn{margin-left:5px}.btn-group>.btn{position:relative;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu,.btn-group>.popover{font-size:14px}.btn-group>.btn-mini{font-size:10.5px}.btn-group>.btn-small{font-size:11.9px}.btn-group>.btn-large{font-size:17.5px}.btn-group>.btn:first-child{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{border-top-right-radius:4px;border-bottom-right-radius:4px}.btn-group>.btn.large:first-child{margin-left:0;border-top-left-radius:6px;border-bottom-left-radius:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{border-top-right-radius:6px;border-bottom-right-radius:6px}.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px;box-shadow:inset 1px 0 0 rgba(255,255,255,.125),inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05)}.btn-group>.btn-mini+.dropdown-toggle{padding-left:5px;padding-right:5px}.btn-group>.btn-large+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{background-image:none;box-shadow:inset 0 2px 4px rgba(0,0,0,.15),0 1px 2px rgba(0,0,0,.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-large .caret{margin-top:6px;border-left-width:5px;border-right-width:5px;border-top-width:5px}.btn-mini .caret,.btn-small .caret{margin-top:8px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-danger .caret,.btn-info .caret,.btn-inverse .caret,.btn-primary .caret,.btn-success .caret,.btn-warning .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block}.btn-group-vertical>.btn{display:block;float:none;max-width:100%;border-radius:0}.btn-group-vertical>.btn+.btn{margin-left:0;margin-top:-1px}.btn-group-vertical>.btn:first-child{border-radius:4px 4px 0 0}.btn-group-vertical>.btn:last-child{border-radius:0 0 4px 4px}.btn-group-vertical>.btn-large:first-child{border-radius:6px 6px 0 0}.btn-group-vertical>.btn-large:last-child{border-radius:0 0 6px 6px}.alert{text-shadow:0 1px 0 rgba(255,255,255,.5);background-color:#fcf8e3}.alert,.alert h4{color:#c09853}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#468847}.alert-success h4{color:#468847}.alert-danger,.alert-error{background-color:#f2dede;border-color:#eed3d7;color:#b94a48}.alert-danger h4,.alert-error h4{color:#b94a48}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#3a87ad}.alert-info h4{color:#3a87ad}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-left:0;margin-bottom:20px;list-style:none}.nav>li>a{display:block}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li>a>img{max-width:none}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:700;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-left:15px;padding-right:15px;margin-bottom:0}.nav-list .nav-header,.nav-list>li>a{margin-left:-15px;margin-right:-15px;text-shadow:0 1px 0 rgba(255,255,255,.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:focus,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.2);background-color:#08c}.nav-list [class*=" icon-"],.nav-list [class^=icon-]{margin-right:2px}.nav-list .divider{height:1px;margin:9px 1px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-pills:after,.nav-pills:before,.nav-tabs:after,.nav-tabs:before{display:table;content:"";line-height:0}.nav-pills:after,.nav-tabs:after{clear:both}.nav-pills>li,.nav-tabs>li{float:left}.nav-pills>li>a,.nav-tabs>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:focus,.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:focus,.nav-tabs>.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:focus,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{border-top-right-radius:4px;border-top-left-radius:4px}.nav-tabs.nav-stacked>li:last-child>a{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.nav-tabs.nav-stacked>li>a:focus,.nav-tabs.nav-stacked>li>a:hover{border-color:#ddd;z-index:2}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{border-radius:6px}.nav .dropdown-toggle .caret{border-top-color:#08c;border-bottom-color:#08c;margin-top:6px}.nav .dropdown-toggle:focus .caret,.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:focus,.nav>.dropdown.active>a:hover{cursor:pointer}.nav-pills .open .dropdown-toggle,.nav-tabs .open .dropdown-toggle,.nav>li.dropdown.open.active>a:focus,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open a:focus .caret,.nav li.dropdown.open a:hover .caret,.nav li.dropdown.open.active .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:focus,.tabs-stacked .open>a:hover{border-color:#999}.tabbable:after,.tabbable:before{display:table;content:"";line-height:0}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-left>.nav-tabs,.tabs-right>.nav-tabs{border-bottom:0}.pill-content>.pill-pane,.tab-content>.tab-pane{display:none}.pill-content>.active,.tab-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:focus,.tabs-below>.nav-tabs>li>a:hover{border-bottom-color:transparent;border-top-color:#ddd}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:focus,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:focus,.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:focus,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:focus,.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:focus,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent}.nav>.disabled>a{color:#999}.nav>.disabled>a:focus,.nav>.disabled>a:hover{text-decoration:none;background-color:transparent;cursor:default}.navbar{overflow:visible}.navbar-inner{min-height:40px;padding-left:20px;padding-right:20px;background-color:#fafafa;background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);border:1px solid #d4d4d4;border-radius:4px;box-shadow:0 1px 4px rgba(0,0,0,.065)}.navbar-inner:after,.navbar-inner:before{display:table;content:"";line-height:0}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{float:left;display:block;padding:10px 20px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:focus,.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px;color:#777}.navbar-link{color:#777}.navbar-link:focus,.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-left:1px solid #f2f2f2;border-right:1px solid #fff}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-append .btn,.navbar .input-append .btn-group,.navbar .input-prepend .btn,.navbar .input-prepend .btn-group{margin-top:0}.navbar-form{margin-bottom:0}.navbar-form:after,.navbar-form:before{display:table;content:"";line-height:0}.navbar-form:after{clear:both}.navbar-form .checkbox,.navbar-form .radio,.navbar-form input,.navbar-form select{margin-top:5px}.navbar-form .btn,.navbar-form input,.navbar-form select{display:inline-block;margin-bottom:0}.navbar-form input[type=checkbox],.navbar-form input[type=image],.navbar-form input[type=radio]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:5px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{margin-bottom:0;padding:4px 14px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:400;line-height:1;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{border-radius:0}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-bottom .navbar-inner,.navbar-fixed-top .navbar-inner{padding-left:0;padding-right:0;border-radius:0}.navbar-fixed-bottom .container,.navbar-fixed-top .container,.navbar-static-top .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{box-shadow:0 1px 10px rgba(0,0,0,.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{box-shadow:0 -1px 10px rgba(0,0,0,.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{background-color:transparent;color:#333;text-decoration:none}.navbar .nav>.active>a,.navbar .nav>.active>a:focus,.navbar .nav>.active>a:hover{color:#555;text-decoration:none;background-color:#e5e5e5;box-shadow:inset 0 3px 8px rgba(0,0,0,.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-left:5px;margin-right:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#ededed;background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.075)}.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar:active,.navbar .btn-navbar:focus,.navbar .btn-navbar:hover,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;border-radius:1px;box-shadow:0 1px 0 rgba(0,0,0,.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{content:'';display:inline-block;border-left:7px solid transparent;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-bottom-color:rgba(0,0,0,.2);position:absolute;top:-7px;left:9px}.navbar .nav>li>.dropdown-menu:after{content:'';display:inline-block;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #fff;position:absolute;top:-6px;left:10px}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{border-top:7px solid #ccc;border-top-color:rgba(0,0,0,.2);border-bottom:0;bottom:-7px;top:auto}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{border-top:6px solid #fff;border-bottom:0;bottom:-6px;top:auto}.navbar .nav li.dropdown>a:focus .caret,.navbar .nav li.dropdown>a:hover .caret{border-top-color:#333;border-bottom-color:#333}.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle,.navbar .nav li.dropdown.open>.dropdown-toggle{background-color:#e5e5e5;color:#555}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .nav>li>.dropdown-menu.pull-right,.navbar .pull-right>li>.dropdown-menu{left:auto;right:0}.navbar .nav>li>.dropdown-menu.pull-right:before,.navbar .pull-right>li>.dropdown-menu:before{left:auto;right:12px}.navbar .nav>li>.dropdown-menu.pull-right:after,.navbar .pull-right>li>.dropdown-menu:after{left:auto;right:13px}.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu,.navbar .pull-right>li>.dropdown-menu .dropdown-menu{left:auto;right:100%;margin-left:0;margin-right:-1px;border-radius:6px 0 6px 6px}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);border-color:#252525}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-inverse .brand:focus,.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .brand,.navbar-inverse .navbar-text{color:#999}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{background-color:transparent;color:#fff}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:focus,.navbar-inverse .nav .active>a:hover{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:focus,.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-left-color:#111;border-right-color:#222}.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open>.dropdown-toggle{background-color:#111;color:#fff}.navbar-inverse .nav li.dropdown>a:focus .caret,.navbar-inverse .nav li.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;box-shadow:inset 0 1px 2px rgba(0,0,0,.1),0 1px 0 rgba(255,255,255,.15);transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query.focused,.navbar-inverse .navbar-search .search-query:focus{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;box-shadow:0 0 3px rgba(0,0,0,.15);outline:0}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#0e0e0e;background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar:focus,.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block;text-shadow:0 1px 0 #fff}.breadcrumb>li>.divider{padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;margin-left:0;margin-bottom:0;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>.active>a,.pagination ul>.active>span,.pagination ul>li>a:focus,.pagination ul>li>a:hover{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>a,.pagination ul>.disabled>a:focus,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>span{color:#999;background-color:transparent;cursor:default}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-mini ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>a,.pagination-small ul>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-mini ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>a,.pagination-small ul>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.pager{margin:20px 0;list-style:none;text-align:center}.pager:after,.pager:before{display:table;content:"";line-height:0}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#999;background-color:#fff;cursor:default}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal{left:50%;z-index:1050;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.3);border-radius:6px;box-shadow:0 3px 7px rgba(0,0,0,.3);background-clip:padding-box}.modal-header{border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{position:relative;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;border-radius:0 0 6px 6px;box-shadow:inset 0 1px 0 #fff}.modal-footer:after,.modal-footer:before{display:table;content:"";line-height:0}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;display:block;visibility:visible;font-size:11px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-title:empty{display:none}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right .arrow:after{left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom .arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left .arrow:after{right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.thumbnails{margin-left:-20px;list-style:none}.thumbnails:after,.thumbnails:before{display:table;content:"";line-height:0}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;border-radius:4px;box-shadow:0 1px 3px rgba(0,0,0,.055);transition:all .2s ease-in-out}a.thumbnail:focus,a.thumbnail:hover{border-color:#08c;box-shadow:0 1px 4px rgba(0,105,214,.25)}.thumbnail>img{display:block;max-width:100%;margin-left:auto;margin-right:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.badge,.label{display:inline-block;padding:2px 4px;font-size:11.84px;font-weight:700;line-height:14px;color:#fff;vertical-align:baseline;white-space:nowrap;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#999}.label{border-radius:3px}.badge{padding-left:9px;padding-right:9px;border-radius:9px}.badge:empty,.label:empty{display:none}a.badge:focus,a.badge:hover,a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.badge-important,.label-important{background-color:#b94a48}.badge-important[href],.label-important[href]{background-color:#953b39}.badge-warning,.label-warning{background-color:#f89406}.badge-warning[href],.label-warning[href]{background-color:#c67605}.badge-success,.label-success{background-color:#468847}.badge-success[href],.label-success[href]{background-color:#356635}.badge-info,.label-info{background-color:#3a87ad}.badge-info[href],.label-info[href]{background-color:#2d6987}.badge-inverse,.label-inverse{background-color:#333}.badge-inverse[href],.label-inverse[href]{background-color:#1a1a1a}.btn .badge,.btn .label{position:relative;top:-1px}.btn-mini .badge,.btn-mini .label{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f7f7f7;background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);box-shadow:inset 0 1px 2px rgba(0,0,0,.1);border-radius:4px}.progress .bar{width:0;height:100%;color:#fff;float:left;font-size:12px;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,.25);background-color:#0e90d2;background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-sizing:border-box;transition:width .6s ease}.progress .bar+.bar{box-shadow:inset 1px 0 0 rgba(0,0,0,.15),inset 0 -1px 0 rgba(0,0,0,.15)}.progress-striped .bar{background-color:#149bdf;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:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress .bar-danger,.progress-danger .bar{background-color:#dd514c;background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;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)}.progress .bar-success,.progress-success .bar{background-color:#5eb95e;background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0)}.progress-striped .bar-success,.progress-success.progress-striped .bar{background-color:#62c462;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)}.progress .bar-info,.progress-info .bar{background-color:#4bb1cf;background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;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)}.progress .bar-warning,.progress-warning .bar{background-color:#faa732;background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0)}.progress-striped .bar-warning,.progress-warning.progress-striped .bar{background-color:#fbb450;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)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{overflow:hidden;width:100%;position:relative}.carousel-inner>.item{display:none;position:relative;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{display:block;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{left:auto;right:15px}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-indicators{position:absolute;top:15px;right:15px;z-index:5;margin:0;list-style:none}.carousel-indicators li{display:block;float:left;width:10px;height:10px;margin-left:5px;text-indent:-999px;background-color:#ccc;background-color:rgba(255,255,255,.25);border-radius:5px}.carousel-indicators .active{background-color:#fff}.carousel-caption{position:absolute;left:0;right:0;bottom:0;padding:15px;background:#333;background:rgba(0,0,0,.75)}.carousel-caption h4,.carousel-caption p{color:#fff;line-height:20px}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}/*! + * Bootstrap Responsive v2.3.1 + * + * Copyright 2012 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world @twitter by @mdo and @fat. + */.clearfix:after,.clearfix:before{display:table;content:"";line-height:0}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;box-sizing:border-box}@-ms-viewport{width:device-width}.hidden{display:none;visibility:hidden}.hidden-desktop,.visible-phone,.visible-tablet{display:none!important}.visible-desktop{display:inherit!important}@media (min-width:768px) and (max-width:839px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media (max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:inherit!important}.hidden-print{display:none!important}}@media (min-width:1200px){.row{margin-left:-30px}.row:after,.row:before{display:table;content:"";line-height:0}.row:after{clear:both}[class*=span]{float:left;min-height:1px;margin-left:30px}.container,.navbar-fixed-bottom .container,.navbar-fixed-top .container,.navbar-static-top .container,.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%}.row-fluid:after,.row-fluid:before{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*=span]{display:block;width:100%;min-height:30px;box-sizing:border-box;float:left;margin-left:2.56410256%}.row-fluid [class*=span]:first-child{margin-left:0}.row-fluid .controls-row [class*=span]+[class*=span]{margin-left:2.56410256%}.row-fluid .span12{width:100%}.row-fluid .span11{width:91.45299145%}.row-fluid .span10{width:82.90598291%}.row-fluid .span9{width:74.35897436%}.row-fluid .span8{width:65.81196581%}.row-fluid .span7{width:57.26495726%}.row-fluid .span6{width:48.71794872%}.row-fluid .span5{width:40.17094017%}.row-fluid .span4{width:31.62393162%}.row-fluid .span3{width:23.07692308%}.row-fluid .span2{width:14.52991453%}.row-fluid .span1{width:5.98290598%}.row-fluid .offset12{margin-left:105.12820513%}.row-fluid .offset12:first-child{margin-left:102.56410256%}.row-fluid .offset11{margin-left:96.58119658%}.row-fluid .offset11:first-child{margin-left:94.01709402%}.row-fluid .offset10{margin-left:88.03418803%}.row-fluid .offset10:first-child{margin-left:85.47008547%}.row-fluid .offset9{margin-left:79.48717949%}.row-fluid .offset9:first-child{margin-left:76.92307692%}.row-fluid .offset8{margin-left:70.94017094%}.row-fluid .offset8:first-child{margin-left:68.37606838%}.row-fluid .offset7{margin-left:62.39316239%}.row-fluid .offset7:first-child{margin-left:59.82905983%}.row-fluid .offset6{margin-left:53.84615385%}.row-fluid .offset6:first-child{margin-left:51.28205128%}.row-fluid .offset5{margin-left:45.2991453%}.row-fluid .offset5:first-child{margin-left:42.73504274%}.row-fluid .offset4{margin-left:36.75213675%}.row-fluid .offset4:first-child{margin-left:34.18803419%}.row-fluid .offset3{margin-left:28.20512821%}.row-fluid .offset3:first-child{margin-left:25.64102564%}.row-fluid .offset2{margin-left:19.65811966%}.row-fluid .offset2:first-child{margin-left:17.09401709%}.row-fluid .offset1{margin-left:11.11111111%}.row-fluid .offset1:first-child{margin-left:8.54700855%}.uneditable-input,input,textarea{margin-left:0}.controls-row [class*=span]+[class*=span]{margin-left:30px}.uneditable-input.span12,input.span12,textarea.span12{width:1156px}.uneditable-input.span11,input.span11,textarea.span11{width:1056px}.uneditable-input.span10,input.span10,textarea.span10{width:956px}.uneditable-input.span9,input.span9,textarea.span9{width:856px}.uneditable-input.span8,input.span8,textarea.span8{width:756px}.uneditable-input.span7,input.span7,textarea.span7{width:656px}.uneditable-input.span6,input.span6,textarea.span6{width:556px}.uneditable-input.span5,input.span5,textarea.span5{width:456px}.uneditable-input.span4,input.span4,textarea.span4{width:356px}.uneditable-input.span3,input.span3,textarea.span3{width:256px}.uneditable-input.span2,input.span2,textarea.span2{width:156px}.uneditable-input.span1,input.span1,textarea.span1{width:56px}.thumbnails{margin-left:-30px}.thumbnails>li{margin-left:30px}.row-fluid .thumbnails{margin-left:0}}@media (min-width:768px) and (max-width:839px){.row{margin-left:-20px}.row:after,.row:before{display:table;content:"";line-height:0}.row:after{clear:both}[class*=span]{float:left;min-height:1px;margin-left:20px}.container,.navbar-fixed-bottom .container,.navbar-fixed-top .container,.navbar-static-top .container,.span12{width:724px}.span11{width:662px}.span10{width:600px}.span9{width:538px}.span8{width:476px}.span7{width:414px}.span6{width:352px}.span5{width:290px}.span4{width:228px}.span3{width:166px}.span2{width:104px}.span1{width:42px}.offset12{margin-left:764px}.offset11{margin-left:702px}.offset10{margin-left:640px}.offset9{margin-left:578px}.offset8{margin-left:516px}.offset7{margin-left:454px}.offset6{margin-left:392px}.offset5{margin-left:330px}.offset4{margin-left:268px}.offset3{margin-left:206px}.offset2{margin-left:144px}.offset1{margin-left:82px}.row-fluid{width:100%}.row-fluid:after,.row-fluid:before{display:table;content:"";line-height:0}.row-fluid:after{clear:both}.row-fluid [class*=span]{display:block;width:100%;min-height:30px;box-sizing:border-box;float:left;margin-left:2.76243094%}.row-fluid [class*=span]:first-child{margin-left:0}.row-fluid .controls-row [class*=span]+[class*=span]{margin-left:2.76243094%}.row-fluid .span12{width:100%}.row-fluid .span11{width:91.43646409%}.row-fluid .span10{width:82.87292818%}.row-fluid .span9{width:74.30939227%}.row-fluid .span8{width:65.74585635%}.row-fluid .span7{width:57.18232044%}.row-fluid .span6{width:48.61878453%}.row-fluid .span5{width:40.05524862%}.row-fluid .span4{width:31.49171271%}.row-fluid .span3{width:22.9281768%}.row-fluid .span2{width:14.36464088%}.row-fluid .span1{width:5.80110497%}.row-fluid .offset12{margin-left:105.52486188%}.row-fluid .offset12:first-child{margin-left:102.76243094%}.row-fluid .offset11{margin-left:96.96132597%}.row-fluid .offset11:first-child{margin-left:94.19889503%}.row-fluid .offset10{margin-left:88.39779006%}.row-fluid .offset10:first-child{margin-left:85.63535912%}.row-fluid .offset9{margin-left:79.83425414%}.row-fluid .offset9:first-child{margin-left:77.0718232%}.row-fluid .offset8{margin-left:71.27071823%}.row-fluid .offset8:first-child{margin-left:68.50828729%}.row-fluid .offset7{margin-left:62.70718232%}.row-fluid .offset7:first-child{margin-left:59.94475138%}.row-fluid .offset6{margin-left:54.14364641%}.row-fluid .offset6:first-child{margin-left:51.38121547%}.row-fluid .offset5{margin-left:45.5801105%}.row-fluid .offset5:first-child{margin-left:42.81767956%}.row-fluid .offset4{margin-left:37.01657459%}.row-fluid .offset4:first-child{margin-left:34.25414365%}.row-fluid .offset3{margin-left:28.45303867%}.row-fluid .offset3:first-child{margin-left:25.69060773%}.row-fluid .offset2{margin-left:19.88950276%}.row-fluid .offset2:first-child{margin-left:17.12707182%}.row-fluid .offset1{margin-left:11.32596685%}.row-fluid .offset1:first-child{margin-left:8.56353591%}.uneditable-input,input,textarea{margin-left:0}.controls-row [class*=span]+[class*=span]{margin-left:20px}.uneditable-input.span12,input.span12,textarea.span12{width:710px}.uneditable-input.span11,input.span11,textarea.span11{width:648px}.uneditable-input.span10,input.span10,textarea.span10{width:586px}.uneditable-input.span9,input.span9,textarea.span9{width:524px}.uneditable-input.span8,input.span8,textarea.span8{width:462px}.uneditable-input.span7,input.span7,textarea.span7{width:400px}.uneditable-input.span6,input.span6,textarea.span6{width:338px}.uneditable-input.span5,input.span5,textarea.span5{width:276px}.uneditable-input.span4,input.span4,textarea.span4{width:214px}.uneditable-input.span3,input.span3,textarea.span3{width:152px}.uneditable-input.span2,input.span2,textarea.span2{width:90px}.uneditable-input.span1,input.span1,textarea.span1{width:28px}}@media (max-width:767px){body{padding-left:20px;padding-right:20px}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{margin-left:-20px;margin-right:-20px}.container-fluid{padding:0}.dl-horizontal dt{float:none;clear:none;width:auto;text-align:left}.dl-horizontal dd{margin-left:0}.container{width:auto}.row-fluid{width:100%}.row,.thumbnails{margin-left:0}.thumbnails>li{float:none;margin-left:0}.row-fluid [class*=span],.uneditable-input[class*=span],[class*=span]{float:none;display:block;width:100%;margin-left:0;box-sizing:border-box}.row-fluid .span12,.span12{width:100%;box-sizing:border-box}.row-fluid [class*=offset]:first-child{margin-left:0}.input-large,.input-xlarge,.input-xxlarge,.uneditable-input,input[class*=span],select[class*=span],textarea[class*=span]{display:block;width:100%;min-height:30px;box-sizing:border-box}.input-append input,.input-append input[class*=span],.input-prepend input,.input-prepend input[class*=span]{display:inline-block;width:auto}.controls-row [class*=span]+[class*=span]{margin-left:0}.modal{position:fixed;top:20px;left:20px;right:20px;width:auto;margin:0}.modal.fade{top:-100px}.modal.fade.in{top:20px}}@media (max-width:480px){.nav-collapse{-webkit-transform:translate3d(0,0,0)}.page-header h1 small{display:block;line-height:20px}input[type=checkbox],input[type=radio]{border:1px solid #ccc}.form-horizontal .control-label{float:none;width:auto;padding-top:0;text-align:left}.form-horizontal .controls{margin-left:0}.form-horizontal .control-list{padding-top:0}.form-horizontal .form-actions{padding-left:10px;padding-right:10px}.media .pull-left,.media .pull-right{float:none;display:block;margin-bottom:10px}.media-object{margin-right:0;margin-left:0}.modal{top:10px;left:10px;right:10px}.modal-header .close{padding:10px;margin:-10px}.carousel-caption{position:static}}@media (max-width:839px){body{padding-top:0}.navbar-fixed-bottom,.navbar-fixed-top{position:static}.navbar-fixed-top{margin-bottom:20px}.navbar-fixed-bottom{margin-top:20px}.navbar-fixed-bottom .navbar-inner,.navbar-fixed-top .navbar-inner{padding:5px}.navbar .container{width:auto;padding:0}.navbar .brand{padding-left:10px;padding-right:10px;margin:0 0 0 -5px}.nav-collapse{clear:both}.nav-collapse .nav{float:none;margin:0 0 10px}.nav-collapse .nav>li{float:none}.nav-collapse .nav>li>a{margin-bottom:2px}.nav-collapse .nav>.divider-vertical{display:none}.nav-collapse .nav .nav-header{color:#777;text-shadow:none}.nav-collapse .dropdown-menu a,.nav-collapse .nav>li>a{padding:9px 15px;font-weight:700;color:#777;border-radius:3px}.nav-collapse .btn{padding:4px 10px;font-weight:400;border-radius:4px}.nav-collapse .dropdown-menu li+li a{margin-bottom:2px}.nav-collapse .dropdown-menu a:focus,.nav-collapse .dropdown-menu a:hover,.nav-collapse .nav>li>a:focus,.nav-collapse .nav>li>a:hover{background-color:#f2f2f2}.navbar-inverse .nav-collapse .dropdown-menu a,.navbar-inverse .nav-collapse .nav>li>a{color:#999}.navbar-inverse .nav-collapse .dropdown-menu a:focus,.navbar-inverse .nav-collapse .dropdown-menu a:hover,.navbar-inverse .nav-collapse .nav>li>a:focus,.navbar-inverse .nav-collapse .nav>li>a:hover{background-color:#111}.nav-collapse.in .btn-group{margin-top:5px;padding:0}.nav-collapse .dropdown-menu{position:static;top:auto;left:auto;float:none;display:none;max-width:none;margin:0 15px;padding:0;background-color:transparent;border:none;border-radius:0;box-shadow:none}.nav-collapse .open>.dropdown-menu{display:block}.nav-collapse .dropdown-menu .divider,.nav-collapse .dropdown-menu:after,.nav-collapse .dropdown-menu:before,.nav-collapse .nav>li>.dropdown-menu:after,.nav-collapse .nav>li>.dropdown-menu:before{display:none}.nav-collapse .navbar-form,.nav-collapse .navbar-search{float:none;padding:10px 15px;margin:10px 0;border-top:1px solid #f2f2f2;border-bottom:1px solid #f2f2f2;box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}.navbar-inverse .nav-collapse .navbar-form,.navbar-inverse .nav-collapse .navbar-search{border-top-color:#111;border-bottom-color:#111}.navbar .nav-collapse .nav.pull-right{float:none;margin-left:0}.nav-collapse,.nav-collapse.collapse{overflow:hidden;height:0}.navbar .btn-navbar{display:block}.navbar-static .navbar-inner{padding-left:10px;padding-right:10px}}@media (min-width:839px + 1){.nav-collapse.collapse{height:auto!important;overflow:visible!important}}.lightbox{background-color:transparent;text-align:center;line-height:0;z-index:1050;position:relative;top:70px;outline:0}.lightbox .hide{display:none}.lightbox .in{display:block}.lightbox-content{display:inline-block;background-color:#fff}.lightbox-content .lightbox-caption{padding:2%;position:absolute;left:11px;right:12px;bottom:11px;background:#000;background:rgba(0,0,0,.6);color:#fff;text-align:center;text-shadow:0 -1px 0 #000;text-shadow:0 -1px 0 rgba(0,0,0,.3);font-size:14px;line-height:18px}.lightbox-header .close{color:#fff;margin-right:-16px;margin-top:-16px;font-size:2em;opacity:.8;filter:alpha(opacity=80)}.lightbox-header .close :hover{opacity:.4;filter:alpha(opacity=40)}/*! + * Bootstrap Modal + * + * Copyright Jordan Schroter + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + */.modal-open{overflow:hidden}.modal-open.page-overflow .modal-scrollable,.modal-open.page-overflow .page-container,.modal-open.page-overflow .page-container .navbar-fixed-bottom,.modal-open.page-overflow .page-container .navbar-fixed-top{overflow-y:scroll}@media (max-width:979px){.modal-open.page-overflow .page-container .navbar-fixed-bottom,.modal-open.page-overflow .page-container .navbar-fixed-top{overflow-y:visible}}.modal-scrollable{position:fixed;top:0;bottom:0;left:0;right:0;overflow:auto}.modal{outline:0;position:absolute;margin-top:0;top:50%;overflow:visible}.modal.fade{top:-100%;-webkit-transition:opacity .3s linear,top .3s ease-out,bottom .3s ease-out,margin-top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out,bottom .3s ease-out,margin-top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out,bottom .3s ease-out,margin-top .3s ease-out;transition:opacity .3s linear,top .3s ease-out,bottom .3s ease-out,margin-top .3s ease-out}.modal.fade.in{top:50%}.modal-body{max-height:none;overflow:visible}.modal.modal-absolute{position:absolute;z-index:950}.modal .loading-mask{position:absolute;top:0;bottom:0;left:0;right:0;background:#fff;border-radius:6px}.modal-backdrop.modal-absolute{position:absolute;z-index:940}.modal-backdrop,.modal-backdrop.fade.in{opacity:.7;filter:alpha(opacity=70);background:#fff}.modal.container{width:940px;margin-left:-470px}.modal-overflow.modal{top:1%}.modal-overflow.modal.fade{top:-100%}.modal-overflow.modal.fade.in{top:1%}.modal-overflow .modal-body{overflow:auto;-webkit-overflow-scrolling:touch}@media (min-width:1200px){.modal.container{width:1170px;margin-left:-585px}}@media (max-width:979px){.modal,.modal.container,.modal.modal-overflow{top:1%;right:1%;left:1%;bottom:auto;width:auto!important;height:auto!important;margin:0!important;padding:0!important}.modal.container.fade.in,.modal.fade.in,.modal.modal-overflow.fade.in{top:1%;bottom:auto}.modal-body,.modal-overflow .modal-body{position:static;margin:0;height:auto!important;max-height:none!important;overflow:visible!important}.modal-footer,.modal-overflow .modal-footer{position:static}}.loading-spinner{position:absolute;top:50%;left:50%;margin:-12px 0 0 -12px}.animated{-webkit-animation-duration:1s;-moz-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both}@-webkit-keyframes shake{0%,100%{-webkit-transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px)}}@-moz-keyframes shake{0%,100%{-moz-transform:translateX(0)}10%,30%,50%,70%,90%{-moz-transform:translateX(-10px)}20%,40%,60%,80%{-moz-transform:translateX(10px)}}@-o-keyframes shake{0%,100%{-o-transform:translateX(0)}10%,30%,50%,70%,90%{-o-transform:translateX(-10px)}20%,40%,60%,80%{-o-transform:translateX(10px)}}@keyframes shake{0%,100%{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-10px)}20%,40%,60%,80%{transform:translateX(10px)}}.shake{-webkit-animation-name:shake;-moz-animation-name:shake;-o-animation-name:shake;animation-name:shake}@-webkit-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%,70%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-moz-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%,70%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%,70%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-webkit-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}}@keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0);-moz-transform:translateY(0);-ms-transform:translateY(0);-o-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@-moz-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:.5}.dropzone .dz-message{text-align:center;margin:2em 0}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom,#eee,#ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:#fff}.dropzone .dz-preview.dz-image-preview .dz-details{-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;-ms-transition:opacity .2s linear;-o-transition:opacity .2s linear;transition:opacity .2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,.8);background-color:rgba(255,255,255,.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid transparent}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:rgba(255,255,255,.4);padding:0 .4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{-webkit-transform:scale(1.05,1.05);-moz-transform:scale(1.05,1.05);-ms-transform:scale(1.05,1.05);-o-transform:scale(1.05,1.05);transform:scale(1.05,1.05);-webkit-filter:blur(8px);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{-webkit-animation:passing-through 3s cubic-bezier(.77,0,.175,1);-moz-animation:passing-through 3s cubic-bezier(.77,0,.175,1);-ms-animation:passing-through 3s cubic-bezier(.77,0,.175,1);-o-animation:passing-through 3s cubic-bezier(.77,0,.175,1);animation:passing-through 3s cubic-bezier(.77,0,.175,1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;-webkit-animation:slide-in 3s cubic-bezier(.77,0,.175,1);-moz-animation:slide-in 3s cubic-bezier(.77,0,.175,1);-ms-animation:slide-in 3s cubic-bezier(.77,0,.175,1);-o-animation:slide-in 3s cubic-bezier(.77,0,.175,1);animation:slide-in 3s cubic-bezier(.77,0,.175,1)}.dropzone .dz-preview .dz-error-mark,.dropzone .dz-preview .dz-success-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px}.dropzone .dz-preview .dz-error-mark svg,.dropzone .dz-preview .dz-success-mark svg{display:block;width:54px;height:54px}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;-webkit-transition:all .2s linear;-moz-transition:all .2s linear;-ms-transition:all .2s linear;-o-transition:all .2s linear;transition:all .2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;-webkit-transition:opacity .4s ease-in;-moz-transition:opacity .4s ease-in;-ms-transition:opacity .4s ease-in;-o-transition:opacity .4s ease-in;transition:opacity .4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{-webkit-animation:pulse 6s ease infinite;-moz-animation:pulse 6s ease infinite;-ms-animation:pulse 6s ease infinite;-o-animation:pulse 6s ease infinite;animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:16px;left:50%;top:50%;margin-top:-8px;width:80px;margin-left:-40px;background:rgba(255,255,255,.9);-webkit-transform:scale(1);border-radius:8px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#333;background:linear-gradient(to bottom,#666,#444);position:absolute;top:0;left:0;bottom:0;width:0;-webkit-transition:width 300ms ease-in-out;-moz-transition:width 300ms ease-in-out;-ms-transition:width 300ms ease-in-out;-o-transition:width 300ms ease-in-out;transition:width 300ms ease-in-out}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;-webkit-transition:opacity .3s ease;-moz-transition:opacity .3s ease;-ms-transition:opacity .3s ease;-o-transition:opacity .3s ease;transition:opacity .3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#be2626;background:linear-gradient(to bottom,#be2626,#a92222);padding:.5em 1.2em;color:#fff}.dropzone .dz-preview .dz-error-message:after{content:'';position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #be2626}/*! + * jQuery contextMenu - Plugin for simple contextMenu handling + * + * Version: 1.6.6 + * + * Authors: Rodney Rehm, Addy Osmani (patches for FF) + * Web: http://medialize.github.com/jQuery-contextMenu/ + * + * Licensed under + * MIT License http://www.opensource.org/licenses/mit-license + * GPL v3 http://opensource.org/licenses/GPL-3.0 + * + */.context-menu-list{min-width:120px;max-width:250px;display:inline-block;position:absolute;list-style-type:none;border:1px solid #DDD;-webkit-box-shadow:0 2px 5px rgba(0,0,0,.5);-moz-box-shadow:0 2px 5px rgba(0,0,0,.5);-ms-box-shadow:0 2px 5px rgba(0,0,0,.5);-o-box-shadow:0 2px 5px rgba(0,0,0,.5);box-shadow:0 2px 5px rgba(0,0,0,.5)}.context-menu-item>label>input,.context-menu-item>label>textarea{-webkit-user-select:text;-moz-user-select:text;-ms-user-select:text;user-select:text}.context-menu-item.hover{cursor:pointer}.context-menu-item.disabled{color:#666}.context-menu-submenu:after{content:">";color:#666;position:absolute;top:0;right:3px;z-index:1}.context-menu-item.icon{min-height:18px;background-repeat:no-repeat}.context-menu-input>label>*{vertical-align:top}.context-menu-input>label>input[type=checkbox],.context-menu-input>label>input[type=radio]{margin-left:-17px}.context-menu-input>label>span{margin-left:5px}.context-menu-input>label,.context-menu-input>label>input[type=text],.context-menu-input>label>select,.context-menu-input>label>textarea{display:block;width:100%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;-o-box-sizing:border-box;box-sizing:border-box}.context-menu-input>label>textarea{height:100px}.context-menu-item>.context-menu-list{display:none;right:-5px;top:5px}.context-menu-item.hover>.context-menu-list{display:block}.context-menu-accesskey{text-decoration:underline}*,:after,:before{box-sizing:border-box}body,html{font-family:'Open Sans',sans-serif;font-size:100%;padding:0;margin:0}body{padding-top:32px;font-weight:200;background:#ececec;overflow:-moz-scrollbars-vertical}.btn{background-image:none!important;text-shadow:none!important;border-color:none!important;box-shadow:none!important}.btn:focus{outline:0}.btn-inverse{background-image:none!important;background:#333}pre.no-prettify,pre.prettyprint{height:300px;margin:0!important;width:100%!important;overflow:scroll;border-radius:0!important}.input-append .add-on:last-child,.input-append .btn-group:last-child>.dropdown-toggle,.input-append .btn:last-child{border-radius:0!important}[class*=" rficon-"],[class^=rficon-]{display:inline-block;width:16px;height:16px;margin-top:1px;line-height:16px;vertical-align:text-top;background-position:0 0;background-repeat:no-repeat}.rficon-clipboard-apply{background-image:url(../img/clipboard_apply.png)}.rficon-clipboard-clear{background-image:url(../img/clipboard_clear.png)}.rficon-upload{background-image:url(../img/upload.png)}.btn{border-radius:0}.container-fluid{margin-top:10px!important}.img-precontainer{margin:auto;width:100%;text-align:center;background:#fff;border:none}.img-container{height:91px;width:122px;padding:0;border:none;overflow:hidden;display:table-cell;text-align:center;vertical-align:middle;margin:auto;background-image:url(../img/trans.jpg);background-size:13px}.img-container img{max-width:122px;max-height:91px}ul.breadcrumb{margin-bottom:5px;border-radius:0;padding-bottom:4px;padding-top:6px;background:#f0f0f0;box-shadow:0 1px 4px rgba(0,0,0,.065);border-bottom:1px solid #bbb}ul.breadcrumb .pull-left i{margin-top:2px}.alert{padding:8px 35px 8px 14px;margin-bottom:2px;border:1px solid #aaa;color:#666;font-weight:200;font-size:13px;border-radius:0;background:#fff}.img-container *,.img-container-mini *{vertical-align:middle}#help{display:none}.text-center{text-align:center}iframe{overflow:auto;-webkit-overflow-scrolling:touch}.upload-help{font-size:11px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff;text-align:center}.upload-tabbable{margin-left:5px;margin-right:5px}.upload-tabbable .nav{margin:0}.upload-tabbable .nav li a{font-size:13px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.upload-tabbable .tab-content{padding:5px;border-bottom:1px solid #ddd;border-left:1px solid #ddd;border-right:1px solid #ddd;background:#fff;margin-bottom:5px;height:400px;overflow-y:scroll}input#filter-input{margin:0 0 2px;width:84px;height:26px;vertical-align:bottom;border-radius:0;font-size:12px;font-weight:200;position:relative;left:1px}.qq-uploader .span9{margin-left:14px!important;width:690px!important}.space10{clear:both;height:10px}h4{font-size:12px;font-weight:200;margin:6px 0 0;text-align:center;padding:0;line-height:18px}h3{font-size:14px;font-weight:200}.boxes{border:1px solid #CCC;word-wrap:break-word;background:#fff;box-shadow:1px 1px 2px 0 rgba(0,0,0,.2);min-height:115px;text-align:center}.container-fluid{padding:0 10px!important}body .avpw .avpw_primary_button,body .avpw .avpw_primary_button:active,body .avpw .avpw_primary_button:link,body .avpw .avpw_primary_button:visited{color:#fff;background:#999;border:none}body .avpw .avpw_primary_button:hover{border:none;background:#666}.uploader{position:fixed;top:50px;left:14px;margin:auto;width:100%;z-index:9999;overflow:hidden;background:#eee;padding-top:20px;border:1px solid #ccc;display:none;box-shadow:0 0 10px 0 rgba(1,1,1,.5)}.download-form{margin-bottom:25px}.grid li i{margin-left:2px;margin-right:2px;z-index:0}.box,.boxx{word-wrap:break-word;vertical-align:top;text-align:left;position:relative;border:none;box-shadow:none;z-index:100;padding:4px}.box .btn{width:100%;background:0 0;box-shadow:none;border:none;z-index:200}.navbar{margin-bottom:0;border-bottom:1px solid #bbb}.navbar .navbar-inner{border:none;min-height:35px;border-radius:0;padding-bottom:2px;margin:0;padding-right:8px;padding-left:8px}.navbar .navbar-inner .container-fluid{margin:0;margin-top:0!important;padding:0}.navbar .navbar-inner .container-fluid .brand{display:none}.navbar .navbar-inner .container-fluid .filters span{margin-top:0;font-size:13px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}ul.sorting{position:absolute;left:-25px;top:20px;min-width:0;background:#eee}ul.sorting li a:hover{background:#aaa}.btn-group .dropdown-toggle.sorting-btn{background:0 0;border:none;box-shadow:none;position:relative;-webkit-box-shadow:none;top:-5px;font-size:13px}.btn-group .dropdown-toggle.sorting-btn:hover{background:0 0;border:none;box-shadow:none;-webkit-box-shadow:none}ul.sorting.dropdown-menu>li>a{font-size:12px;text-shadow:none}ul.sorting.dropdown-menu>li>a.descending{background-image:url(../img/down.png);background-repeat:no-repeat;background-position:6px 8px}ul.sorting.dropdown-menu>li>a.ascending{background-image:url(../img/up.png);background-repeat:no-repeat;background-position:6px 8px}.sorter-container{margin-top:5px;margin-bottom:0;border-radius:0;padding-bottom:4px;padding-top:6px;box-shadow:0 1px 4px rgba(0,0,0,.065);background-color:#f5f5f5;position:relative;border-bottom:1px solid #bbb;height:24px}.sorter-container a.sorter{color:#000}.sorter-container a.descending{padding-left:9px;background-image:url(../img/down.png);background-repeat:no-repeat;background-position:0 3px}.sorter-container a.ascending{padding-left:9px;background-image:url(../img/up.png);background-repeat:no-repeat;background-position:0 4px}.sorter-container .file-date,.sorter-container .file-extension,.sorter-container .file-name,.sorter-container .file-operations,.sorter-container .file-size,.sorter-container .img-dimension{display:block;position:absolute;top:0;z-index:100;box-shadow:none;text-align:left;font-size:13px;margin-top:1px;color:#999}.sorter-container .file-operations{width:110px;right:0}.sorter-container .img-dimension{width:65px;right:123px}.sorter-container .file-date{width:70px;right:188px}.sorter-container .file-size{width:55px;right:258px}.sorter-container .file-extension{width:40px;right:313px}.sorter-container .file-name{width:50px;left:52px}.file-date,.file-extension,.file-name,.file-operations,.file-size,.img-dimension{font-size:12px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff;display:none}.view-controller{text-align:left}.view-controller .btn-group>.btn:first-child,.view-controller .btn-group>.btn:last-child{border-radius:0}.navbar .filters .btn{margin-bottom:2px;padding:2px 8px;margin-top:5px}.filters .types{text-align:right}@media (max-width:780px){#view2{display:none}}@media (min-width:840px){.mobile-inline-visible{display:none!important}}@media (max-width:839px){body{padding-top:0}.mobile-inline-visible{display:inline!important}.filters .types{text-align:left}.navbar .navbar-inner .container-fluid .brand{display:block}.navbar .navbar-inner{padding-bottom:4px}.filters div.span2.half,.filters div.span3.half,.filters div.span4.half{float:left;width:auto;margin-right:10px}.filters div.entire{float:none;width:100%;clear:both}.container-fluid{margin:0!important;padding:0}#qLbar{height:50px!important}}@media (min-width:400px) and (max-width:839px){.filters .row-fluid .half{width:48.61878453038674%;float:left}}.tooltip.in{z-index:10000;opacity:1;filter:alpha(opacity=1);font-weight:700}.tooltip{font-weight:700;z-index:10000}.grid{padding:0;margin:0 auto;list-style:none;-webkit-overflow-scrolling:touch}.ui-draggable-helper{z-index:10}.grid li{display:inline-block;width:124px;border:none;margin:4px 4px 8px;padding:0;vertical-align:top}.grid figure{position:relative;display:block;width:122px;margin:auto}.grid figure:hover{background:#e0e0e0!important}.list-view1.grid li,.list-view1.grid li figure{width:100%}.grid figcaption{text-align:center;padding:8px 2px 2px;color:#fff;height:30px;width:122px;margin-left:0;margin-right:0;position:absolute;top:auto;bottom:0;box-shadow:inset 0 0 8px 0 rgba(41,41,41,.5)}.grid figcaption a{margin:0;padding:3px}.grid figcaption h3{margin:0;padding:0;color:#fff}.grid h4{text-align:center;color:#000;padding:0;margin-bottom:4px;margin-top:4px}.grid figure .box{box-sizing:content-box;cursor:pointer}.list-view0.grid figure .box,.list-view1.grid figure .box,.list-view2.grid figure .box{max-width:100%;display:block;position:relative;overflow:hidden;z-index:1}.list-view0.grid figure .box h4.ellipsis,.list-view1.grid figure .box h4.ellipsis,.list-view2.grid figure .box h4.ellipsis{height:18px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.list-view0.grid figure .box h4,.list-view0.grid figure .box h4 a,.list-view1.grid figure .box h4,.list-view1.grid figure .box h4 a,.list-view2.grid figure .box h4,.list-view2.grid figure .box h4 a{z-index:1}.no-touch .list-view0 figure .box{z-index:1;transition:-webkit-transform .3s;transition:transform .3s}.list-view0.grid .ui-state-highlight .img-precontainer{background:grey!important}.list-view0.grid .ui-state-highlight .img-precontainer .img-container{background:repeating-linear-gradient(45deg,transparent,transparent 5px,rgba(255,255,255,.4) 5px,rgba(255,255,255,.3) 10px);border:none;overflow:hidden}.list-view0.grid .ui-state-hover .img-precontainer .img-container{background:#666}.list-view1.grid .ui-state-highlight:nth-child(odd) figure,.list-view2.grid .ui-state-highlight:nth-child(odd) figure{background:#ddd!important;border-bottom-color:#444!important}.list-view1.grid .ui-state-highlight:nth-child(even) figure,.list-view2.grid .ui-state-highlight:nth-child(even) figure{background:#ccc!important;border-bottom-color:#aaa!important}.list-view1.grid .ui-state-highlight.ui-state-hover figure,.list-view2.grid .ui-state-highlight.ui-state-hover figure{background-color:#aaa!important}.no-touch .list-view0 figure.cs-hover .box,.no-touch .list-view0 figure:hover .box{box-shadow:0 0 4px 0 rgba(1,1,1,.5);-webkit-transform:translateY(-26px);transform:translateY(-26px)}.list-view0 figure.cs-hover .box.no-effect,.list-view0 figure:hover .box.no-effect,.no-effect{box-shadow:none;-webkit-transform:none;transform:none}.list-view0 .img-precontainer-mini{display:none;background:0 0}a,a:hover{color:#000;text-decoration:none}.back-directory .box,.back-directory .img-precontainer,.back-directory .img-precontainer-mini{background:0 0}form{margin:0;padding:0}.google-iframe,.viewer-iframe{width:100%;height:500px;border:none}.modal{width:60%;margin-left:-30%}.modal-body{padding:6px}.modal-body form,.modal-body input,.modal-body textarea{margin:0;border-radius:0}.modal-body .text-center{padding-bottom:6px}.modal-footer{padding:7px}.modal-header{padding:7px 8px!important}.modal-header h3{font-weight:300;font-size:20px}.list-view1.sorter-container{display:block}.list-view0.sorter-container,.list-view2.sorter-container{display:none}.list-view0.grid .img-precontainer .img-container img{max-width:122px!important;max-height:91px!important}.list-view0.grid .img-precontainer .img-container img.icon{width:122px;margin-top:0}.list-view0.grid .img-precontainer .filetype{position:absolute;top:0;width:122px;text-align:center;color:#fff;font-size:13px;line-height:22px}.list-view0.grid .cover{background:rgba(255,255,255,.25);width:122px;position:absolute;top:22px;right:0;height:69px}.list-view0.grid .box{background:#fff}.list-view0.grid .directory{background:#ddd}.list-view0.grid figure.back-directory .directory{background:#bbb}.list-view0.grid figcaption{background:#fff}.list-view0.grid .selected figure{border:none;height:126px}.list-view0.grid .selected figure .box,.list-view0.grid .selected figure figcaption,.list-view0.grid .selected figure>a{display:none}.list-view1.grid li,.list-view2.grid li{margin:0}.list-view1.grid li.back figure.back-directory,.list-view2.grid li.back figure.back-directory{height:34px}.list-view1.grid li:nth-child(odd) figure,.list-view2.grid li:nth-child(odd) figure{background:#f9f9f9}.list-view1.grid li:nth-child(odd) figure.directory,.list-view2.grid li:nth-child(odd) figure.directory{background:#eaeaea}.list-view1.grid li figure,.list-view2.grid li figure{border-bottom:1px solid #aaa;background:#fff}.list-view1.grid li figure.back-directory,.list-view2.grid li figure.back-directory{background:#bbb}.list-view1.grid li figure.back-directory .box,.list-view2.grid li figure.back-directory .box{background:0 0}.list-view1.grid li figure.directory,.list-view2.grid li figure.directory{background:#efefef}.list-view1.grid li figure.directory box,.list-view2.grid li figure.directory box{padding:0;min-height:10px}.list-view1.grid li figure .box,.list-view2.grid li figure .box{margin-left:50px;transition:none}.list-view1.grid li figure .box h4,.list-view2.grid li figure .box h4{padding-top:1px;font-size:13px;text-align:left}.list-view1.grid .img-precontainer-mini,.list-view2.grid .img-precontainer-mini{display:block;position:absolute;width:45px;height:34px;overflow:hidden;text-align:center}.list-view1.grid .img-precontainer-mini img,.list-view2.grid .img-precontainer-mini img{max-width:45px}.list-view1.grid .img-precontainer-mini .filetype,.list-view2.grid .img-precontainer-mini .filetype{position:absolute;top:1px;text-align:center;left:0;padding:1px 2px;font-size:13px;line-height:32px;width:45px;height:32px;color:#fff;background:#333}.list-view1.grid .cover,.list-view2.grid .cover{display:none}.list-view1.grid .img-container-mini,.list-view2.grid .img-container-mini{width:45px;height:34px;border:none;overflow:hidden;display:table-cell;text-align:center;vertical-align:middle;margin:auto}.list-view1.grid .img-precontainer-mini.original-thumb,.list-view2.grid .img-precontainer-mini.original-thumb{padding:0}.list-view1.grid .img-precontainer-mini.original-thumb img,.list-view2.grid .img-precontainer-mini.original-thumb img{width:auto;max-height:32px}.list-view1.grid .img-precontainer-mini.original-thumb img.original,.list-view2.grid .img-precontainer-mini.original-thumb img.original{width:auto;height:auto}.list-view1.grid .img-precontainer,.list-view2.grid .img-precontainer{display:none}.list-view1.grid figcaption,.list-view2.grid figcaption{background:0 0;width:120px;position:absolute;right:0;top:0;z-index:1;bottom:0;box-shadow:none;text-align:right}.list-view1.grid .selected figure,.list-view2.grid .selected figure{background:#ccc!important}.list-view1.grid .file-date,.list-view1.grid .file-extension,.list-view1.grid .file-size,.list-view1.grid .img-dimension{overflow:hidden;display:block;position:absolute;top:0;z-index:1;box-shadow:none;text-align:left;margin-top:7px}.list-view1.grid .img-dimension{width:65px;right:120px}.list-view1.grid .file-date{width:70px;right:185px}.list-view1.grid .file-size{width:55px;right:255px}.list-view1.grid .file-extension{width:40px;right:310px}.list-view1.grid figure .box{padding-right:352px}.list-view2.grid figure .box{padding-right:115px}@media (max-width:610px){.list-view1.grid figure .box{padding-right:312px}.list-view1.grid figure .file-extension,.sorter-container .file-extension{display:none}}@media (max-width:565px){.list-view1.grid figure .box{padding-right:257px}.list-view1.grid figure .file-size,.sorter-container .file-size{display:none}}@media (max-width:495px){.list-view1.grid figure .box{padding-right:187px}.list-view1.grid figure .file-date,.sorter-container .file-date{display:none}}@media (max-width:430px){.list-view1.grid figure .box{padding-right:115px}.list-view1.grid figure .img-dimension,.sorter-container .img-dimension{display:none}.breadcrumb{margin-left:0;margin-right:0}}.list-view1.grid .img-precontainer-mini .filetype.pdf,.list-view2.grid .img-precontainer-mini .filetype.pdf{background:#CB0011}.list-view1.grid .img-precontainer-mini .filetype.ai,.list-view2.grid .img-precontainer-mini .filetype.ai{background:#D6772F}.list-view1.grid .img-precontainer-mini .filetype.psd,.list-view2.grid .img-precontainer-mini .filetype.psd{background:#0960A4}.list-view1.grid .img-precontainer-mini .filetype.html,.list-view1.grid .img-precontainer-mini .filetype.xhtml,.list-view2.grid .img-precontainer-mini .filetype.html,.list-view2.grid .img-precontainer-mini .filetype.xhtml{background:#035BC4}.list-view1.grid .img-precontainer-mini .filetype.fla,.list-view1.grid .img-precontainer-mini .filetype.flv,.list-view2.grid .img-precontainer-mini .filetype.fla,.list-view2.grid .img-precontainer-mini .filetype.flv{background:#CF302E}.list-view1.grid .img-precontainer-mini .filetype.ppt,.list-view1.grid .img-precontainer-mini .filetype.pptx,.list-view2.grid .img-precontainer-mini .filetype.ppt,.list-view2.grid .img-precontainer-mini .filetype.pptx{background:#DA5B00}.list-view1.grid .img-precontainer-mini .filetype.css,.list-view1.grid .img-precontainer-mini .filetype.xls,.list-view1.grid .img-precontainer-mini .filetype.xlsx,.list-view2.grid .img-precontainer-mini .filetype.css,.list-view2.grid .img-precontainer-mini .filetype.xls,.list-view2.grid .img-precontainer-mini .filetype.xlsx{background:#1A712C}.list-view1.grid .img-precontainer-mini .filetype.doc,.list-view1.grid .img-precontainer-mini .filetype.docx,.list-view1.grid .img-precontainer-mini .filetype.rts,.list-view2.grid .img-precontainer-mini .filetype.doc,.list-view2.grid .img-precontainer-mini .filetype.docx,.list-view2.grid .img-precontainer-mini .filetype.rts{background:#002093}.list-view1.grid .img-precontainer-mini .filetype.gzip,.list-view1.grid .img-precontainer-mini .filetype.rar,.list-view1.grid .img-precontainer-mini .filetype.zip,.list-view2.grid .img-precontainer-mini .filetype.gzip,.list-view2.grid .img-precontainer-mini .filetype.rar,.list-view2.grid .img-precontainer-mini .filetype.zip{background:#FE9221}.list-view1.grid .img-precontainer-mini .filetype.avi,.list-view1.grid .img-precontainer-mini .filetype.mov,.list-view1.grid .img-precontainer-mini .filetype.mp4,.list-view1.grid .img-precontainer-mini .filetype.mpeg,.list-view1.grid .img-precontainer-mini .filetype.mpg,.list-view1.grid .img-precontainer-mini .filetype.webm,.list-view1.grid .img-precontainer-mini .filetype.wma,.list-view2.grid .img-precontainer-mini .filetype.avi,.list-view2.grid .img-precontainer-mini .filetype.mov,.list-view2.grid .img-precontainer-mini .filetype.mp4,.list-view2.grid .img-precontainer-mini .filetype.mpeg,.list-view2.grid .img-precontainer-mini .filetype.mpg,.list-view2.grid .img-precontainer-mini .filetype.webm,.list-view2.grid .img-precontainer-mini .filetype.wma{background:#31231E}.list-view1.grid .img-precontainer-mini .filetype.ac3,.list-view1.grid .img-precontainer-mini .filetype.aiff,.list-view1.grid .img-precontainer-mini .filetype.m4a,.list-view1.grid .img-precontainer-mini .filetype.mid,.list-view1.grid .img-precontainer-mini .filetype.mp3,.list-view1.grid .img-precontainer-mini .filetype.ogg,.list-view1.grid .img-precontainer-mini .filetype.wav,.list-view2.grid .img-precontainer-mini .filetype.ac3,.list-view2.grid .img-precontainer-mini .filetype.aiff,.list-view2.grid .img-precontainer-mini .filetype.m4a,.list-view2.grid .img-precontainer-mini .filetype.mid,.list-view2.grid .img-precontainer-mini .filetype.mp3,.list-view2.grid .img-precontainer-mini .filetype.ogg,.list-view2.grid .img-precontainer-mini .filetype.wav{background:#9F008B}.list-view1.grid .img-precontainer-mini .filetype.odb,.list-view1.grid .img-precontainer-mini .filetype.odf,.list-view1.grid .img-precontainer-mini .filetype.odg,.list-view1.grid .img-precontainer-mini .filetype.odp,.list-view1.grid .img-precontainer-mini .filetype.ods,.list-view1.grid .img-precontainer-mini .filetype.odt,.list-view1.grid .img-precontainer-mini .filetype.otg,.list-view1.grid .img-precontainer-mini .filetype.otp,.list-view1.grid .img-precontainer-mini .filetype.ots,.list-view1.grid .img-precontainer-mini .filetype.ott,.list-view2.grid .img-precontainer-mini .filetype.odb,.list-view2.grid .img-precontainer-mini .filetype.odf,.list-view2.grid .img-precontainer-mini .filetype.odg,.list-view2.grid .img-precontainer-mini .filetype.odp,.list-view2.grid .img-precontainer-mini .filetype.ods,.list-view2.grid .img-precontainer-mini .filetype.odt,.list-view2.grid .img-precontainer-mini .filetype.otg,.list-view2.grid .img-precontainer-mini .filetype.otp,.list-view2.grid .img-precontainer-mini .filetype.ots,.list-view2.grid .img-precontainer-mini .filetype.ott{background:#367BBE}.list-view1.grid .img-precontainer-mini .filetype.bmp,.list-view1.grid .img-precontainer-mini .filetype.gif,.list-view1.grid .img-precontainer-mini .filetype.jpeg,.list-view1.grid .img-precontainer-mini .filetype.jpg,.list-view1.grid .img-precontainer-mini .filetype.png,.list-view1.grid .img-precontainer-mini .filetype.svg,.list-view1.grid .img-precontainer-mini .filetype.tiff,.list-view2.grid .img-precontainer-mini .filetype.bmp,.list-view2.grid .img-precontainer-mini .filetype.gif,.list-view2.grid .img-precontainer-mini .filetype.jpeg,.list-view2.grid .img-precontainer-mini .filetype.jpg,.list-view2.grid .img-precontainer-mini .filetype.png,.list-view2.grid .img-precontainer-mini .filetype.svg,.list-view2.grid .img-precontainer-mini .filetype.tiff{background:#CFA554}.list-view1.grid .img-precontainer-mini .filetype.dmg,.list-view1.grid .img-precontainer-mini .filetype.iso,.list-view1.grid .img-precontainer-mini .filetype.log,.list-view1.grid .img-precontainer-mini .filetype.sql,.list-view1.grid .img-precontainer-mini .filetype.txt,.list-view1.grid .img-precontainer-mini .filetype.xml,.list-view2.grid .img-precontainer-mini .filetype.dmg,.list-view2.grid .img-precontainer-mini .filetype.iso,.list-view2.grid .img-precontainer-mini .filetype.log,.list-view2.grid .img-precontainer-mini .filetype.sql,.list-view2.grid .img-precontainer-mini .filetype.txt,.list-view2.grid .img-precontainer-mini .filetype.xml{background:#CACACA}.list-view1.grid .img-precontainer-mini .filetype.accdb,.list-view1.grid .img-precontainer-mini .filetype.ade,.list-view1.grid .img-precontainer-mini .filetype.adp,.list-view1.grid .img-precontainer-mini .filetype.mdb,.list-view2.grid .img-precontainer-mini .filetype.accdb,.list-view2.grid .img-precontainer-mini .filetype.ade,.list-view2.grid .img-precontainer-mini .filetype.adp,.list-view2.grid .img-precontainer-mini .filetype.mdb{background:#B61C19}.lightbox-content{overflow:hidden;padding:0;background:0 0;box-shadow:none;border-radius:0;border:0}.context-menu-list{font-family:'Open Sans',sans-serif;width:200px;background:#fff;font-size:12px;margin:0;padding:5px}.context-menu-item{background-color:#fff;position:relative;height:auto;word-wrap:break-word;-webkit-user-select:none;-moz-user-select:0;-ms-user-select:none;user-select:none;padding:5px 5px 5px 30px}.context-menu-item:last-child{border:none}.context-menu-separator{padding-bottom:0;border-bottom:1px solid #ddd}.context-menu-item.hover{background-color:#ddd}.context-menu-input.hover,.context-menu-item.disabled.hover{cursor:default;background-color:#eee}.context-menu-item.icon{vertical-align:middle;background-position:4px 5px;width:auto;display:list-item}.context-menu-item.icon-edit{background-image:url(../img/file_edit.png)}.context-menu-item.icon-cut{background-image:url(../img/cut.png)}.context-menu-item.icon-copy{background-image:url(../img/copy.png)}.context-menu-item.icon-rename{background-image:url(../img/rename.png)}.context-menu-item.icon-preview{background-image:url(../img/preview.png)}.context-menu-item.icon-dimension{background-image:url(../img/dimension.png)}.context-menu-item.icon-date{background-image:url(../img/date.png)}.context-menu-item.icon-label{background-image:url(../img/label.png)}.context-menu-item.icon-size{background-image:url(../img/size.png)}.context-menu-item.icon-download{background-image:url(../img/download.png)}.context-menu-item.icon-paste{background-image:url(../img/page_white_paste.png)}.context-menu-item.icon-clipboard-apply{background-image:url(../img/clipboard_apply.png)}.context-menu-item.icon-delete{background-image:url(../img/page_white_delete.png)}.context-menu-item.icon-add{background-image:url(../img/page_white_add.png)}.context-menu-item.icon-quit{background-image:url(../img/door.png)}.context-menu-item.icon-info{background-image:url(../img/info.png)}.context-menu-item.icon-extract{background-image:url(../img/zip.png)}.context-menu-item.icon-url{background-image:url(../img/url.png)}.context-menu-item.icon-edit_img{background-image:url(../img/edit_img.png)}.context-menu-item.icon-duplicate{background-image:url(../img/duplicate.png)}.context-menu-item.icon-key{background-image:url(../img/key.png)}.dropzone{border:1px solid rgba(0,0,0,.03);min-height:360px;border-radius:3px;background:rgba(0,0,0,.03);padding:23px}.dropzone .dz-success *{cursor:pointer!important}.dropzone .dz-default.dz-message{opacity:1;-ms-filter:none;-webkit-filter:none;filter:none;transition:opacity .3s ease-in-out;background-repeat:no-repeat;background-position:0 0;position:absolute;width:428px;height:123px;margin-left:-214px;margin-top:-61.5px;top:50%;left:50%}.btn-primary,.btn-primary.disabled,.btn-primary[disabled]{background-color:#333}.btn-primary.active,.btn-primary:active,.btn-primary:focus,.btn-primary:hover{background-color:#222} \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/dialog.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/dialog.php new file mode 100755 index 0000000..d34ec9b --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/dialog.php @@ -0,0 +1,1262 @@ +isDir($ftp_base_folder.$upload_dir.$rfm_subfolder.$subdir)) || (!$ftp && !file_exists($current_path.$rfm_subfolder.$subdir))) { + $subdir = ''; + $rfm_subfolder = ''; +} + +if (trim($rfm_subfolder) == '') { + $cur_dir = $upload_dir.$subdir; + $cur_path = $current_path.$subdir; + $thumbs_path = $thumbs_base_path; + $parent = $subdir; +} else { + $cur_dir = $upload_dir.$rfm_subfolder.$subdir; + $cur_path = $current_path.$rfm_subfolder.$subdir; + $thumbs_path = $thumbs_base_path.$rfm_subfolder; + $parent = $rfm_subfolder.$subdir; +} + +if ($ftp) { + $cur_dir = $ftp_base_folder.$cur_dir; + $cur_path = str_replace(['/..', '..'], '', $cur_dir); + $thumbs_path = str_replace(['/..', '..'], '', $ftp_base_folder.$ftp_thumbs_dir.$rfm_subfolder); + $parent = $ftp_base_folder.$parent; +} + +if (!$ftp) { + $cycle = true; + $max_cycles = 50; + $i = 0; + while ($cycle && $i < $max_cycles) { + ++$i; + if ($parent == './') { + $parent = ''; + } + + if (file_exists($current_path.$parent.'config.php')) { + require_once $current_path.$parent.'config.php'; + $cycle = false; + } + + if ($parent == '') { + $cycle = false; + } else { + $parent = fix_dirname($parent).'/'; + } + } + + if (!is_dir($thumbs_path.$subdir)) { + create_folder(false, $thumbs_path.$subdir); + } +} +if (isset($_GET['callback'])) { + $callback = strip_tags($_GET['callback']); +} else { + $callback = 0; +} +if (isset($_GET['popup'])) { + $popup = strip_tags($_GET['popup']); +} else { + $popup = 0; +} +// Sanitize popup +$popup = (bool) $popup; + +if (isset($_GET['crossdomain'])) { + $crossdomain = strip_tags($_GET['crossdomain']); +} else { + $crossdomain = 0; +} + +// Sanitize crossdomain +$crossdomain = (bool) $crossdomain; + +// view type +if (!isset($_SESSION['RF']['view_type'])) { + $view = $default_view; + $_SESSION['RF']['view_type'] = $view; +} + +if (isset($_GET['view'])) { + $view = fix_get_params($_GET['view']); + $_SESSION['RF']['view_type'] = $view; +} + +$view = $_SESSION['RF']['view_type']; + +// filter +$filter = ''; +if (isset($_SESSION['RF']['filter'])) { + $filter = $_SESSION['RF']['filter']; +} + +if (isset($_GET['filter'])) { + $filter = fix_get_params($_GET['filter']); +} + +if (!isset($_SESSION['RF']['sort_by'])) { + $_SESSION['RF']['sort_by'] = 'name'; +} + +if (isset($_GET['sort_by'])) { + $sort_by = $_SESSION['RF']['sort_by'] = fix_get_params($_GET['sort_by']); +} else { + $sort_by = $_SESSION['RF']['sort_by']; +} + +if (!isset($_SESSION['RF']['descending'])) { + $_SESSION['RF']['descending'] = true; +} + +if (isset($_GET['descending'])) { + $descending = $_SESSION['RF']['descending'] = fix_get_params($_GET['descending']) == 1; +} else { + $descending = $_SESSION['RF']['descending']; +} + +$boolarray = [false => 'false', true => 'true']; + +$return_relative_url = isset($_GET['relative_url']) && $_GET['relative_url'] == '1' ? true : false; + +if (!isset($_GET['type'])) { + $_GET['type'] = 0; +} + +if ($_GET['type'] == 1 || $_GET['type'] == 3) { + $filter = ''; +} + +$extensions = null; +if (isset($_GET['extensions'])) { + $extensions = json_decode(urldecode($_GET['extensions'])); + if ($extensions) { + $ext = $extensions; + $show_filter_buttons = false; + } +} + +if (isset($_GET['editor'])) { + $editor = strip_tags($_GET['editor']); +} else { + if ($_GET['type'] == 0) { + $editor = false; + } else { + $editor = 'tinymce'; + } +} + +if (!isset($_GET['field_id'])) { + $_GET['field_id'] = ''; +} + +$field_id = isset($_GET['field_id']) ? fix_get_params($_GET['field_id']) : ''; +$type_param = fix_get_params($_GET['type']); + +if ($type_param == 1) { + $apply = 'apply_img'; +} elseif ($type_param == 2) { + $apply = 'apply_link'; +} elseif ($type_param == 0 && $_GET['field_id'] == '') { + $apply = 'apply_none'; +} elseif ($type_param == 3) { + $apply = 'apply_video'; +} else { + $apply = 'apply'; +} + +$get_params = [ + 'editor' => $editor, + 'type' => $type_param, + 'lang' => $lang, + 'popup' => $popup, + 'crossdomain' => $crossdomain, + 'extensions' => ($extensions) ? urlencode(json_encode($extensions)) : null, + 'field_id' => $field_id, + 'relative_url' => $return_relative_url, + 'akey' => (isset($_GET['akey']) && $_GET['akey'] != '' ? $_GET['akey'] : 'key'), +]; +if (isset($_GET['CKEditorFuncNum'])) { + $get_params['CKEditorFuncNum'] = $_GET['CKEditorFuncNum']; + $get_params['CKEditor'] = ($_GET['CKEditor'] ?? ''); +} +$get_params['fldr'] = ''; + +$get_params = http_build_query($get_params); +?> + + + + + + + + Responsive FileManager + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+
+ + +
+

:


+ + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+ +
+ + + +
+ +scanDir('/'.$ftp_base_folder.$upload_dir.$rfm_subfolder.$subdir); + if (!$ftp->isDir('/'.$ftp_base_folder.$ftp_thumbs_dir.$rfm_subfolder.$subdir)) { + create_folder(false, '/'.$ftp_base_folder.$ftp_thumbs_dir.$rfm_subfolder.$subdir, $ftp, $config); + } + } catch (FtpClient\FtpException $e) { + echo 'Error: '; + echo $e->getMessage(); + echo '
Please check configurations'; + exit; + } +} else { + $files = scandir($current_path.$rfm_subfolder.$subdir); +} + +$n_files = count($files); + +// php sorting +$sorted = []; +// $current_folder=array(); +// $prev_folder=array(); +$current_files_number = 0; +$current_folders_number = 0; + +foreach ($files as $k => $file) { + if ($ftp) { + $date = strtotime($file['day'].' '.$file['month'].' '.date('Y').' '.$file['time']); + $size = $file['size']; + if ($file['type'] == 'file') { + ++$current_files_number; + $file_ext = substr(strrchr($file['name'], '.'), 1); + } else { + ++$current_folders_number; + $file_ext = trans('Type_dir'); + } + $sorted[$k] = [ + 'file' => $file['name'], + 'file_lcase' => strtolower($file['name']), + 'date' => $date, + 'size' => $size, + 'permissions' => $file['permissions'], + 'extension' => strtolower($file_ext), + ]; + } else { + if ($file != '.' && $file != '..') { + if (is_dir($current_path.$rfm_subfolder.$subdir.$file)) { + $date = filemtime($current_path.$rfm_subfolder.$subdir.$file); + ++$current_folders_number; + if ($show_folder_size) { + [$size,$nfiles,$nfolders] = folder_info($current_path.$rfm_subfolder.$subdir.$file, false); + } else { + $size = 0; + } + $file_ext = trans('Type_dir'); + $sorted[$k] = [ + 'file' => $file, + 'file_lcase' => strtolower($file), + 'date' => $date, + 'size' => $size, + 'permissions' => '', + 'extension' => strtolower($file_ext), + ]; + if ($show_folder_size) { + $sorted[$k]['nfiles'] = $nfiles; + $sorted[$k]['nfolders'] = $nfolders; + } + } else { + ++$current_files_number; + $file_path = $current_path.$rfm_subfolder.$subdir.$file; + $date = filemtime($file_path); + $size = filesize($file_path); + $file_ext = substr(strrchr($file, '.'), 1); + $sorted[$k] = [ + 'file' => $file, + 'file_lcase' => strtolower($file), + 'date' => $date, + 'size' => $size, + 'permissions' => '', + 'extension' => strtolower($file_ext), + ]; + } + } + } +} + +// Should lazy loading be enabled +$lazy_loading_enabled = ($lazy_loading_file_number_threshold == 0 || $lazy_loading_file_number_threshold != -1 && $n_files > $lazy_loading_file_number_threshold) ? true : false; + +function filenameSort($x, $y) +{ + return $x['file_lcase'] < $y['file_lcase']; +} +function dateSort($x, $y) +{ + return $x['date'] < $y['date']; +} +function sizeSort($x, $y) +{ + return $x['size'] < $y['size']; +} +function extensionSort($x, $y) +{ + return $x['extension'] < $y['extension']; +} + +switch ($sort_by) { + case 'date': + usort($sorted, 'dateSort'); + break; + case 'size': + usort($sorted, 'sizeSort'); + break; + case 'extension': + usort($sorted, 'extensionSort'); + break; + default: + usort($sorted, 'filenameSort'); + break; +} + +if (!$descending) { + $sorted = array_reverse($sorted); +} + +if ($subdir != '') { + $sorted = array_merge([['file' => '..']], $sorted); +} +$files = $sorted; + +?> + + + + + + + +
+ + +
+ +
+
+ isDir($ftp_base_folder.$upload_dir.$rfm_subfolder.$subdir)) || (!$ftp && @opendir($current_path.$rfm_subfolder.$subdir) === false)) { ?> +
+
There is an error! The upload folder there isn't. Check your config.php file.
+ +

+ +
+ + + +
+
+
+
+
+
+
+
+ + + + +
    + $file_number_limit_js && $file != '..' && stripos($file, $filter) === false)) { + continue; + } + $new_name = fix_filename($file, $config); + if ($ftp && $file != '..' && $file != $new_name) { + // rename + rename_folder($current_path.$subdir.$file, $new_name, $ftp, $config); + $file = $new_name; + } + // add in thumbs folder if not exist + if ($file != '..') { + if (!$ftp && !file_exists($thumbs_path.$subdir.$file)) { + create_folder(false, $thumbs_path.$subdir.$file, $ftp, $config); + } + } + + $class_ext = 3; + if ($file == '..' && trim($subdir) != '') { + $src = explode('/', $subdir); + unset($src[count($src) - 2]); + $src = implode('/', $src); + if ($src == '') { + $src = '/'; + } + } elseif ($file != '..') { + $src = $subdir.$file.'/'; + } ?> + + $file_array) { + $file = $file_array['file']; + + if ($file == '.' || $file == '..' || $file_array['extension'] == trans('Type_dir') || in_array($file, $hidden_files) || !in_array(fix_strtolower($file_array['extension']), $ext) || ($filter != '' && $n_files > $file_number_limit_js && stripos($file, $filter) === false)) { + continue; + } + $filename = substr($file, 0, '-'.(strlen($file_array['extension']) + 1)); + if (!$ftp) { + $file_path = $current_path.$rfm_subfolder.$subdir.$file; + // check if file have illegal caracter + + if ($file != fix_filename($file, $config)) { + $file1 = fix_filename($file, $config); + $file_path1 = ($current_path.$rfm_subfolder.$subdir.$file1); + if (file_exists($file_path1)) { + $i = 1; + $info = pathinfo($file1); + while (file_exists($current_path.$rfm_subfolder.$subdir.$info['filename'].'.['.$i.'].'.$info['extension'])) { + ++$i; + } + $file1 = $info['filename'].'.['.$i.'].'.$info['extension']; + $file_path1 = ($current_path.$rfm_subfolder.$subdir.$file1); + } + + $filename = substr($file1, 0, '-'.(strlen($file_array['extension']) + 1)); + rename_file($file_path, fix_filename($filename, $config), $ftp, $config); + $file = $file1; + $file_array['extension'] = fix_filename($file_array['extension'], $config); + $file_path = $file_path1; + } + } else { + $file_path = $config['ftp_base_url'].$upload_dir.$rfm_subfolder.$subdir.$file; + } + + $is_img = false; + $is_video = false; + $is_audio = false; + $show_original = false; + $show_original_mini = false; + $mini_src = ''; + $src_thumb = ''; + if (in_array($file_array['extension'], $ext_img)) { + $src = $base_url.$cur_dir.$file; + $is_img = true; + + $img_width = $img_height = ''; + if ($ftp) { + $mini_src = $src_thumb = $config['ftp_base_url'].$ftp_thumbs_dir.$subdir.$file; + $creation_thumb_path = '/'.$config['ftp_base_folder'].$ftp_thumbs_dir.$subdir.$file; + } else { + $creation_thumb_path = $mini_src = $src_thumb = $thumbs_path.$subdir.$file; + + if (!file_exists($src_thumb)) { + if (!create_img($file_path, $creation_thumb_path, 122, 91, 'crop', $ftp, $config)) { + $src_thumb = $mini_src = ''; + } else { + new_thumbnails_creation($current_path.$rfm_subfolder.$subdir, $file_path, $file, $current_path, '', '', '', '', '', '', '', $fixed_image_creation, $fixed_path_from_filemanager, $fixed_image_creation_name_to_prepend, $fixed_image_creation_to_append, $fixed_image_creation_width, $fixed_image_creation_height, $fixed_image_creation_option); + } + } + // check if is smaller than thumb + [$img_width, $img_height, $img_type, $attr] = @getimagesize($file_path); + if ($img_width < 122 && $img_height < 91) { + $src_thumb = $file_path; + $show_original = true; + } + + if ($img_width < 45 && $img_height < 38) { + $mini_src = $current_path.$rfm_subfolder.$subdir.$file; + $show_original_mini = true; + } + } + } + $is_icon_thumb = false; + $is_icon_thumb_mini = false; + $no_thumb = false; + if ($src_thumb == '') { + $no_thumb = true; + if (file_exists('img/'.$icon_theme.'/'.$file_array['extension'].'.jpg')) { + $src_thumb = 'img/'.$icon_theme.'/'.$file_array['extension'].'.jpg'; + } else { + $src_thumb = 'img/'.$icon_theme.'/default.jpg'; + } + $is_icon_thumb = true; + } else { + $src_thumb = $base_url.$thumbs_dir.$subdir.$file; + } + if ($mini_src == '') { + $is_icon_thumb_mini = false; + } else { + $mini_src = $src_thumb; + } + + $class_ext = 0; + if (in_array($file_array['extension'], $ext_video)) { + $class_ext = 4; + $is_video = true; + } elseif (in_array($file_array['extension'], $ext_img)) { + $class_ext = 2; + } elseif (in_array($file_array['extension'], $ext_music)) { + $class_ext = 5; + $is_audio = true; + } elseif (in_array($file_array['extension'], $ext_misc)) { + $class_ext = 3; + } else { + $class_ext = 1; + } + if ((!($_GET['type'] == 1 && !$is_img) && !(($_GET['type'] == 3 && !$is_video) && ($_GET['type'] == 3 && !$is_audio))) && $class_ext > 0) { + ?> + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/execute.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/execute.php new file mode 100755 index 0000000..703c62b --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/execute.php @@ -0,0 +1,505 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$config = include 'config/config.php'; +// TODO switch to array +extract($config, \EXTR_OVERWRITE); + +include 'include/utils.php'; + +if ($_SESSION['RF']['verify'] != 'RESPONSIVEfilemanager') { + response(trans('forbiden').AddErrorLocation())->send(); + exit; +} + +if (strpos($_POST['path'], '/') === 0 + || strpos($_POST['path'], '../') !== false + || strpos($_POST['path'], './') === 0 + || strpos($_POST['path'], '..\\') !== false + || strpos($_POST['path'], '.\\') === 0) { + response(trans('wrong path'.AddErrorLocation()))->send(); + exit; +} + +if (isset($_SESSION['RF']['language']) && file_exists('lang/'.basename($_SESSION['RF']['language']).'.php')) { + $languages = include 'lang/languages.php'; + if (array_key_exists($_SESSION['RF']['language'], $languages)) { + include 'lang/'.basename($_SESSION['RF']['language']).'.php'; + } else { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; + } +} else { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; +} + +$ftp = ftp_con($config); + +$base = $current_path; +$path = $base.$_POST['path']; +$cycle = true; +$max_cycles = 50; +$i = 0; +while ($cycle && $i < $max_cycles) { + ++$i; + if ($path == $base) { + $cycle = false; + } + + if (file_exists($path.'config.php')) { + require_once $path.'config.php'; + $cycle = false; + } + $path = fix_dirname($path).'/'; +} + +$path = $current_path.$_POST['path']; +$path_thumb = $thumbs_base_path.$_POST['path']; + +if ($ftp) { + $path = $ftp_base_folder.$upload_dir.$_POST['path']; + $path_thumb = $ftp_base_folder.$ftp_thumbs_dir.$_POST['path']; +} + +if (isset($_POST['name'])) { + $name = fix_filename($_POST['name'], $config); + if (strpos($name, '../') !== false || strpos($name, '..\\') !== false) { + response(trans('wrong name').AddErrorLocation())->send(); + exit; + } +} + +$info = pathinfo($path); +if (isset($info['extension']) && !(isset($_GET['action']) && $_GET['action'] == 'delete_folder') && !in_array(strtolower($info['extension']), $ext) && $_GET['action'] != 'create_file') { + response(trans('wrong extension').AddErrorLocation())->send(); + exit; +} + +if (isset($_GET['action'])) { + switch ($_GET['action']) { + case 'delete_file': + if ($delete_files) { + if ($ftp) { + try { + $ftp->delete('/'.$path); + @$ftp->delete('/'.$path_thumb); + } catch (FtpClient\FtpException $e) { + return; + } + } else { + unlink($path); + if (file_exists($path_thumb)) { + unlink($path_thumb); + } + } + + $info = pathinfo($path); + if (!$ftp && $relative_image_creation) { + foreach ($relative_path_from_current_pos as $k => $path) { + if ($path != '' && $path[strlen($path) - 1] != '/') { + $path .= '/'; + } + + if (file_exists($info['dirname'].'/'.$path.$relative_image_creation_name_to_prepend[$k].$info['filename'].$relative_image_creation_name_to_append[$k].'.'.$info['extension'])) { + unlink($info['dirname'].'/'.$path.$relative_image_creation_name_to_prepend[$k].$info['filename'].$relative_image_creation_name_to_append[$k].'.'.$info['extension']); + } + } + } + + if (!$ftp && $fixed_image_creation) { + foreach ($fixed_path_from_filemanager as $k => $path) { + if ($path != '' && $path[strlen($path) - 1] != '/') { + $path .= '/'; + } + + $base_dir = $path.substr_replace($info['dirname'].'/', '', 0, strlen($current_path)); + if (file_exists($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'])) { + unlink($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension']); + } + } + } + } + break; + case 'delete_folder': + if ($delete_folders) { + if ($ftp) { + deleteDir($path, $ftp, $config); + deleteDir($path_thumb, $ftp, $config); + } else { + if (is_dir($path_thumb)) { + deleteDir($path_thumb); + } + + if (is_dir($path)) { + deleteDir($path); + if ($fixed_image_creation) { + foreach ($fixed_path_from_filemanager as $k => $paths) { + if ($paths != '' && $paths[strlen($paths) - 1] != '/') { + $paths .= '/'; + } + + $base_dir = $paths.substr_replace($path, '', 0, strlen($current_path)); + if (is_dir($base_dir)) { + deleteDir($base_dir); + } + } + } + } + } + } + break; + case 'create_folder': + if ($create_folders) { + $name = fix_filename($_POST['name'], $config); + $path .= $name; + $path_thumb .= $name; + create_folder(fix_path($path, $config), fix_path($path_thumb, $config), $ftp, $config); + } + break; + case 'rename_folder': + if ($rename_folders) { + $name = fix_filename($name, $config); + $name = str_replace('.', '', $name); + + if (!empty($name)) { + if (!rename_folder($path, $name, $ftp, $config)) { + response(trans('Rename_existing_folder').AddErrorLocation())->send(); + exit; + } + rename_folder($path_thumb, $name, $ftp, $config); + if (!$ftp && $fixed_image_creation) { + foreach ($fixed_path_from_filemanager as $k => $paths) { + if ($paths != '' && $paths[strlen($paths) - 1] != '/') { + $paths .= '/'; + } + + $base_dir = $paths.substr_replace($path, '', 0, strlen($current_path)); + rename_folder($base_dir, $name, $ftp, $config); + } + } + } else { + response(trans('Empty_name').AddErrorLocation())->send(); + exit; + } + } + break; + case 'create_file': + if ($create_text_files === false) { + response(sprintf(trans('File_Open_Edit_Not_Allowed'), strtolower(trans('Edit'))).AddErrorLocation())->send(); + exit; + } + + if (!isset($editable_text_file_exts) || !is_array($editable_text_file_exts)) { + $editable_text_file_exts = []; + } + + // check if user supplied extension + if (strpos($name, '.') === false) { + response(trans('No_Extension').' '.sprintf(trans('Valid_Extensions'), implode(', ', $editable_text_file_exts)).AddErrorLocation())->send(); + exit; + } + + // correct name + $old_name = $name; + $name = fix_filename($name, $config); + if (empty($name)) { + response(trans('Empty_name').AddErrorLocation())->send(); + exit; + } + + // check extension + $parts = explode('.', $name); + if (!in_array(end($parts), $editable_text_file_exts)) { + response(trans('Error_extension').' '.sprintf(trans('Valid_Extensions'), implode(', ', $editable_text_file_exts)), 400)->send(); + exit; + } + + $content = $_POST['new_content']; + + if ($ftp) { + $tmp = time().$name; + file_put_contents($tmp, $content); + $ftp->put('/'.$path.$name, $tmp, \FTP_BINARY); + unlink($tmp); + response(trans('File_Save_OK'))->send(); + } else { + if (!checkresultingsize(strlen($content))) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + // file already exists + if (file_exists($path.$name)) { + response(trans('Rename_existing_file').AddErrorLocation())->send(); + exit; + } + + if (@file_put_contents($path.$name, $content) === false) { + response(trans('File_Save_Error').AddErrorLocation())->send(); + exit; + } + if (is_function_callable('chmod') !== false) { + chmod($path.$name, 0644); + } + response(trans('File_Save_OK'))->send(); + exit; + } + + break; + case 'rename_file': + if ($rename_files) { + $name = fix_filename($name, $config); + if (!empty($name)) { + if (!rename_file($path, $name, $ftp, $config)) { + response(trans('Rename_existing_file').AddErrorLocation())->send(); + exit; + } + + rename_file($path_thumb, $name, $ftp, $config); + + if ($fixed_image_creation) { + $info = pathinfo($path); + + foreach ($fixed_path_from_filemanager as $k => $paths) { + if ($paths != '' && $paths[strlen($paths) - 1] != '/') { + $paths .= '/'; + } + + $base_dir = $paths.substr_replace($info['dirname'].'/', '', 0, strlen($current_path)); + if (file_exists($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'])) { + rename_file($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'], $fixed_image_creation_name_to_prepend[$k].$name.$fixed_image_creation_to_append[$k], $ftp, $config); + } + } + } + } else { + response(trans('Empty_name').AddErrorLocation())->send(); + exit; + } + } + break; + case 'duplicate_file': + if ($duplicate_files) { + $name = fix_filename($name, $config); + if (!empty($name)) { + if (!$ftp && !checkresultingsize(filesize($path))) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + if (!duplicate_file($path, $name, $ftp, $config)) { + response(trans('Rename_existing_file').AddErrorLocation())->send(); + exit; + } + + duplicate_file($path_thumb, $name, $ftp, $config); + + if (!$ftp && $fixed_image_creation) { + $info = pathinfo($path); + foreach ($fixed_path_from_filemanager as $k => $paths) { + if ($paths != '' && $paths[strlen($paths) - 1] != '/') { + $paths .= '/'; + } + + $base_dir = $paths.substr_replace($info['dirname'].'/', '', 0, strlen($current_path)); + + if (file_exists($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'])) { + duplicate_file($base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'], $fixed_image_creation_name_to_prepend[$k].$name.$fixed_image_creation_to_append[$k]); + } + } + } + } else { + response(trans('Empty_name').AddErrorLocation())->send(); + exit; + } + } + break; + case 'paste_clipboard': + if (!isset($_SESSION['RF']['clipboard_action'], $_SESSION['RF']['clipboard']['path']) + || $_SESSION['RF']['clipboard_action'] == '' + || $_SESSION['RF']['clipboard']['path'] == '') { + response()->send(); + exit; + } + + $action = $_SESSION['RF']['clipboard_action']; + $data = $_SESSION['RF']['clipboard']; + + if ($ftp) { + if ($_POST['path'] != '') { + $path .= \DIRECTORY_SEPARATOR; + $path_thumb .= \DIRECTORY_SEPARATOR; + } + $path_thumb .= basename($data['path']); + $path .= basename($data['path']); + $data['path_thumb'] = \DIRECTORY_SEPARATOR.$config['ftp_base_folder'].$config['ftp_thumbs_dir'].$data['path']; + $data['path'] = \DIRECTORY_SEPARATOR.$config['ftp_base_folder'].$config['upload_dir'].$data['path']; + } else { + $data['path_thumb'] = $thumbs_base_path.$data['path']; + $data['path'] = $current_path.$data['path']; + } + + $pinfo = pathinfo($data['path']); + + // user wants to paste to the same dir. nothing to do here... + if ($pinfo['dirname'] == rtrim($path, \DIRECTORY_SEPARATOR)) { + response()->send(); + exit; + } + + // user wants to paste folder to it's own sub folder.. baaaah. + if (is_dir($data['path']) && strpos($path, $data['path']) !== false) { + response()->send(); + exit; + } + + // something terribly gone wrong + if ($action != 'copy' && $action != 'cut') { + response(trans('wrong action').AddErrorLocation())->send(); + exit; + } + if ($ftp) { + if ($action == 'copy') { + $tmp = time().basename($data['path']); + $ftp->get($tmp, $data['path'], \FTP_BINARY); + $ftp->put(\DIRECTORY_SEPARATOR.$path, $tmp, \FTP_BINARY); + unlink($tmp); + + if (url_exists($data['path_thumb'])) { + $tmp = time().basename($data['path_thumb']); + @$ftp->get($tmp, $data['path_thumb'], \FTP_BINARY); + @$ftp->put(\DIRECTORY_SEPARATOR.$path_thumb, $tmp, \FTP_BINARY); + unlink($tmp); + } + } elseif ($action == 'cut') { + $ftp->rename($data['path'], \DIRECTORY_SEPARATOR.$path); + if (url_exists($data['path_thumb'])) { + @$ftp->rename($data['path_thumb'], \DIRECTORY_SEPARATOR.$path_thumb); + } + } + } else { + // check for writability + if (is_really_writable($path) === false || is_really_writable($path_thumb) === false) { + response(trans('Dir_No_Write').'
'.str_replace('../', '', $path).'
'.str_replace('../', '', $path_thumb).AddErrorLocation())->send(); + exit; + } + + // check if server disables copy or rename + if (is_function_callable($action == 'copy' ? 'copy' : 'rename') === false) { + response(sprintf(trans('Function_Disabled'), $action == 'copy' ? (trans('Copy')) : (trans('Cut'))).AddErrorLocation())->send(); + exit; + } + if ($action == 'copy') { + [$sizeFolderToCopy,$fileNum,$foldersCount] = folder_info($path, false); + if (!checkresultingsize($sizeFolderToCopy)) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + rcopy($data['path'], $path); + rcopy($data['path_thumb'], $path_thumb); + } elseif ($action == 'cut') { + rrename($data['path'], $path); + rrename($data['path_thumb'], $path_thumb); + + // cleanup + if (is_dir($data['path']) === true) { + rrename_after_cleaner($data['path']); + rrename_after_cleaner($data['path_thumb']); + } + } + } + + // cleanup + $_SESSION['RF']['clipboard']['path'] = null; + $_SESSION['RF']['clipboard_action'] = null; + + break; + case 'chmod': + $mode = $_POST['new_mode']; + $rec_option = $_POST['is_recursive']; + $valid_options = ['none', 'files', 'folders', 'both']; + $chmod_perm = ($_POST['folder'] ? $chmod_dirs : $chmod_files); + + // check perm + if ($chmod_perm === false) { + response(sprintf(trans('File_Permission_Not_Allowed'), is_dir($path) ? (trans('Folders')) : (trans('Files'))).AddErrorLocation())->send(); + exit; + } + // check mode + if (!preg_match('/^[0-7]{3}$/', $mode)) { + response(trans('File_Permission_Wrong_Mode').AddErrorLocation())->send(); + exit; + } + // check recursive option + if (!in_array($rec_option, $valid_options)) { + response(trans('wrong option').AddErrorLocation())->send(); + exit; + } + // check if server disabled chmod + if (!$ftp && is_function_callable('chmod') === false) { + response(sprintf(trans('Function_Disabled'), 'chmod').AddErrorLocation())->send(); + exit; + } + + $mode = '0'.$mode; + $mode = octdec($mode); + if ($ftp) { + $ftp->chmod($mode, '/'.$path); + } else { + rchmod($path, $mode, $rec_option); + } + + break; + case 'save_text_file': + $content = $_POST['new_content']; + // $content = htmlspecialchars($content); not needed + // $content = stripslashes($content); + + if ($ftp) { + $tmp = time(); + file_put_contents($tmp, $content); + try { + $ftp->put('/'.$path, $tmp, \FTP_BINARY); + } catch (FtpClient\FtpException $e) { + echo $e->getMessage(); + } + unlink($tmp); + response(trans('File_Save_OK'))->send(); + } else { + // no file + if (!file_exists($path)) { + response(trans('File_Not_Found').AddErrorLocation())->send(); + exit; + } + + // not writable or edit not allowed + if (!is_writable($path) || $edit_text_files === false) { + response(sprintf(trans('File_Open_Edit_Not_Allowed'), strtolower(trans('Edit'))).AddErrorLocation())->send(); + exit; + } + + if (!checkresultingsize(strlen($content))) { + response(sprintf(trans('max_size_reached'), $MaxSizeTotal).AddErrorLocation())->send(); + exit; + } + if (@file_put_contents($path, $content) === false) { + response(trans('File_Save_Error').AddErrorLocation())->send(); + exit; + } + response(trans('File_Save_OK'))->send(); + exit; + } + + break; + default: + response(trans('wrong action').AddErrorLocation())->send(); + exit; + } +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/force_download.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/force_download.php new file mode 100755 index 0000000..fde5e83 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/force_download.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +$config = include 'config/config.php'; + +// TODO switch to array +extract($config, \EXTR_OVERWRITE); + +include 'include/utils.php'; + +$ftp = ftp_con($config); + +if ($_SESSION['RF']['verify'] != 'RESPONSIVEfilemanager') { + response(trans('forbiden').AddErrorLocation(), 403)->send(); + exit; +} + +include 'include/mime_type_lib.php'; + +if ( + strpos($_POST['path'], '/') === 0 + || strpos($_POST['path'], '../') !== false + || strpos($_POST['path'], './') === 0 + || strpos($_POST['path'], '..\\') !== false + || strpos($_POST['path'], '.\\') === 0 +) { + response(trans('wrong path'.AddErrorLocation()), 400)->send(); + exit; +} + +if (strpos($_POST['name'], '/') !== false) { + response(trans('wrong path'.AddErrorLocation()), 400)->send(); + exit; +} +if ($ftp) { + $path = $ftp_base_url.$upload_dir.$_POST['path']; +} else { + $path = $current_path.$_POST['path']; +} + +$name = $_POST['name']; + +$info = pathinfo($name); + +if (!in_array(fix_strtolower($info['extension']), $ext)) { + response(trans('wrong extension'.AddErrorLocation()), 400)->send(); + exit; +} + +$file_name = $info['basename']; +$file_ext = $info['extension']; +$file_path = $path.$name; + +// make sure the file exists +if ($ftp) { + $file_url = 'http://www.myremoteserver.com/file.exe'; + header('Content-Type: application/octet-stream'); + header('Content-Transfer-Encoding: Binary'); + header('Content-disposition: attachment; filename="'.$file_name.'"'); + readfile($file_path); +} elseif (is_file($file_path) && is_readable($file_path)) { + if (!file_exists($path.$name)) { + response(trans('File_Not_Found'.AddErrorLocation()), 404)->send(); + exit; + } + + $size = filesize($file_path); + $file_name = rawurldecode($file_name); + if (function_exists('mime_content_type')) { + $mime_type = mime_content_type($file_path); + } elseif (function_exists('finfo_open')) { + $finfo = finfo_open(\FILEINFO_MIME_TYPE); + $mime_type = finfo_file($finfo, $file_path); + } else { + include 'include/mime_type_lib.php'; + $mime_type = get_file_mime_type($file_path); + } + + @ob_end_clean(); + if (ini_get('zlib.output_compression')) { + ini_set('zlib.output_compression', 'Off'); + } + header('Content-Type: '.$mime_type); + header('Content-Disposition: attachment; filename="'.$file_name.'"'); + header('Content-Transfer-Encoding: binary'); + header('Accept-Ranges: bytes'); + + if (isset($_SERVER['HTTP_RANGE'])) { + [$a, $range] = explode('=', $_SERVER['HTTP_RANGE'], 2); + [$range] = explode(',', $range, 2); + [$range, $range_end] = explode('-', $range); + $range = (int) $range; + if (!$range_end) { + $range_end = $size - 1; + } else { + $range_end = (int) $range_end; + } + + $new_length = $range_end - $range + 1; + header('HTTP/1.1 206 Partial Content'); + header("Content-Length: $new_length"); + header("Content-Range: bytes $range-$range_end/$size"); + } else { + $new_length = $size; + header('Content-Length: '.$size); + } + + $chunksize = 1 * (1024 * 1024); + $bytes_send = 0; + if ($file = fopen($file_path, 'r')) { + if (isset($_SERVER['HTTP_RANGE'])) { + fseek($file, $range); + } + + while (!feof($file) && + (!connection_aborted()) && + ($bytes_send < $new_length) + ) { + $buffer = fread($file, $chunksize); + echo $buffer; + flush(); + $bytes_send += strlen($buffer); + } + fclose($file); + } else { + exit('Error - can not open file.'); + } + + exit; +} else { + // file does not exist + header('HTTP/1.0 404 Not Found'); + exit; +} + +exit; diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_apply.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_apply.png new file mode 100644 index 0000000..d470c44 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_apply.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_clear.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_clear.png new file mode 100644 index 0000000..e7fb903 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/clipboard_clear.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/copy.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/copy.png new file mode 100644 index 0000000..e1d8911 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/copy.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/cut.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/cut.png new file mode 100755 index 0000000..a139267 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/cut.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/date.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/date.png new file mode 100644 index 0000000..ef310f4 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/date.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/dimension.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/dimension.png new file mode 100755 index 0000000..43dcc10 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/dimension.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/down.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/down.png new file mode 100644 index 0000000..7651122 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/down.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/download.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/download.png new file mode 100755 index 0000000..76125f2 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/download.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/duplicate.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/duplicate.png new file mode 100644 index 0000000..faa0f97 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/duplicate.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/edit_img.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/edit_img.png new file mode 100644 index 0000000..ca55e58 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/edit_img.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/file_edit.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/file_edit.png new file mode 100644 index 0000000..4bcd072 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/file_edit.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings-white.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings-white.png new file mode 100755 index 0000000..a92969a Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings-white.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings.png new file mode 100755 index 0000000..f43139a Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/glyphicons-halflings.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ac3.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ac3.jpg new file mode 100644 index 0000000..3ce97c7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ac3.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/accdb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/accdb.jpg new file mode 100644 index 0000000..4581e5e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/accdb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ade.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ade.jpg new file mode 100644 index 0000000..b653b3d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ade.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/adp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/adp.jpg new file mode 100644 index 0000000..b653b3d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/adp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ai.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ai.jpg new file mode 100644 index 0000000..e469be3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ai.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/aiff.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/aiff.jpg new file mode 100644 index 0000000..053ff30 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/aiff.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/avi.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/avi.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/avi.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/bmp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/bmp.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/bmp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/c4d.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/c4d.jpg new file mode 100644 index 0000000..62994da Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/c4d.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/css.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/css.jpg new file mode 100644 index 0000000..e1673b0 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/css.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/csv.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/csv.jpg new file mode 100644 index 0000000..ca93201 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/csv.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/default.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/default.jpg new file mode 100644 index 0000000..94ca669 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/default.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dmg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dmg.jpg new file mode 100644 index 0000000..2494e87 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dmg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/doc.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/doc.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/doc.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/docx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/docx.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/docx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dwg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dwg.jpg new file mode 100644 index 0000000..bf5d63f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dwg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dxf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dxf.jpg new file mode 100644 index 0000000..7041cbe Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/dxf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/favicon.ico b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/favicon.ico new file mode 100644 index 0000000..7383707 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/favicon.ico differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/fla.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/fla.jpg new file mode 100644 index 0000000..41bd2ec Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/fla.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/flv.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/flv.jpg new file mode 100644 index 0000000..75aff12 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/flv.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder.png new file mode 100644 index 0000000..d56b85e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder_back.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder_back.png new file mode 100644 index 0000000..dc0786e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/folder_back.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gif.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gif.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gif.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gz.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gz.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/gz.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/html.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/html.jpg new file mode 100644 index 0000000..a1af20d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/html.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/iso.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/iso.jpg new file mode 100644 index 0000000..379f506 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/iso.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpeg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpeg.jpg new file mode 100644 index 0000000..0be5ac5 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpeg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpg.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/jpg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/log.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/log.jpg new file mode 100644 index 0000000..ec6d3e3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/log.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/m4a.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/m4a.jpg new file mode 100644 index 0000000..5c6417c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/m4a.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mdb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mdb.jpg new file mode 100644 index 0000000..4581e5e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mdb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mid.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mid.jpg new file mode 100644 index 0000000..176cd71 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mid.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mov.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mov.jpg new file mode 100644 index 0000000..78a2ffa Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mov.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp3.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp3.jpg new file mode 100644 index 0000000..b79ba99 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp3.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp4.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp4.jpg new file mode 100644 index 0000000..50184ef Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mp4.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpeg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpeg.jpg new file mode 100644 index 0000000..50f99ff Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpeg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpg.jpg new file mode 100644 index 0000000..daa2bc4 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/mpg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odb.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odf.jpg new file mode 100644 index 0000000..da290dc Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odg.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odp.jpg new file mode 100644 index 0000000..da290dc Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ods.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ods.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ods.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odt.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/odt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ogg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ogg.jpg new file mode 100644 index 0000000..e20ab2f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ogg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otg.jpg new file mode 100644 index 0000000..af44482 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otp.jpg new file mode 100644 index 0000000..802e78c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/otp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ots.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ots.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ots.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ott.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ott.jpg new file mode 100644 index 0000000..1a4157e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ott.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pdf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pdf.jpg new file mode 100644 index 0000000..2080921 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pdf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/png.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/png.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/png.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ppt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ppt.jpg new file mode 100644 index 0000000..aa13f73 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/ppt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pptx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pptx.jpg new file mode 100644 index 0000000..8504969 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/pptx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/psd.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/psd.jpg new file mode 100644 index 0000000..53028c5 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/psd.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rar.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rar.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rar.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rtf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rtf.jpg new file mode 100644 index 0000000..c0e14b3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/rtf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/skp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/skp.jpg new file mode 100644 index 0000000..6ebcde0 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/skp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/sql.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/sql.jpg new file mode 100644 index 0000000..46794ad Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/sql.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/stp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/stp.jpg new file mode 100644 index 0000000..cab6077 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/stp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/svg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/svg.jpg new file mode 100644 index 0000000..8c771ca Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/svg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tar.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tar.jpg new file mode 100644 index 0000000..665cd03 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tar.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tiff.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tiff.jpg new file mode 100644 index 0000000..afe2cde Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/tiff.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/txt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/txt.jpg new file mode 100644 index 0000000..ec6d3e3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/txt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/vwx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/vwx.jpg new file mode 100644 index 0000000..c56cad7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/vwx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wav.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wav.jpg new file mode 100644 index 0000000..e20ab2f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wav.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/webm.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/webm.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/webm.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wma.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wma.jpg new file mode 100644 index 0000000..b9ddc2d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/wma.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xhtml.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xhtml.jpg new file mode 100644 index 0000000..5979c91 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xhtml.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xls.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xls.jpg new file mode 100644 index 0000000..4f656f7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xls.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xlsx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xlsx.jpg new file mode 100644 index 0000000..b4c33ff Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xlsx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xml.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xml.jpg new file mode 100644 index 0000000..73301e7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/xml.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/zip.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/zip.jpg new file mode 100644 index 0000000..36d1591 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico/zip.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ac3.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ac3.jpg new file mode 100644 index 0000000..0530f28 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ac3.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/accdb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/accdb.jpg new file mode 100644 index 0000000..13607b1 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/accdb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ade.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ade.jpg new file mode 100644 index 0000000..92d67d9 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ade.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/adp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/adp.jpg new file mode 100644 index 0000000..92d67d9 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/adp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ai.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ai.jpg new file mode 100644 index 0000000..b7c353b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ai.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/aiff.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/aiff.jpg new file mode 100644 index 0000000..f0422da Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/aiff.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/avi.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/avi.jpg new file mode 100644 index 0000000..9dfa9fe Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/avi.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/bmp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/bmp.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/bmp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/css.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/css.jpg new file mode 100644 index 0000000..8c80e15 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/css.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/csv.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/csv.jpg new file mode 100644 index 0000000..b81a32b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/csv.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/default.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/default.jpg new file mode 100644 index 0000000..433adcf Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/default.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/dmg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/dmg.jpg new file mode 100644 index 0000000..509039e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/dmg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/doc.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/doc.jpg new file mode 100644 index 0000000..122d382 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/doc.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/docx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/docx.jpg new file mode 100644 index 0000000..9b0bc4b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/docx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/favicon.ico b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/favicon.ico new file mode 100644 index 0000000..7383707 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/favicon.ico differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/fla.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/fla.jpg new file mode 100644 index 0000000..e8757e6 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/fla.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/flv.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/flv.jpg new file mode 100644 index 0000000..c53f135 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/flv.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder.png new file mode 100644 index 0000000..a5fac96 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder_back.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder_back.png new file mode 100644 index 0000000..dc0786e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/folder_back.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gif.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gif.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gif.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gz.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gz.jpg new file mode 100644 index 0000000..414d5da Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/gz.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/html.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/html.jpg new file mode 100644 index 0000000..6bb6743 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/html.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/iso.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/iso.jpg new file mode 100644 index 0000000..aaf5d5b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/iso.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpeg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpeg.jpg new file mode 100644 index 0000000..b4f258a Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpeg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpg.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/jpg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/log.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/log.jpg new file mode 100644 index 0000000..c2fe0e7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/log.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/m4a.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/m4a.jpg new file mode 100644 index 0000000..f3997e6 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/m4a.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mdb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mdb.jpg new file mode 100644 index 0000000..13607b1 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mdb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mid.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mid.jpg new file mode 100644 index 0000000..966b39c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mid.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mov.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mov.jpg new file mode 100644 index 0000000..2e98f5e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mov.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp3.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp3.jpg new file mode 100644 index 0000000..fd66149 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp3.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp4.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp4.jpg new file mode 100644 index 0000000..0b045ed Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mp4.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpeg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpeg.jpg new file mode 100644 index 0000000..f075cc3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpeg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpg.jpg new file mode 100644 index 0000000..0da2aad Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/mpg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odb.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odb.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odb.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odf.jpg new file mode 100644 index 0000000..8f40c74 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odg.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odp.jpg new file mode 100644 index 0000000..8f40c74 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ods.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ods.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ods.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odt.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/odt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ogg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ogg.jpg new file mode 100644 index 0000000..23ed22b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ogg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otg.jpg new file mode 100644 index 0000000..b36cae5 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otp.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otp.jpg new file mode 100644 index 0000000..fc995f5 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/otp.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ots.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ots.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ots.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ott.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ott.jpg new file mode 100644 index 0000000..eb6522c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ott.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pdf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pdf.jpg new file mode 100644 index 0000000..809b5e6 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pdf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/png.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/png.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/png.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ppt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ppt.jpg new file mode 100644 index 0000000..b87590a Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/ppt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pptx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pptx.jpg new file mode 100644 index 0000000..62cbe2f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/pptx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/psd.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/psd.jpg new file mode 100644 index 0000000..312af5c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/psd.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rar.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rar.jpg new file mode 100644 index 0000000..6057cbc Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rar.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rtf.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rtf.jpg new file mode 100644 index 0000000..122d382 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/rtf.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/sql.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/sql.jpg new file mode 100644 index 0000000..73485f1 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/sql.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/svg.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/svg.jpg new file mode 100644 index 0000000..f479380 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/svg.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tar.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tar.jpg new file mode 100644 index 0000000..bb5adaf Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tar.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tiff.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tiff.jpg new file mode 100644 index 0000000..e25985d Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/tiff.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/txt.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/txt.jpg new file mode 100644 index 0000000..c2fe0e7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/txt.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wav.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wav.jpg new file mode 100644 index 0000000..23ed22b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wav.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/webm.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/webm.jpg new file mode 100644 index 0000000..b6c568c Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/webm.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wma.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wma.jpg new file mode 100644 index 0000000..9dfa9fe Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/wma.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xhtml.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xhtml.jpg new file mode 100644 index 0000000..3420b33 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xhtml.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xls.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xls.jpg new file mode 100644 index 0000000..62e21dd Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xls.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xlsx.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xlsx.jpg new file mode 100644 index 0000000..c1728bc Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xlsx.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xml.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xml.jpg new file mode 100644 index 0000000..7b2d75b Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/xml.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/zip.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/zip.jpg new file mode 100644 index 0000000..414d5da Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/ico_dark/zip.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/info.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/info.png new file mode 100644 index 0000000..6baffc3 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/info.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/key.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/key.png new file mode 100644 index 0000000..463d082 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/key.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/label.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/label.png new file mode 100644 index 0000000..fa65317 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/label.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/loading.gif b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/loading.gif new file mode 100755 index 0000000..6fba776 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/loading.gif differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/logo.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/logo.png new file mode 100644 index 0000000..2d2c0b2 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/logo.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/preview.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/preview.png new file mode 100755 index 0000000..b124752 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/preview.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/processing.gif b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/processing.gif new file mode 100755 index 0000000..7c99504 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/processing.gif differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/rename.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/rename.png new file mode 100755 index 0000000..a425ee7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/rename.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/size.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/size.png new file mode 100644 index 0000000..fcc302f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/size.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/sort.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/sort.png new file mode 100644 index 0000000..0a029be Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/sort.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/storing_animation.gif b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/storing_animation.gif new file mode 100644 index 0000000..eca3a53 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/storing_animation.gif differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/trans.jpg b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/trans.jpg new file mode 100644 index 0000000..147175e Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/trans.jpg differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/up.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/up.png new file mode 100644 index 0000000..680dade Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/up.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/upload.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/upload.png new file mode 100644 index 0000000..7380e8f Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/upload.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/url.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/url.png new file mode 100644 index 0000000..f18499a Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/url.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/zip.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/zip.png new file mode 100644 index 0000000..9ef55c7 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/img/zip.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpClient.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpClient.php new file mode 100644 index 0000000..3476138 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpClient.php @@ -0,0 +1,895 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FtpClient; + +use Countable; + +/** + * The FTP and SSL-FTP client for PHP. + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpClient implements Countable +{ + /** + * The connection with the server. + * + * @var resource + */ + protected $conn; + + /** + * PHP FTP functions wrapper. + * + * @var FtpWrapper + */ + private $ftp; + + /** + * Constructor. + * + * @param resource|null $connection + * + * @throws FtpException if FTP extension is not loaded + */ + public function __construct($connection = null) + { + if (!\extension_loaded('ftp')) { + throw new FtpException('FTP extension is not loaded!'); + } + + if ($connection) { + $this->conn = $connection; + } + + $this->setWrapper(new FtpWrapper($this->conn)); + } + + /** + * Close the connection when the object is destroyed. + */ + public function __destruct() + { + if ($this->conn) { + $this->ftp->close(); + } + } + + /** + * Call an internal method or a FTP method handled by the wrapper. + * + * Wrap the FTP PHP functions to call as method of FtpClient object. + * The connection is automaticaly passed to the FTP PHP functions. + * + * @param string $method + * + * @throws FtpException When the function is not valid + */ + public function __call($method, array $arguments) + { + return $this->ftp->__call($method, $arguments); + } + + /** + * Overwrites the PHP limit. + * + * @param string|null $memory The memory limit, if null is not modified + * @param int $time_limit The max execution time, unlimited by default + * @param bool $ignore_user_abort Ignore user abort, true by default + * + * @return FtpClient + */ + public function setPhpLimit($memory = null, $time_limit = 0, $ignore_user_abort = true) + { + if (null !== $memory) { + ini_set('memory_limit', $memory); + } + + ignore_user_abort(true); + set_time_limit($time_limit); + + return $this; + } + + /** + * Get the help information of the remote FTP server. + * + * @return array + */ + public function help() + { + return $this->ftp->raw('help'); + } + + /** + * Open a FTP connection. + * + * @param string $host + * @param bool $ssl + * @param int $port + * @param int $timeout + * + * @return FTPClient + * + * @throws FtpException If unable to connect + */ + public function connect($host, $ssl = false, $port = 21, $timeout = 90) + { + if ($ssl) { + $this->conn = @$this->ftp->ssl_connect($host, $port, $timeout); + } else { + $this->conn = @$this->ftp->connect($host, $port, $timeout); + } + + if (!$this->conn) { + throw new FtpException('Unable to connect'); + } + + return $this; + } + + /** + * Closes the current FTP connection. + * + * @return bool + */ + public function close() + { + if ($this->conn) { + $this->ftp->close(); + $this->conn = null; + } + } + + /** + * Get the connection with the server. + * + * @return resource + */ + public function getConnection() + { + return $this->conn; + } + + /** + * Get the wrapper. + * + * @return FtpWrapper + */ + public function getWrapper() + { + return $this->ftp; + } + + /** + * Logs in to an FTP connection. + * + * @param string $username + * @param string $password + * + * @return FtpClient + * + * @throws FtpException If the login is incorrect + */ + public function login($username = 'anonymous', $password = '') + { + $result = $this->ftp->login($username, $password); + + if ($result === false) { + throw new FtpException('Login incorrect'); + } + + return $this; + } + + /** + * Returns the last modified time of the given file. + * Return -1 on error. + * + * @param string $remoteFile + * @param string|null $format + * + * @return int + */ + public function modifiedTime($remoteFile, $format = null) + { + $time = $this->ftp->mdtm($remoteFile); + + if ($time !== -1 && $format !== null) { + return date($format, $time); + } + + return $time; + } + + /** + * Changes to the parent directory. + * + * @throws FtpException + * + * @return FtpClient + */ + public function up() + { + $result = @$this->ftp->cdup(); + + if ($result === false) { + throw new FtpException('Unable to get parent folder'); + } + + return $this; + } + + /** + * Returns a list of files in the given directory. + * + * @param string $directory The directory, by default is "." the current directory + * @param bool $recursive + * @param callable $filter A callable to filter the result, by default is asort() PHP function. + * The result is passed in array argument, + * must take the argument by reference ! + * The callable should proceed with the reference array + * because is the behavior of several PHP sorting + * functions (by reference ensure directly the compatibility + * with all PHP sorting functions). + * + * @return array + * + * @throws FtpException If unable to list the directory + */ + public function nlist($directory = '.', $recursive = false, $filter = 'sort') + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory'); + } + + $files = $this->ftp->nlist($directory); + + if ($files === false) { + throw new FtpException('Unable to list directory'); + } + + $result = []; + $dir_len = \strlen($directory); + + // if it's the current + if (false !== ($kdot = array_search('.', $files))) { + unset($files[$kdot]); + } + + // if it's the parent + if (false !== ($kdot = array_search('..', $files))) { + unset($files[$kdot]); + } + + if (!$recursive) { + foreach ($files as $file) { + $result[] = $directory.'/'.$file; + } + + // working with the reference (behavior of several PHP sorting functions) + $filter($result); + + return $result; + } + + // utils for recursion + $flatten = function (array $arr) use (&$flatten) { + $flat = []; + + foreach ($arr as $k => $v) { + if (\is_array($v)) { + $flat = array_merge($flat, $flatten($v)); + } else { + $flat[] = $v; + } + } + + return $flat; + }; + + foreach ($files as $file) { + $file = $directory.'/'.$file; + + // if contains the root path (behavior of the recursivity) + if (0 === strpos($file, $directory, $dir_len)) { + $file = substr($file, $dir_len); + } + + if ($this->isDir($file)) { + $result[] = $file; + $items = $flatten($this->nlist($file, true, $filter)); + + foreach ($items as $item) { + $result[] = $item; + } + } else { + $result[] = $file; + } + } + + $result = array_unique($result); + + $filter($result); + + return $result; + } + + /** + * Creates a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::remove() + * @see FtpClient::put() + * @see FtpClient::putAll() + * + * @param string $directory The directory + * @param bool $recursive + * + * @return array + */ + public function mkdir($directory, $recursive = false) + { + if (!$recursive || $this->isDir($directory)) { + return $this->ftp->mkdir($directory); + } + + $result = false; + $pwd = $this->ftp->pwd(); + $parts = explode('/', $directory); + + foreach ($parts as $part) { + if (!@$this->ftp->chdir($part)) { + $result = $this->ftp->mkdir($part); + $this->ftp->chdir($part); + } + } + + $this->ftp->chdir($pwd); + + return $result; + } + + /** + * Remove a directory. + * + * @see FtpClient::mkdir() + * @see FtpClient::cleanDir() + * @see FtpClient::remove() + * @see FtpClient::delete() + * + * @param string $directory + * @param bool $recursive Forces deletion if the directory is not empty + * + * @return bool + * + * @throws FtpException If unable to list the directory to remove + */ + public function rmdir($directory, $recursive = true) + { + if ($recursive) { + $files = $this->nlist($directory, false, 'rsort'); + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + } + + // remove the directory + return $this->ftp->rmdir($directory); + } + + /** + * Empty directory. + * + * @see FtpClient::remove() + * @see FtpClient::delete() + * @see FtpClient::rmdir() + * + * @param string $directory + * + * @return bool + */ + public function cleanDir($directory) + { + if (!$files = $this->nlist($directory)) { + return $this->isEmpty($directory); + } + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + + return $this->isEmpty($directory); + } + + /** + * Remove a file or a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::cleanDir() + * @see FtpClient::delete() + * + * @param string $path The path of the file or directory to remove + * @param bool $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()} + * + * @return bool + */ + public function remove($path, $recursive = false) + { + try { + if (@$this->ftp->delete($path) + || ($this->isDir($path) && @$this->rmdir($path, $recursive))) { + return true; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Check if a directory exist. + * + * @param string $directory + * + * @return bool + * + * @throws FtpException + */ + public function isDir($directory) + { + $pwd = $this->ftp->pwd(); + + if ($pwd === false) { + throw new FtpException('Unable to resolve the current directory'); + } + + if (@$this->ftp->chdir($directory)) { + $this->ftp->chdir($pwd); + + return true; + } + + $this->ftp->chdir($pwd); + + return false; + } + + /** + * Check if a directory is empty. + * + * @param string $directory + * + * @return bool + */ + public function isEmpty($directory) + { + return $this->count($directory, null, false) === 0 ? true : false; + } + + /** + * Scan a directory and returns the details of each item. + * + * @see FtpClient::nlist() + * @see FtpClient::rawlist() + * @see FtpClient::parseRawList() + * @see FtpClient::dirSize() + * + * @param string $directory + * @param bool $recursive + * + * @return array + */ + public function scanDir($directory = '.', $recursive = false) + { + return $this->parseRawList($this->rawlist($directory, $recursive)); + } + + /** + * Returns the total size of the given directory in bytes. + * + * @param string $directory the directory, by default is the current directory + * @param bool $recursive true by default + * + * @return int the size in bytes + */ + public function dirSize($directory = '.', $recursive = true) + { + $items = $this->scanDir($directory, $recursive); + $size = 0; + + foreach ($items as $item) { + $size += (int) $item['size']; + } + + return $size; + } + + /** + * Count the items (file, directory, link, unknown). + * + * @param string $directory the directory, by default is the current directory + * @param string|null $type The type of item to count (file, directory, link, unknown) + * @param bool $recursive true by default + * + * @return int + */ + public function count($directory = '.', $type = null, $recursive = true) + { + $items = (null === $type ? $this->nlist($directory, $recursive) + : $this->scanDir($directory, $recursive)); + + $count = 0; + foreach ($items as $item) { + if (null === $type || $item['type'] == $type) { + ++$count; + } + } + + return $count; + } + + /** + * Uploads a file to the server from a string. + * + * @param string $remote_file + * @param string $content + * + * @return FtpClient + * + * @throws FtpException When the transfer fails + */ + public function putFromString($remote_file, $content) + { + $handle = fopen('php://temp', 'w'); + + fwrite($handle, $content); + rewind($handle); + + if ($this->ftp->fput($remote_file, $handle, \FTP_BINARY)) { + return $this; + } + + throw new FtpException('Unable to put the file "'.$remote_file.'"'); + } + + /** + * Uploads a file to the server. + * + * @param string $local_file + * + * @return FtpClient + * + * @throws FtpException When the transfer fails + */ + public function putFromPath($local_file) + { + $remote_file = basename($local_file); + $handle = fopen($local_file, 'r'); + + if ($this->ftp->fput($remote_file, $handle, \FTP_BINARY)) { + rewind($handle); + + return $this; + } + + throw new FtpException( + 'Unable to put the remote file from the local file "'.$local_file.'"' + ); + } + + /** + * Upload files. + * + * @param string $source_directory + * @param string $target_directory + * @param int $mode + * + * @return FtpClient + */ + public function putAll($source_directory, $target_directory, $mode = \FTP_BINARY) + { + $d = dir($source_directory); + + // do this for each file in the directory + while ($file = $d->read()) { + // to prevent an infinite loop + if ($file != '.' && $file != '..') { + // do the following if it is a directory + if (is_dir($source_directory.'/'.$file)) { + if (!$this->isDir($target_directory.'/'.$file)) { + // create directories that do not yet exist + $this->ftp->mkdir($target_directory.'/'.$file); + } + + // recursive part + $this->putAll( + $source_directory.'/'.$file, $target_directory.'/'.$file, + $mode + ); + } else { + // put the files + $this->ftp->put( + $target_directory.'/'.$file, $source_directory.'/'.$file, + $mode + ); + } + } + } + + return $this; + } + + /** + * Returns a detailed list of files in the given directory. + * + * @see FtpClient::nlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * + * @param string $directory The directory, by default is the current directory + * @param bool $recursive + * + * @return array + * + * @throws FtpException + */ + public function rawlist($directory = '.', $recursive = false) + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory.'); + } + + $list = $this->ftp->rawlist($directory); + $items = []; + + if (!$list) { + return $items; + } + + if (false == $recursive) { + foreach ($list as $path => $item) { + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = \count($chunks); + + for ($i = 9; $i < $nbChunks; ++$i) { + $path .= ' '.$chunks[$i]; + } + } + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[$this->rawToType($item).'#'.$path] = $item; + } + + return $items; + } + + $path = ''; + + foreach ($list as $item) { + $len = \strlen($item); + + if (!$len + // "." + || ($item[$len - 1] == '.' && $item[$len - 2] == ' ' + // ".." + || $item[$len - 1] == '.' && $item[$len - 2] == '.' && $item[$len - 3] == ' ') + ) { + continue; + } + + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = \count($chunks); + + for ($i = 9; $i < $nbChunks; ++$i) { + $path .= ' '.$chunks[$i]; + } + } + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[$this->rawToType($item).'#'.$path] = $item; + + if ($item[0] == 'd') { + $sublist = $this->rawlist($path, true); + + foreach ($sublist as $subpath => $subitem) { + $items[$subpath] = $subitem; + } + } + } + + return $items; + } + + /** + * Parse raw list. + * + * @see FtpClient::rawlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * + * @return array + */ + public function parseRawList(array $rawlist) + { + $items = []; + $path = ''; + + foreach ($rawlist as $key => $child) { + $chunks = preg_split("/\s+/", $child); + + if (isset($chunks[8]) && ($chunks[8] == '.' || $chunks[8] == '..')) { + continue; + } + + if (\count($chunks) === 1) { + $len = \strlen($chunks[0]); + + if ($len && $chunks[0][$len - 1] == ':') { + $path = substr($chunks[0], 0, -1); + } + + continue; + } + + $item = [ + 'permissions' => $chunks[0], + 'number' => $chunks[1], + 'owner' => $chunks[2], + 'group' => $chunks[3], + 'size' => $chunks[4], + 'month' => $chunks[5], + 'day' => $chunks[6], + 'time' => $chunks[7], + 'name' => $chunks[8], + 'type' => $this->rawToType($chunks[0]), + ]; + + unset($chunks[0]); + unset($chunks[1]); + unset($chunks[2]); + unset($chunks[3]); + unset($chunks[4]); + unset($chunks[5]); + unset($chunks[6]); + unset($chunks[7]); + $item['name'] = implode(' ', $chunks); + + if ($item['type'] == 'link') { + $item['target'] = $chunks[10]; // 9 is "->" + } + + // if the key is not the path, behavior of ftp_rawlist() PHP function + if (\is_int($key) || false === strpos($key, $item['name'])) { + array_splice($chunks, 0, 8); + + $key = $item['type'].'#' + .($path ? $path.'/' : '') + .implode(' ', $chunks); + + if ($item['type'] == 'link') { + // get the first part of 'link#the-link.ext -> /path/of/the/source.ext' + $exp = explode(' ->', $key); + $key = rtrim($exp[0]); + } + + $items[$key] = $item; + } else { + // the key is the path, behavior of FtpClient::rawlist() method() + $items[$key] = $item; + } + } + + return $items; + } + + /** + * Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown). + * Only the first char is used for resolving. + * + * @param string $permission Example : drwx---r-x + * + * @return string The file type (file, directory, link, unknown) + * + * @throws FtpException + */ + public function rawToType($permission) + { + if (!\is_string($permission)) { + throw new FtpException('The "$permission" argument must be a string, "' + .\gettype($permission).'" given.'); + } + + if (empty($permission[0])) { + return 'unknown'; + } + + switch ($permission[0]) { + case '-': + return 'file'; + case 'd': + return 'directory'; + case 'l': + return 'link'; + default: + return 'unknown'; + } + } + + /** + * Set the wrapper which forward the PHP FTP functions to use in FtpClient instance. + * + * @return FtpClient + */ + protected function setWrapper(FtpWrapper $wrapper) + { + $this->ftp = $wrapper; + + return $this; + } +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpException.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpException.php new file mode 100644 index 0000000..6311665 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpException.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FtpClient; + +/** + * The FtpException class. + * Exception thrown if an error on runtime of the FTP client occurs. + * {@inheritDoc} + * + * @author Nicolas Tallefourtane + */ +class FtpException extends \Exception +{ +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpWrapper.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpWrapper.php new file mode 100644 index 0000000..e8de4a0 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/FtpWrapper.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FtpClient; + +/** + * Wrap the PHP FTP functions. + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool close() close() Closes an FTP connection + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method bool login() login(string $username, string $password) Logs in to an FTP connection + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method string mkdir() mkdir(string $directory) Creates a directory + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method array nlist() nlist(string $directory) Returns a list of files in the given directory + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method array rawlist() rawlist(string $directory, bool $recursive = false) Returns a detailed list of files in the given directory + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool rmdir() rmdir(string $directory) Removes a directory + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpWrapper +{ + /** + * The connection with the server. + * + * @var resource + */ + protected $conn; + + /** + * Constructor. + * + * @param resource &$connection The FTP (or SSL-FTP) connection (takes by reference) + */ + public function __construct(&$connection) + { + $this->conn = &$connection; + } + + /** + * Forward the method call to FTP functions. + * + * @param string $function + * + * @throws FtpException When the function is not valid + */ + public function __call($function, array $arguments) + { + $function = 'ftp_'.$function; + + if (\function_exists($function)) { + array_unshift($arguments, $this->conn); + + return \call_user_func_array($function, $arguments); + } + + throw new FtpException("{$function} is not a valid FTP function"); + } + + /** + * Opens a FTP connection. + * + * @param string $host + * @param int $port + * @param int $timeout + * + * @return resource + */ + public function connect($host, $port = 21, $timeout = 90) + { + return ftp_connect($host, $port, $timeout); + } + + /** + * Opens a Secure SSL-FTP connection. + * + * @param string $host + * @param int $port + * @param int $timeout + * + * @return resource + */ + public function ssl_connect($host, $port = 21, $timeout = 90) + { + return ftp_ssl_connect($host, $port, $timeout); + } +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/Response.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/Response.php new file mode 100644 index 0000000..5adea55 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/Response.php @@ -0,0 +1,364 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Class Response + * Simplified copy of Symfony/Http-Foundation Response + * to allow compatibility with frameworks. + */ +class Response +{ + public const HTTP_CONTINUE = 100; + public const HTTP_SWITCHING_PROTOCOLS = 101; + public const HTTP_PROCESSING = 102; // RFC2518 + public const HTTP_OK = 200; + public const HTTP_CREATED = 201; + public const HTTP_ACCEPTED = 202; + public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + public const HTTP_NO_CONTENT = 204; + public const HTTP_RESET_CONTENT = 205; + public const HTTP_PARTIAL_CONTENT = 206; + public const HTTP_MULTI_STATUS = 207; // RFC4918 + public const HTTP_ALREADY_REPORTED = 208; // RFC5842 + public const HTTP_IM_USED = 226; // RFC3229 + public const HTTP_MULTIPLE_CHOICES = 300; + public const HTTP_MOVED_PERMANENTLY = 301; + public const HTTP_FOUND = 302; + public const HTTP_SEE_OTHER = 303; + public const HTTP_NOT_MODIFIED = 304; + public const HTTP_USE_PROXY = 305; + public const HTTP_RESERVED = 306; + public const HTTP_TEMPORARY_REDIRECT = 307; + public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + public const HTTP_BAD_REQUEST = 400; + public const HTTP_UNAUTHORIZED = 401; + public const HTTP_PAYMENT_REQUIRED = 402; + public const HTTP_FORBIDDEN = 403; + public const HTTP_NOT_FOUND = 404; + public const HTTP_METHOD_NOT_ALLOWED = 405; + public const HTTP_NOT_ACCEPTABLE = 406; + public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + public const HTTP_REQUEST_TIMEOUT = 408; + public const HTTP_CONFLICT = 409; + public const HTTP_GONE = 410; + public const HTTP_LENGTH_REQUIRED = 411; + public const HTTP_PRECONDITION_FAILED = 412; + public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + public const HTTP_REQUEST_URI_TOO_LONG = 414; + public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + public const HTTP_EXPECTATION_FAILED = 417; + public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + public const HTTP_LOCKED = 423; // RFC4918 + public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + public const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 + public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + public const HTTP_INTERNAL_SERVER_ERROR = 500; + public const HTTP_NOT_IMPLEMENTED = 501; + public const HTTP_BAD_GATEWAY = 502; + public const HTTP_SERVICE_UNAVAILABLE = 503; + public const HTTP_GATEWAY_TIMEOUT = 504; + public const HTTP_VERSION_NOT_SUPPORTED = 505; + public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + public const HTTP_LOOP_DETECTED = 508; // RFC5842 + public const HTTP_NOT_EXTENDED = 510; // RFC2774 + public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2012-02-13). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Reserved for WebDAV advanced collections expired proposal', // RFC2817 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * @var string + */ + protected $content; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var array + */ + public $headers; + + /** + * @var string + */ + protected $version; + + /** + * Construct the response. + * + * @param int $statusCode + * @param array $headers + */ + public function __construct($content = '', $statusCode = 200, $headers = []) + { + $this->setContent($content); + $this->setStatusCode($statusCode); + $this->headers = $headers; + $this->version = '1.1'; + } + + /** + * Set the content on the response. + * + * @return $this + */ + public function setContent($content) + { + if ($content instanceof ArrayObject || is_array($content)) { + $this->headers['Content-Type'] = ['application/json']; + + $content = json_encode($content); + } + + $this->content = $content; + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Sets the response status code. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise + * + * @return Response + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + * + * @api + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = self::$statusTexts[$code] ?? ''; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + + /** + * Is response invalid? + * + * @return bool + * + * @api + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Set a header on the Response. + * + * @param string $key + * @param string $value + * @param bool $replace + * + * @return $this + */ + public function header($key, $value, $replace = true) + { + if (empty($this->headers[$key])) { + $this->headers[$key] = []; + } + if ($replace) { + $this->headers[$key] = [$value]; + } else { + $this->headers[$key][] = $value; + } + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return Response + * + * @api + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return Response + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return Response + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + // headers + foreach ($this->headers as $name => $values) { + if (is_array($values)) { + foreach ($values as $value) { + header($name.': '.$value, false, $this->statusCode); + } + } else { + header($name.': '.$values, false, $this->statusCode); + } + } + + return $this; + } +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/ftp_class.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/ftp_class.php new file mode 100755 index 0000000..e760cf7 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/ftp_class.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class ftp_class +{ + // *** Class variables + private $connectionId; + private $loginOk = false; + private $messageArray = []; + + public function __construct() + { + } + + private function logMessage($message): void + { + $this->messageArray[] = $message; + } + + public function getMessages() + { + return $this->messageArray; + } + + public function connect($server, $ftpUser, $ftpPassword, $isPassive = false) + { + // *** Set up basic connection + $this->connectionId = ftp_connect($server); + + // *** Login with username and password + $loginResult = ftp_login($this->connectionId, $ftpUser, $ftpPassword); + + // *** Sets passive mode on/off (default off) + ftp_pasv($this->connectionId, $isPassive); + + // *** Check connection + if ((!$this->connectionId) || (!$loginResult)) { + $this->logMessage('FTP connection has failed!'); + $this->logMessage('Attempted to connect to '.$server.' for user '.$ftpUser, true); + + return false; + } + $this->logMessage('Connected to '.$server.', for user '.$ftpUser); + $this->loginOk = true; + + return true; + } + + public function makeDir($directory) + { + // *** If creating a directory is successful... + if (ftp_mkdir($this->connectionId, $directory)) { + $this->logMessage('Directory "'.$directory.'" created successfully'); + + return true; + } + + // *** ...Else, FAIL. + $this->logMessage('Failed creating directory "'.$directory.'"'); + + return false; + } + + public function changeDir($directory) + { + if (ftp_chdir($this->connectionId, $directory)) { + $this->logMessage('Current directory is now: '.ftp_pwd($this->connectionId)); + + return true; + } + $this->logMessage('Couldn\'t change directory'); + + return false; + } + + public function getDirListing($directory = '.', $parameters = '-la') + { + echo shell_exec('whoami').' is who i am
'; + echo 'Current directory is now: '.ftp_pwd($this->connectionId).'
'; + + // get contents of the current directory + $contentsArray = ftp_rawlist($this->connectionId, $parameters.' '.$directory); + echo error_get_last(); + + return $contentsArray; + } +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/mime_type_lib.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/mime_type_lib.php new file mode 100755 index 0000000..d1e1984 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/mime_type_lib.php @@ -0,0 +1,323 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/* REMOVE ME TO TEST +echo "magicfile: " . ini_get( 'mime_magic.magicfile' ) . "\n"; + +if ( function_exists( 'finfo_open' ) ) + echo "Found finfo_open function\n"; +else + echo "Did not find finfo_open function\n"; + +if ( function_exists( 'mime_content_type' ) ) + echo "Found mime_content_type function\n"; +else + echo "Did not find mime_content_type function\n"; +REMOVE ME TO TEST */ + +$mime_types = [ + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'asx' => 'video/x-ms-asf', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'bcpio' => 'application/x-bcpio', + 'bin' => 'application/octet-stream', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bzip2', + 'cdf' => 'application/x-netcdf', + 'chrt' => 'application/x-kchart', + 'class' => 'application/octet-stream', + 'cpio' => 'application/x-cpio', + 'cpt' => 'application/mac-compactpro', + 'csh' => 'application/x-csh', + 'css' => 'text/css', + 'dcr' => 'application/x-director', + 'dir' => 'application/x-director', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwg' => 'image/vnd.dwg', + 'dxr' => 'application/x-director', + 'eps' => 'application/postscript', + 'etx' => 'text/x-setext', + 'exe' => 'application/octet-stream', + 'ez' => 'application/andrew-inset', + 'flv' => 'video/x-flv', + 'gif' => 'image/gif', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'hdf' => 'application/x-hdf', + 'hqx' => 'application/mac-binhex40', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ice' => 'x-conference/x-cooltalk', + 'ief' => 'image/ief', + 'iges' => 'model/iges', + 'igs' => 'model/iges', + 'img' => 'application/octet-stream', + 'iso' => 'application/octet-stream', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jar' => 'application/x-java-archive', + 'jnlp' => 'application/x-java-jnlp-file', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'js' => 'application/x-javascript', + 'kar' => 'audio/midi', + 'kil' => 'application/x-killustrator', + 'kpr' => 'application/x-kpresenter', + 'kpt' => 'application/x-kpresenter', + 'ksp' => 'application/x-kspread', + 'kwd' => 'application/x-kword', + 'kwt' => 'application/x-kword', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'latex' => 'application/x-latex', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'm3u' => 'audio/x-mpegurl', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'mesh' => 'model/mesh', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'ms' => 'application/x-troff-ms', + 'msh' => 'model/mesh', + 'mxu' => 'video/vnd.mpegurl', + 'nc' => 'application/x-netcdf', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ogg' => 'application/ogg', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'pbm' => 'image/x-portable-bitmap', + 'pdb' => 'chemical/x-pdb', + 'pdf' => 'application/pdf', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'php' => 'text/x-php', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'ppm' => 'image/x-portable-pixmap', + 'ppt' => 'application/vnd.ms-powerpoint', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'ras' => 'image/x-cmu-raster', + 'rgb' => 'image/x-rgb', + 'rm' => 'audio/x-pn-realaudio', + 'roff' => 'application/x-troff', + 'rpm' => 'application/x-rpm', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'snd' => 'audio/basic', + 'svg' => 'image/svg+xml', + 'so' => 'application/octet-stream', + 'spl' => 'application/x-futuresplash', + 'src' => 'application/x-wais-source', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'swf' => 'application/x-shockwave-flash', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'application/x-troff', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'tgz' => 'application/x-gzip', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'torrent' => 'application/x-bittorrent', + 'tr' => 'application/x-troff', + 'tsv' => 'text/tab-separated-values', + 'txt' => 'text/plain', + 'ustar' => 'application/x-ustar', + 'vcd' => 'application/x-cdlink', + 'vrml' => 'model/vrml', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wrl' => 'model/vrml', + 'wvx' => 'video/x-ms-wvx', + 'xbm' => 'image/x-xbitmap', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xml' => 'text/xml', + 'xml' => 'application/xml', + 'xpm' => 'image/x-xpixmap', + 'xsl' => 'text/xsl', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'zip' => 'application/zip', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'docm' => 'application/vnd.ms-word.document.macroEnabled.12', + 'xls' => 'application/vnd.ms-excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potm' => 'application/vnd.ms-powerpoint.template.macroEnabled.12', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', +]; + +if (!function_exists('get_extension_from_mime')) { + function get_extension_from_mime($mime) + { + global $mime_types; + if (strpos($mime, ';') !== false) { + $mime = substr($mime, 0, strpos($mime, ';')); + } + $mime_types_conv = array_flip($mime_types); + if (isset($mime_types_conv[$mime])) { + return $mime_types_conv[$mime]; + } + + return ''; + } +} + +if (!function_exists('get_file_mime_type')) { + function get_file_mime_type($filename, $debug = false) + { + if (function_exists('finfo_open') && function_exists('finfo_file') && function_exists('finfo_close')) { + $fileinfo = finfo_open(\FILEINFO_MIME_TYPE); + $mime_type = finfo_file($fileinfo, $filename); + finfo_close($fileinfo); + + if (!empty($mime_type)) { + if (true === $debug) { + return ['mime_type' => $mime_type, 'method' => 'fileinfo']; + } + + return $mime_type; + } + } + + if (function_exists('mime_content_type')) { + $mime_type = mime_content_type($filename); + + if (!empty($mime_type)) { + if (true === $debug) { + return ['mime_type' => $mime_type, 'method' => 'mime_content_type']; + } + + return $mime_type; + } + } + + global $mime_types; + + $tmp_array = explode('.', $filename); + $ext = strtolower(array_pop($tmp_array)); + + if (!empty($mime_types[$ext])) { + if (true === $debug) { + return ['mime_type' => $mime_types[$ext], 'method' => 'from_array']; + } + + return $mime_types[$ext]; + } + + if (true === $debug) { + return ['mime_type' => 'application/octet-stream', 'method' => 'last_resort']; + } + + return 'application/octet-stream'; + } +} + +/******************** + * The following code can be used to test the function. + * First put a plain text file named "test.txt" and a + * JPEG image file named "image.jpg" in the same folder + * as this file. + * + * Simply remove the "REMOVE ME TO TEST" lines below to have + * the code run when this file runs. + * + * Run the code with this command: + * php mime_type_lib.php + ********************/ + +/* REMOVE ME TO TEST +echo get_file_mime_type( 'test.txt' ) . "\n"; +echo print_r( get_file_mime_type( 'image.jpg', true ), true ) . "\n"; +REMOVE ME TO TEST */ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/php_image_magician.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/php_image_magician.php new file mode 100755 index 0000000..8288e23 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/php_image_magician.php @@ -0,0 +1,3273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +// +// This work is licensed under the Creative Commons Attribution 3.0 Unported +// License. To view a copy of this license, +// visit http://creativecommons.org/licenses/by/3.0/ or send a letter to +// Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, +// 94041, USA. +// +// All rights reserved. +// +// Author: Jarrod Oberto +// Version: 1.5.1 +// Date: 10-05-11 +// Purpose: Provide tools for image manipulation using GD +// Param In: See functions. +// Param Out: Produces a resized image +// Requires : Requires PHP GD library. +// Usage Example: +// include("lib/php_image_magician.php"); +// $magicianObj = new resize('images/car.jpg'); +// $magicianObj -> resizeImage(150, 100, 0); +// $magicianObj -> saveImage('images/car_small.jpg', 100); +// +// - See end of doc for more examples - +// +// Supported file types include: jpg, png, gif, bmp, psd (read) +// +// +// +// The following functions are taken from phpThumb() [available from +// http://phpthumb.sourceforge.net], and are used with written permission +// from James Heinrich. +// - GD2BMPstring +// - GetPixelColor +// - LittleEndian2String +// +// The following functions are from Marc Hibbins and are used with written +// permission (are also under the Attribution-ShareAlike +// [http://creativecommons.org/licenses/by-sa/3.0/] license. +// - +// +// PhpPsdReader is used with written permission from Tim de Koning. +// [http://www.kingsquare.nl/phppsdreader] +// +// +// +// Modificatoin history +// Date Initials Ver Description +// 10-05-11 J.C.O 0.0 Initial build +// 01-06-11 J.C.O 0.1.1 * Added reflections +// * Added Rounded corners +// * You can now use PNG interlacing +// * Added shadow +// * Added caption box +// * Added vintage filter +// * Added dynamic image resizing (resize on the fly) +// * minor bug fixes +// 05-06-11 J.C.O 0.1.1.1 * Fixed undefined variables +// 17-06-11 J.C.O 0.1.2 * Added image_batch_class.php class +// * Minor bug fixes +// 26-07-11 J.C.O 0.1.4 * Added support for external images +// * Can now set the crop poisition +// 03-08-11 J.C.O 0.1.5 * Added reset() method to reset resource to +// original input file. +// * Added method addTextToCaptionBox() to +// simplify adding text to a caption box. +// * Added experimental writeIPTC. (not finished) +// * Added experimental readIPTC. (not finished) +// 11-08-11 J.C.O * Added initial border presets. +// 30-08-11 J.C.O * Added 'auto' crop option to crop portrait +// images near the top. +// 08-09-11 J.C.O * Added cropImage() method to allow standalone +// cropping. +// 17-09-11 J.C.O * Added setCropFromTop() set method - set the +// percentage to crop from the top when using +// crop 'auto' option. +// * Added setTransparency() set method - allows you +// to turn transparency off (like when saving +// as a jpg). +// * Added setFillColor() set method - set the +// background color to use instead of transparency. +// 05-11-11 J.C.O 0.1.5.1 * Fixed interlacing option +// 0-07-12 J.C.O 1.0 +// +// Known issues & Limitations: +// ------------------------------- +// Not so much an issue, the image is destroyed on the deconstruct rather than +// when we have finished with it. The reason for this is that we don't know +// when we're finished with it as you can both save the image and display +// it directly to the screen (imagedestroy($this->imageResized)) +// +// Opening BMP files is slow. A test with 884 bmp files processed in a loop +// takes forever - over 5 min. This test inlcuded opening the file, then +// getting and displaying its width and height. +// +// $forceStretch: +// ------------------------------- +// On by default. +// $forceStretch can be disabled by calling method setForceStretch with false +// parameter. If disabled, if an images original size is smaller than the size +// specified by the user, the original size will be used. This is useful when +// dealing with small images. +// +// If enabled, images smaller than the size specified will be stretched to +// that size. +// +// Tips: +// ------------------------------- +// * If you're resizing a transparent png and saving it as a jpg, set +// $keepTransparency to false with: $magicianObj->setTransparency(false); +// +// FEATURES: +// * EASY TO USE +// * BMP SUPPORT (read & write) +// * PSD (photoshop) support (read) +// * RESIZE IMAGES +// - Preserve transparency (png, gif) +// - Apply sharpening (jpg) (requires PHP >= 5.1.0) +// - Set image quality (jpg, png) +// - Resize modes: +// - exact size +// - resize by width (auto height) +// - resize by height (auto width) +// - auto (automatically determine the best of the above modes to use) +// - crop - resize as best as it can then crop the rest +// - Force stretching of smaller images (upscale) +// * APPLY FILTERS +// - Convert to grey scale +// - Convert to black and white +// - Convert to sepia +// - Convert to negative +// * ROTATE IMAGES +// - Rotate using predefined "left", "right", or "180"; or any custom degree amount +// * EXTRACT EXIF DATA (requires exif module) +// - make +// - model +// - date +// - exposure +// - aperture +// - f-stop +// - iso +// - focal length +// - exposure program +// - metering mode +// - flash status +// - creator +// - copyright +// * ADD WATERMARK +// - Specify exact x, y placement +// - Or, specify using one of the 9 pre-defined placements such as "tl" +// (for top left), "m" (for middle), "br" (for bottom right) +// - also specify padding from edge amount (optional). +// - Set opacity of watermark (png). +// * ADD BORDER +// * USE HEX WHEN SPECIFYING COLORS (eg: #ffffff) +// * SAVE IMAGE OR OUTPUT TO SCREEN +// +// +// ========================================================================# + +class php_image_magician +{ + private $fileName; + private $image; + protected $imageResized; + private $widthOriginal; // Always be the original width + private $heightOriginal; + private $width; // Current width (width after resize) + private $height; + private $imageSize; + private $fileExtension; + + private $debug = true; + private $errorArray = []; + + private $forceStretch = true; + private $aggresiveSharpening = false; + + private $transparentArray = ['.png', '.gif']; + private $keepTransparency = true; + private $fillColorArray = ['r' => 255, 'g' => 255, 'b' => 255]; + + private $sharpenArray = ['jpg']; + + private $psdReaderPath; + private $filterOverlayPath; + + private $isInterlace; + + private $captionBoxPositionArray = []; + + private $fontDir = 'fonts'; + + private $cropFromTopPercent = 10; + + // # -------------------------------------------------------- + + public function __construct($fileName) + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Constructor + // Param in: $fileName: File name and path. + // Param out: n/a + // Reference: + // Notes: + // + { + if (!$this->testGDInstalled()) { + if ($this->debug) { + throw new Exception('The GD Library is not installed.'); + } + + throw new Exception(); + } + + $this->initialise(); + + // *** Save the image file name. Only store this incase you want to display it + $this->fileName = $fileName; + $this->fileExtension = fix_strtolower(strrchr($fileName, '.')); + + // *** Open up the file + $this->image = $this->openImage($fileName); + + // *** Assign here so we don't modify the original + $this->imageResized = $this->image; + + // *** If file is an image + if ($this->testIsImage($this->image)) { + // *** Get width and height + $this->width = imagesx($this->image); + $this->widthOriginal = imagesx($this->image); + $this->height = imagesy($this->image); + $this->heightOriginal = imagesy($this->image); + + /* Added 15-09-08 + * Get the filesize using this build in method. + * Stores an array of size + * + * $this->imageSize[1] = width + * $this->imageSize[2] = height + * $this->imageSize[3] = width x height + * + */ + $this->imageSize = getimagesize($this->fileName); + } else { + $this->errorArray[] = 'File is not an image'; + } + } + + // # -------------------------------------------------------- + + private function initialise(): void + { + $this->psdReaderPath = __DIR__.'/classPhpPsdReader.php'; + $this->filterOverlayPath = __DIR__.'/filters'; + + // *** Set if image should be interlaced or not. + $this->isInterlace = false; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Resize +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function resizeImage($newWidth, $newHeight, $option = 0, $sharpen = false, $autoRotate = false): void + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Resizes the image + // Param in: $newWidth: + // $newHeight: + // $option: 0 / exact = defined size; + // 1 / portrait = keep aspect set height; + // 2 / landscape = keep aspect set width; + // 3 / auto = auto; + // 4 / crop= resize and crop; + // + // $option can also be an array containing options for + // cropping. E.G., array('crop', 'r') + // + // This array only applies to 'crop' and the 'r' refers to + // "crop right". Other value include; tl, t, tr, l, m (default), + // r, bl, b, br, or you can specify your own co-ords (which + // isn't recommended. + // + // $sharpen: true: sharpen (jpg only); + // false: don't sharpen + // Param out: n/a + // Reference: + // Notes: To clarify the $option input: + // 0 = The exact height and width dimensions you set. + // 1 = Whatever height is passed in will be the height that + // is set. The width will be calculated and set automatically + // to a the value that keeps the original aspect ratio. + // 2 = The same but based on the width. We try make the image the + // biggest size we can while stil fitting inside the box size + // 3 = Depending whether the image is landscape or portrait, this + // will automatically determine whether to resize via + // dimension 1,2 or 0 + // 4 = Will resize and then crop the image for best fit + // + // forceStretch can be applied to options 1,2,3 and 4 + // + { + // *** We can pass in an array of options to change the crop position + $cropPos = 'm'; + if (is_array($option) && fix_strtolower($option[0]) == 'crop') { + $cropPos = $option[1]; // get the crop option + } else { + if (strpos($option, '-') !== false) { + // *** Or pass in a hyphen seperated option + $optionPiecesArray = explode('-', $option); + $cropPos = end($optionPiecesArray); + } + } + + // *** Check the option is valid + $option = $this->prepOption($option); + + // *** Make sure the file passed in is valid + if (!$this->image) { + if ($this->debug) { + throw new Exception('file '.$this->getFileName().' is missing or invalid'); + } + + throw new Exception(); + } + + // *** Get optimal width and height - based on $option + $dimensionsArray = $this->getDimensions($newWidth, $newHeight, $option); + + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + + // *** Resample - create image canvas of x, y size + $this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight); + $this->keepTransparancy($optimalWidth, $optimalHeight, $this->imageResized); + imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height); + + // *** If '4', then crop too + if ($option == 4 || $option == 'crop') { + if ($optimalWidth >= $newWidth && $optimalHeight >= $newHeight) { + $this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos); + } + } + + // *** If Rotate. + if ($autoRotate) { + $exifData = $this->getExif(false); + if (count($exifData) > 0) { + switch ($exifData['orientation']) { + case 8: + $this->imageResized = imagerotate($this->imageResized, 90, 0); + break; + case 3: + $this->imageResized = imagerotate($this->imageResized, 180, 0); + break; + case 6: + $this->imageResized = imagerotate($this->imageResized, -90, 0); + break; + } + } + } + + // *** Sharpen image (if jpg and the user wishes to do so) + if ($sharpen && in_array($this->fileExtension, $this->sharpenArray)) { + // *** Sharpen + $this->sharpen(); + } + } + + // # -------------------------------------------------------- + + public function cropImage($newWidth, $newHeight, $cropPos = 'm'): void + // Author: Jarrod Oberto + // Date: 08-09-11 + // Purpose: Crops the image + // Param in: $newWidth: crop with + // $newHeight: crop height + // $cropPos: Can be any of the following: + // tl, t, tr, l, m, r, bl, b, br, auto + // Or: + // a custom position such as '30x50' + // Param out: n/a + // Reference: + // Notes: + // + { + // *** Make sure the file passed in is valid + if (!$this->image) { + if ($this->debug) { + throw new Exception('file '.$this->getFileName().' is missing or invalid'); + } + + throw new Exception(); + } + + $this->imageResized = $this->image; + $this->crop($this->width, $this->height, $newWidth, $newHeight, $cropPos); + } + + // # -------------------------------------------------------- + + private function keepTransparancy($width, $height, $im): void + // Author: Jarrod Oberto + // Date: 08-04-11 + // Purpose: Keep transparency for png and gif image + // Param in: + // Param out: n/a + // Reference: + // Notes: + // + { + // *** If PNG, perform some transparency retention actions (gif untested) + if (in_array($this->fileExtension, $this->transparentArray) && $this->keepTransparency) { + imagealphablending($im, false); + imagesavealpha($im, true); + $transparent = imagecolorallocatealpha($im, 255, 255, 255, 127); + imagefilledrectangle($im, 0, 0, $width, $height, $transparent); + } else { + $color = imagecolorallocate($im, $this->fillColorArray['r'], $this->fillColorArray['g'], $this->fillColorArray['b']); + imagefilledrectangle($im, 0, 0, $width, $height, $color); + } + } + + // # -------------------------------------------------------- + + private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos): void + // Author: Jarrod Oberto + // Date: 15-09-08 + // Purpose: Crops the image + // Param in: $newWidth: + // $newHeight: + // Param out: n/a + // Reference: + // Notes: + // + { + // *** Get cropping co-ordinates + $cropArray = $this->getCropPlacing($optimalWidth, $optimalHeight, $newWidth, $newHeight, $cropPos); + $cropStartX = $cropArray['x']; + $cropStartY = $cropArray['y']; + + // *** Crop this bad boy + $crop = imagecreatetruecolor($newWidth, $newHeight); + $this->keepTransparancy($optimalWidth, $optimalHeight, $crop); + imagecopyresampled($crop, $this->imageResized, 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight, $newWidth, $newHeight); + + $this->imageResized = $crop; + + // *** Set new width and height to our variables + $this->width = $newWidth; + $this->height = $newHeight; + } + + // # -------------------------------------------------------- + + private function getCropPlacing($optimalWidth, $optimalHeight, $newWidth, $newHeight, $pos = 'm') + // + // Author: Jarrod Oberto + // Date: July 11 + // Purpose: Set the cropping area. + // Params in: + // Params out: (array) the crop x and y co-ordinates. + // Notes: When specifying the exact pixel crop position (eg 10x15), be + // very careful as it's easy to crop out of the image leaving + // black borders. + // + { + $pos = fix_strtolower($pos); + + // *** If co-ords have been entered + if (strstr($pos, 'x')) { + $pos = str_replace(' ', '', $pos); + + $xyArray = explode('x', $pos); + [$cropStartX, $cropStartY] = $xyArray; + } else { + switch ($pos) { + case 'tl': + $cropStartX = 0; + $cropStartY = 0; + break; + case 't': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = 0; + break; + case 'tr': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = 0; + break; + case 'l': + $cropStartX = 0; + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + case 'm': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + case 'r': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + case 'bl': + $cropStartX = 0; + $cropStartY = $optimalHeight - $newHeight; + break; + case 'b': + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = $optimalHeight - $newHeight; + break; + case 'br': + $cropStartX = $optimalWidth - $newWidth; + $cropStartY = $optimalHeight - $newHeight; + break; + case 'auto': + // *** If image is a portrait crop from top, not center. v1.5 + if ($optimalHeight > $optimalWidth) { + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($this->cropFromTopPercent / 100) * $optimalHeight; + } else { + // *** Else crop from the center + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + } + break; + default: + // *** Default to center + $cropStartX = ($optimalWidth / 2) - ($newWidth / 2); + $cropStartY = ($optimalHeight / 2) - ($newHeight / 2); + break; + } + } + + return ['x' => $cropStartX, 'y' => $cropStartY]; + } + + // # -------------------------------------------------------- + + private function getDimensions($newWidth, $newHeight, $option) + // Author: Jarrod Oberto + // Date: 17-11-09 + // Purpose: Get new image dimensions based on user specificaions + // Param in: $newWidth: + // $newHeight: + // Param out: Array of new width and height values + // Reference: + // Notes: If $option = 3 then this function is call recursivly + // + // To clarify the $option input: + // 0 = The exact height and width dimensions you set. + // 1 = Whatever height is passed in will be the height that + // is set. The width will be calculated and set automatically + // to a the value that keeps the original aspect ratio. + // 2 = The same but based on the width. + // 3 = Depending whether the image is landscape or portrait, this + // will automatically determine whether to resize via + // dimension 1,2 or 0. + // 4 = Resize the image as much as possible, then crop the + // remainder. + { + switch ((string) $option) { + case '0': + case 'exact': + $optimalWidth = $newWidth; + $optimalHeight = $newHeight; + break; + case '1': + case 'portrait': + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '2': + case 'landscape': + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '3': + case 'auto': + $dimensionsArray = $this->getSizeByAuto($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + case '4': + case 'crop': + $dimensionsArray = $this->getOptimalCrop($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + break; + } + + return ['optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight]; + } + + // # -------------------------------------------------------- + + private function getSizeByFixedHeight($newWidth, $newHeight) + { + // *** If forcing is off... + if (!$this->forceStretch) { + // *** ...check if actual height is less than target height + if ($this->height < $newHeight) { + return ['optimalWidth' => $this->width, 'optimalHeight' => $this->height]; + } + } + + $ratio = $this->width / $this->height; + + $newWidth = $newHeight * $ratio; + + // return $newWidth; + return ['optimalWidth' => $newWidth, 'optimalHeight' => $newHeight]; + } + + // # -------------------------------------------------------- + + private function getSizeByFixedWidth($newWidth, $newHeight) + { + // *** If forcing is off... + if (!$this->forceStretch) { + // *** ...check if actual width is less than target width + if ($this->width < $newWidth) { + return ['optimalWidth' => $this->width, 'optimalHeight' => $this->height]; + } + } + + $ratio = $this->height / $this->width; + + $newHeight = $newWidth * $ratio; + + // return $newHeight; + return ['optimalWidth' => $newWidth, 'optimalHeight' => $newHeight]; + } + + // # -------------------------------------------------------- + + private function getSizeByAuto($newWidth, $newHeight) + // Author: Jarrod Oberto + // Date: 19-08-08 + // Purpose: Depending on the height, choose to resize by 0, 1, or 2 + // Param in: The new height and new width + // Notes: + // + { + // *** If forcing is off... + if (!$this->forceStretch) { + // *** ...check if actual size is less than target size + if ($this->width < $newWidth && $this->height < $newHeight) { + return ['optimalWidth' => $this->width, 'optimalHeight' => $this->height]; + } + } + + if ($this->height < $this->width) { + // *** Image to be resized is wider (landscape) + // $optimalWidth = $newWidth; + // $optimalHeight= $this->getSizeByFixedWidth($newWidth); + + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } elseif ($this->height > $this->width) { + // *** Image to be resized is taller (portrait) + // $optimalWidth = $this->getSizeByFixedHeight($newHeight); + // $optimalHeight= $newHeight; + + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } else { // *** Image to be resizerd is a square + if ($newHeight < $newWidth) { + // $optimalWidth = $newWidth; + // $optimalHeight= $this->getSizeByFixedWidth($newWidth); + $dimensionsArray = $this->getSizeByFixedWidth($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } else { + if ($newHeight > $newWidth) { + // $optimalWidth = $this->getSizeByFixedHeight($newHeight); + // $optimalHeight= $newHeight; + $dimensionsArray = $this->getSizeByFixedHeight($newWidth, $newHeight); + $optimalWidth = $dimensionsArray['optimalWidth']; + $optimalHeight = $dimensionsArray['optimalHeight']; + } else { + // *** Sqaure being resized to a square + $optimalWidth = $newWidth; + $optimalHeight = $newHeight; + } + } + } + + return ['optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight]; + } + + // # -------------------------------------------------------- + + private function getOptimalCrop($newWidth, $newHeight) + // Author: Jarrod Oberto + // Date: 17-11-09 + // Purpose: Get optimal crop dimensions + // Param in: width and height as requested by user (fig 3) + // Param out: Array of optimal width and height (fig 2) + // Reference: + // Notes: The optimal width and height return are not the same as the + // same as the width and height passed in. For example: + // + // + // |-----------------| |------------| |-------| + // | | => |**| |**| => | | + // | | |**| |**| | | + // | | |------------| |-------| + // |-----------------| + // original optimal crop + // size size size + // Fig 1 2 3 + // + // 300 x 250 150 x 125 150 x 100 + // + // The optimal size is the smallest size (that is closest to the crop size) + // while retaining proportion/ratio. + // + // The crop size is the optimal size that has been cropped on one axis to + // make the image the exact size specified by the user. + // + // * represent cropped area + // + { + // *** If forcing is off... + if (!$this->forceStretch) { + // *** ...check if actual size is less than target size + if ($this->width < $newWidth && $this->height < $newHeight) { + return ['optimalWidth' => $this->width, 'optimalHeight' => $this->height]; + } + } + + $heightRatio = $this->height / $newHeight; + $widthRatio = $this->width / $newWidth; + + if ($heightRatio < $widthRatio) { + $optimalRatio = $heightRatio; + } else { + $optimalRatio = $widthRatio; + } + + $optimalHeight = round($this->height / $optimalRatio); + $optimalWidth = round($this->width / $optimalRatio); + + return ['optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight]; + } + + // # -------------------------------------------------------- + + private function sharpen(): void + // Author: Jarrod Oberto + // Date: 08 04 2011 + // Purpose: Sharpen image + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // Credit: Incorporates Joe Lencioni (August 6, 2008) code + { + if (version_compare(\PHP_VERSION, '5.1.0') >= 0) { + // *** + if ($this->aggresiveSharpening) { // A more aggressive sharpening solution + $sharpenMatrix = [[-1, -1, -1], + [-1, 16, -1], + [-1, -1, -1], ]; + $divisor = 8; + $offset = 0; + + imageconvolution($this->imageResized, $sharpenMatrix, $divisor, $offset); + } else { // More subtle and personally more desirable + $sharpness = $this->findSharp($this->widthOriginal, $this->width); + + $sharpenMatrix = [ + [-1, -2, -1], + [-2, $sharpness + 12, -2], // Lessen the effect of a filter by increasing the value in the center cell + [-1, -2, -1], + ]; + $divisor = $sharpness; // adjusts brightness + $offset = 0; + imageconvolution($this->imageResized, $sharpenMatrix, $divisor, $offset); + } + } else { + if ($this->debug) { + throw new Exception('Sharpening required PHP 5.1.0 or greater.'); + } + } + } + + // # -------------------------------------------------------- + + private function sharpen2($level): void + { + $sharpenMatrix = [ + [$level, $level, $level], + [$level, (8 * $level) + 1, $level], // Lessen the effect of a filter by increasing the value in the center cell + [$level, $level, $level], + ]; + } + + // # -------------------------------------------------------- + + private function findSharp($orig, $final) + // Author: Ryan Rud (http://adryrun.com) + // Purpose: Find optimal sharpness + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // + { + $final = $final * (750.0 / $orig); + $a = 52; + $b = -0.27810650887573124; + $c = .00047337278106508946; + + $result = $a + $b * $final + $c * $final * $final; + + return max(round($result), 0); + } + + // # -------------------------------------------------------- + + private function prepOption($option) + // Author: Jarrod Oberto + // Purpose: Prep option like change the passed in option to lowercase + // Param in: (str/int) $option: eg. 'exact', 'crop'. 0, 4 + // Param out: lowercase string + // Reference: + // Notes: + // + { + if (is_array($option)) { + if (fix_strtolower($option[0]) == 'crop' && count($option) == 2) { + return 'crop'; + } + + throw new Exception('Crop resize option array is badly formatted.'); + } else { + if (strpos($option, 'crop') !== false) { + return 'crop'; + } + } + + if (is_string($option)) { + return fix_strtolower($option); + } + + return $option; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Presets +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + +// + // Preset are pre-defined templates you can apply to your image. +// + // These are inteded to be applied to thumbnail images. +// + + public function borderPreset($preset): void + { + switch ($preset) { + case 'simple': + $this->addBorder(7, '#fff'); + $this->addBorder(6, '#f2f1f0'); + $this->addBorder(2, '#fff'); + $this->addBorder(1, '#ccc'); + break; + default: + break; + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Draw border +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addBorder($thickness = 1, $rgbArray = [255, 255, 255]): void + // Author: Jarrod Oberto + // Date: 05-05-11 + // Purpose: Add a border to the image + // Param in: + // Param out: + // Reference: + // Notes: This border is added to the INSIDE of the image + // + { + if ($this->imageResized) { + $rgbArray = $this->formatColor($rgbArray); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + $x1 = 0; + $y1 = 0; + $x2 = imagesx($this->imageResized) - 1; + $y2 = imagesy($this->imageResized) - 1; + + $rgbArray = imagecolorallocate($this->imageResized, $r, $g, $b); + + for ($i = 0; $i < $thickness; ++$i) { + imagerectangle($this->imageResized, $x1++, $y1++, $x2--, $y2--, $rgbArray); + } + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Gray Scale +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function greyScale(): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Make image greyscale + // Param in: n/a + // Param out: + // Reference: + // Notes: + // + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_GRAYSCALE); + } + } + + // # -------------------------------------------------------- + + public function greyScaleEnhanced(): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Make image greyscale + // Param in: n/a + // Param out: + // Reference: + // Notes: + // + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, \IMG_FILTER_CONTRAST, -15); + imagefilter($this->imageResized, \IMG_FILTER_BRIGHTNESS, 2); + $this->sharpen($this->width); + } + } + + // # -------------------------------------------------------- + + public function greyScaleDramatic(): void + // Alias of gd_filter_monopin + { + $this->gd_filter_monopin(); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Black 'n White +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function blackAndWhite(): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Make image black and white + // Param in: n/a + // Param out: + // Reference: + // Notes: + // + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, \IMG_FILTER_CONTRAST, -1000); + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Negative +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function negative(): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Make image negative + // Param in: n/a + // Param out: + // Reference: + // Notes: + // + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_NEGATE); + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Sepia +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function sepia(): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Make image sepia + // Param in: n/a + // Param out: + // Reference: + // Notes: + // + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, \IMG_FILTER_BRIGHTNESS, -10); + imagefilter($this->imageResized, \IMG_FILTER_CONTRAST, -20); + imagefilter($this->imageResized, \IMG_FILTER_COLORIZE, 60, 30, -15); + } + } + + // # -------------------------------------------------------- + + public function sepia2(): void + { + if ($this->imageResized) { + $total = imagecolorstotal($this->imageResized); + for ($i = 0; $i < $total; ++$i) { + $index = imagecolorsforindex($this->imageResized, $i); + $red = ($index['red'] * 0.393 + $index['green'] * 0.769 + $index['blue'] * 0.189) / 1.351; + $green = ($index['red'] * 0.349 + $index['green'] * 0.686 + $index['blue'] * 0.168) / 1.203; + $blue = ($index['red'] * 0.272 + $index['green'] * 0.534 + $index['blue'] * 0.131) / 2.140; + imagecolorset($this->imageResized, $i, $red, $green, $blue); + } + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Vintage +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function vintage(): void + // Alias of gd_filter_monopin + { + $this->gd_filter_vintage(); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Presets By Marc Hibbins +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + /** Apply 'Monopin' preset */ + public function gd_filter_monopin(): void + { + if ($this->imageResized) { + imagefilter($this->imageResized, \IMG_FILTER_GRAYSCALE); + imagefilter($this->imageResized, \IMG_FILTER_BRIGHTNESS, -15); + imagefilter($this->imageResized, \IMG_FILTER_CONTRAST, -15); + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'vignette', 100); + } + } + + // # -------------------------------------------------------- + + public function gd_filter_vintage(): void + { + if ($this->imageResized) { + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'vignette', 45); + imagefilter($this->imageResized, \IMG_FILTER_BRIGHTNESS, 20); + imagefilter($this->imageResized, \IMG_FILTER_CONTRAST, -35); + imagefilter($this->imageResized, \IMG_FILTER_COLORIZE, 60, -10, 35); + imagefilter($this->imageResized, \IMG_FILTER_SMOOTH, 7); + $this->imageResized = $this->gd_apply_overlay($this->imageResized, 'scratch', 10); + } + } + + // # -------------------------------------------------------- + + /** Apply a PNG overlay */ + private function gd_apply_overlay($im, $type, $amount) + // + // Original Author: Marc Hibbins + // License: Attribution-ShareAlike 3.0 + // Purpose: + // Params in: + // Params out: + // Notes: + // + { + $width = imagesx($im); + $height = imagesy($im); + $filter = imagecreatetruecolor($width, $height); + + imagealphablending($filter, false); + imagesavealpha($filter, true); + + $transparent = imagecolorallocatealpha($filter, 255, 255, 255, 127); + imagefilledrectangle($filter, 0, 0, $width, $height, $transparent); + + // *** Resize overlay + $overlay = $this->filterOverlayPath.'/'.$type.'.png'; + $png = imagecreatefrompng($overlay); + imagecopyresampled($filter, $png, 0, 0, 0, 0, $width, $height, imagesx($png), imagesy($png)); + + $comp = imagecreatetruecolor($width, $height); + imagecopy($comp, $im, 0, 0, 0, 0, $width, $height); + imagecopy($comp, $filter, 0, 0, 0, 0, $width, $height); + imagecopymerge($im, $comp, 0, 0, 0, 0, $width, $height, $amount); + + imagedestroy($comp); + + return $im; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Colorise +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function image_colorize($rgb) + { + imagetruecolortopalette($this->imageResized, true, 256); + $numColors = imagecolorstotal($this->imageResized); + + for ($x = 0; $x < $numColors; ++$x) { + [$r, $g, $b] = array_values(imagecolorsforindex($this->imageResized, $x)); + + // calculate grayscale in percent + $grayscale = ($r + $g + $b) / 3 / 0xFF; + + imagecolorset($this->imageResized, $x, + $grayscale * $rgb[0], + $grayscale * $rgb[1], + $grayscale * $rgb[2] + ); + } + + return true; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Reflection +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addReflection($reflectionHeight = 50, $startingTransparency = 30, $inside = false, $bgColor = '#fff', $stretch = false, $divider = 0): void + { + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + $im = $this->imageResized; + $li = imagecreatetruecolor($this->width, 1); + + $bgc = imagecolorallocate($li, $r, $g, $b); + imagefilledrectangle($li, 0, 0, $this->width, 1, $bgc); + + $bg = imagecreatetruecolor($this->width, $reflectionHeight); + $wh = imagecolorallocate($im, 255, 255, 255); + + $im = imagerotate($im, -180, $wh); + imagecopyresampled($bg, $im, 0, 0, 0, 0, $this->width, $this->height, $this->width, $this->height); + + $im = $bg; + + $bg = imagecreatetruecolor($this->width, $reflectionHeight); + + for ($x = 0; $x < $this->width; ++$x) { + imagecopy($bg, $im, $x, 0, $this->width - $x - 1, 0, 1, $reflectionHeight); + } + $im = $bg; + + $transaprencyAmount = $this->invertTransparency($startingTransparency, 100); + + // *** Fade + if ($stretch) { + $step = 100 / ($reflectionHeight + $startingTransparency); + } else { + $step = 100 / $reflectionHeight; + } + for ($i = 0; $i <= $reflectionHeight; ++$i) { + if ($startingTransparency > 100) { + $startingTransparency = 100; + } + if ($startingTransparency < 1) { + $startingTransparency = 1; + } + imagecopymerge($bg, $li, 0, $i, 0, 0, $this->width, 1, $startingTransparency); + $startingTransparency += $step; + } + + // *** Apply fade + imagecopymerge($im, $li, 0, 0, 0, 0, $this->width, $divider, 100); // Divider + + // *** width, height of reflection. + $x = imagesx($im); + $y = imagesy($im); + + // *** Determines if the reflection should be displayed inside or outside the image + if ($inside) { + // Create new blank image with sizes. + $final = imagecreatetruecolor($this->width, $this->height); + + imagecopymerge($final, $this->imageResized, 0, 0, 0, $reflectionHeight, $this->width, $this->height - $reflectionHeight, 100); + imagecopymerge($final, $im, 0, $this->height - $reflectionHeight, 0, 0, $x, $y, 100); + } else { + // Create new blank image with sizes. + $final = imagecreatetruecolor($this->width, $this->height + $y); + + imagecopymerge($final, $this->imageResized, 0, 0, 0, 0, $this->width, $this->height, 100); + imagecopymerge($final, $im, 0, $this->height, 0, 0, $x, $y, 100); + } + + $this->imageResized = $final; + + imagedestroy($li); + imagedestroy($im); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Rotate +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function rotate($value = 90, $bgColor = 'transparent'): void + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Rotate image + // Param in: (mixed) $degrees: (int) number of degress to rotate image + // (str) param "left": rotate left + // (str) param "right": rotate right + // (str) param "upside": upside-down image + // Param out: + // Reference: + // Notes: The default direction of imageRotate() is counter clockwise. + // + { + if ($this->imageResized) { + if (is_int($value)) { + $degrees = $value; + } + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + if (isset($rgbArray['a'])) { + $a = $rgbArray['a']; + } + + if (is_string($value)) { + $value = fix_strtolower($value); + + switch ($value) { + case 'left': + $degrees = 90; + break; + case 'right': + $degrees = 270; + break; + case 'upside': + $degrees = 180; + break; + default: + break; + } + } + + // *** The default direction of imageRotate() is counter clockwise + // * This makes it clockwise + $degrees = 360 - $degrees; + + // *** Create background color + $bg = imagecolorallocatealpha($this->imageResized, $r, $g, $b, $a); + + // *** Fill with background + imagefill($this->imageResized, 0, 0, $bg); + + // *** Rotate + $this->imageResized = imagerotate($this->imageResized, $degrees, $bg); // Rotate 45 degrees and allocated the transparent colour as the one to make transparent (obviously) + + // Ensure alpha transparency + imagesavealpha($this->imageResized, true); + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Round corners +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function roundCorners($radius = 5, $bgColor = 'transparent'): void + // Author: Jarrod Oberto + // Date: 19-05-2011 + // Purpose: Create rounded corners on your image + // Param in: (int) radius = the amount of curvature + // (mixed) $bgColor = the corner background color + // Param out: n/a + // Reference: + // Notes: + // + { + // *** Check if the user wants transparency + $isTransparent = false; + if (!is_array($bgColor)) { + if (fix_strtolower($bgColor) == 'transparent') { + $isTransparent = true; + } + } + + // *** If we use transparency, we need to color our curved mask with a unique color + if ($isTransparent) { + $bgColor = $this->findUnusedGreen(); + } + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + if (isset($rgbArray['a'])) { + $a = $rgbArray['a']; + } + + // *** Create top-left corner mask (square) + $cornerImg = imagecreatetruecolor($radius, $radius); + // $cornerImg = imagecreate($radius, $radius); + + // imagealphablending($cornerImg, true); + // imagesavealpha($cornerImg, true); + + // imagealphablending($this->imageResized, false); + // imagesavealpha($this->imageResized, true); + + // *** Give it a color + $maskColor = imagecolorallocate($cornerImg, 0, 0, 0); + + // *** Replace the mask color (black) to transparent + imagecolortransparent($cornerImg, $maskColor); + + // *** Create the image background color + $imagebgColor = imagecolorallocate($cornerImg, $r, $g, $b); + + // *** Fill the corner area to the user defined color + imagefill($cornerImg, 0, 0, $imagebgColor); + + imagefilledellipse($cornerImg, $radius, $radius, $radius * 2, $radius * 2, $maskColor); + + // *** Map to top left corner + imagecopymerge($this->imageResized, $cornerImg, 0, 0, 0, 0, $radius, $radius, 100); // tl + + // *** Map rounded corner to other corners by rotating and applying the mask + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, 0, $this->height - $radius, 0, 0, $radius, $radius, 100); // bl + + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, $this->width - $radius, $this->height - $radius, 0, 0, $radius, $radius, 100); // br + + $cornerImg = imagerotate($cornerImg, 90, 0); + imagecopymerge($this->imageResized, $cornerImg, $this->width - $radius, 0, 0, 0, $radius, $radius, 100); // tr + + // *** If corners are to be transparent, we fill our chromakey color as transparent. + if ($isTransparent) { + // imagecolortransparent($this->imageResized, $imagebgColor); + $this->imageResized = $this->transparentImage($this->imageResized); + imagesavealpha($this->imageResized, true); + } + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Shadow +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addShadow($shadowAngle = 45, $blur = 15, $bgColor = 'transparent'): void + // + // Author: Jarrod Oberto (Adapted from Pascal Naidon) + // Ref: http://www.les-stooges.org/pascal/webdesign/vignettes/index.php?la=en + // Purpose: Add a drop shadow to your image + // Params in: (int) $angle: the angle of the shadow + // (int) $blur: the blur distance + // (mixed) $bgColor: the color of the background + // Params out: + // Notes: + // + { + // *** A higher number results in a smoother shadow + define('STEPS', $blur * 2); + + // *** Set the shadow distance + $shadowDistance = $blur * 0.25; + + // *** Set blur width and height + $blurWidth = $blurHeight = $blur; + + if ($shadowAngle == 0) { + $distWidth = 0; + $distHeight = 0; + } else { + $distWidth = $shadowDistance * cos(deg2rad($shadowAngle)); + $distHeight = $shadowDistance * sin(deg2rad($shadowAngle)); + } + + // *** Convert color + if (fix_strtolower($bgColor) != 'transparent') { + $rgbArray = $this->formatColor($bgColor); + $r0 = $rgbArray['r']; + $g0 = $rgbArray['g']; + $b0 = $rgbArray['b']; + } + + $image = $this->imageResized; + $width = $this->width; + $height = $this->height; + + $newImage = imagecreatetruecolor($width, $height); + imagecopyresampled($newImage, $image, 0, 0, 0, 0, $width, $height, $width, $height); + + // *** RGB + $rgb = imagecreatetruecolor($width + $blurWidth, $height + $blurHeight); + $colour = imagecolorallocate($rgb, 0, 0, 0); + imagefilledrectangle($rgb, 0, 0, $width + $blurWidth, $height + $blurHeight, $colour); + $colour = imagecolorallocate($rgb, 255, 255, 255); + // imagefilledrectangle($rgb, $blurWidth*0.5-$distWidth, $blurHeight*0.5-$distHeight, $width+$blurWidth*0.5-$distWidth, $height+$blurWidth*0.5-$distHeight, $colour); + imagefilledrectangle($rgb, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, $width + $blurWidth * 0.5 - $distWidth, $height + $blurWidth * 0.5 - $distHeight, $colour); + // imagecopymerge($rgb, $newImage, 1+$blurWidth*0.5-$distWidth, 1+$blurHeight*0.5-$distHeight, 0,0, $width, $height, 100); + imagecopymerge($rgb, $newImage, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, 0, 0, $width + $blurWidth, $height + $blurHeight, 100); + + // *** Shadow (alpha) + $shadow = imagecreatetruecolor($width + $blurWidth, $height + $blurHeight); + imagealphablending($shadow, false); + $colour = imagecolorallocate($shadow, 0, 0, 0); + imagefilledrectangle($shadow, 0, 0, $width + $blurWidth, $height + $blurHeight, $colour); + + for ($i = 0; $i <= STEPS; ++$i) { + $t = ((1.0 * $i) / STEPS); + $intensity = 255 * $t * $t; + + $colour = imagecolorallocate($shadow, $intensity, $intensity, $intensity); + $points = [ + $blurWidth * $t, $blurHeight, // Point 1 (x, y) + $blurWidth, $blurHeight * $t, // Point 2 (x, y) + $width, $blurHeight * $t, // Point 3 (x, y) + $width + $blurWidth * (1 - $t), $blurHeight, // Point 4 (x, y) + $width + $blurWidth * (1 - $t), $height, // Point 5 (x, y) + $width, $height + $blurHeight * (1 - $t), // Point 6 (x, y) + $blurWidth, $height + $blurHeight * (1 - $t), // Point 7 (x, y) + $blurWidth * $t, $height, // Point 8 (x, y) + ]; + imagepolygon($shadow, $points, 8, $colour); + } + + for ($i = 0; $i <= STEPS; ++$i) { + $t = ((1.0 * $i) / STEPS); + $intensity = 255 * $t * $t; + + $colour = imagecolorallocate($shadow, $intensity, $intensity, $intensity); + imagefilledarc($shadow, $blurWidth - 1, $blurHeight - 1, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 180, 268, $colour, \IMG_ARC_PIE); + imagefilledarc($shadow, $width, $blurHeight - 1, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 270, 358, $colour, \IMG_ARC_PIE); + imagefilledarc($shadow, $width, $height, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 0, 90, $colour, \IMG_ARC_PIE); + imagefilledarc($shadow, $blurWidth - 1, $height, 2 * (1 - $t) * $blurWidth, 2 * (1 - $t) * $blurHeight, 90, 180, $colour, \IMG_ARC_PIE); + } + + $colour = imagecolorallocate($shadow, 255, 255, 255); + imagefilledrectangle($shadow, $blurWidth, $blurHeight, $width, $height, $colour); + imagefilledrectangle($shadow, $blurWidth * 0.5 - $distWidth, $blurHeight * 0.5 - $distHeight, $width + $blurWidth * 0.5 - 1 - $distWidth, $height + $blurHeight * 0.5 - 1 - $distHeight, $colour); + + // *** The magic + imagealphablending($rgb, false); + + for ($theX = 0; $theX < imagesx($rgb); ++$theX) { + for ($theY = 0; $theY < imagesy($rgb); ++$theY) { + // *** Get the RGB values for every pixel of the RGB image + $colArray = imagecolorat($rgb, $theX, $theY); + $r = ($colArray >> 16) & 0xFF; + $g = ($colArray >> 8) & 0xFF; + $b = $colArray & 0xFF; + + // *** Get the alpha value for every pixel of the shadow image + $colArray = imagecolorat($shadow, $theX, $theY); + $a = $colArray & 0xFF; + $a = 127 - floor($a / 2); + $t = $a / 128.0; + + // *** Create color + if (fix_strtolower($bgColor) == 'transparent') { + $myColour = imagecolorallocatealpha($rgb, $r, $g, $b, $a); + } else { + $myColour = imagecolorallocate($rgb, $r * (1.0 - $t) + $r0 * $t, $g * (1.0 - $t) + $g0 * $t, $b * (1.0 - $t) + $b0 * $t); + } + + // *** Add color to new rgb image + imagesetpixel($rgb, $theX, $theY, $myColour); + } + } + + imagealphablending($rgb, true); + imagesavealpha($rgb, true); + + $this->imageResized = $rgb; + + imagedestroy($image); + imagedestroy($newImage); + imagedestroy($shadow); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Caption Box +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addCaptionBox($side = 'b', $thickness = 50, $padding = 0, $bgColor = '#000', $transaprencyAmount = 30): void + // + // Author: Jarrod Oberto + // Date: 26 May 2011 + // Purpose: Add a caption box + // Params in: (str) $side: the side to add the caption box (t, r, b, or l). + // (int) $thickness: how thick you want the caption box to be. + // (mixed) $bgColor: The color of the caption box. + // (int) $transaprencyAmount: The amount of transparency to be + // applied. + // Params out: n/a + // Notes: + // + { + $side = fix_strtolower($side); + + // *** Convert color + $rgbArray = $this->formatColor($bgColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + $positionArray = $this->calculateCaptionBoxPosition($side, $thickness, $padding); + + // *** Store incase we want to use method addTextToCaptionBox() + $this->captionBoxPositionArray = $positionArray; + + $transaprencyAmount = $this->invertTransparency($transaprencyAmount, 127, false); + $transparent = imagecolorallocatealpha($this->imageResized, $r, $g, $b, $transaprencyAmount); + imagefilledrectangle($this->imageResized, $positionArray['x1'], $positionArray['y1'], $positionArray['x2'], $positionArray['y2'], $transparent); + } + + // # -------------------------------------------------------- + + public function addTextToCaptionBox($text, $fontColor = '#fff', $fontSize = 12, $angle = 0, $font = null) + // + // Author: Jarrod Oberto + // Date: 03 Aug 11 + // Purpose: Simplify adding text to a caption box by automatically + // locating the center of the caption box + // Params in: The usually text paams (less a couple) + // Params out: n/a + // Notes: + // + { + // *** Get the caption box measurements + if (count($this->captionBoxPositionArray) == 4) { + $x1 = $this->captionBoxPositionArray['x1']; + $x2 = $this->captionBoxPositionArray['x2']; + $y1 = $this->captionBoxPositionArray['y1']; + $y2 = $this->captionBoxPositionArray['y2']; + } else { + if ($this->debug) { + throw new Exception('No caption box found.'); + } + + return false; + } + + // *** Get text font + $font = $this->getTextFont($font); + + // *** Get text size + $textSizeArray = $this->getTextSize($fontSize, $angle, $font, $text); + $textWidth = $textSizeArray['width']; + $textHeight = $textSizeArray['height']; + + // *** Find the width/height middle points + $boxXMiddle = (($x2 - $x1) / 2); + $boxYMiddle = (($y2 - $y1) / 2); + + // *** Box middle - half the text width/height + $xPos = ($x1 + $boxXMiddle) - ($textWidth / 2); + $yPos = ($y1 + $boxYMiddle) - ($textHeight / 2); + + $pos = $xPos.'x'.$yPos; + + $this->addText($text, $pos, $padding = 0, $fontColor, $fontSize, $angle, $font); + } + + // # -------------------------------------------------------- + + private function calculateCaptionBoxPosition($side, $thickness, $padding) + { + $positionArray = []; + + switch ($side) { + case 't': + $positionArray['x1'] = 0; + $positionArray['y1'] = $padding; + $positionArray['x2'] = $this->width; + $positionArray['y2'] = $thickness + $padding; + break; + case 'r': + $positionArray['x1'] = $this->width - $thickness - $padding; + $positionArray['y1'] = 0; + $positionArray['x2'] = $this->width - $padding; + $positionArray['y2'] = $this->height; + break; + case 'b': + $positionArray['x1'] = 0; + $positionArray['y1'] = $this->height - $thickness - $padding; + $positionArray['x2'] = $this->width; + $positionArray['y2'] = $this->height - $padding; + break; + case 'l': + $positionArray['x1'] = $padding; + $positionArray['y1'] = 0; + $positionArray['x2'] = $thickness + $padding; + $positionArray['y2'] = $this->height; + break; + default: + break; + } + + return $positionArray; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Get EXIF Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function getExif($debug = false) + // Author: Jarrod Oberto + // Date: 07-05-2011 + // Purpose: Get image EXIF data + // Param in: n/a + // Param out: An associate array of EXIF data + // Reference: + // Notes: + // 23 May 13 : added orientation flag -jco + // + { + if (!$this->debug || !$debug) { + $debug = false; + } + + // *** Check all is good - check the EXIF library exists and the file exists, too. + if (!$this->testEXIFInstalled()) { + if ($debug) { + throw new Exception('The EXIF Library is not installed.'); + } + + return []; + } + if (!file_exists($this->fileName)) { + if ($debug) { + throw new Exception('Image not found.'); + } + + return []; + } + if ($this->fileExtension != '.jpg') { + if ($debug) { + throw new Exception('Metadata not supported for this image type.'); + } + + return []; + } + $exifData = exif_read_data($this->fileName, 'IFD0'); + + // *** Format the apperture value + $ev = $exifData['ApertureValue']; + $apPeicesArray = explode('/', $ev); + if (count($apPeicesArray) == 2) { + $apertureValue = round($apPeicesArray[0] / $apPeicesArray[1], 2, \PHP_ROUND_HALF_DOWN).' EV'; + } else { + $apertureValue = ''; + } + + // *** Format the focal length + $focalLength = $exifData['FocalLength']; + $flPeicesArray = explode('/', $focalLength); + if (count($flPeicesArray) == 2) { + $focalLength = $flPeicesArray[0] / $flPeicesArray[1].'.0 mm'; + } else { + $focalLength = ''; + } + + // *** Format fNumber + $fNumber = $exifData['FNumber']; + $fnPeicesArray = explode('/', $fNumber); + if (count($fnPeicesArray) == 2) { + $fNumber = $fnPeicesArray[0] / $fnPeicesArray[1]; + } else { + $fNumber = ''; + } + + // *** Resolve ExposureProgram + if (isset($exifData['ExposureProgram'])) { + $ep = $exifData['ExposureProgram']; + } + if (isset($ep)) { + $ep = $this->resolveExposureProgram($ep); + } + + // *** Resolve MeteringMode + $mm = $exifData['MeteringMode']; + $mm = $this->resolveMeteringMode($mm); + + // *** Resolve Flash + $flash = $exifData['Flash']; + $flash = $this->resolveFlash($flash); + + if (isset($exifData['Make'])) { + $exifDataArray['make'] = $exifData['Make']; + } else { + $exifDataArray['make'] = ''; + } + + if (isset($exifData['Model'])) { + $exifDataArray['model'] = $exifData['Model']; + } else { + $exifDataArray['model'] = ''; + } + + if (isset($exifData['DateTime'])) { + $exifDataArray['date'] = $exifData['DateTime']; + } else { + $exifDataArray['date'] = ''; + } + + if (isset($exifData['ExposureTime'])) { + $exifDataArray['exposure time'] = $exifData['ExposureTime'].' sec.'; + } else { + $exifDataArray['exposure time'] = ''; + } + + if ($apertureValue != '') { + $exifDataArray['aperture value'] = $apertureValue; + } else { + $exifDataArray['aperture value'] = ''; + } + + if (isset($exifData['COMPUTED']['ApertureFNumber'])) { + $exifDataArray['f-stop'] = $exifData['COMPUTED']['ApertureFNumber']; + } else { + $exifDataArray['f-stop'] = ''; + } + + if (isset($exifData['FNumber'])) { + $exifDataArray['fnumber'] = $exifData['FNumber']; + } else { + $exifDataArray['fnumber'] = ''; + } + + if ($fNumber != '') { + $exifDataArray['fnumber value'] = $fNumber; + } else { + $exifDataArray['fnumber value'] = ''; + } + + if (isset($exifData['ISOSpeedRatings'])) { + $exifDataArray['iso'] = $exifData['ISOSpeedRatings']; + } else { + $exifDataArray['iso'] = ''; + } + + if ($focalLength != '') { + $exifDataArray['focal length'] = $focalLength; + } else { + $exifDataArray['focal length'] = ''; + } + + if (isset($ep)) { + $exifDataArray['exposure program'] = $ep; + } else { + $exifDataArray['exposure program'] = ''; + } + + if ($mm != '') { + $exifDataArray['metering mode'] = $mm; + } else { + $exifDataArray['metering mode'] = ''; + } + + if ($flash != '') { + $exifDataArray['flash status'] = $flash; + } else { + $exifDataArray['flash status'] = ''; + } + + if (isset($exifData['Artist'])) { + $exifDataArray['creator'] = $exifData['Artist']; + } else { + $exifDataArray['creator'] = ''; + } + + if (isset($exifData['Copyright'])) { + $exifDataArray['copyright'] = $exifData['Copyright']; + } else { + $exifDataArray['copyright'] = ''; + } + + // *** Orientation + if (isset($exifData['Orientation'])) { + $exifDataArray['orientation'] = $exifData['Orientation']; + } else { + $exifDataArray['orientation'] = ''; + } + + return $exifDataArray; + } + + // # -------------------------------------------------------- + + private function resolveExposureProgram($ep) + { + switch ($ep) { + case 0: + $ep = ''; + break; + case 1: + $ep = 'manual'; + break; + case 2: + $ep = 'normal program'; + break; + case 3: + $ep = 'aperture priority'; + break; + case 4: + $ep = 'shutter priority'; + break; + case 5: + $ep = 'creative program'; + break; + case 6: + $ep = 'action program'; + break; + case 7: + $ep = 'portrait mode'; + break; + case 8: + $ep = 'landscape mode'; + break; + default: + break; + } + + return $ep; + } + + // # -------------------------------------------------------- + + private function resolveMeteringMode($mm) + { + switch ($mm) { + case 0: + $mm = 'unknown'; + break; + case 1: + $mm = 'average'; + break; + case 2: + $mm = 'center weighted average'; + break; + case 3: + $mm = 'spot'; + break; + case 4: + $mm = 'multi spot'; + break; + case 5: + $mm = 'pattern'; + break; + case 6: + $mm = 'partial'; + break; + case 255: + $mm = 'other'; + break; + default: + break; + } + + return $mm; + } + + // # -------------------------------------------------------- + + private function resolveFlash($flash) + { + switch ($flash) { + case 0: + $flash = 'flash did not fire'; + break; + case 1: + $flash = 'flash fired'; + break; + case 5: + $flash = 'strobe return light not detected'; + break; + case 7: + $flash = 'strobe return light detected'; + break; + case 9: + $flash = 'flash fired, compulsory flash mode'; + break; + case 13: + $flash = 'flash fired, compulsory flash mode, return light not detected'; + break; + case 15: + $flash = 'flash fired, compulsory flash mode, return light detected'; + break; + case 16: + $flash = 'flash did not fire, compulsory flash mode'; + break; + case 24: + $flash = 'flash did not fire, auto mode'; + break; + case 25: + $flash = 'flash fired, auto mode'; + break; + case 29: + $flash = 'flash fired, auto mode, return light not detected'; + break; + case 31: + $flash = 'flash fired, auto mode, return light detected'; + break; + case 32: + $flash = 'no flash function'; + break; + case 65: + $flash = 'flash fired, red-eye reduction mode'; + break; + case 69: + $flash = 'flash fired, red-eye reduction mode, return light not detected'; + break; + case 71: + $flash = 'flash fired, red-eye reduction mode, return light detected'; + break; + case 73: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode'; + break; + case 77: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode, return light not detected'; + break; + case 79: + $flash = 'flash fired, compulsory flash mode, red-eye reduction mode, return light detected'; + break; + case 89: + $flash = 'flash fired, auto mode, red-eye reduction mode'; + break; + case 93: + $flash = 'flash fired, auto mode, return light not detected, red-eye reduction mode'; + break; + case 95: + $flash = 'flash fired, auto mode, return light detected, red-eye reduction mode'; + break; + default: + break; + } + + return $flash; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Get IPTC Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Write IPTC Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function writeIPTCcaption($value): void + // Caption + { + $this->writeIPTC(120, $value); + } + + // # -------------------------------------------------------- + + public function writeIPTCwriter($value): void + { + // $this->writeIPTC(65, $value); + } + + // # -------------------------------------------------------- + + private function writeIPTC($dat, $value): void + { + // LIMIT TO JPG + + $caption_block = $this->iptc_maketag(2, $dat, $value); + $image_string = iptcembed($caption_block, $this->fileName); + file_put_contents('iptc.jpg', $image_string); + } + + // # -------------------------------------------------------- + + private function iptc_maketag($rec, $dat, $val) + // Author: Thies C. Arntzen + // Purpose: Function to format the new IPTC text + // Param in: $rec: Application record. (We’re working with #2) + // $dat: Index. (120 for caption, 118 for contact. See the IPTC IIM + // specification: + // http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf + // $val: Value/data/text. Make sure this is within the length + // constraints of the IPTC IIM specification + // Ref: http://blog.peterhaza.no/working-with-image-meta-data-in-exif-and-iptc-headers-from-php/ + // http://php.net/manual/en/function.iptcembed.php + // + { + $len = strlen($val); + if ($len < 0x8000) { + return chr(0x1C).chr($rec).chr($dat). + chr($len >> 8). + chr($len & 0xFF). + $val; + } + + return chr(0x1C).chr($rec).chr($dat). + chr(0x80).chr(0x04). + chr(($len >> 24) & 0xFF). + chr(($len >> 16) & 0xFF). + chr(($len >> 8) & 0xFF). + chr($len & 0xFF). + $val; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Write XMP Data +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + // http://xmpphptoolkit.sourceforge.net/ + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Text +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addText($text, $pos = '20x20', $padding = 0, $fontColor = '#fff', $fontSize = 12, $angle = 0, $font = null): void + // Author: Jarrod Oberto + // Date: 18-11-09 + // Purpose: Add text to an image + // Param in: + // Param out: + // Reference: http://php.net/manual/en/function.imagettftext.php + // Notes: Make sure you supply the font. + // + { + // *** Convert color + $rgbArray = $this->formatColor($fontColor); + $r = $rgbArray['r']; + $g = $rgbArray['g']; + $b = $rgbArray['b']; + + // *** Get text font + $font = $this->getTextFont($font); + + // *** Get text size + $textSizeArray = $this->getTextSize($fontSize, $angle, $font, $text); + $textWidth = $textSizeArray['width']; + $textHeight = $textSizeArray['height']; + + // *** Find co-ords to place text + $posArray = $this->calculatePosition($pos, $padding, $textWidth, $textHeight, false); + $x = $posArray['width']; + $y = $posArray['height']; + + $fontColor = imagecolorallocate($this->imageResized, $r, $g, $b); + + // *** Add text + imagettftext($this->imageResized, $fontSize, $angle, $x, $y, $fontColor, $font, $text); + } + + // # -------------------------------------------------------- + + private function getTextFont($font) + { + // *** Font path (shou + $fontPath = __DIR__.'/'.$this->fontDir; + + // *** The below is/may be needed depending on your version (see ref) + putenv('GDFONTPATH='.realpath('.')); + + // *** Check if the passed in font exsits... + if ($font == null || !file_exists($font)) { + // *** ...If not, default to this font. + $font = $fontPath.'/arimo.ttf'; + + // *** Check our default font exists... + if (!file_exists($font)) { + // *** If not, return false + if ($this->debug) { + throw new Exception('Font not found'); + } + + return false; + } + } + + return $font; + } + + // # -------------------------------------------------------- + + private function getTextSize($fontSize, $angle, $font, $text) + { + // *** Define box (so we can get the width) + $box = @imagettfbbox($fontSize, $angle, $font, $text); + + // *** Get width of text from dimensions + $textWidth = abs($box[4] - $box[0]); + + // *** Get height of text from dimensions (should also be same as $fontSize) + $textHeight = abs($box[5] - $box[1]); + + return ['height' => $textHeight, 'width' => $textWidth]; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + Add Watermark +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + public function addWatermark($watermarkImage, $pos, $padding = 0, $opacity = 0): void + // Author: Jarrod Oberto + // Date: 18-11-09 + // Purpose: Add watermark image + // Param in: (str) $watermark: The watermark image + // (str) $pos: Could be a pre-determined position such as: + // tl = top left, + // t = top (middle), + // tr = top right, + // l = left, + // m = middle, + // r = right, + // bl = bottom left, + // b = bottom (middle), + // br = bottom right + // Or, it could be a co-ordinate position such as: 50x100 + // + // (int) $padding: If using a pre-determined position you can + // adjust the padding from the edges by passing an amount + // in pixels. If using co-ordinates, this value is ignored. + // Param out: + // Reference: http://www.php.net/manual/en/image.examples-watermark.php + // Notes: Based on example in reference. + // + // + { + // Load the stamp and the photo to apply the watermark to + $stamp = $this->openImage($watermarkImage); // stamp + $im = $this->imageResized; // photo + + // *** Get stamps width and height + $sx = imagesx($stamp); + $sy = imagesy($stamp); + + // *** Find co-ords to place image + $posArray = $this->calculatePosition($pos, $padding, $sx, $sy); + $x = $posArray['width']; + $y = $posArray['height']; + + // *** Set watermark opacity + if (fix_strtolower(strrchr($watermarkImage, '.')) == '.png') { + $opacity = $this->invertTransparency($opacity, 100); + $this->filterOpacity($stamp, $opacity); + } + + // Copy the watermark image onto our photo + imagecopy($im, $stamp, $x, $y, 0, 0, imagesx($stamp), imagesy($stamp)); + } + + // # -------------------------------------------------------- + + private function calculatePosition($pos, $padding, $assetWidth, $assetHeight, $upperLeft = true) + // + // Author: Jarrod Oberto + // Date: 08-05-11 + // Purpose: Calculate the x, y pixel cordinates of the asset to place + // Params in: (str) $pos: Either something like: "tl", "l", "br" or an + // exact position like: "100x50" + // (int) $padding: The amount of padding from the edge. Only + // used for the predefined $pos. + // (int) $assetWidth: The width of the asset to add to the image + // (int) $assetHeight: The height of the asset to add to the image + // (bol) $upperLeft: if true, the asset will be positioned based + // on the upper left x, y coords. If false, it means you're + // using the lower left as the basepoint and this will + // convert it to the upper left position + // Params out: + // NOTE: this is done from the UPPER left corner!! But will convert lower + // left basepoints to upper left if $upperleft is set to false + // + // + { + $pos = fix_strtolower($pos); + + // *** If co-ords have been entered + if (strstr($pos, 'x')) { + $pos = str_replace(' ', '', $pos); + + $xyArray = explode('x', $pos); + [$width, $height] = $xyArray; + } else { + switch ($pos) { + case 'tl': + $width = 0 + $padding; + $height = 0 + $padding; + break; + case 't': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = 0 + $padding; + break; + case 'tr': + $width = $this->width - $assetWidth - $padding; + $height = 0 + $padding; + break; + case 'l': + $width = 0 + $padding; + $height = ($this->height / 2) - ($assetHeight / 2); + break; + case 'm': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = ($this->height / 2) - ($assetHeight / 2); + break; + case 'r': + $width = $this->width - $assetWidth - $padding; + $height = ($this->height / 2) - ($assetHeight / 2); + break; + case 'bl': + $width = 0 + $padding; + $height = $this->height - $assetHeight - $padding; + break; + case 'b': + $width = ($this->width / 2) - ($assetWidth / 2); + $height = $this->height - $assetHeight - $padding; + break; + case 'br': + $width = $this->width - $assetWidth - $padding; + $height = $this->height - $assetHeight - $padding; + break; + default: + $width = 0; + $height = 0; + break; + } + } + + if (!$upperLeft) { + $height = $height + $assetHeight; + } + + return ['width' => $width, 'height' => $height]; + } + + // # -------------------------------------------------------- + + private function filterOpacity(&$img, $opacity = 75) + // + // Author: aiden dot mail at freemail dot hu + // Author date: 29-03-08 08:16 + // Date added: 08-05-11 + // Purpose: Change opacity of image + // Params in: $img: Image resource id + // (int) $opacity: the opacity amount: 0-100, 100 being not opaque. + // Params out: (bool) true on success, else false + // Ref: http://www.php.net/manual/en/function.imagefilter.php#82162 + // Notes: png only + // + { + if (!isset($opacity)) { + return false; + } + + if ($opacity == 100) { + return true; + } + + $opacity /= 100; + + // get image width and height + $w = imagesx($img); + $h = imagesy($img); + + // turn alpha blending off + imagealphablending($img, false); + + // find the most opaque pixel in the image (the one with the smallest alpha value) + $minalpha = 127; + for ($x = 0; $x < $w; ++$x) { + for ($y = 0; $y < $h; ++$y) { + $alpha = (imagecolorat($img, $x, $y) >> 24) & 0xFF; + if ($alpha < $minalpha) { + $minalpha = $alpha; + } + } + } + + // loop through image pixels and modify alpha for each + for ($x = 0; $x < $w; ++$x) { + for ($y = 0; $y < $h; ++$y) { + // get current alpha value (represents the TANSPARENCY!) + $colorxy = imagecolorat($img, $x, $y); + $alpha = ($colorxy >> 24) & 0xFF; + // calculate new alpha + if ($minalpha !== 127) { + $alpha = 127 + 127 * $opacity * ($alpha - 127) / (127 - $minalpha); + } else { + $alpha += 127 * $opacity; + } + // get the color index with new alpha + $alphacolorxy = imagecolorallocatealpha($img, ($colorxy >> 16) & 0xFF, ($colorxy >> 8) & 0xFF, $colorxy & 0xFF, $alpha); + // set pixel with the new color + opacity + if (!imagesetpixel($img, $x, $y, $alphacolorxy)) { + return false; + } + } + } + + return true; + } + + // # -------------------------------------------------------- + + private function openImage($file) + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: + // Param in: + // Param out: n/a + // Reference: + // Notes: + // + { + if (!file_exists($file) && !$this->checkStringStartsWith('http://', $file) && !$this->checkStringStartsWith('https://', $file)) { + if ($this->debug) { + throw new Exception('Image not found.'); + } + + throw new Exception(); + } + + // *** Get extension + $extension = strrchr($file, '.'); + $extension = fix_strtolower($extension); + switch ($extension) { + case '.jpg': + case '.jpeg': + $img = @imagecreatefromjpeg($file); + break; + case '.gif': + $img = @imagecreatefromgif($file); + break; + case '.png': + $img = @imagecreatefrompng($file); + break; + case '.bmp': + $img = @$this->imagecreatefrombmp($file); + break; + case '.psd': + $img = @$this->imagecreatefrompsd($file); + break; + // ... etc + + default: + $img = false; + break; + } + + return $img; + } + + // # -------------------------------------------------------- + + public function reset(): void + // + // Author: Jarrod Oberto + // Date: 30-08-11 + // Purpose: Reset the resource (allow further editing) + // Params in: + // Params out: + // Notes: + // + { + $this->__construct($this->fileName); + } + + // # -------------------------------------------------------- + + public function saveImage($savePath, $imageQuality = '100'): void + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Saves the image + // Param in: $savePath: Where to save the image including filename: + // $imageQuality: image quality you want the image saved at 0-100 + // Param out: n/a + // Reference: + // Notes: * gif doesn't have a quality parameter + // * jpg has a quality setting 0-100 (100 being the best) + // * png has a quality setting 0-9 (0 being the best) + // + // * bmp files have no native support for bmp files. We use a + // third party class to save as bmp. + { + // *** Perform a check or two. + if (!is_resource($this->imageResized) && !$this->imageResized instanceof GdImage) { + if ($this->debug) { + throw new Exception('saveImage: This is not a resource.'); + } + + throw new Exception(); + } + $fileInfoArray = pathinfo($savePath); + clearstatcache(); + if (!is_writable($fileInfoArray['dirname'])) { + if ($this->debug) { + throw new Exception('The path is not writable. Please check your permissions.'); + } + + throw new Exception(); + } + + // *** Get extension + $extension = strrchr($savePath, '.'); + $extension = fix_strtolower($extension); + + $error = ''; + + switch ($extension) { + case '.jpg': + case '.jpeg': + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & \IMG_JPG) { + imagejpeg($this->imageResized, $savePath, $imageQuality); + } else { + $error = 'jpg'; + } + break; + case '.gif': + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & \IMG_GIF) { + imagegif($this->imageResized, $savePath); + } else { + $error = 'gif'; + } + break; + case '.png': + // *** Scale quality from 0-100 to 0-9 + $scaleQuality = round(($imageQuality / 100) * 9); + + // *** Invert qualit setting as 0 is best, not 9 + $invertScaleQuality = 9 - $scaleQuality; + + $this->checkInterlaceImage($this->isInterlace); + if (imagetypes() & \IMG_PNG) { + imagepng($this->imageResized, $savePath, $invertScaleQuality); + } else { + $error = 'png'; + } + break; + case '.bmp': + file_put_contents($savePath, $this->GD2BMPstring($this->imageResized)); + break; + // ... etc + + default: + // *** No extension - No save. + $this->errorArray[] = 'This file type ('.$extension.') is not supported. File not saved.'; + break; + } + + // imagedestroy($this->imageResized); + + // *** Display error if a file type is not supported. + if ($error != '') { + $this->errorArray[] = $error.' support is NOT enabled. File not saved.'; + } + } + + // # -------------------------------------------------------- + + public function displayImage($fileType = 'jpg', $imageQuality = '100'): void + // Author: Jarrod Oberto + // Date: 18-11-09 + // Purpose: Display images directly to the browser + // Param in: The image type you want to display + // Param out: + // Reference: + // Notes: + // + { + if (!is_resource($this->imageResized)) { + if ($this->debug) { + throw new Exception('saveImage: This is not a resource.'); + } + + throw new Exception(); + } + + switch ($fileType) { + case 'jpg': + case 'jpeg': + header('Content-type: image/jpeg'); + imagejpeg($this->imageResized, '', $imageQuality); + break; + case 'gif': + header('Content-type: image/gif'); + imagegif($this->imageResized); + break; + case 'png': + header('Content-type: image/png'); + + // *** Scale quality from 0-100 to 0-9 + $scaleQuality = round(($imageQuality / 100) * 9); + + // *** Invert qualit setting as 0 is best, not 9 + $invertScaleQuality = 9 - $scaleQuality; + + imagepng($this->imageResized, '', $invertScaleQuality); + break; + case 'bmp': + echo 'bmp file format is not supported.'; + break; + // ... etc + + default: + // *** No extension - No save. + break; + } + + // imagedestroy($this->imageResized); + } + + // # -------------------------------------------------------- + + public function setTransparency($bool): void + // Sep 2011 + { + $this->keepTransparency = $bool; + } + + // # -------------------------------------------------------- + + public function setFillColor($value): void + // Sep 2011 + // Param in: (mixed) $value: (array) Could be an array of RGB + // (str) Could be hex #ffffff or #fff, fff, ffffff + // + // If the keepTransparency is set to false, then no transparency is to be used. + // This is ideal when you want to save as jpg. + // + // this method allows you to set the background color to use instead of + // transparency. + // + { + $colorArray = $this->formatColor($value); + $this->fillColorArray = $colorArray; + } + + // # -------------------------------------------------------- + + public function setCropFromTop($value): void + // Sep 2011 + { + $this->cropFromTopPercent = $value; + } + + // # -------------------------------------------------------- + + public function testGDInstalled() + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Test to see if GD is installed + // Param in: n/a + // Param out: (bool) True is gd extension loaded otherwise false + // Reference: + // Notes: + // + { + if (extension_loaded('gd') && function_exists('gd_info')) { + $gdInstalled = true; + } else { + $gdInstalled = false; + } + + return $gdInstalled; + } + + // # -------------------------------------------------------- + + public function testEXIFInstalled() + // Author: Jarrod Oberto + // Date: 08-05-11 + // Purpose: Test to see if EXIF is installed + // Param in: n/a + // Param out: (bool) True is exif extension loaded otherwise false + // Reference: + // Notes: + // + { + if (extension_loaded('exif')) { + $exifInstalled = true; + } else { + $exifInstalled = false; + } + + return $exifInstalled; + } + + // # -------------------------------------------------------- + + public function testIsImage($image) + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Test if file is an image + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // + { + if ($image) { + $fileIsImage = true; + } else { + $fileIsImage = false; + } + + return $fileIsImage; + } + + // # -------------------------------------------------------- + + public function testFunct(): void + // Author: Jarrod Oberto + // Date: 27-02-08 + // Purpose: Test Function + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // + { + echo $this->height; + } + + // # -------------------------------------------------------- + + public function setForceStretch($value): void + // Author: Jarrod Oberto + // Date: 23-12-10 + // Purpose: + // Param in: (bool) $value + // Param out: n/a + // Reference: + // Notes: + // + { + $this->forceStretch = $value; + } + + // # -------------------------------------------------------- + + public function setFile($fileName): void + // Author: Jarrod Oberto + // Date: 28-02-08 + // Purpose: + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // + { + self::__construct($fileName); + } + + // # -------------------------------------------------------- + + public function getFileName() + // Author: Jarrod Oberto + // Date: 10-09-08 + // Purpose: + // Param in: n/a + // Param out: n/a + // Reference: + // Notes: + // + { + return $this->fileName; + } + + // # -------------------------------------------------------- + + public function getHeight() + { + return $this->height; + } + + // # -------------------------------------------------------- + + public function getWidth() + { + return $this->width; + } + + // # -------------------------------------------------------- + + public function getOriginalHeight() + { + return $this->heightOriginal; + } + + // # -------------------------------------------------------- + + public function getOriginalWidth() + { + return $this->widthOriginal; + } + + // # -------------------------------------------------------- + + public function getErrors() + // Author: Jarrod Oberto + // Date: 19-11-09 + // Purpose: Returns the error array + // Param in: n/a + // Param out: Array of errors + // Reference: + // Notes: + // + { + return $this->errorArray; + } + + // # -------------------------------------------------------- + + private function checkInterlaceImage($isEnabled): void + // jpg will use progressive (they don't use interace) + { + if ($isEnabled) { + imageinterlace($this->imageResized, $isEnabled); + } + } + + // # -------------------------------------------------------- + + protected function formatColor($value) + // Author: Jarrod Oberto + // Date: 09-05-11 + // Purpose: Determine color method passed in and return color as RGB + // Param in: (mixed) $value: (array) Could be an array of RGB + // (str) Could be hex #ffffff or #fff, fff, ffffff + // Param out: + // Reference: + // Notes: + // + { + $rgbArray = []; + + // *** If it's an array it should be R, G, B + if (is_array($value)) { + if (key($value) == 0 && count($value) == 3) { + $rgbArray['r'] = $value[0]; + $rgbArray['g'] = $value[1]; + $rgbArray['b'] = $value[2]; + } else { + $rgbArray = $value; + } + } else { + if (fix_strtolower($value) == 'transparent') { + $rgbArray = [ + 'r' => 255, + 'g' => 255, + 'b' => 255, + 'a' => 127, + ]; + } else { + // *** ...Else it should be hex. Let's make it RGB + $rgbArray = $this->hex2dec($value); + } + } + + return $rgbArray; + } + + // # -------------------------------------------------------- + + public function hex2dec($hex) + // Purpose: Convert #hex color to RGB + { + $color = str_replace('#', '', $hex); + + if (strlen($color) == 3) { + $color = $color.$color; + } + + $rgb = [ + 'r' => hexdec(substr($color, 0, 2)), + 'g' => hexdec(substr($color, 2, 2)), + 'b' => hexdec(substr($color, 4, 2)), + 'a' => 0, + ]; + + return $rgb; + } + + // # -------------------------------------------------------- + + private function createImageColor($colorArray) + { + $r = $colorArray['r']; + $g = $colorArray['g']; + $b = $colorArray['b']; + + return imagecolorallocate($this->imageResized, $r, $g, $b); + } + + // # -------------------------------------------------------- + + private function testColorExists($colorArray) + { + $r = $colorArray['r']; + $g = $colorArray['g']; + $b = $colorArray['b']; + + if (imagecolorexact($this->imageResized, $r, $g, $b) == -1) { + return false; + } + + return true; + } + + // # -------------------------------------------------------- + + private function findUnusedGreen() + // Purpose: We find a green color suitable to use like green-screen effect. + // Therefore, the color must not exist in the image. + { + $green = 255; + + do { + $greenChroma = [0, $green, 0]; + $colorArray = $this->formatColor($greenChroma); + $match = $this->testColorExists($colorArray); + --$green; + } while ($match == false && $green > 0); + + // *** If no match, just bite the bullet and use green value of 255 + if (!$match) { + $greenChroma = [0, $green, 0]; + } + + return $greenChroma; + } + + // # -------------------------------------------------------- + + private function findUnusedBlue() + // Purpose: We find a green color suitable to use like green-screen effect. + // Therefore, the color must not exist in the image. + { + $blue = 255; + + do { + $blueChroma = [0, 0, $blue]; + $colorArray = $this->formatColor($blueChroma); + $match = $this->testColorExists($colorArray); + --$blue; + } while ($match == false && $blue > 0); + + // *** If no match, just bite the bullet and use blue value of 255 + if (!$match) { + $blueChroma = [0, 0, $blue]; + } + + return $blueChroma; + } + + // # -------------------------------------------------------- + + private function invertTransparency($value, $originalMax, $invert = true) + // Purpose: This does two things: + // 1) Convert the range from 0-127 to 0-100 + // 2) Inverts value to 100 is not transparent while 0 is fully + // transparent (like Photoshop) + { + // *** Test max range + if ($value > $originalMax) { + $value = $originalMax; + } + + // *** Test min range + if ($value < 0) { + $value = 0; + } + + if ($invert) { + return $originalMax - (($value / 100) * $originalMax); + } + + return ($value / 100) * $originalMax; + } + + // # -------------------------------------------------------- + + private function transparentImage($src) + { + // *** making images with white bg transparent + $r1 = 0; + $g1 = 255; + $b1 = 0; + for ($x = 0; $x < imagesx($src); ++$x) { + for ($y = 0; $y < imagesy($src); ++$y) { + $color = imagecolorat($src, $x, $y); + $r = ($color >> 16) & 0xFF; + $g = ($color >> 8) & 0xFF; + $b = $color & 0xFF; + for ($i = 0; $i < 270; ++$i) { + // if ($r . $g . $b == ($r1 + $i) . ($g1 + $i) . ($b1 + $i)) { + if ($r == 0 && $g == 255 && $b == 0) { + // if ($g == 255) { + $trans_colour = imagecolorallocatealpha($src, 0, 0, 0, 127); + imagefill($src, $x, $y, $trans_colour); + } + } + } + } + + return $src; + } + + // # -------------------------------------------------------- + + public function checkStringStartsWith($needle, $haystack) + // Check if a string starts with a specific pattern + { + return substr($haystack, 0, strlen($needle)) == $needle; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + BMP SUPPORT (SAVING) - James Heinrich +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function GD2BMPstring(&$gd_image) + // Author: James Heinrich + // Purpose: Save file as type bmp + // Param in: The image canvas (passed as ref) + // Param out: + // Reference: + // Notes: This code was stripped out of two external files + // (phpthumb.bmp.php,phpthumb.functions.php) and added below to + // avoid dependancies. + // + { + $imageX = imagesx($gd_image); + $imageY = imagesy($gd_image); + + $BMP = ''; + for ($y = ($imageY - 1); $y >= 0; --$y) { + $thisline = ''; + for ($x = 0; $x < $imageX; ++$x) { + $argb = $this->GetPixelColor($gd_image, $x, $y); + $thisline .= chr($argb['blue']).chr($argb['green']).chr($argb['red']); + } + while (strlen($thisline) % 4) { + $thisline .= "\x00"; + } + $BMP .= $thisline; + } + + $bmpSize = strlen($BMP) + 14 + 40; + // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp + $BITMAPFILEHEADER = 'BM'; // WORD bfType; + $BITMAPFILEHEADER .= $this->LittleEndian2String($bmpSize, 4); // DWORD bfSize; + $BITMAPFILEHEADER .= $this->LittleEndian2String(0, 2); // WORD bfReserved1; + $BITMAPFILEHEADER .= $this->LittleEndian2String(0, 2); // WORD bfReserved2; + $BITMAPFILEHEADER .= $this->LittleEndian2String(54, 4); // DWORD bfOffBits; + + // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp + $BITMAPINFOHEADER = $this->LittleEndian2String(40, 4); // DWORD biSize; + $BITMAPINFOHEADER .= $this->LittleEndian2String($imageX, 4); // LONG biWidth; + $BITMAPINFOHEADER .= $this->LittleEndian2String($imageY, 4); // LONG biHeight; + $BITMAPINFOHEADER .= $this->LittleEndian2String(1, 2); // WORD biPlanes; + $BITMAPINFOHEADER .= $this->LittleEndian2String(24, 2); // WORD biBitCount; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biCompression; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biSizeImage; + $BITMAPINFOHEADER .= $this->LittleEndian2String(2835, 4); // LONG biXPelsPerMeter; + $BITMAPINFOHEADER .= $this->LittleEndian2String(2835, 4); // LONG biYPelsPerMeter; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biClrUsed; + $BITMAPINFOHEADER .= $this->LittleEndian2String(0, 4); // DWORD biClrImportant; + + return $BITMAPFILEHEADER.$BITMAPINFOHEADER.$BMP; + } + + // # -------------------------------------------------------- + + private function GetPixelColor(&$img, $x, $y) + // Author: James Heinrich + // Purpose: + // Param in: + // Param out: + // Reference: + // Notes: + // + { + if (!is_resource($img)) { + return false; + } + + return @imagecolorsforindex($img, @imagecolorat($img, $x, $y)); + } + + // # -------------------------------------------------------- + + private function LittleEndian2String($number, $minbytes = 1) + // Author: James Heinrich + // Purpose: BMP SUPPORT (SAVING) + // Param in: + // Param out: + // Reference: + // Notes: + // + { + $intstring = ''; + while ($number > 0) { + $intstring = $intstring.chr($number & 255); + $number >>= 8; + } + + return str_pad($intstring, $minbytes, "\x00", \STR_PAD_RIGHT); + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + BMP SUPPORT (READING) +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function ImageCreateFromBMP($filename) + // Author: DHKold + // Date: The 15th of June 2005 + // Version: 2.0B + // Purpose: To create an image from a BMP file. + // Param in: BMP file to open. + // Param out: Return a resource like the other ImageCreateFrom functions + // Reference: http://us3.php.net/manual/en/function.imagecreate.php#53879 + // Bug fix: Author: domelca at terra dot es + // Date: 06 March 2008 + // Fix: Correct 16bit BMP support + // Notes: + // + { + // Ouverture du fichier en mode binaire + if (!$f1 = fopen($filename, 'r')) { + return false; + } + + // 1 : Chargement des ent�tes FICHIER + $FILE = unpack('vfile_type/Vfile_size/Vreserved/Vbitmap_offset', fread($f1, 14)); + if ($FILE['file_type'] != 19778) { + return false; + } + + // 2 : Chargement des ent�tes BMP + $BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'. + '/Vcompression/Vsize_bitmap/Vhoriz_resolution'. + '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1, 40)); + $BMP['colors'] = 2 ** $BMP['bits_per_pixel']; + + if ($BMP['size_bitmap'] == 0) { + $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset']; + } + + $BMP['bytes_per_pixel'] = $BMP['bits_per_pixel'] / 8; + $BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']); + $BMP['decal'] = ($BMP['width'] * $BMP['bytes_per_pixel'] / 4); + $BMP['decal'] -= floor($BMP['width'] * $BMP['bytes_per_pixel'] / 4); + $BMP['decal'] = 4 - (4 * $BMP['decal']); + + if ($BMP['decal'] == 4) { + $BMP['decal'] = 0; + } + + // 3 : Chargement des couleurs de la palette + $PALETTE = []; + if ($BMP['colors'] < 16777216) { + $PALETTE = unpack('V'.$BMP['colors'], fread($f1, $BMP['colors'] * 4)); + } + + // 4 : Cr�ation de l'image + $IMG = fread($f1, $BMP['size_bitmap']); + $VIDE = chr(0); + + $res = imagecreatetruecolor($BMP['width'], $BMP['height']); + $P = 0; + $Y = $BMP['height'] - 1; + while ($Y >= 0) { + $X = 0; + while ($X < $BMP['width']) { + if ($BMP['bits_per_pixel'] == 24) { + $COLOR = unpack('V', substr($IMG, $P, 3).$VIDE); + } elseif ($BMP['bits_per_pixel'] == 16) { + /* + * BMP 16bit fix + * ================= + * + * Ref: http://us3.php.net/manual/en/function.imagecreate.php#81604 + * + * Notes: + * "don't work with bmp 16 bits_per_pixel. change pixel + * generator for this." + * + */ + + // *** Original code (don't work) + // $COLOR = unpack("n",substr($IMG,$P,2)); + // $COLOR[1] = $PALETTE[$COLOR[1]+1]; + + $COLOR = unpack('v', substr($IMG, $P, 2)); + $blue = ($COLOR[1] & 0x001F) << 3; + $green = ($COLOR[1] & 0x07E0) >> 3; + $red = ($COLOR[1] & 0xF800) >> 8; + $COLOR[1] = $red * 65536 + $green * 256 + $blue; + } elseif ($BMP['bits_per_pixel'] == 8) { + $COLOR = unpack('n', $VIDE.substr($IMG, $P, 1)); + $COLOR[1] = $PALETTE[$COLOR[1] + 1]; + } elseif ($BMP['bits_per_pixel'] == 4) { + $COLOR = unpack('n', $VIDE.substr($IMG, floor($P), 1)); + if (($P * 2) % 2 == 0) { + $COLOR[1] = ($COLOR[1] >> 4); + } else { + $COLOR[1] = ($COLOR[1] & 0x0F); + } + $COLOR[1] = $PALETTE[$COLOR[1] + 1]; + } elseif ($BMP['bits_per_pixel'] == 1) { + $COLOR = unpack('n', $VIDE.substr($IMG, floor($P), 1)); + if (($P * 8) % 8 == 0) { + $COLOR[1] = $COLOR[1] >> 7; + } elseif (($P * 8) % 8 == 1) { + $COLOR[1] = ($COLOR[1] & 0x40) >> 6; + } elseif (($P * 8) % 8 == 2) { + $COLOR[1] = ($COLOR[1] & 0x20) >> 5; + } elseif (($P * 8) % 8 == 3) { + $COLOR[1] = ($COLOR[1] & 0x10) >> 4; + } elseif (($P * 8) % 8 == 4) { + $COLOR[1] = ($COLOR[1] & 0x8) >> 3; + } elseif (($P * 8) % 8 == 5) { + $COLOR[1] = ($COLOR[1] & 0x4) >> 2; + } elseif (($P * 8) % 8 == 6) { + $COLOR[1] = ($COLOR[1] & 0x2) >> 1; + } elseif (($P * 8) % 8 == 7) { + $COLOR[1] = ($COLOR[1] & 0x1); + } + $COLOR[1] = $PALETTE[$COLOR[1] + 1]; + } else { + return false; + } + + imagesetpixel($res, $X, $Y, $COLOR[1]); + ++$X; + $P += $BMP['bytes_per_pixel']; + } + + --$Y; + $P += $BMP['decal']; + } + // Fermeture du fichier + fclose($f1); + + return $res; + } + + /*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*- + PSD SUPPORT (READING) +*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-**-*-*-*-*-*-*-*-*-*-*-*-*-*/ + + private function imagecreatefrompsd($fileName) + // Author: Tim de Koning + // Version: 1.3 + // Purpose: To create an image from a PSD file. + // Param in: PSD file to open. + // Param out: Return a resource like the other ImageCreateFrom functions + // Reference: http://www.kingsquare.nl/phppsdreader + // Notes: + // + { + if (file_exists($this->psdReaderPath)) { + include_once $this->psdReaderPath; + + $psdReader = new PhpPsdReader($fileName); + + if (isset($psdReader->infoArray['error'])) { + return ''; + } + + return $psdReader->getImage(); + } + + return false; + } + + // # -------------------------------------------------------- + + public function __destruct() + { + if (is_resource($this->imageResized)) { + imagedestroy($this->imageResized); + } + } + + // # -------------------------------------------------------- +} + +/* + * Example with some API calls (outdated): + * + * + * =============================== + * Compulsary + * =============================== + * + * include("classes/resize_class.php"); + * + * // *** Initialise object + * $magicianObj = new resize('images/cars/large/a.jpg'); + * + * // *** Turn off stretching (optional) + * $magicianObj -> setForceStretch(false); + * + * // *** Resize object + * $magicianObj -> resizeImage(150, 100, 0); + * + * =============================== + * Image options - can run none, one, or all. + * =============================== + * + * // *** Add watermark + * $magicianObj -> addWatermark('stamp.png'); + * + * // *** Add text + * $magicianObj -> addText('testing...'); + * + * =============================== + * Output options - can run one, or the other, or both. + * =============================== + * + * // *** Save image to disk + * $magicianObj -> saveImage('images/cars/large/b.jpg', 100); + * + * // *** Or output to screen (params in can be jpg, gif, png) + * $magicianObj -> displayImage('png'); + * + * =============================== + * Return options - return errors. nice for debuggin. + * =============================== + * + * // *** Return error array + * $errorArray = $magicianObj -> getErrors(); + * + * + * =============================== + * Cleanup options - not really neccessary, but good practice + * =============================== + * + * // *** Free used memory + * $magicianObj -> __destruct(); + */ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/utils.php b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/utils.php new file mode 100755 index 0000000..7fbb4f2 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/include/utils.php @@ -0,0 +1,1038 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if ($_SESSION['RF']['verify'] != 'RESPONSIVEfilemanager') { + exit('forbiden'); +} +require __DIR__.'/Response.php'; + +if (!function_exists('response')) { + /** + * Response construction helper. + * + * @param string $content + * @param int $statusCode + * @param array $headers + * + * @return \Response|\Illuminate\Http\Response + */ + function response($content = '', $statusCode = 200, $headers = []) + { + $responseClass = class_exists('Illuminate\Http\Response') ? '\Illuminate\Http\Response' : 'Response'; + + return new $responseClass($content, $statusCode, $headers); + } +} + +if (!function_exists('trans')) { + // language + if (!isset($_SESSION['RF']['language']) + || file_exists('lang/'.basename($_SESSION['RF']['language']).'.php') === false + || !is_readable('lang/'.basename($_SESSION['RF']['language']).'.php') + ) { + $lang = $default_language; + + if (isset($_GET['lang']) && $_GET['lang'] != 'undefined' && $_GET['lang'] != '') { + $lang = fix_get_params($_GET['lang']); + $lang = trim($lang); + } + + if ($lang != $default_language) { + $path_parts = pathinfo($lang); + $lang = $path_parts['basename']; + $languages = include 'lang/languages.php'; + } + + // add lang file to session for easy include + $_SESSION['RF']['language'] = $lang; + } else { + if (file_exists('lang/languages.php')) { + $languages = include 'lang/languages.php'; + } else { + $languages = include '../lang/languages.php'; + } + + if (array_key_exists($_SESSION['RF']['language'], $languages)) { + $lang = $_SESSION['RF']['language']; + } else { + response(trans('Lang_Not_Found').AddErrorLocation())->send(); + exit; + } + } + if (file_exists('lang/'.$lang.'.php')) { + $lang_vars = include 'lang/'.$lang.'.php'; + } else { + $lang_vars = include '../lang/'.$lang.'.php'; + } + + if (!is_array($lang_vars)) { + $lang_vars = []; + } + /** + * Translate language variable. + * + * @param $var string name + * + * @return string translated variable + */ + function trans($var) + { + global $lang_vars; + + return (array_key_exists($var, $lang_vars)) ? $lang_vars[$var] : $var; + } +} + +/** + * Delete directory. + * + * @param string $dir + * + * @return bool + */ +function deleteDir($dir, $ftp = null, $config = null) +{ + if ($ftp) { + try { + $ftp->rmdir($dir); + + return true; + } catch (FtpClient\FtpException $e) { + return null; + } + } else { + if (!file_exists($dir)) { + return false; + } + if (!is_dir($dir)) { + return unlink($dir); + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if (!deleteDir($dir.\DIRECTORY_SEPARATOR.$item)) { + return false; + } + } + } + + return rmdir($dir); +} + +/** + * Make a file copy. + * + * @param string $old_path + * @param string $name New file name without extension + * + * @return bool + */ +function duplicate_file($old_path, $name, $ftp = null, $config = null) +{ + $info = pathinfo($old_path); + $new_path = $info['dirname'].'/'.$name.'.'.$info['extension']; + if ($ftp) { + try { + $tmp = time().$name.'.'.$info['extension']; + $ftp->get($tmp, '/'.$old_path, \FTP_BINARY); + $ftp->put('/'.$new_path, $tmp, \FTP_BINARY); + unlink($tmp); + + return true; + } catch (FtpClient\FtpException $e) { + return null; + } + } else { + if (file_exists($old_path)) { + if (file_exists($new_path) && $old_path == $new_path) { + return false; + } + + return copy($old_path, $new_path); + } + } +} + +/** + * Rename file. + * + * @param string $old_path File to rename + * @param string $name New file name without extension + * + * @return bool + */ +function rename_file($old_path, $name, $ftp = null, $config = null) +{ + $name = fix_filename($name, $config); + $info = pathinfo($old_path); + $new_path = $info['dirname'].'/'.$name.'.'.$info['extension']; + if ($ftp) { + try { + return $ftp->rename('/'.$old_path, '/'.$new_path); + } catch (FtpClient\FtpException $e) { + return false; + } + } else { + if (file_exists($old_path)) { + $new_path = $info['dirname'].'/'.$name.'.'.$info['extension']; + if (file_exists($new_path) && $old_path == $new_path) { + return false; + } + + return rename($old_path, $new_path); + } + } +} + +function url_exists($url) +{ + if (!$fp = curl_init($url)) { + return false; + } + + return true; +} + +function tempdir() +{ + $tempfile = tempnam(sys_get_temp_dir(), ''); + if (file_exists($tempfile)) { + unlink($tempfile); + } + mkdir($tempfile); + if (is_dir($tempfile)) { + return $tempfile; + } +} + +/** + * Rename directory. + * + * @param string $old_path Directory to rename + * @param string $name New directory name + * + * @return bool + */ +function rename_folder($old_path, $name, $ftp = null, $config = null) +{ + $name = fix_filename($name, $config, true); + $new_path = fix_dirname($old_path).'/'.$name; + if ($ftp) { + if ($ftp->chdir('/'.$old_path)) { + if (@$ftp->chdir($new_path)) { + return false; + } + + return $ftp->rename('/'.$old_path, '/'.$new_path); + } + } else { + if (file_exists($old_path)) { + if (file_exists($new_path) && $old_path == $new_path) { + return false; + } + + return rename($old_path, $new_path); + } + } +} + +function ftp_con($config) +{ + if (isset($config['ftp_host']) && $config['ftp_host']) { + // *** Include the class + include 'include/FtpClient.php'; + include 'include/FtpException.php'; + include 'include/FtpWrapper.php'; + + $ftp = new \FtpClient\FtpClient(); + try { + $ftp->connect($config['ftp_host'], $config['ftp_ssl'], $config['ftp_port']); + $ftp->login($config['ftp_user'], $config['ftp_pass']); + $ftp->pasv(true); + + return $ftp; + } catch (FtpClient\FtpException $e) { + echo 'Error: '; + echo $e->getMessage(); + echo ' to server '; + $tmp = $e->getTrace(); + echo $tmp[0]['args'][0]; + echo '
Please check configurations'; + exit; + } + } else { + return false; + } +} + +/** + * Create new image from existing file. + * + * @param string $imgfile Source image file name + * @param string $imgthumb Thumbnail file name + * @param int $newwidth Thumbnail width + * @param int $newheight Optional thumbnail height + * @param string $option Type of resize + * + * @return bool + * + * @throws \Exception + */ +function create_img($imgfile, $imgthumb, $newwidth, $newheight = null, $option = 'crop', $ftp = false, $config = []) +{ + $result = false; + if ($ftp) { + if (url_exists($imgfile)) { + $temp = tempnam('/tmp', 'RF'); + unlink($temp); + $temp .= '.'.substr(strrchr($imgfile, '.'), 1); + $handle = fopen($temp, 'w'); + fwrite($handle, file_get_contents($imgfile)); + fclose($handle); + $imgfile = $temp; + $save_ftp = $imgthumb; + $imgthumb = $temp; + } + } + if (file_exists($imgfile) || strpos($imgfile, 'http') === 0) { + if (strpos($imgfile, 'http') === 0 || image_check_memory_usage($imgfile, $newwidth, $newheight)) { + require_once 'php_image_magician.php'; + $magicianObj = new php_image_magician($imgfile); + $magicianObj->resizeImage($newwidth, $newheight, $option); + $magicianObj->saveImage($imgthumb, 80); + $result = true; + } + } + if ($result && $ftp) { + $ftp->put($save_ftp, $imgthumb, \FTP_BINARY); + unlink($imgthumb); + } + + return $result; +} + +/** + * Convert convert size in bytes to human readable. + * + * @param int $size + * + * @return string + */ +function makeSize($size) +{ + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $u = 0; + while ((round($size / 1024) > 0) && ($u < 4)) { + $size = $size / 1024; + ++$u; + } + + return number_format($size, 0).' '.trans($units[$u]); +} + +/** + * Determine directory size. + * + * @param string $path + * + * @return int + */ +function folder_info($path, $count_hidden = true) +{ + global $hidden_folders,$hidden_files; + $total_size = 0; + $files = scandir($path); + $cleanPath = rtrim($path, '/').'/'; + $files_count = 0; + $folders_count = 0; + foreach ($files as $t) { + if ($t != '.' && $t != '..') { + if ($count_hidden || !(in_array($t, $hidden_folders) || in_array($t, $hidden_files))) { + $currentFile = $cleanPath.$t; + if (is_dir($currentFile)) { + [$size,$tmp,$tmp1] = folder_info($currentFile); + $total_size += $size; + ++$folders_count; + } else { + $size = filesize($currentFile); + $total_size += $size; + ++$files_count; + } + } + } + } + + return [$total_size, $files_count, $folders_count]; +} +/** + * Get number of files in a directory. + * + * @param string $path + * + * @return int + */ +function filescount($path, $count_hidden = true) +{ + global $hidden_folders,$hidden_files; + $total_count = 0; + $files = scandir($path); + $cleanPath = rtrim($path, '/').'/'; + + foreach ($files as $t) { + if ($t != '.' && $t != '..') { + if ($count_hidden || !(in_array($t, $hidden_folders) || in_array($t, $hidden_files))) { + $currentFile = $cleanPath.$t; + if (is_dir($currentFile)) { + $size = filescount($currentFile); + $total_count += $size; + } else { + ++$total_count; + } + } + } + } + + return $total_count; +} +/** + * check if the current folder size plus the added size is over the overall size limite. + * + * @param int $sizeAdded + * + * @return bool + */ +function checkresultingsize($sizeAdded) +{ + global $MaxSizeTotal,$current_path; + if ($MaxSizeTotal !== false && is_int($MaxSizeTotal)) { + [$sizeCurrentFolder,$fileCurrentNum,$foldersCurrentCount] = folder_info($current_path, false); + // overall size over limit + if (($MaxSizeTotal * 1024 * 1024) < ($sizeCurrentFolder + $sizeAdded)) { + return false; + } + } + + return true; +} + +/** + * Create directory for images and/or thumbnails. + * + * @param string $path + * @param string $path_thumbs + */ +function create_folder($path = null, $path_thumbs = null, $ftp = null, $config = null): void +{ + if ($ftp) { + $ftp->mkdir($path); + $ftp->mkdir($path_thumbs); + } else { + $oldumask = umask(0); + if ($path && !file_exists($path)) { + mkdir($path, 0755, true); + } // or even 01777 so you get the sticky bit set + if ($path_thumbs && !file_exists($path_thumbs)) { + mkdir($path_thumbs, 0755, true) || exit("$path_thumbs cannot be found"); + } // or even 01777 so you get the sticky bit set + umask($oldumask); + } +} + +/** + * Get file extension present in directory. + * + * @param string $path + * @param string $ext + */ +function check_files_extensions_on_path($path, $ext): void +{ + if (!is_dir($path)) { + $fileinfo = pathinfo($path); + if (!in_array(mb_strtolower($fileinfo['extension']), $ext)) { + unlink($path); + } + } else { + $files = scandir($path); + foreach ($files as $file) { + check_files_extensions_on_path(trim($path, '/').'/'.$file, $ext); + } + } +} + +/** + * Get file extension present in PHAR file. + * + * @param string $phar + * @param array $files + * @param string $basepath + * @param string $ext + */ +function check_files_extensions_on_phar($phar, &$files, $basepath, $ext): void +{ + foreach ($phar as $file) { + if ($file->isFile()) { + if (in_array(mb_strtolower($file->getExtension()), $ext)) { + $files[] = $basepath.$file->getFileName(); + } + } else { + if ($file->isDir()) { + $iterator = new DirectoryIterator($file); + check_files_extensions_on_phar($iterator, $files, $basepath.$file->getFileName().'/', $ext); + } + } + } +} + +/** + * Cleanup input. + * + * @param string $str + * + * @return string + */ +function fix_get_params($str) +{ + return strip_tags(preg_replace("/[^a-zA-Z0-9\.\[\]_| -]/", '', $str)); +} + +/** + * Cleanup filename. + * + * @param string $str + * @param bool $is_folder + * + * @return string + */ +function fix_filename($str, $config, $is_folder = false) +{ + if ($config['convert_spaces']) { + $str = str_replace(' ', $config['replace_with'], $str); + } + + if ($config['transliteration']) { + if (!mb_detect_encoding($str, 'UTF-8', true)) { + $str = utf8_encode($str); + } + if (function_exists('transliterator_transliterate')) { + $str = transliterator_transliterate('Any-Latin; Latin-ASCII', $str); + } else { + $str = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str); + } + + $str = preg_replace("/[^a-zA-Z0-9\.\[\]_| -]/", '', $str); + } + + $str = str_replace(['"', "'", '/', '\\'], '', $str); + $str = strip_tags($str); + + // Empty or incorrectly transliterated filename. + // Here is a point: a good file UNKNOWN_LANGUAGE.jpg could become .jpg in previous code. + // So we add that default 'file' name to fix that issue. + if (strpos($str, '.') === 0 && $is_folder === false) { + $str = 'file'.$str; + } + + return trim($str); +} + +/** + * Cleanup directory name. + * + * @param string $str + * + * @return string + */ +function fix_dirname($str) +{ + return str_replace('~', ' ', dirname(str_replace(' ', '~', $str))); +} + +/** + * Correct strtoupper handling. + * + * @param string $str + * + * @return string + */ +function fix_strtoupper($str) +{ + if (function_exists('mb_strtoupper')) { + return mb_strtoupper($str); + } + + return strtoupper($str); +} + +/** + * Correct strtolower handling. + * + * @param string $str + * + * @return string + */ +function fix_strtolower($str) +{ + if (function_exists('mb_strtoupper')) { + return mb_strtolower($str); + } + + return strtolower($str); +} + +function fix_path($path, $config) +{ + $info = pathinfo($path); + $tmp_path = $info['dirname']; + $str = fix_filename($info['filename'], $config); + if ($tmp_path != '') { + return $tmp_path.\DIRECTORY_SEPARATOR.$str; + } + + return $str; +} + +/** + * @param $current_path + * @param $fld + * + * @return bool + */ +function config_loading($current_path, $fld) +{ + if (file_exists($current_path.$fld.'.config')) { + require_once $current_path.$fld.'.config'; + + return true; + } + echo '!!!!'.$parent = fix_dirname($fld); + if ($parent != '.' && !empty($parent)) { + config_loading($current_path, $parent); + } + + return false; +} + +/** + * Check if memory is enough to process image. + * + * @param string $img + * @param int $max_breedte + * @param int $max_hoogte + * + * @return bool + */ +function image_check_memory_usage($img, $max_breedte, $max_hoogte) +{ + if (file_exists($img)) { + $K64 = 65536; // number of bytes in 64K + $memory_usage = memory_get_usage(); + $memory_limit = abs((int) (str_replace('M', '', ini_get('memory_limit')) * 1024 * 1024)); + $image_properties = getimagesize($img); + $image_width = $image_properties[0] ?? 0; + $image_height = $image_properties[1] ?? 0; + if (isset($image_properties['bits'])) { + $image_bits = $image_properties['bits']; + } else { + $image_bits = 0; + } + $image_memory_usage = $K64 + ($image_width * $image_height * $image_bits * 2); + $thumb_memory_usage = $K64 + ($max_breedte * $max_hoogte * $image_bits * 2); + $memory_needed = abs((int) ($memory_usage + $image_memory_usage + $thumb_memory_usage)); + + if ($memory_needed > $memory_limit) { + return false; + } + + return true; + } + + return false; +} + +/** + * Check is string is ended with needle. + * + * @param string $haystack + * @param string $needle + * + * @return bool + */ +function endsWith($haystack, $needle) +{ + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; +} + +/** + * TODO REFACTOR THIS! + * + * @param $targetPath + * @param $targetFile + * @param $name + * @param $current_path + * @param $relative_image_creation + * @param $relative_path_from_current_pos + * @param $relative_image_creation_name_to_prepend + * @param $relative_image_creation_name_to_append + * @param $relative_image_creation_width + * @param $relative_image_creation_height + * @param $relative_image_creation_option + * @param $fixed_image_creation + * @param $fixed_path_from_filemanager + * @param $fixed_image_creation_name_to_prepend + * @param $fixed_image_creation_to_append + * @param $fixed_image_creation_width + * @param $fixed_image_creation_height + * @param $fixed_image_creation_option + * + * @return bool + */ +function new_thumbnails_creation($targetPath, $targetFile, $name, $current_path, $relative_image_creation, $relative_path_from_current_pos, $relative_image_creation_name_to_prepend, $relative_image_creation_name_to_append, $relative_image_creation_width, $relative_image_creation_height, $relative_image_creation_option, $fixed_image_creation, $fixed_path_from_filemanager, $fixed_image_creation_name_to_prepend, $fixed_image_creation_to_append, $fixed_image_creation_width, $fixed_image_creation_height, $fixed_image_creation_option) +{ + // create relative thumbs + $all_ok = true; + if ($relative_image_creation) { + foreach ($relative_path_from_current_pos as $k => $path) { + if ($path != '' && $path[strlen($path) - 1] != '/') { + $path .= '/'; + } + if (!file_exists($targetPath.$path)) { + create_folder($targetPath.$path, false); + } + $info = pathinfo($name); + if (!endsWith($targetPath, $path)) { + if (!create_img($targetFile, $targetPath.$path.$relative_image_creation_name_to_prepend[$k].$info['filename'].$relative_image_creation_name_to_append[$k].'.'.$info['extension'], $relative_image_creation_width[$k], $relative_image_creation_height[$k], $relative_image_creation_option[$k])) { + $all_ok = false; + } + } + } + } + + // create fixed thumbs + if ($fixed_image_creation) { + foreach ($fixed_path_from_filemanager as $k => $path) { + if ($path != '' && $path[strlen($path) - 1] != '/') { + $path .= '/'; + } + $base_dir = $path.substr_replace($targetPath, '', 0, strlen($current_path)); + if (!file_exists($base_dir)) { + create_folder($base_dir, false); + } + $info = pathinfo($name); + if (!create_img($targetFile, $base_dir.$fixed_image_creation_name_to_prepend[$k].$info['filename'].$fixed_image_creation_to_append[$k].'.'.$info['extension'], $fixed_image_creation_width[$k], $fixed_image_creation_height[$k], $fixed_image_creation_option[$k])) { + $all_ok = false; + } + } + } + + return $all_ok; +} + +/** + * Get a remote file, using whichever mechanism is enabled. + * + * @param string $url + * + * @return bool|mixed|string + */ +function get_file_by_url($url) +{ + if (ini_get('allow_url_fopen')) { + $arrContextOptions = [ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + ], + ]; + + return file_get_contents($url, false, stream_context_create($arrContextOptions)); + } + if (!function_exists('curl_version')) { + return false; + } + + $ch = curl_init(); + + curl_setopt($ch, \CURLOPT_HEADER, 0); + curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, \CURLOPT_URL, $url); + + $data = curl_exec($ch); + curl_close($ch); + + return $data; +} + +/** + * test for dir/file writability properly. + * + * @param string $dir + * + * @return bool + */ +function is_really_writable($dir) +{ + $dir = rtrim($dir, '/'); + // linux, safe off + if (\DIRECTORY_SEPARATOR == '/' && @ini_get('safe_mode') == false) { + return is_writable($dir); + } + + // Windows, safe ON. (have to write a file :S) + if (is_dir($dir)) { + $dir = $dir.'/'.md5(random_int(1, 1000).random_int(1, 1000)); + + if (($fp = @fopen($dir, 'a')) === false) { + return false; + } + + fclose($fp); + @chmod($dir, 0755); + @unlink($dir); + + return true; + } + if (!is_file($dir) || ($fp = @fopen($dir, 'a')) === false) { + return false; + } + + fclose($fp); + + return true; +} + +/** + * Check if a function is callable. + * Some servers disable copy,rename etc. + * + * @parm string $name + * + * @return bool + */ +function is_function_callable($name) +{ + if (function_exists($name) === false) { + return false; + } + $disabled = explode(',', ini_get('disable_functions')); + + return !in_array($name, $disabled); +} + +/** + * recursivly copies everything. + * + * @param string $source + * @param string $destination + * @param bool $is_rec + */ +function rcopy($source, $destination, $is_rec = false): void +{ + if (is_dir($source)) { + if ($is_rec === false) { + $pinfo = pathinfo($source); + $destination = rtrim($destination, '/').\DIRECTORY_SEPARATOR.$pinfo['basename']; + } + if (is_dir($destination) === false) { + mkdir($destination, 0755, true); + } + + $files = scandir($source); + foreach ($files as $file) { + if ($file != '.' && $file != '..') { + rcopy($source.\DIRECTORY_SEPARATOR.$file, rtrim($destination, '/').\DIRECTORY_SEPARATOR.$file, true); + } + } + } else { + if (file_exists($source)) { + if (is_dir($destination) === true) { + $pinfo = pathinfo($source); + $dest2 = rtrim($destination, '/').\DIRECTORY_SEPARATOR.$pinfo['basename']; + } else { + $dest2 = $destination; + } + + copy($source, $dest2); + } + } +} + +/** + * recursivly renames everything. + * + * I know copy and rename could be done with just one function + * but i split the 2 because sometimes rename fails on windows + * Need more feedback from users and refactor if needed + * + * @param string $source + * @param string $destination + * @param bool $is_rec + */ +function rrename($source, $destination, $is_rec = false): void +{ + if (is_dir($source)) { + if ($is_rec === false) { + $pinfo = pathinfo($source); + $destination = rtrim($destination, '/').\DIRECTORY_SEPARATOR.$pinfo['basename']; + } + if (is_dir($destination) === false) { + mkdir($destination, 0755, true); + } + + $files = scandir($source); + foreach ($files as $file) { + if ($file != '.' && $file != '..') { + rrename($source.\DIRECTORY_SEPARATOR.$file, rtrim($destination, '/').\DIRECTORY_SEPARATOR.$file, true); + } + } + } else { + if (file_exists($source)) { + if (is_dir($destination) === true) { + $pinfo = pathinfo($source); + $dest2 = rtrim($destination, '/').\DIRECTORY_SEPARATOR.$pinfo['basename']; + } else { + $dest2 = $destination; + } + + rename($source, $dest2); + } + } +} + +// On windows rename leaves folders sometime +// This will clear leftover folders +// After more feedback will merge it with rrename +function rrename_after_cleaner($source) +{ + $files = scandir($source); + + foreach ($files as $file) { + if ($file != '.' && $file != '..') { + if (is_dir($source.\DIRECTORY_SEPARATOR.$file)) { + rrename_after_cleaner($source.\DIRECTORY_SEPARATOR.$file); + } else { + unlink($source.\DIRECTORY_SEPARATOR.$file); + } + } + } + + return rmdir($source); +} + +/** + * Recursive chmod. + * + * @param string $source + * @param int $mode + * @param string $rec_option + * @param bool $is_rec + */ +function rchmod($source, $mode, $rec_option = 'none', $is_rec = false): void +{ + if ($rec_option == 'none') { + chmod($source, $mode); + } else { + if ($is_rec === false) { + chmod($source, $mode); + } + + $files = scandir($source); + + foreach ($files as $file) { + if ($file != '.' && $file != '..') { + if (is_dir($source.\DIRECTORY_SEPARATOR.$file)) { + if ($rec_option == 'folders' || $rec_option == 'both') { + chmod($source.\DIRECTORY_SEPARATOR.$file, $mode); + } + rchmod($source.\DIRECTORY_SEPARATOR.$file, $mode, $rec_option, true); + } else { + if ($rec_option == 'files' || $rec_option == 'both') { + chmod($source.\DIRECTORY_SEPARATOR.$file, $mode); + } + } + } + } + } +} + +/** + * @param string $input + * @param bool $trace + * @param bool $halt + */ +function debugger($input, $trace = false, $halt = false): void +{ + ob_start(); + + echo '
----- DEBUG DUMP -----'; + echo '
';
+    var_dump($input);
+    echo '
'; + + if ($trace) { + if (is_php('5.3.6')) { + $debug = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS); + } else { + $debug = debug_backtrace(false); + } + + echo '
-----STACK TRACE-----'; + echo '
';
+        var_dump($debug);
+        echo '
'; + } + + echo ''; + echo '---------------------------
'; + + $ret = ob_get_contents(); + ob_end_clean(); + + echo $ret; + + if ($halt == true) { + exit; + } +} + +/** + * @param string $version + * + * @return bool + */ +function is_php($version = '5.0.0') +{ + static $phpVer; + $version = (string) $version; + + if (!isset($phpVer[$version])) { + $phpVer[$version] = (version_compare(\PHP_VERSION, $version) < 0) ? false : true; + } + + return $phpVer[$version]; +} + +/** + * Return the caller location if set in config.php. + * + * @return bool + */ +function AddErrorLocation() +{ + if (defined('DEBUG_ERROR_MESSAGE') && DEBUG_ERROR_MESSAGE) { + $pile = debug_backtrace(); + + return ' (@'.$pile[0]['file'].'#'.$pile[0]['line'].')'; + } + + return ''; +} diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/ZeroClipboard.swf b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/ZeroClipboard.swf new file mode 100644 index 0000000..d4e2561 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/ZeroClipboard.swf differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/include.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/include.js new file mode 100644 index 0000000..989b3ab --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/filemanager/js/include.js @@ -0,0 +1,2 @@ +var encodeURL,show_animation,hide_animation,apply,apply_none,apply_img,apply_any,apply_video,apply_link,apply_file_rename,apply_file_duplicate,apply_folder_rename;!function(e,a,r){"use strict";function t(e){show_animation();var a=new Image;a.src=e,jQuery(a).on("load",function(){hide_animation()})}function n(){jQuery("#textfile_create_area").parent().parent().remove(),e.ajax({type:"GET",url:"ajax_calls.php?action=new_file_form"}).done(function(a){bootbox.dialog(a,[{label:jQuery("#cancel").val(),"class":"btn"},{label:jQuery("#ok").val(),"class":"btn-inverse",callback:function(){var a=jQuery("#create_text_file_name").val()+jQuery("#create_text_file_extension").val(),r=jQuery("#textfile_create_area").val();if(null!==a){a=_(a);var t=jQuery("#sub_folder").val()+jQuery("#fldr_value").val();e.ajax({type:"POST",url:"execute.php?action=create_file",data:{path:t,name:a,new_content:r}}).done(function(e){""!=e&&bootbox.alert(e,function(){setTimeout(function(){window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()},500)})})}}}],{header:jQuery("#lang_new_file").val()})})}function i(a){jQuery("#textfile_edit_area").parent().parent().remove();var r=a.find(".rename-file-paths").attr("data-path");e.ajax({type:"POST",url:"ajax_calls.php?action=get_file&sub_action=edit&preview_mode=text",data:{path:r}}).done(function(t){bootbox.dialog(t,[{label:jQuery("#cancel").val(),"class":"btn"},{label:jQuery("#ok").val(),"class":"btn-inverse",callback:function(){var a=jQuery("#textfile_edit_area").val();e.ajax({type:"POST",url:"execute.php?action=save_text_file",data:{path:r,new_content:a}}).done(function(e){""!=e&&bootbox.alert(e)})}}],{header:a.find(".name_download").val()})})}function l(){e.ajax({type:"POST",url:"ajax_calls.php?action=get_lang",data:{}}).done(function(a){bootbox.dialog(a,[{label:jQuery("#cancel").val(),"class":"btn"},{label:jQuery("#ok").val(),"class":"btn-inverse",callback:function(){var a=jQuery("#new_lang_select").val();e.ajax({type:"POST",url:"ajax_calls.php?action=change_lang",data:{choosen_lang:a}}).done(function(e){""!=e?bootbox.alert(e):setTimeout(function(){window.location.href=jQuery("#refresh").attr("href").replace(/lang=[\w]*&/i,"lang="+a+"&")+"&"+(new Date).getTime()},100)})}}],{header:jQuery("#lang_lang_change").val()})})}function o(a){jQuery("#files_permission_start").parent().parent().remove();var r=a.find(".rename-file-paths"),t=r.attr("data-path"),n=r.attr("data-permissions"),i=r.attr("data-folder");e.ajax({type:"POST",url:"ajax_calls.php?action=chmod",data:{path:t,permissions:n,folder:i}}).done(function(a){bootbox.dialog(a,[{label:jQuery("#cancel").val(),"class":"btn"},{label:jQuery("#ok").val(),"class":"btn-inverse",callback:function(){var a="-";a+=jQuery("#u_4").is(":checked")?"r":"-",a+=jQuery("#u_2").is(":checked")?"w":"-",a+=jQuery("#u_1").is(":checked")?"x":"-",a+=jQuery("#g_4").is(":checked")?"r":"-",a+=jQuery("#g_2").is(":checked")?"w":"-",a+=jQuery("#g_1").is(":checked")?"x":"-",a+=jQuery("#a_4").is(":checked")?"r":"-",a+=jQuery("#a_2").is(":checked")?"w":"-",a+=jQuery("#a_1").is(":checked")?"x":"-";var n=jQuery("#chmod_form #chmod_value").val();if(""!=n&&"undefined"!=typeof n){var l=jQuery("#chmod_form input[name=apply_recursive]:checked").val();""!=l&&"undefined"!=typeof l||(l="none"),e.ajax({type:"POST",url:"execute.php?action=chmod",data:{path:t,new_mode:n,is_recursive:l,folder:i}}).done(function(e){""!=e?bootbox.alert(e):r.attr("data-permissions",a)})}}}],{header:jQuery("#lang_file_permission").val()}),setTimeout(function(){u(!1)},100)})}function u(a){var r=[];if(r.user=0,r.group=0,r.all=0,"undefined"!=typeof a&&1==a){var t=jQuery("#chmod_form #chmod_value").val();r.user=t.substr(0,1),r.group=t.substr(1,1),r.all=t.substr(2,1),e.each(r,function(a){(""==r[a]||0==e.isNumeric(r[a])||parseInt(r[a])<0||parseInt(r[a])>7)&&(r[a]="0")}),jQuery("#chmod_form input:checkbox").each(function(){var e=jQuery(this).attr("data-group"),a=jQuery(this).attr("data-value");c(r[e],a)?jQuery(this).prop("checked",!0):jQuery(this).prop("checked",!1)})}else jQuery("#chmod_form input:checkbox:checked").each(function(){var e=jQuery(this).attr("data-group"),a=jQuery(this).attr("data-value");r[e]=parseInt(r[e])+parseInt(a)}),jQuery("#chmod_form #chmod_value").val(r.user.toString()+r.group.toString()+r.all.toString())}function c(a,r){var t=[];return t[1]=[1,3,5,7],t[2]=[2,3,6,7],t[4]=[4,5,6,7],a=parseInt(a),r=parseInt(r),e.inArray(a,t[r])!=-1}function s(){bootbox.confirm(jQuery("#lang_clear_clipboard_confirm").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(a){1==a&&e.ajax({type:"POST",url:"ajax_calls.php?action=clear_clipboard",data:{}}).done(function(e){""!=e?bootbox.alert(e):jQuery("#clipboard").val("0"),y(!1)})})}function d(a,r){if("copy"==r||"cut"==r){var t;t=a.hasClass("directory")?a.find(".rename-file-paths").attr("data-path"):a.find(".rename-file-paths").attr("data-path"),e.ajax({type:"POST",url:"ajax_calls.php?action=copy_cut",data:{path:t,sub_action:r}}).done(function(e){""!=e?bootbox.alert(e):(jQuery("#clipboard").val("1"),y(!0))})}}function f(a){bootbox.confirm(jQuery("#lang_paste_confirm").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(r){if(1==r){var t;t="undefined"!=typeof a?a.find(".rename-folder").attr("data-path"):jQuery("#sub_folder").val()+jQuery("#fldr_value").val(),e.ajax({type:"POST",url:"execute.php?action=paste_clipboard",data:{path:t}}).done(function(e){""!=e?bootbox.alert(e):(jQuery("#clipboard").val("0"),y(!1),setTimeout(function(){window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()},300))})}})}function p(a,r){var t;t=a.hasClass("directory")?a.find(".rename-folder"):a.find(".rename-file");var n=t.attr("data-path");a.parent().hide(100),e.ajax({type:"POST",url:"ajax_calls.php?action=copy_cut",data:{path:n,sub_action:"cut"}}).done(function(t){if(""!=t)bootbox.alert(t);else{var n;n="undefined"!=typeof r?r.hasClass("back-directory")?r.find(".path").val():r.find(".rename-folder").attr("data-path"):jQuery("#sub_folder").val()+jQuery("#fldr_value").val(),e.ajax({type:"POST",url:"execute.php?action=paste_clipboard",data:{path:n}}).done(function(e){""!=e?(bootbox.alert(e),a.parent().show(100)):(jQuery("#clipboard").val("0"),y(!1),a.parent().remove())})}}).error(function(){a.parent().show(100)})}function y(e){1==e?jQuery(".paste-here-btn, .clear-clipboard-btn").removeClass("disabled"):jQuery(".paste-here-btn, .clear-clipboard-btn").addClass("disabled")}function v(e){var r=jQuery(".breadcrumb").width()+e,t=jQuery("#view"),n=jQuery("#help");if(jQuery(".uploader").css("width",r),t.val()>0){if(1==t.val())jQuery("ul.grid li, ul.grid figure").css("width","100%");else{var i=Math.floor(r/380);0==i&&(i=1,jQuery("h4").css("font-size",12)),r=Math.floor(r/i-3),jQuery("ul.grid li, ul.grid figure").css("width",r)}n.hide()}else a.touch&&n.show()}function m(){var e=jQuery(this);0==jQuery("#view").val()&&(1==e.attr("toggle")?(e.attr("toggle",0),e.animate({top:"0px"},{queue:!1,duration:300})):(e.attr("toggle",1),e.animate({top:"-30px"},{queue:!1,duration:300})))}function j(e){var a=new RegExp("(?:[?&]|&)"+e+"=([^&]+)","i"),r=window.location.search.match(a);return r&&r.length>1?r[1]:null}function Q(){1==jQuery("#popup").val()?window.close():("function"==typeof parent.jQuery(".modal").modal&&parent.jQuery(".modal").modal("hide"),"undefined"!=typeof parent.jQuery&&parent.jQuery?"function"==typeof parent.jQuery.fancybox&&parent.jQuery.fancybox.close():"function"==typeof parent.$.fancybox&&parent.$.fancybox.close())}function Q(){1==jQuery("#popup").val()?window.close():("function"==typeof parent.jQuery(".modal:has(iframe[src*=filemanager])").modal&&parent.jQuery(".modal:has(iframe[src*=filemanager])").modal("hide"),"undefined"!=typeof parent.jQuery&&parent.jQuery?"function"==typeof parent.jQuery.fancybox&&parent.jQuery.fancybox.close():"function"==typeof parent.$.fancybox&&parent.$.fancybox.close())}function h(e){for(var e,a=[/[\300-\306]/g,/[\340-\346]/g,/[\310-\313]/g,/[\350-\353]/g,/[\314-\317]/g,/[\354-\357]/g,/[\322-\330]/g,/[\362-\370]/g,/[\331-\334]/g,/[\371-\374]/g,/[\321]/g,/[\361]/g,/[\307]/g,/[\347]/g],r=["A","a","E","e","I","i","O","o","U","u","N","n","C","c"],t=0;t]+(>|$)/g,""),e.trim(a)):null}function g(a,r,t,n,i){null!==t&&(t=_(t),e.ajax({type:"POST",url:"execute.php?action="+a,data:{path:r,name:t.replace("/","")}}).done(function(e){return""!=e?(bootbox.alert(e),!1):(""!=i&&window[i](n,t),!0)}))}function b(a,r){var t=jQuery("li.dir","ul.grid").filter(":visible"),n=jQuery("li.file","ul.grid").filter(":visible"),i=[],l=[],o=[],u=[];t.each(function(){var a=jQuery(this),t=a.find(r).val();if(e.isNumeric(t))for(t=parseFloat(t);"undefined"!=typeof i[t]&&i[t];)t=parseFloat(parseFloat(t)+parseFloat(.001));else t=t+"a"+a.find("h4 a").attr("data-file");i[t]=a.html(),l.push(t)}),n.each(function(){var a=jQuery(this),t=a.find(r).val();if(e.isNumeric(t))for(t=parseFloat(t);"undefined"!=typeof o[t]&&o[t];)t=parseFloat(parseFloat(t)+parseFloat(.001));else t=t+"a"+a.find("h4 a").attr("data-file");o[t]=a.html(),u.push(t)}),e.isNumeric(l[0])?l.sort(function(e,a){return parseFloat(e)-parseFloat(a)}):l.sort(),e.isNumeric(u[0])?u.sort(function(e,a){return parseFloat(e)-parseFloat(a)}):u.sort(),a&&(l.reverse(),u.reverse()),t.each(function(e){var a=jQuery(this);a.html(i[l[e]])}),n.each(function(e){var a=jQuery(this);a.html(o[u[e]])})}function w(e,a){return featherEditor.launch({image:e,url:a}),!1}function x(){jQuery(".lazy-loaded").lazyload()}var k="9.11.3",C=!0,T=0,I=function(){var e=0;return function(a,r){clearTimeout(e),e=setTimeout(a,r)}}(),S=function(e){if(1==jQuery("#ftp").val())var a=jQuery("#ftp_base_url").val()+jQuery("#upload_dir").val()+jQuery("#fldr_value").val();else var a=jQuery("#base_url").val()+jQuery("#cur_dir").val();var r=e.find("a.link").attr("data-file");return""!=r&&null!=r&&(a+=r),r=e.find("h4 a.folder-link").attr("data-file"),""!=r&&null!=r&&(a+=r),a},U={contextActions:{copy_url:function(e){var a=S(e);bootbox.alert('URL:
'),jQuery("#copy-button"+T).html(' '+jQuery("#lang_copy").val());var r=new ZeroClipboard(jQuery("#copy-button"+T));r.on("ready",function(e){r.on("wrongFlash noFlash",function(){ZeroClipboard.destroy()}),r.on("aftercopy",function(e){jQuery("#copy-button"+T).html(' '+jQuery("#ok").val()),jQuery("#copy-button"+T).attr("class","btn disabled"),T++}),r.on("error",function(e){})})},unzip:function(a){var r=jQuery("#sub_folder").val()+jQuery("#fldr_value").val()+a.find("a.link").attr("data-file");show_animation(),e.ajax({type:"POST",url:"ajax_calls.php?action=extract",data:{path:r}}).done(function(e){hide_animation(),""!=e?bootbox.alert(e):window.location.href=jQuery("#refresh").attr("href")+"&"+(new Date).getTime()})},edit_img:function(e){var a=e.attr("data-name");if(1==jQuery("#ftp").val())var r=jQuery("#ftp_base_url").val()+jQuery("#upload_dir").val()+jQuery("#fldr_value").val()+a;else var r=jQuery("#base_url").val()+jQuery("#cur_dir").val()+a;var t=jQuery("#aviary_img");t.attr("data-name",a),show_animation(),t.attr("src",r).load(w(t.attr("id"),r))},duplicate:function(e){var a=e.find("h4").text().trim();bootbox.prompt(jQuery("#lang_duplicate").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(r){if(null!==r&&(r=_(r),r!=a)){var t=e.find(".rename-file");g("duplicate_file",t.attr("data-path"),r,t,"apply_file_duplicate")}},a)},select:function(e){var a,r=S(e),t=jQuery("#field_id").val(),n=jQuery("#return_relative_url").val();if(1==n&&(r=r.replace(jQuery("#base_url").val(),""),r=r.replace(jQuery("#cur_dir").val(),"")),a=1==jQuery("#popup").val()?window.opener:window.parent,""!=t)if(1==jQuery("#crossdomain").val())a.postMessage({sender:"responsivefilemanager",url:r,field_id:t},"*");else{var i=jQuery("#"+t,a.document);i.val(r).trigger("change"),"function"==typeof a.responsive_filemanager_callback&&a.responsive_filemanager_callback(t),Q()}else apply_any(r)},copy:function(e){d(e,"copy")},cut:function(e){d(e,"cut")},paste:function(){f()},chmod:function(e){o(e)},edit_text_file:function(e){i(e)}},makeContextMenu:function(){var a=this;e.contextMenu({selector:"figure:not(.back-directory), .list-view2 figure:not(.back-directory)",autoHide:!0,build:function(e){e.addClass("selected");var t={callback:function(r,t){a.contextActions[r](e)},items:{}};return(e.find(".img-precontainer-mini .filetype").hasClass("png")||e.find(".img-precontainer-mini .filetype").hasClass("jpg")||e.find(".img-precontainer-mini .filetype").hasClass("jpeg"))&&r&&(t.items.edit_img={name:jQuery("#lang_edit_image").val(),icon:"edit_img",disabled:!1}),e.hasClass("directory")&&0!=jQuery("#type_param").val()&&(t.items.select={name:jQuery("#lang_select").val(),icon:"",disabled:!1}),t.items.copy_url={name:jQuery("#lang_show_url").val(),icon:"url",disabled:!1},(e.find(".img-precontainer-mini .filetype").hasClass("zip")||e.find(".img-precontainer-mini .filetype").hasClass("tar")||e.find(".img-precontainer-mini .filetype").hasClass("gz"))&&(t.items.unzip={name:jQuery("#lang_extract").val(),icon:"extract",disabled:!1}),e.find(".img-precontainer-mini .filetype").hasClass("edit-text-file-allowed")&&(t.items.edit_text_file={name:jQuery("#lang_edit_file").val(),icon:"edit",disabled:!1}),e.hasClass("directory")||1!=jQuery("#duplicate").val()||(t.items.duplicate={name:jQuery("#lang_duplicate").val(),icon:"duplicate",disabled:!1}),e.hasClass("directory")||1!=jQuery("#copy_cut_files_allowed").val()?e.hasClass("directory")&&1==jQuery("#copy_cut_dirs_allowed").val()&&(t.items.copy={name:jQuery("#lang_copy").val(),icon:"copy",disabled:!1},t.items.cut={name:jQuery("#lang_cut").val(),icon:"cut",disabled:!1}):(t.items.copy={name:jQuery("#lang_copy").val(),icon:"copy",disabled:!1},t.items.cut={name:jQuery("#lang_cut").val(),icon:"cut",disabled:!1}),0==jQuery("#clipboard").val()||e.hasClass("directory")||(t.items.paste={name:jQuery("#lang_paste_here").val(),icon:"clipboard-apply",disabled:!1}),e.hasClass("directory")||1!=jQuery("#chmod_files_allowed").val()?e.hasClass("directory")&&1==jQuery("#chmod_dirs_allowed").val()&&(t.items.chmod={name:jQuery("#lang_file_permission").val(),icon:"key",disabled:!1}):t.items.chmod={name:jQuery("#lang_file_permission").val(),icon:"key",disabled:!1},t.items.sep="----",t.items.info={name:""+jQuery("#lang_file_info").val()+"",disabled:!0},t.items.name={name:e.attr("data-name"),icon:"label",disabled:!0},"img"==e.attr("data-type")&&(t.items.dimension={name:e.find(".img-dimension").html(),icon:"dimension",disabled:!0}),"true"!==jQuery("#show_folder_size").val()&&"true"!==jQuery("#show_folder_size").val()||(e.hasClass("directory")?t.items.size={name:e.find(".file-size").html()+" - "+e.find(".nfiles").val()+" "+jQuery("#lang_files").val()+" - "+e.find(".nfolders").val()+" "+jQuery("#lang_folders").val(),icon:"size",disabled:!0}:t.items.size={name:e.find(".file-size").html(),icon:"size",disabled:!0}),t.items.date={name:e.find(".file-date").html(),icon:"date",disabled:!0},t},events:{hide:function(){jQuery("figure").removeClass("selected")}}}),jQuery(document).on("contextmenu",function(e){if(!jQuery(e.target).is("figure"))return!1})},bindGridEvents:function(){function a(e){window[e.attr("data-function")](e.attr("data-file"),jQuery("#field_id").val())}var r=jQuery("ul.grid");r.on("click",".modalAV",function(a){var r=jQuery(this);a.preventDefault();var t=jQuery("#previewAV"),n=jQuery(".body-preview");t.removeData("modal"),t.modal({backdrop:"static",keyboard:!1}),r.hasClass("audio")?n.css("height","80px"):n.css("height","345px"),e.ajax({url:r.attr("data-url"),success:function(e){n.html(e)}})}),r.on("click",".file-preview-btn",function(a){var r=jQuery(this);a.preventDefault(),e.ajax({url:r.attr("data-url"),success:function(e){bootbox.modal(e," "+r.parent().parent().parent().find(".name").val())}})}),r.on("click",".preview",function(){var e=jQuery(this);return 0==e.hasClass("disabled")&&jQuery("#full-img").attr("src",decodeURIComponent(e.attr("data-url"))),!0}),r.on("click",".rename-file",function(){var a=jQuery(this),r=a.parent().parent().parent(),t=r.find("h4"),n=e.trim(t.text());bootbox.prompt(jQuery("#rename").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){null!==e&&(e=_(e),e!=n&&g("rename_file",a.attr("data-path"),e,r,"apply_file_rename"))},n)}),r.on("click",".rename-folder",function(){var a=jQuery(this),r=a.parent().parent().parent(),t=r.find("h4"),n=e.trim(t.text());bootbox.prompt(jQuery("#rename").val(),jQuery("#cancel").val(),jQuery("#ok").val(),function(e){null!==e&&(e=_(e).replace(".",""),e!=n&&g("rename_folder",a.attr("data-path"),e,r,"apply_folder_rename"))},n)}),r.on("click",".delete-file",function(){var e=jQuery(this);bootbox.confirm(e.attr("data-confirm"),jQuery("#cancel").val(),jQuery("#ok").val(),function(a){if(1==a){g("delete_file",e.attr("data-path"),"","","");var r=jQuery("#files_number");r.text(parseInt(r.text())-1),e.parent().parent().parent().parent().remove()}})}),r.on("click",".delete-folder",function(){var e=jQuery(this);bootbox.confirm(e.attr("data-confirm"),jQuery("#cancel").val(),jQuery("#ok").val(),function(a){if(1==a){g("delete_folder",e.attr("data-path"),"","","");var r=jQuery("#folders_number");r.text(parseInt(r.text())-1),e.parent().parent().parent().remove()}})}),jQuery("ul.grid").on("click",".link",function(){a(jQuery(this))}),jQuery("ul.grid").on("click","div.box",function(e){var r=jQuery(this).find(".link");if(0!==r.length)a(r);else{var t=jQuery(this).find(".folder-link");0!==t.length&&(document.location=jQuery(t).prop("href"))}})},makeFilters:function(a){jQuery("#filter-input").on("keyup",function(){jQuery(".filters label").removeClass("btn-inverse"),jQuery(".filters label").find("i").removeClass("icon-white"),jQuery("#ff-item-type-all").addClass("btn-inverse"),jQuery("#ff-item-type-all").find("i").addClass("icon-white");var r=_(jQuery(this).val()).toLowerCase();jQuery(this).val(r),a&&I(function(){jQuery("li","ul.grid ").each(function(){var e=jQuery(this);""!=r&&e.attr("data-name").toLowerCase().indexOf(r)==-1?e.hide(100):e.show(100)}),e.ajax({url:"ajax_calls.php?action=filter&type="+r}).done(function(e){""!=e&&bootbox.alert(e)}),I(function(){var e=0!=jQuery("#descending").val();b(e,"."+jQuery("#sort_by").val()),x()},500)},300)}).keypress(function(e){13==e.which&&jQuery("#filter").trigger("click")}),jQuery("#filter").on("click",function(){var e=_(jQuery("#filter-input").val());window.location.href=jQuery("#current_url").val()+"&filter="+e})},makeUploader:function(){jQuery("#uploader-btn").on("click",function(){var e=jQuery("#sub_folder").val()+jQuery("#fldr_value").val()+"/";e=e.substring(0,e.length-1),jQuery("#iframe-container").html(jQuery(""}else"application/x-shockwave-flash"==i.source1mime?(o+='',i.poster&&(o+=''),o+=""):-1!=i.source1mime.indexOf("audio")?e.settings.audio_template_callback?o=e.settings.audio_template_callback(i):o+='":"script"==i.type?o+='':o=e.settings.video_template_callback?e.settings.video_template_callback(i):'"}return o}function s(e){var t={};return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!0,special:"script,noscript",start:function(e,n){if(t.source1||"param"!=e||(t.source1=n.map.movie),("iframe"==e||"object"==e||"embed"==e||"video"==e||"audio"==e)&&(t.type||(t.type=e),t=tinymce.extend(n.map,t)),"script"==e){var i=r(n.map.src);if(!i)return;t={type:"script",source1:n.map.src,width:i.width,height:i.height}}"source"==e&&(t.source1?t.source2||(t.source2=n.map.src):t.source1=n.map.src),"img"!=e||t.poster||(t.poster=n.map.src)}}).parse(e),t.source1=t.source1||t.src||t.data,t.source2=t.source2||"",t.poster=t.poster||"",t}function l(t){return t.getAttribute("data-mce-object")?s(e.serializer.serialize(t,{selection:!0})):{}}function c(t){if(e.settings.media_filter_html===!1)return t;var n,r=new tinymce.html.Writer;return new tinymce.html.SaxParser({validate:!1,allow_conditional_comments:!1,special:"script,noscript",comment:function(e){r.comment(e)},cdata:function(e){r.cdata(e)},text:function(e,t){r.text(e,t)},start:function(t,i,o){if(n=!0,"script"!=t&&"noscript"!=t){for(var a=0;a=a&&(r(s,{src:t["source"+a],type:t["source"+a+"mime"]}),!t["source"+a]))return;break;case"img":if(!t.poster)return;i=!0}o.start(e,s,l)},end:function(e){if("video"==e&&n)for(var s=1;2>=s;s++)if(t["source"+s]){var l=[];l.map={},s>a&&(r(l,{src:t["source"+s],type:t["source"+s+"mime"]}),o.start("source",l,!0))}if(t.poster&&"object"==e&&n&&!i){var c=[];c.map={},r(c,{src:t.poster,width:t.width,height:t.height}),o.start("img",c,!0)}o.end(e)}},new tinymce.html.Schema({})).parse(e),o.getContent()}function d(t,n){var r,i,o,a,s;for(o=t.attributes,a=o.length;a--;)r=o[a].name,i=o[a].value,"width"!==r&&"height"!==r&&"style"!==r&&(("data"==r||"src"==r)&&(i=e.convertURL(i,r)),n.attr("data-mce-p-"+r,i));s=t.firstChild&&t.firstChild.value,s&&(n.attr("data-mce-html",escape(s)),n.firstChild=null)}function f(e){var t,n=e.name;return t=new tinymce.html.Node("img",1),t.shortEnded=!0,d(e,t),t.attr({width:e.attr("width")||"300",height:e.attr("height")||("audio"==n?"30":"150"),style:e.attr("style"),src:tinymce.Env.transparentSrc,"data-mce-object":n,"class":"mce-object mce-object-"+n}),t}function p(e){var t,n,r,i=e.name;return t=new tinymce.html.Node("span",1),t.attr({contentEditable:"false",style:e.attr("style"),"data-mce-object":i,"class":"mce-preview-object mce-object-"+i}),d(e,t),n=new tinymce.html.Node(i,1),n.attr({src:e.attr("src"),allowfullscreen:e.attr("allowfullscreen"),width:e.attr("width")||"300",height:e.attr("height")||("audio"==i?"30":"150"),frameborder:"0"}),r=new tinymce.html.Node("span",1),r.attr("class","mce-shim"),t.append(n),t.append(r),t}var m=[{regex:/youtu\.be\/([\w\-.]+)/,type:"iframe",w:560,h:314,url:"//www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/youtube\.com(.+)v=([^&]+)/,type:"iframe",w:560,h:314,url:"//www.youtube.com/embed/$2",allowFullscreen:!0},{regex:/youtube.com\/embed\/([a-z0-9\-]+)/i,type:"iframe",w:560,h:314,url:"//www.youtube.com/embed/$1",allowFullscreen:!0},{regex:/vimeo\.com\/([0-9]+)/,type:"iframe",w:425,h:350,url:"//player.vimeo.com/video/$1?title=0&byline=0&portrait=0&color=8dc7dc",allowfullscreen:!0},{regex:/vimeo\.com\/(.*)\/([0-9]+)/,type:"iframe",w:425,h:350,url:"//player.vimeo.com/video/$2?title=0&byline=0",allowfullscreen:!0},{regex:/maps\.google\.([a-z]{2,3})\/maps\/(.+)msid=(.+)/,type:"iframe",w:425,h:350,url:'//maps.google.com/maps/ms?msid=$2&output=embed"',allowFullscreen:!1}],h=tinymce.Env.ie&&tinymce.Env.ie<=8?"onChange":"onInput";e.on("ResolveName",function(e){var t;1==e.target.nodeType&&(t=e.target.getAttribute("data-mce-object"))&&(e.name=t)}),e.on("preInit",function(){var t=e.schema.getSpecialElements();tinymce.each("video audio iframe object".split(" "),function(e){t[e]=new RegExp("]*>","gi")});var n=e.schema.getBoolAttrs();tinymce.each("webkitallowfullscreen mozallowfullscreen allowfullscreen".split(" "),function(e){n[e]={}}),e.parser.addNodeFilter("iframe,video,audio,object,embed,script",function(t){for(var n,i,o,a=t.length;a--;)n=t[a],n.parent&&(n.parent.attr("data-mce-object")||("script"!=n.name||(o=r(n.attr("src"))))&&(o&&(o.width&&n.attr("width",o.width.toString()),o.height&&n.attr("height",o.height.toString())),i="iframe"==n.name&&e.settings.media_live_embeds!==!1&&tinymce.Env.ceFalse?p(n):f(n),n.replace(i)))}),e.serializer.addAttributeFilter("data-mce-object",function(e,t){for(var n,r,i,o,a,s,l,u,d=e.length;d--;)if(n=e[d],n.parent){for(l=n.attr(t),r=new tinymce.html.Node(l,1),"audio"!=l&&"script"!=l&&(u=n.attr("class"),u&&-1!==u.indexOf("mce-preview-object")?r.attr({width:n.firstChild.attr("width"),height:n.firstChild.attr("height")}):r.attr({width:n.attr("width"),height:n.attr("height")})),r.attr({style:n.attr("style")}),o=n.attributes,i=o.length;i--;){var f=o[i].name;0===f.indexOf("data-mce-p-")&&r.attr(f.substr(11),o[i].value)}"script"==l&&r.attr("type","text/javascript"),a=n.attr("data-mce-html"),a&&(s=new tinymce.html.Node("#text",3),s.raw=!0,s.value=c(unescape(a)),r.append(s)),n.replace(r)}})}),e.on("ObjectSelected",function(e){var t=e.target.getAttribute("data-mce-object");("audio"==t||"script"==t)&&e.preventDefault()}),e.on("objectResized",function(e){var t,n=e.target;n.getAttribute("data-mce-object")&&(t=n.getAttribute("data-mce-html"),t&&(t=unescape(t),n.setAttribute("data-mce-html",escape(u(t,{width:e.width,height:e.height})))))}),e.addButton("media",{tooltip:"Insert/edit video",onclick:i,stateSelector:["img[data-mce-object]","span[data-mce-object]"]}),e.addMenuItem("media",{icon:"media",text:"Insert/edit video",onclick:i,context:"insert",prependToContext:!0}),e.addCommand("mceMedia",i),this.showDialog=i}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/nonbreaking/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/nonbreaking/plugin.min.js new file mode 100644 index 0000000..1e4f334 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/nonbreaking/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("nonbreaking",function(e){var t=e.getParam("nonbreaking_force_tab");if(e.addCommand("mceNonBreaking",function(){e.insertContent(e.plugins.visualchars&&e.plugins.visualchars.state?' ':" "),e.dom.setAttrib(e.dom.select("span.mce-nbsp"),"data-mce-bogus","1")}),e.addButton("nonbreaking",{title:"Nonbreaking space",cmd:"mceNonBreaking"}),e.addMenuItem("nonbreaking",{text:"Nonbreaking space",cmd:"mceNonBreaking",context:"insert"}),t){var n=+t>1?+t:3;e.on("keydown",function(t){if(9==t.keyCode){if(t.shiftKey)return;t.preventDefault();for(var r=0;n>r;r++)e.execCommand("mceNonBreaking")}})}}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/noneditable/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/noneditable/plugin.min.js new file mode 100644 index 0000000..b3d2add --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/noneditable/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("noneditable",function(e){function t(e){return function(t){return-1!==(" "+t.attr("class")+" ").indexOf(e)}}function n(t){function n(t){var n=arguments,r=n[n.length-2];return r>0&&'"'==a.charAt(r-1)?t:''+e.dom.encode("string"==typeof n[1]?n[1]:n[0])+""}var r=o.length,a=t.content,s=tinymce.trim(i);if("raw"!=t.format){for(;r--;)a=a.replace(o[r],n);t.content=a}}var r,i,o,a="contenteditable";r=" "+tinymce.trim(e.getParam("noneditable_editable_class","mceEditable"))+" ",i=" "+tinymce.trim(e.getParam("noneditable_noneditable_class","mceNonEditable"))+" ";var s=t(r),l=t(i);o=e.getParam("noneditable_regexp"),o&&!o.length&&(o=[o]),e.on("PreInit",function(){o&&e.on("BeforeSetContent",n),e.parser.addAttributeFilter("class",function(e){for(var t,n=e.length;n--;)t=e[n],s(t)?t.attr(a,"true"):l(t)&&t.attr(a,"false")}),e.serializer.addAttributeFilter(a,function(e){for(var t,n=e.length;n--;)t=e[n],(s(t)||l(t))&&(o&&t.attr("data-mce-content")?(t.name="#text",t.type=3,t.raw=!0,t.value=t.attr("data-mce-content")):t.attr(a,null))})})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/pagebreak/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/pagebreak/plugin.min.js new file mode 100644 index 0000000..b76e584 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/pagebreak/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("pagebreak",function(e){var t="mce-pagebreak",n=e.getParam("pagebreak_separator",""),r=new RegExp(n.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(e){return"\\"+e}),"gi"),i='';e.addCommand("mcePageBreak",function(){e.settings.pagebreak_split_block?e.insertContent("

"+i+"

"):e.insertContent(i)}),e.addButton("pagebreak",{title:"Page break",cmd:"mcePageBreak"}),e.addMenuItem("pagebreak",{text:"Page break",icon:"pagebreak",cmd:"mcePageBreak",context:"insert"}),e.on("ResolveName",function(n){"IMG"==n.target.nodeName&&e.dom.hasClass(n.target,t)&&(n.name="pagebreak")}),e.on("click",function(n){n=n.target,"IMG"===n.nodeName&&e.dom.hasClass(n,t)&&e.selection.select(n)}),e.on("BeforeSetContent",function(e){e.content=e.content.replace(r,i)}),e.on("PreInit",function(){e.serializer.addNodeFilter("img",function(t){for(var r,i,o=t.length;o--;)if(r=t[o],i=r.attr("class"),i&&-1!==i.indexOf("mce-pagebreak")){var a=r.parent;if(e.schema.getBlockElements()[a.name]&&e.settings.pagebreak_split_block){a.type=3,a.value=n,a.raw=!0,r.remove();continue}r.type=3,r.value=n,r.raw=!0}})})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/paste/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/paste/plugin.min.js new file mode 100644 index 0000000..a81b792 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/paste/plugin.min.js @@ -0,0 +1 @@ +!function(e,t){"use strict";function n(e,t){for(var n,r=[],o=0;o/g]),o(s.parse(i)),l}function o(e){function t(e,t,n){return t||n?"\xa0":" "}return e=r(e,[/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g,/|/g,[/( ?)\u00a0<\/span>( ?)/g,t],/
/g,/
$/i])}return{filter:r,innerText:i,trimHtml:o}}),r("tinymce/pasteplugin/Clipboard",["tinymce/Env","tinymce/dom/RangeUtils","tinymce/util/VK","tinymce/pasteplugin/Utils","tinymce/util/Delay"],function(e,t,n,r,i){return function(o){function a(e){var t,n=o.dom;if(t=o.fire("BeforePastePreProcess",{content:e}),t=o.fire("PastePreProcess",t),e=t.content,!t.isDefaultPrevented()){if(o.hasEventListeners("PastePostProcess")&&!t.isDefaultPrevented()){var r=n.add(o.getBody(),"div",{style:"display:none"},e);t=o.fire("PastePostProcess",{node:r}),n.remove(r),e=t.node.innerHTML}t.isDefaultPrevented()||o.insertContent(e,{merge:o.settings.paste_merge_formats!==!1,data:{paste:!0}})}}function s(e){e=o.dom.encode(e).replace(/\r\n/g,"\n");var t,n=o.dom.getParent(o.selection.getStart(),o.dom.isBlock),i=o.settings.forced_root_block;i&&(t=o.dom.createHTML(i,o.settings.forced_root_block_attrs),t=t.substr(0,t.length-3)+">"),n&&/^(PRE|DIV)$/.test(n.nodeName)||!i?e=r.filter(e,[[/\n/g,"
"]]):(e=r.filter(e,[[/\n\n/g,"

"+t],[/^(.*<\/p>)(

)$/,t+"$1"],[/\n/g,"
"]]),-1!=e.indexOf("

")&&(e=t+e)),a(e)}function l(){function t(e){var t,n,i,o=e.startContainer;if(t=e.getClientRects(),t.length)return t[0];if(e.collapsed&&1==o.nodeType){for(i=o.childNodes[w.startOffset];i&&3==i.nodeType&&!i.data.length;)i=i.nextSibling;if(i)return"BR"==i.tagName&&(n=r.doc.createTextNode("\ufeff"),i.parentNode.insertBefore(n,i),e=r.createRng(),e.setStartBefore(n),e.setEndAfter(n),t=e.getClientRects(),r.remove(n)),t.length?t[0]:void 0}}var n,r=o.dom,i=o.getBody(),a=o.dom.getViewPort(o.getWin()),s=a.y,l=20;if(w=o.selection.getRng(),o.inline&&(n=o.selection.getScrollContainer(),n&&n.scrollTop>0&&(s=n.scrollTop)),w.getClientRects){var c=t(w);if(c)l=s+(c.top-r.getPos(i).y);else{l=s;var u=w.startContainer;u&&(3==u.nodeType&&u.parentNode!=i&&(u=u.parentNode),1==u.nodeType&&(l=r.getPos(u,n||i).y))}}C=r.add(o.getBody(),"div",{id:"mcepastebin",contentEditable:!0,"data-mce-bogus":"all",style:"position: absolute; top: "+l+"px;width: 10px; height: 10px; overflow: hidden; opacity: 0"},S),(e.ie||e.gecko)&&r.setStyle(C,"left","rtl"==r.getStyle(i,"direction",!0)?65535:-65535),r.bind(C,"beforedeactivate focusin focusout",function(e){e.stopPropagation()}),C.focus(),o.selection.select(C,!0)}function c(){if(C){for(var e;e=o.dom.get("mcepastebin");)o.dom.remove(e),o.dom.unbind(e);w&&o.selection.setRng(w)}C=w=null}function u(){var e,t,n,r,i="";for(e=o.dom.select("div[id=mcepastebin]"),t=0;t>8);return decodeURIComponent(escape(n))}function f(e){var t,n,r;return n="",t=e.indexOf(n),-1!==t&&(e=e.substr(t+n.length)),r="",t=e.indexOf(r),-1!==t&&(e=e.substr(0,t)),e}function p(e){var t={};if(e){if(e.getData){var n=e.getData("Text");n&&n.length>0&&-1==n.indexOf(T)&&(t["text/plain"]=n)}if(e.types)for(var r=0;r')}var i,s,l,c=!1;if(n)for(i=0;i0}function b(e){return n.metaKeyPressed(e)&&86==e.keyCode||e.shiftKey&&45==e.keyCode}function x(){function t(e,t,n){var i;return y(e,"text/html")?i=e["text/html"]:(i=u(),i==S&&(n=!0)),i=r.trimHtml(i),C&&C.firstChild&&"mcepastebin"===C.firstChild.id&&(n=!0),c(),i.length||(n=!0),n&&(i=y(e,"text/plain")&&-1==i.indexOf("

")?e["text/plain"]:r.innerText(i)),i==S?void(t||o.windowManager.alert("Please use Ctrl+V/Cmd+V keyboard shortcuts to paste contents.")):void(n?s(i):a(i))}o.on("keydown",function(t){function n(e){b(e)&&!e.isDefaultPrevented()&&c()}if(b(t)&&!t.isDefaultPrevented()){if(N=t.shiftKey&&86==t.keyCode,N&&e.webkit&&-1!=navigator.userAgent.indexOf("Version/"))return;if(t.stopImmediatePropagation(),E=(new Date).getTime(),e.ie&&N)return t.preventDefault(),void o.fire("paste",{ieFake:!0});c(),l(),o.once("keyup",n),o.once("paste",function(){o.off("keyup",n)})}}),o.on("paste",function(n){var r=(new Date).getTime(),a=m(n),s=(new Date).getTime()-r,d=(new Date).getTime()-E-s<1e3,f="text"==_.pasteFormat||N;return N=!1,n.isDefaultPrevented()||g(n)?void c():h(n)?void c():(d||n.preventDefault(),!e.ie||d&&!n.ieFake||(l(),o.dom.bind(C,"paste",function(e){e.stopPropagation()}),o.getDoc().execCommand("Paste",!1,null),a["text/html"]=u()),void(y(a,"text/html")?(n.preventDefault(),t(a,d,f)):i.setEditorTimeout(o,function(){t(a,d,f)},0)))}),o.on("dragstart dragend",function(e){k="dragstart"==e.type}),o.on("drop",function(e){var t=v(e);if(!e.isDefaultPrevented()&&!k&&!h(e,t)&&t&&o.settings.paste_filter_drop!==!1){var n=p(e.dataTransfer),i=n["mce-internal"]||n["text/html"]||n["text/plain"];i&&(e.preventDefault(),o.undoManager.transact(function(){n["mce-internal"]&&o.execCommand("Delete"),o.selection.setRng(t),i=r.trimHtml(i),n["text/html"]?a(i):s(i)}))}}),o.on("dragover dragend",function(e){o.settings.paste_data_images&&e.preventDefault()})}var C,w,N,_=this,E=0,k=!1,S="%MCEPASTEBIN%",T="data:text/mce-internal,";_.pasteHtml=a,_.pasteText=s,o.on("preInit",function(){x(),o.parser.addNodeFilter("img",function(t,n,r){function i(e){return e.data&&e.data.paste===!0}function a(t){t.attr("data-mce-object")||u===e.transparentSrc||t.remove()}function s(e){return 0===e.indexOf("webkit-fake-url")}function l(e){return 0===e.indexOf("data:")}if(!o.settings.paste_data_images&&i(r))for(var c=t.length;c--;){var u=t[c].attributes.map.src;u&&(s(u)?a(t[c]):!o.settings.allow_html_data_urls&&l(u)&&a(t[c]))}})})}}),r("tinymce/pasteplugin/WordFilter",["tinymce/util/Tools","tinymce/html/DomParser","tinymce/html/Schema","tinymce/html/Serializer","tinymce/html/Node","tinymce/pasteplugin/Utils"],function(e,t,n,r,i,o){function a(e){return/s?a&&(a=a.parent.parent):(c=a,a=null)),a&&a.name==t?a.append(e):(c=c||a,a=new i(t,1),o>1&&a.attr("start",""+o),e.wrap(a)),e.name="li",s>u&&c&&c.lastChild.append(a),u=s,r(e),n(e,/^\u00a0+/),n(e,/^\s*([\u2022\u00b7\u00a7\u25CF]|\w+\.)/),n(e,/^\u00a0+/)}for(var a,c,u=1,d=[],f=e.firstChild;"undefined"!=typeof f&&null!==f;)if(d.push(f),f=f.walk(),null!==f)for(;"undefined"!=typeof f&&f.parent!==e;)f=f.walk();for(var p=0;p]+id="?docs-internal-[^>]*>/gi,""),g=g.replace(/
/gi,""),m=u.paste_retain_style_properties,m&&(h=e.makeMap(m.split(/[, ]/))),u.paste_enable_default_filters!==!1&&a(d.content)){d.wordContent=!0,g=o.filter(g,[//gi,/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|img|meta|link|style|\w:\w+)(?=[\s\/>]))[^>]*>/gi,[/<(\/?)s>/gi,"<$1strike>"],[/ /gi,"\xa0"],[/([\s\u00a0]*)<\/span>/gi,function(e,t){return t.length>0?t.replace(/./," ").slice(Math.floor(t.length/2)).split("").join("\xa0"):""}]]);var v=u.paste_word_valid_elements;v||(v="-strong/b,-em/i,-u,-span,-p,-ol,-ul,-li,-h1,-h2,-h3,-h4,-h5,-h6,-p/div,-a[href|name],sub,sup,strike,br,del,table[width],tr,td[colspan|rowspan|width],th[colspan|rowspan|width],thead,tfoot,tbody");var y=new n({valid_elements:v,valid_children:"-li[p]"});e.each(y.elements,function(e){e.attributes["class"]||(e.attributes["class"]={},e.attributesOrder.push("class")),e.attributes.style||(e.attributes.style={},e.attributesOrder.push("style"))});var b=new t({},y);b.addAttributeFilter("style",function(e){for(var t,n=e.length;n--;)t=e[n],t.attr("style",p(t,t.attr("style"))),"span"==t.name&&t.parent&&!t.attributes.length&&t.unwrap()}),b.addAttributeFilter("class",function(e){for(var t,n,r=e.length;r--;)t=e[r],n=t.attr("class"),/^(MsoCommentReference|MsoCommentText|msoDel)$/i.test(n)&&t.remove(),t.attr("class",null)}),b.addNodeFilter("del",function(e){for(var t=e.length;t--;)e[t].remove()}),b.addNodeFilter("a",function(e){for(var t,n,r,i=e.length;i--;)if(t=e[i],n=t.attr("href"),r=t.attr("name"),n&&-1!=n.indexOf("#_msocom_"))t.remove();else if(n&&0===n.indexOf("file://")&&(n=n.split("#")[1],n&&(n="#"+n)),n||r){if(r&&!/^_?(?:toc|edn|ftn)/i.test(r)){t.unwrap();continue}t.attr({href:n,name:r})}else t.unwrap()});var x=b.parse(g);u.paste_convert_word_fake_lists!==!1&&f(x),d.content=new r({validate:u.validate},y).serialize(x)}})}return c.isWordContent=a,c}),r("tinymce/pasteplugin/Quirks",["tinymce/Env","tinymce/util/Tools","tinymce/pasteplugin/WordFilter","tinymce/pasteplugin/Utils"],function(e,t,n,r){return function(i){function o(e){i.on("BeforePastePreProcess",function(t){t.content=e(t.content)})}function a(e){if(!n.isWordContent(e))return e;var o=[];t.each(i.schema.getBlockElements(),function(e,t){o.push(t)});var a=new RegExp("(?:
 [\\s\\r\\n]+|
)*(<\\/?("+o.join("|")+")[^>]*>)(?:
 [\\s\\r\\n]+|
)*","g");return e=r.filter(e,[[a,"$1"]]),e=r.filter(e,[[/

/g,"

"],[/
/g," "],[/

/g,"
"]])}function s(e){if(n.isWordContent(e))return e;var t=i.settings.paste_webkit_styles;if(i.settings.paste_remove_styles_if_webkit===!1||"all"==t)return e;if(t&&(t=t.split(/[, ]/)),t){var r=i.dom,o=i.selection.getNode();e=e.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi,function(e,n,i,a){var s=r.parseStyle(i,"span"),l={};if("none"===t)return n+a;for(var c=0;c]+) style="([^"]*)"([^>]*>)/gi,"$1$3");return e=e.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi,function(e,t,n,r){return t+' style="'+n+'"'+r})}e.webkit&&o(s),e.ie&&o(a)}}),r("tinymce/pasteplugin/Plugin",["tinymce/PluginManager","tinymce/pasteplugin/Clipboard","tinymce/pasteplugin/WordFilter","tinymce/pasteplugin/Quirks"],function(e,t,n,r){var i;e.add("paste",function(e){function o(){if("text"==a.pasteFormat)this.active(!1),a.pasteFormat="html";else if(a.pasteFormat="text",this.active(!0),!i){var t=e.translate("Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.");e.notificationManager.open({text:t,type:"info"}),i=!0}}var a,s=this,l=e.settings;s.clipboard=a=new t(e),s.quirks=new r(e),s.wordFilter=new n(e),e.settings.paste_as_text&&(s.clipboard.pasteFormat="text"),l.paste_preprocess&&e.on("PastePreProcess",function(e){l.paste_preprocess.call(s,s,e)}),l.paste_postprocess&&e.on("PastePostProcess",function(e){l.paste_postprocess.call(s,s,e)}),e.addCommand("mceInsertClipboardContent",function(e,t){t.content&&s.clipboard.pasteHtml(t.content),t.text&&s.clipboard.pasteText(t.text)}),e.paste_block_drop&&e.on("dragend dragover draggesture dragdrop drop drag",function(e){e.preventDefault(),e.stopPropagation()}),e.settings.paste_data_images||e.on("drop",function(e){var t=e.dataTransfer;t&&t.files&&t.files.length>0&&e.preventDefault()}),e.addButton("pastetext",{icon:"pastetext",tooltip:"Paste as text",onclick:o,active:"text"==s.clipboard.pasteFormat}),e.addMenuItem("pastetext",{text:"Paste as text",selectable:!0,active:a.pasteFormat,onclick:o})})}),o(["tinymce/pasteplugin/Utils"])}(this); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/preview/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/preview/plugin.min.js new file mode 100644 index 0000000..7d5e047 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/preview/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("preview",function(e){var t=e.settings,n=!tinymce.Env.ie;e.addCommand("mcePreview",function(){e.windowManager.open({title:"Preview",width:parseInt(e.getParam("plugin_preview_width","650"),10),height:parseInt(e.getParam("plugin_preview_height","500"),10),html:'",buttons:{text:"Close",onclick:function(){this.parent().parent().close()}},onPostRender:function(){var r,i="";i+='',tinymce.each(e.contentCSS,function(t){i+=''});var o=t.body_id||"tinymce";-1!=o.indexOf("=")&&(o=e.getParam("body_id","","hash"),o=o[e.id]||o);var a=t.body_class||"";-1!=a.indexOf("=")&&(a=e.getParam("body_class","","hash"),a=a[e.id]||"");var s=e.settings.directionality?' dir="'+e.settings.directionality+'"':"";if(r=""+i+'"+e.getContent()+"",n)this.getEl("body").firstChild.src="data:text/html;charset=utf-8,"+encodeURIComponent(r);else{var l=this.getEl("body").firstChild.contentWindow.document;l.open(),l.write(r),l.close()}}})}),e.addButton("preview",{title:"Preview",cmd:"mcePreview"}),e.addMenuItem("preview",{text:"Preview",cmd:"mcePreview",context:"view"})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/print/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/print/plugin.min.js new file mode 100644 index 0000000..9f58535 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/print/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("print",function(e){e.addCommand("mcePrint",function(){e.getWin().print()}),e.addButton("print",{title:"Print",cmd:"mcePrint"}),e.addShortcut("Meta+P","","mcePrint"),e.addMenuItem("print",{text:"Print",cmd:"mcePrint",icon:"print",shortcut:"Meta+P",context:"file"})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/editor_plugin.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/editor_plugin.js new file mode 100644 index 0000000..fd42009 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/editor_plugin.js @@ -0,0 +1,126 @@ +(function() { + + tinymce.create('tinymce.plugins.ResponsiveFileManager', + { + + init : function(editor, url) + { + + // File manager callback + function openmanager() + { + editor.focus(true); + var title="RESPONSIVE FileManager"; + if (typeof editor.settings.filemanager_title !== "undefined" && editor.settings.filemanager_title) { + title=editor.settings.filemanager_title; + } + var akey="key"; + if (typeof editor.settings.filemanager_access_key !== "undefined" && editor.settings.filemanager_access_key) { + akey=editor.settings.filemanager_access_key; + } + var sort_by=""; + if (typeof editor.settings.filemanager_sort_by !== "undefined" && editor.settings.filemanager_sort_by) { + sort_by="&sort_by="+editor.settings.filemanager_sort_by; + } + var descending="false"; + if (typeof editor.settings.filemanager_descending !== "undefined" && editor.settings.filemanager_descending) { + descending=editor.settings.filemanager_descending; + } + var fldr=""; + if (typeof editor.settings.filemanager_subfolder !== "undefined" && editor.settings.filemanager_subfolder) { + fldr="&fldr="+editor.settings.filemanager_subfolder; + } + // Disabled because of bug + var type=2; + if (typeof editor.settings.filemanager_type !== "undefined" && editor.settings.filemanager_type) { + if ($.isNumeric(editor.settings.filemanager_type) === true && editor.settings.filemanager_type > 0 && editor.settings.filemanager_type <= 3) { + type=editor.settings.filemanager_type; + } + else if (editor.settings.filemanager_type == 'image'){ + type = 1; + } + else if (editor.settings.filemanager_type == 'media'){ + type = 3; + } + else { + type = 2; + } + } + + win = editor.windowManager.open({ + title: title, + file: editor.settings.external_filemanager_path+'dialog.php?type=4&descending='+descending+sort_by+fldr+'&lang='+editor.settings.language+'&akey='+akey, + width: 860, + height: 570, + inline: 1, + resizable: true, + maximizable: true + }); + } + + editor.settings.file_browser_callback = filemanager; + + function filemanager (id, value, type, win) { + // DEFAULT AS FILE + urltype=2; + if (type=='image') { urltype=1; } + if (type=='media') { urltype=3; } + var title="RESPONSIVE FileManager"; + if (typeof editor.settings.filemanager_title !== "undefined" && editor.settings.filemanager_title) { + title=editor.settings.filemanager_title; + } + var akey="key"; + if (typeof editor.settings.filemanager_access_key !== "undefined" && editor.settings.filemanager_access_key) { + akey=editor.settings.filemanager_access_key; + } + var sort_by=""; + if (typeof editor.settings.filemanager_sort_by !== "undefined" && editor.settings.filemanager_sort_by) { + sort_by="&sort_by="+editor.settings.filemanager_sort_by; + } + var descending="false"; + if (typeof editor.settings.filemanager_descending !== "undefined" && editor.settings.filemanager_descending) { + descending=editor.settings.filemanager_descending; + } + var fldr=""; + if (typeof editor.settings.filemanager_subfolder !== "undefined" && editor.settings.filemanager_subfolder) { + fldr="&fldr="+editor.settings.filemanager_subfolder; + } + + tinymce.activeEditor.windowManager.open({ + title: title, + file: editor.settings.external_filemanager_path+'dialog.php?type='+urltype+'&descending='+descending+sort_by+fldr+'&lang='+editor.settings.language+'&akey='+akey, + width: 860, + height: 570, + resizable: true, + maximizable: true, + inline: 1 + }, { + setUrl: function (url) { + var fieldElm = win.document.getElementById(id); + fieldElm.value = editor.convertURL(url); + if ("fireEvent" in fieldElm) { + fieldElm.fireEvent("onchange") + } else { + var evt = document.createEvent("HTMLEvents"); + evt.initEvent("change", false, true); + fieldElm.dispatchEvent(evt); + } + } + }); + }; + + // Register buttons + editor.addButton('responsivefilemanager', + { + title : 'Browse files', + image : url + '/img/insertfile.gif', + shortcut: 'Ctrl+E', + onclick: openmanager + }); + } + + }); + + // Register plugin + tinymce.PluginManager.add('responsivefilemanager', tinymce.plugins.ResponsiveFileManager); +})(); diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/img/insertfile.gif b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/img/insertfile.gif new file mode 100644 index 0000000..93689cb Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/img/insertfile.gif differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.js new file mode 100644 index 0000000..dcb0ec2 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.js @@ -0,0 +1,79 @@ +/** + * plugin.js + * + * Copyright, Alberto Peripolli + * Released under Creative Commons Attribution-NonCommercial 3.0 Unported License. + * + * Contributing: https://github.com/trippo/ResponsiveFilemanager + */ + +tinymce.PluginManager.add('responsivefilemanager', function(editor) { + + function openmanager() { + editor.focus(true); + var title="RESPONSIVE FileManager"; + if (typeof editor.settings.filemanager_title !== "undefined" && editor.settings.filemanager_title) { + title=editor.settings.filemanager_title; + } + var akey="key"; + if (typeof editor.settings.filemanager_access_key !== "undefined" && editor.settings.filemanager_access_key) { + akey=editor.settings.filemanager_access_key; + } + var sort_by=""; + if (typeof editor.settings.filemanager_sort_by !== "undefined" && editor.settings.filemanager_sort_by) { + sort_by="&sort_by="+editor.settings.filemanager_sort_by; + } + var descending="false"; + if (typeof editor.settings.filemanager_descending !== "undefined" && editor.settings.filemanager_descending) { + descending=editor.settings.filemanager_descending; + } + var fldr=""; + if (typeof editor.settings.filemanager_subfolder !== "undefined" && editor.settings.filemanager_subfolder) { + fldr="&fldr="+editor.settings.filemanager_subfolder; + } + // Disabled because of bug + var type=2; + if (typeof editor.settings.filemanager_type !== "undefined" && editor.settings.filemanager_type) { + if ($.isNumeric(editor.settings.filemanager_type) === true && editor.settings.filemanager_type > 0 && editor.settings.filemanager_type <= 3) { + type=editor.settings.filemanager_type; + } + else if (editor.settings.filemanager_type == 'image'){ + type = 1; + } + else if (editor.settings.filemanager_type == 'media'){ + type = 3; + } + else { + type = 2; + } + } + + win = editor.windowManager.open({ + title: title, + file: editor.settings.external_filemanager_path+'dialog.php?type=4&descending='+descending+sort_by+fldr+'&lang='+editor.settings.language+'&akey='+akey, + width: 860, + height: 570, + inline: 1, + resizable: true, + maximizable: true + }); + } + + editor.addButton('responsivefilemanager', { + icon: 'browse', + tooltip: 'Insert file', + shortcut: 'Ctrl+E', + onclick:openmanager + }); + + editor.addShortcut('Ctrl+E', '', openmanager); + + editor.addMenuItem('responsivefilemanager', { + icon: 'browse', + text: 'Insert file', + shortcut: 'Ctrl+E', + onclick: openmanager, + context: 'insert' + }); + +}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.min.js new file mode 100644 index 0000000..d5db47e --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/responsivefilemanager/plugin.min.js @@ -0,0 +1,9 @@ +/** + * plugin.js + * + * Copyright, Alberto Peripolli + * Released under Creative Commons Attribution-NonCommercial 3.0 Unported License. + * + * Contributing: https://github.com/trippo/ResponsiveFilemanager + */ +tinymce.PluginManager.add("responsivefilemanager",function(e){function t(){e.focus(true);var t="RESPONSIVE FileManager";if(typeof e.settings.filemanager_title!=="undefined"&&e.settings.filemanager_title){t=e.settings.filemanager_title}var n="key";if(typeof e.settings.filemanager_access_key!=="undefined"&&e.settings.filemanager_access_key){n=e.settings.filemanager_access_key}var r="";if(typeof e.settings.filemanager_sort_by!=="undefined"&&e.settings.filemanager_sort_by){r="&sort_by="+e.settings.filemanager_sort_by}var i="false";if(typeof e.settings.filemanager_descending!=="undefined"&&e.settings.filemanager_descending){i=e.settings.filemanager_descending}var s="";if(typeof e.settings.filemanager_subfolder!=="undefined"&&e.settings.filemanager_subfolder){s="&fldr="+e.settings.filemanager_subfolder}var o=2;if(typeof e.settings.filemanager_type!=="undefined"&&e.settings.filemanager_type){if($.isNumeric(e.settings.filemanager_type)===true&&e.settings.filemanager_type>0&&e.settings.filemanager_type<=3){o=e.settings.filemanager_type}else if(e.settings.filemanager_type=="image"){o=1}else if(e.settings.filemanager_type=="media"){o=3}else{o=2}}win=e.windowManager.open({title:t,file:e.settings.external_filemanager_path+"dialog.php?type=4&descending="+i+r+s+"&lang="+e.settings.language+"&akey="+n,width:860,height:570,inline:1,resizable:true,maximizable:true})}e.addButton("responsivefilemanager",{icon:"browse",tooltip:"Insert file",shortcut:"Ctrl+E",onclick:t});e.addShortcut("Ctrl+E","",t);e.addMenuItem("responsivefilemanager",{icon:"browse",text:"Insert file",shortcut:"Ctrl+E",onclick:t,context:"insert"})}) \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/save/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/save/plugin.min.js new file mode 100644 index 0000000..ff35bb4 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/save/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("save",function(e){function t(){var t;return t=tinymce.DOM.getParent(e.id,"form"),!e.getParam("save_enablewhendirty",!0)||e.isDirty()?(tinymce.triggerSave(),e.getParam("save_onsavecallback")?(e.execCallback("save_onsavecallback",e),void e.nodeChanged()):void(t?(e.setDirty(!1),(!t.onsubmit||t.onsubmit())&&("function"==typeof t.submit?t.submit():n(e.translate("Error: Form submit field collision."))),e.nodeChanged()):n(e.translate("Error: No form element found.")))):void 0}function n(t){e.notificationManager.open({text:t,type:"error"})}function r(){var t=tinymce.trim(e.startContent);return e.getParam("save_oncancelcallback")?void e.execCallback("save_oncancelcallback",e):(e.setContent(t),e.undoManager.clear(),void e.nodeChanged())}function i(){var t=this;e.on("nodeChange dirty",function(){t.disabled(e.getParam("save_enablewhendirty",!0)&&!e.isDirty())})}e.addCommand("mceSave",t),e.addCommand("mceCancel",r),e.addButton("save",{icon:"save",text:"Save",cmd:"mceSave",disabled:!0,onPostRender:i}),e.addButton("cancel",{text:"Cancel",icon:!1,cmd:"mceCancel",disabled:!0,onPostRender:i}),e.addShortcut("Meta+S","","mceSave")}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/searchreplace/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/searchreplace/plugin.min.js new file mode 100644 index 0000000..811e3e6 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/searchreplace/plugin.min.js @@ -0,0 +1 @@ +!function(){function e(e){return e&&1==e.nodeType&&"false"===e.contentEditable}function t(t,n,r,i,o){function a(e,t){if(t=t||0,!e[0])throw"findAndReplaceDOMText cannot handle zero-length matches";var n=e.index;if(t>0){var r=e[t];if(!r)throw"Invalid capture group";n+=e[0].indexOf(r),e[0]=r}return[n,n+e[0].length,[e[0]]]}function s(t){var n;if(3===t.nodeType)return t.data;if(m[t.nodeName]&&!p[t.nodeName])return"";if(n="",e(t))return"\n";if((p[t.nodeName]||h[t.nodeName])&&(n+="\n"),t=t.firstChild)do n+=s(t);while(t=t.nextSibling);return n}function l(t,n,r){var i,o,a,s,l=[],c=0,u=t,d=n.shift(),f=0;e:for(;;){if((p[u.nodeName]||h[u.nodeName]||e(u))&&c++,3===u.nodeType&&(!o&&u.length+c>=d[1]?(o=u,s=d[1]-c):i&&l.push(u),!i&&u.length+c>d[0]&&(i=u,a=d[0]-c),c+=u.length),i&&o){if(u=r({startNode:i,startNodeIndex:a,endNode:o,endNodeIndex:s,innerNodes:l,match:d[2],matchIndex:f}),c-=o.length-s,i=null,o=null,l=[],d=n.shift(),f++,!d)break}else if(m[u.nodeName]&&!p[u.nodeName]||!u.firstChild){if(u.nextSibling){u=u.nextSibling;continue}}else if(!e(u)){u=u.firstChild;continue}for(;;){if(u.nextSibling){u=u.nextSibling;break}if(u.parentNode===t)break e;u=u.parentNode}}}function c(e){var t;if("function"!=typeof e){var n=e.nodeType?e:f.createElement(e);t=function(e,t){var r=n.cloneNode(!1);return r.setAttribute("data-mce-index",t),e&&r.appendChild(f.createTextNode(e)),r}}else t=e;return function(e){var n,r,i,o=e.startNode,a=e.endNode,s=e.matchIndex;if(o===a){var l=o;i=l.parentNode,e.startNodeIndex>0&&(n=f.createTextNode(l.data.substring(0,e.startNodeIndex)),i.insertBefore(n,l));var c=t(e.match[0],s);return i.insertBefore(c,l),e.endNodeIndexp;++p){var h=e.innerNodes[p],g=t(h.data,s);h.parentNode.replaceChild(g,h),d.push(g)}var v=t(a.data.substring(0,e.endNodeIndex),s);return i=o.parentNode,i.insertBefore(n,o),i.insertBefore(u,o),i.removeChild(o),i=a.parentNode,i.insertBefore(v,a),i.insertBefore(r,a),i.removeChild(a),v}}var u,d,f,p,m,h,g=[],v=0;if(f=n.ownerDocument,p=o.getBlockElements(),m=o.getWhiteSpaceElements(),h=o.getShortEndedElements(),d=s(n)){if(t.global)for(;u=t.exec(d);)g.push(a(u,i));else u=d.match(t),g.push(a(u,i));return g.length&&(v=g.length,l(n,g,c(r))),v}}function n(e){function n(){function t(){o.statusbar.find("#next").disabled(!a(d+1).length),o.statusbar.find("#prev").disabled(!a(d-1).length)}function n(){tinymce.ui.MessageBox.alert("Could not find the specified string.",function(){o.find("#find")[0].focus()})}var r,i={};r=tinymce.trim(e.selection.getContent({format:"text"}));var o=tinymce.ui.Factory.create({type:"window",layout:"flex",pack:"center",align:"center",onClose:function(){e.focus(),u.done()},onSubmit:function(e){var r,s,l,c;return e.preventDefault(),s=o.find("#case").checked(),c=o.find("#words").checked(),l=o.find("#find").value(),l.length?i.text==l&&i.caseState==s&&i.wholeWord==c?0===a(d+1).length?void n():(u.next(),void t()):(r=u.find(l,s,c),r||n(),o.statusbar.items().slice(1).disabled(0===r),t(),void(i={text:l,caseState:s,wholeWord:c})):(u.done(!1),void o.statusbar.items().slice(1).disabled(!0))},buttons:[{text:"Find",subtype:"primary",onclick:function(){o.submit()}},{text:"Replace",disabled:!0,onclick:function(){u.replace(o.find("#replace").value())||(o.statusbar.items().slice(1).disabled(!0),d=-1,i={})}},{text:"Replace all",disabled:!0,onclick:function(){u.replace(o.find("#replace").value(),!0,!0),o.statusbar.items().slice(1).disabled(!0),i={}}},{type:"spacer",flex:1},{text:"Prev",name:"prev",disabled:!0,onclick:function(){u.prev(),t()}},{text:"Next",name:"next",disabled:!0,onclick:function(){u.next(),t()}}],title:"Find and replace",items:{type:"form",padding:20,labelGap:30,spacing:10,items:[{type:"textbox",name:"find",size:40,label:"Find",value:r},{type:"textbox",name:"replace",size:40,label:"Replace with"},{type:"checkbox",name:"case",text:"Match case",label:" "},{type:"checkbox",name:"words",text:"Whole words",label:" "}]}}).renderTo().reflow()}function r(e){var t=e.getAttribute("data-mce-index");return"number"==typeof t?""+t:t}function i(n){var r,i;return i=e.dom.create("span",{"data-mce-bogus":1}),i.className="mce-match-marker",r=e.getBody(),u.done(!1),t(n,r,i,!1,e.schema)}function o(e){var t=e.parentNode;e.firstChild&&t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function a(t){var n,i=[];if(n=tinymce.toArray(e.getBody().getElementsByTagName("span")),n.length)for(var o=0;o0}var u=this,d=-1;u.init=function(e){e.addMenuItem("searchreplace",{text:"Find and replace",shortcut:"Meta+F",onclick:n,separator:"before",context:"edit"}),e.addButton("searchreplace",{tooltip:"Find and replace",shortcut:"Meta+F",onclick:n}),e.addCommand("SearchReplace",n),e.shortcuts.add("Meta+F","",n)},u.find=function(e,t,n){e=e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&"),e=n?"\\b"+e+"\\b":e;var r=i(new RegExp(e,t?"g":"gi"));return r&&(d=-1,d=s(!0)),r},u.next=function(){var e=s(!0);-1!==e&&(d=e)},u.prev=function(){var e=s(!1);-1!==e&&(d=e)},u.replace=function(t,n,i){var s,f,p,m,h,g,v=d;for(n=n!==!1,p=e.getBody(),f=tinymce.grep(tinymce.toArray(p.getElementsByTagName("span")),c),s=0;sd&&f[s].setAttribute("data-mce-index",h-1)}return e.undoManager.add(),d=v,n?(g=a(v+1).length>0,u.next()):(g=a(v-1).length>0,u.prev()),!i&&g},u.done=function(t){var n,i,a,s;for(i=tinymce.toArray(e.getBody().getElementsByTagName("span")),n=0;n=l.end?(o=d,s=l.end-u):i&&c.push(d),!i&&d.length+u>l.start&&(i=d,a=l.start-u),u+=d.length),i&&o){if(d=r({startNode:i,startNodeIndex:a,endNode:o,endNodeIndex:s,innerNodes:c,match:l.text,matchIndex:f}),u-=o.length-s,i=null,o=null,c=[],l=n.shift(),f++,!l)break}else if(_[d.nodeName]&&!N[d.nodeName]||!d.firstChild){if(d.nextSibling){d=d.nextSibling;continue}}else if(!e(d)){d=d.firstChild;continue}for(;;){if(d.nextSibling){d=d.nextSibling;break}if(d.parentNode===t)break e;d=d.parentNode}}}function a(e){function t(t,n){var r=k[n];r.stencil||(r.stencil=e(r));var i=r.stencil.cloneNode(!1);return i.setAttribute("data-mce-index",n),t&&i.appendChild(S.doc.createTextNode(t)),i}return function(e){var n,r,i,o=e.startNode,a=e.endNode,s=e.matchIndex,l=S.doc;if(o===a){var c=o;i=c.parentNode,e.startNodeIndex>0&&(n=l.createTextNode(c.data.substring(0,e.startNodeIndex)),i.insertBefore(n,c));var u=t(e.match,s);return i.insertBefore(u,c),e.endNodeIndexp;++p){var h=e.innerNodes[p],g=t(h.data,s);h.parentNode.replaceChild(g,h),f.push(g)}var v=t(a.data.substring(0,e.endNodeIndex),s);return i=o.parentNode,i.insertBefore(n,o),i.insertBefore(d,o),i.removeChild(o),i=a.parentNode,i.insertBefore(v,a),i.insertBefore(r,a),i.removeChild(a),v}}function s(e){var t=e.parentNode;t.insertBefore(e.firstChild,e),e.parentNode.removeChild(e)}function l(e){var n=t.getElementsByTagName("*"),r=[];e="number"==typeof e?""+e:null;for(var i=0;it&&e(k[t],t)!==!1;t++);return this}function f(e){return k.length&&o(t,k,a(e)),this}function p(e,t){if(w&&e.global)for(;C=e.exec(w);)k.push(r(C,t));return this}function m(e){var t,n=l(e?c(e):null);for(t=n.length;t--;)s(n[t]);return this}function h(e){return k[e.getAttribute("data-mce-index")]}function g(e){return l(c(e))[0]}function v(e,t,n){return k.push({start:e,end:e+t,text:w.substr(e,t),data:n}),this}function y(e){var t=l(c(e)),r=n.dom.createRng();return r.setStartBefore(t[0]),r.setEndAfter(t[t.length-1]),r}function b(e,t){var r=y(e);return r.deleteContents(),t.length>0&&r.insertNode(n.dom.doc.createTextNode(t)),r}function x(){return k.splice(0,k.length),m(),this}var C,w,N,_,E,k=[],S=n.dom;return N=n.schema.getBlockElements(),_=n.schema.getWhiteSpaceElements(),E=n.schema.getShortEndedElements(),w=i(t),{text:w,matches:k,each:d,filter:u,reset:x,matchFromElement:h,elementFromMatch:g,find:p,add:v,wrap:f,unwrap:m,replace:b,rangeFromMatch:y,indexOf:c}}}),r("tinymce/spellcheckerplugin/Plugin",["tinymce/spellcheckerplugin/DomTextMatcher","tinymce/PluginManager","tinymce/util/Tools","tinymce/ui/Menu","tinymce/dom/DOMUtils","tinymce/util/XHR","tinymce/util/URI","tinymce/util/JSON"],function(e,t,n,r,i,o,a,s){t.add("spellchecker",function(t,l){function c(){return A.textMatcher||(A.textMatcher=new e(t.getBody(),t)),A.textMatcher}function u(e,t){var r=[];return n.each(t,function(e){r.push({selectable:!0,text:e.name,data:e.value})}),r}function d(e){for(var t in e)return!1;return!0}function f(e,o){var a=[],s=k[e];n.each(s,function(e){a.push({text:e,onclick:function(){t.insertContent(t.dom.encode(e)),t.dom.remove(o),v()}})}),a.push({text:"-"}),R&&a.push({text:"Add to Dictionary",onclick:function(){y(e,o)}}),a.push.apply(a,[{text:"Ignore",onclick:function(){b(e,o)}},{text:"Ignore all",onclick:function(){b(e,o,!0)}}]),T=new r({items:a,context:"contextmenu",onautohide:function(e){-1!=e.target.className.indexOf("spellchecker")&&e.preventDefault()},onhide:function(){T.remove(),T=null}}),T.renderTo(document.body);var l=i.DOM.getPos(t.getContentAreaContainer()),c=t.dom.getPos(o[0]),u=t.dom.getRoot();"BODY"==u.nodeName?(c.x-=u.ownerDocument.documentElement.scrollLeft||u.scrollLeft,c.y-=u.ownerDocument.documentElement.scrollTop||u.scrollTop):(c.x-=u.scrollLeft,c.y-=u.scrollTop),l.x+=c.x,l.y+=c.y,T.moveTo(l.x,l.y+o[0].offsetHeight)}function p(){return t.getParam("spellchecker_wordchar_pattern")||new RegExp('[^\\s!"#$%&()*+,-./:;<=>?@[\\]^_{|}`\xa7\xa9\xab\xae\xb1\xb6\xb7\xb8\xbb\xbc\xbd\xbe\xbf\xd7\xf7\xa4\u201d\u201c\u201e\xa0\u2002\u2003\u2009]+',"g")}function m(e,r,i,c){var u={method:e},d="";"spellcheck"==e&&(u.text=r,u.lang=B.spellchecker_language),"addToDictionary"==e&&(u.word=r),n.each(u,function(e,t){d&&(d+="&"),d+=t+"="+encodeURIComponent(e)}),o.send({url:new a(l).toAbsolute(B.spellchecker_rpc_url),type:"post",content_type:"application/x-www-form-urlencoded",data:d,success:function(e){if(e=s.parse(e))e.error?c(e.error):i(e);else{var n=t.translate("Server response wasn't proper JSON.");c(n)}},error:function(){var e=t.translate("The spelling service was not found: (")+B.spellchecker_rpc_url+t.translate(")");c(e)}})}function h(e,t,n,r){var i=B.spellchecker_callback||m;i.call(A,e,t,n,r)}function g(){function e(e){t.notificationManager.open({text:e,type:"error"}),t.setProgressState(!1),x()}x()||(t.setProgressState(!0),h("spellcheck",c().text,_,e),t.focus())}function v(){t.dom.select("span.mce-spellchecker-word").length||x()}function y(e,n){t.setProgressState(!0),h("addToDictionary",e,function(){t.setProgressState(!1),t.dom.remove(n,!0),v()},function(e){t.notificationManager.open({text:e,type:"error"}),t.setProgressState(!1)})}function b(e,r,i){t.selection.collapse(),i?n.each(t.dom.select("span.mce-spellchecker-word"),function(n){n.getAttribute("data-mce-word")==e&&t.dom.remove(n,!0)}):t.dom.remove(r,!0),v()}function x(){return c().reset(),A.textMatcher=null,S?(S=!1,t.fire("SpellcheckEnd"),!0):void 0}function C(e){var t=e.getAttribute("data-mce-index");return"number"==typeof t?""+t:t}function w(e){var r,i=[];if(r=n.toArray(t.getBody().getElementsByTagName("span")),r.length)for(var o=0;o0){var i=t.dom.createRng();i.setStartBefore(r[0]),i.setEndAfter(r[r.length-1]),t.selection.setRng(i),f(n.getAttribute("data-mce-word"),r)}}}),t.addMenuItem("spellchecker",{text:"Spellcheck",context:"tools",onclick:g,selectable:!0,onPostRender:function(){var e=this;e.active(S),t.on("SpellcheckStart SpellcheckEnd",function(){e.active(S)})}});var M={tooltip:"Spellcheck",onclick:g,onPostRender:function(){var e=this;t.on("SpellcheckStart SpellcheckEnd",function(){e.active(S)})}};E.length>1&&(M.type="splitbutton",M.menu=E,M.onshow=N,M.onselect=function(e){B.spellchecker_language=e.control.settings.data}),t.addButton("spellchecker",M),t.addCommand("mceSpellCheck",g),t.on("remove",function(){T&&(T.remove(),T=null)}),t.on("change",v),this.getTextMatcher=c,this.getWordCharPattern=p,this.markErrors=_,this.getLanguage=function(){return B.spellchecker_language},B.spellchecker_language=B.spellchecker_language||B.language||"en"})}),o(["tinymce/spellcheckerplugin/DomTextMatcher"])}(this); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/tabfocus/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/tabfocus/plugin.min.js new file mode 100644 index 0000000..25a990f --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/tabfocus/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("tabfocus",function(e){function t(e){9!==e.keyCode||e.ctrlKey||e.altKey||e.metaKey||e.preventDefault()}function n(t){function n(n){function o(e){return"BODY"===e.nodeName||"hidden"!=e.type&&"none"!=e.style.display&&"hidden"!=e.style.visibility&&o(e.parentNode)}function l(e){return/INPUT|TEXTAREA|BUTTON/.test(e.tagName)&&tinymce.get(t.id)&&-1!=e.tabIndex&&o(e)}if(s=r.select(":input:enabled,*[tabindex]:not(iframe)"),i(s,function(t,n){return t.id==e.id?(a=n,!1):void 0}),n>0){for(c=a+1;c=0;c--)if(l(s[c]))return s[c];return null}var a,s,l,c;if(!(9!==t.keyCode||t.ctrlKey||t.altKey||t.metaKey||t.isDefaultPrevented())&&(l=o(e.getParam("tab_focus",e.getParam("tabfocus_elements",":prev,:next"))),1==l.length&&(l[1]=l[0],l[0]=":prev"),s=t.shiftKey?":prev"==l[0]?n(-1):r.get(l[0]):":next"==l[1]?n(1):r.get(l[1]))){var u=tinymce.get(s.id||s.name);s.id&&u?u.focus():tinymce.util.Delay.setTimeout(function(){tinymce.Env.webkit||window.focus(),s.focus()},10),t.preventDefault()}}var r=tinymce.DOM,i=tinymce.each,o=tinymce.explode;e.on("init",function(){e.inline&&tinymce.DOM.setAttrib(e.getBody(),"tabIndex",null),e.on("keyup",t),tinymce.Env.gecko?e.on("keypress keydown",n):e.on("keydown",n)})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/table/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/table/plugin.min.js new file mode 100644 index 0000000..d9f89ca --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/table/plugin.min.js @@ -0,0 +1,2 @@ +!function(e,t){"use strict";function n(e,t){for(var n,r=[],a=0;a10)&&(t.innerHTML='
')}return{getSpanVal:t,paddCell:n}}),r("tinymce/tableplugin/TableGrid",["tinymce/util/Tools","tinymce/Env","tinymce/tableplugin/Utils"],function(e,n,r){var i=e.each,o=r.getSpanVal;return function(a,s){function l(e){return e===a.getBody()}function c(){var e=0;L=[],H=0,i(["thead","tbody","tfoot"],function(t){var n=W.select("> "+t+" tr",s);i(n,function(n,r){r+=e,i(W.select("> td, > th",n),function(e,n){var i,a,s,l;if(L[r])for(;L[r][n];)n++;for(s=o(e,"rowspan"),l=o(e,"colspan"),a=r;r+s>a;a++)for(L[a]||(L[a]=[]),i=n;n+l>i;i++)L[a][i]={part:t,real:a==r&&i==n,elm:e,rowspan:s,colspan:l};H=Math.max(H,n+1)})}),e+=n.length})}function u(e,t){return e=e.cloneNode(t),e.removeAttribute("id"),e}function d(e,t){var n;return n=L[t],n?n[e]:void 0}function f(e,t,n){e&&(n=parseInt(n,10),1===n?e.removeAttribute(t,1):e.setAttribute(t,n,1))}function p(e){return e&&(W.hasClass(e.elm,"mce-item-selected")||e==F)}function m(){var e=[];return i(s.rows,function(t){i(t.cells,function(n){return W.hasClass(n,"mce-item-selected")||F&&n==F.elm?(e.push(t),!1):void 0})}),e}function h(){var e=W.createRng();l(s)||(e.setStartAfter(s),e.setEndAfter(s),z.setRng(e),W.remove(s))}function g(t){var o,s={};return a.settings.table_clone_elements!==!1&&(s=e.makeMap((a.settings.table_clone_elements||"strong em b i span font h1 h2 h3 h4 h5 h6 p div").toUpperCase(),/[ ,]/)),e.walk(t,function(e){var r;return 3==e.nodeType?(i(W.getParents(e.parentNode,null,t).reverse(),function(e){s[e.nodeName]&&(e=u(e,!1),o?r&&r.appendChild(e):o=r=e,r=e)}),r&&(r.innerHTML=n.ie?" ":'
'),!1):void 0},"childNodes"),t=u(t,!1),f(t,"rowSpan",1),f(t,"colSpan",1),o?t.appendChild(o):r.paddCell(t),t}function v(){var e,t=W.createRng();return i(W.select("tr",s),function(e){0===e.cells.length&&W.remove(e)}),0===W.select("tr",s).length?(t.setStartBefore(s),t.setEndBefore(s),z.setRng(t),void W.remove(s)):(i(W.select("thead,tbody,tfoot",s),function(e){0===e.rows.length&&W.remove(e)}),c(),void(I&&(e=L[Math.min(L.length-1,I.y)],e&&(z.select(e[Math.min(e.length-1,I.x)].elm,!0),z.collapse(!0)))))}function y(e,t,n,r){var i,o,a,s,l;for(i=L[t][e].elm.parentNode,a=1;n>=a;a++)if(i=W.getNext(i,"tr")){for(o=e;o>=0;o--)if(l=L[t+a][o].elm,l.parentNode==i){for(s=1;r>=s;s++)W.insertAfter(g(l),l);break}if(-1==o)for(s=1;r>=s;s++)i.insertBefore(g(i.cells[0]),i.cells[0])}}function b(){i(L,function(e,t){i(e,function(e,n){var r,i,a;if(p(e)&&(e=e.elm,r=o(e,"colspan"),i=o(e,"rowspan"),r>1||i>1)){for(f(e,"rowSpan",1),f(e,"colSpan",1),a=0;r-1>a;a++)W.insertAfter(g(e),e);y(n,t,i-1,r)}})})}function x(t,n,r){var o,a,s,l,u,m,h,g,y,x,C;if(t?(o=A(t),a=o.x,s=o.y,l=a+(n-1),u=s+(r-1)):(I=O=null,i(L,function(e,t){i(e,function(e,n){p(e)&&(I||(I={x:n,y:t}),O={x:n,y:t})})}),I&&(a=I.x,s=I.y,l=O.x,u=O.y)),g=d(a,s),y=d(l,u),g&&y&&g.part==y.part){for(b(),c(),g=d(a,s).elm,f(g,"colSpan",l-a+1),f(g,"rowSpan",u-s+1),h=s;u>=h;h++)for(m=a;l>=m;m++)L[h]&&L[h][m]&&(t=L[h][m].elm,t!=g&&(x=e.grep(t.childNodes),i(x,function(e){g.appendChild(e)}),x.length&&(x=e.grep(g.childNodes),C=0,i(x,function(e){"BR"==e.nodeName&&W.getAttrib(e,"data-mce-bogus")&&C++0&&L[n-1][s]&&(m=L[n-1][s].elm,h=o(m,"rowSpan"),h>1)){f(m,"rowSpan",h+1);continue}}else if(h=o(r,"rowspan"),h>1){f(r,"rowSpan",h+1);continue}d=g(r),f(d,"colSpan",r.colSpan),c.appendChild(d),a=r}c.hasChildNodes()&&(e?l.parentNode.insertBefore(c,l):W.insertAfter(c,l))}}function w(e){var t,n;i(L,function(n){return i(n,function(n,r){return p(n)&&(t=r,e)?!1:void 0}),e?!t:void 0}),i(L,function(r,i){var a,s,l;r[t]&&(a=r[t].elm,a!=n&&(l=o(a,"colspan"),s=o(a,"rowspan"),1==l?e?(a.parentNode.insertBefore(g(a),a),y(t,i,s-1,l)):(W.insertAfter(g(a),a),y(t,i,s-1,l)):f(a,"colSpan",a.colSpan+1),n=a))})}function N(t){return e.grep(_(t),p)}function _(e){var t=[];return i(e,function(e){i(e,function(e){t.push(e)})}),t}function E(){var t=[];if(l(s)){if(1==L[0].length)return;if(N(L).length==_(L).length)return}i(L,function(n){i(n,function(n,r){p(n)&&-1===e.inArray(t,r)&&(i(L,function(e){var t,n=e[r].elm;t=o(n,"colSpan"),t>1?f(n,"colSpan",t-1):W.remove(n)}),t.push(r))})}),v()}function k(){function e(e){var t,n;i(e.cells,function(e){var n=o(e,"rowSpan");n>1&&(f(e,"rowSpan",n-1),t=A(e),y(t.x,t.y,1,1))}),t=A(e.cells[0]),i(L[t.y],function(e){var t;e=e.elm,e!=n&&(t=o(e,"rowSpan"),1>=t?W.remove(e):f(e,"rowSpan",t-1),n=e)})}var t;t=m(),l(s)&&t.length==s.rows.length||(i(t.reverse(),function(t){e(t)}),v())}function S(){var e=m();if(!l(s)||e.length!=s.rows.length)return W.remove(e),v(),e}function T(){var e=m();return i(e,function(t,n){e[n]=u(t,!0)}),e}function R(e,t){var n=m(),r=n[t?0:n.length-1],o=r.cells.length;e&&(i(L,function(e){var t;return o=0,i(e,function(e){e.real&&(o+=e.colspan),e.elm.parentNode==r&&(t=1)}),t?!1:void 0}),t||e.reverse(),i(e,function(e){var n,i,a=e.cells.length;for(n=0;a>n;n++)i=e.cells[n],f(i,"colSpan",1),f(i,"rowSpan",1);for(n=a;o>n;n++)e.appendChild(g(e.cells[a-1]));for(n=o;a>n;n++)W.remove(e.cells[n]);t?r.parentNode.insertBefore(e,r):W.insertAfter(e,r)}),W.removeClass(W.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"))}function A(e){var t;return i(L,function(n,r){return i(n,function(n,i){return n.elm==e?(t={x:i,y:r},!1):void 0}),!t}),t}function B(e){I=A(e)}function D(){var e,t;return e=t=0,i(L,function(n,r){i(n,function(n,i){var o,a;p(n)&&(n=L[r][i],i>e&&(e=i),r>t&&(t=r),n.real&&(o=n.colspan-1,a=n.rowspan-1,o&&i+o>e&&(e=i+o),a&&r+a>t&&(t=r+a)))})}),{x:e,y:t}}function M(e){var t,n,r,i,o,a,s,l,c,u;if(O=A(e),I&&O){for(t=Math.min(I.x,O.x),n=Math.min(I.y,O.y),r=Math.max(I.x,O.x),i=Math.max(I.y,O.y),o=r,a=i,u=n;a>=u;u++)e=L[u][t],e.real||t-(e.colspan-1)=c;c++)e=L[n][c],e.real||n-(e.rowspan-1)=u;u++)for(c=t;r>=c;c++)e=L[u][c],e.real&&(s=e.colspan-1,l=e.rowspan-1,s&&c+s>o&&(o=c+s),l&&u+l>a&&(a=u+l));for(W.removeClass(W.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),u=n;a>=u;u++)for(c=t;o>=c;c++)L[u][c]&&W.addClass(L[u][c].elm,"mce-item-selected")}}function P(e,t){var n,r,i;n=A(e),r=n.y*H+n.x;do{if(r+=t,i=d(r%H,Math.floor(r/H)),!i)break;if(i.elm!=e)return z.select(i.elm,!0),W.isEmpty(i.elm)&&z.collapse(!0),!0}while(i.elm==e);return!1}var L,H,I,O,F,z=a.selection,W=z.dom;s=s||W.getParent(z.getStart(!0),"table"),c(),F=W.getParent(z.getStart(!0),"th,td"),F&&(I=A(F),O=D(),F=d(I.x,I.y)),e.extend(this,{deleteTable:h,split:b,merge:x,insertRow:C,insertCol:w,deleteCols:E,deleteRows:k,cutRows:S,copyRows:T,pasteRows:R,getPos:A,setStartCell:B,setEndCell:M,moveRelIdx:P,refresh:c})}}),r("tinymce/tableplugin/Quirks",["tinymce/util/VK","tinymce/util/Delay","tinymce/Env","tinymce/util/Tools","tinymce/tableplugin/Utils"],function(e,t,n,r,i){var o=r.each,a=i.getSpanVal;return function(s){function l(){function n(n){function r(e,t){var r=e?"previousSibling":"nextSibling",o=s.dom.getParent(t,"tr"),a=o[r];if(a)return v(s,t,a,e),n.preventDefault(),!0;var l=s.dom.getParent(o,"table"),d=o.parentNode,f=d.nodeName.toLowerCase();if("tbody"===f||f===(e?"tfoot":"thead")){var p=i(e,l,d,"tbody");if(null!==p)return c(e,p,t)}return u(e,o,r,l)}function i(e,t,n,r){var i=s.dom.select(">"+r,t),o=i.indexOf(n);if(e&&0===o||!e&&o===i.length-1)return l(e,t);if(-1===o){var a="thead"===n.tagName.toLowerCase()?0:i.length-1;return i[a]}return i[o+(e?-1:1)]}function l(e,t){var n=e?"thead":"tfoot",r=s.dom.select(">"+n,t);return 0!==r.length?r[0]:null}function c(e,t,r){var i=d(t,e);return i&&v(s,r,i,e),n.preventDefault(),!0}function u(e,t,i,o){var a=o[i];if(a)return f(a),!0;var l=s.dom.getParent(o,"td,th");if(l)return r(e,l,n);var c=d(t,!e);return f(c),n.preventDefault(),!1}function d(e,t){var n=e&&e[t?"lastChild":"firstChild"];return n&&"BR"===n.nodeName?s.dom.getParent(n,"td,th"):n}function f(e){s.selection.setCursorLocation(e,0)}function p(){return x==e.UP||x==e.DOWN}function m(e){var t=e.selection.getNode(),n=e.dom.getParent(t,"tr");return null!==n}function h(e){for(var t=0,n=e;n.previousSibling;)n=n.previousSibling,t+=a(n,"colspan");return t}function g(e,t){var n=0,r=0;return o(e.children,function(e,i){return n+=a(e,"colspan"),r=i,n>t?!1:void 0}),r}function v(e,t,n,r){var i=h(s.dom.getParent(t,"td,th")),o=g(n,i),a=n.childNodes[o],l=d(a,r);f(l||a)}function y(e){var t=s.selection.getNode(),n=s.dom.getParent(t,"td,th"),r=s.dom.getParent(e,"td,th");return n&&n!==r&&b(n,r)}function b(e,t){return s.dom.getParent(e,"TABLE")===s.dom.getParent(t,"TABLE")}var x=n.keyCode;if(p()&&m(s)){var C=s.selection.getNode();t.setEditorTimeout(s,function(){y(C)&&r(!n.shiftKey&&x===e.UP,C,n)},0)}}s.on("KeyDown",function(e){n(e)})}function c(){function e(e,t){var n,r=t.ownerDocument,i=r.createRange();return i.setStartBefore(t),i.setEnd(e.endContainer,e.endOffset),n=r.createElement("body"),n.appendChild(i.cloneContents()),0===n.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length}s.on("KeyDown",function(t){var n,r,i=s.dom;(37==t.keyCode||38==t.keyCode)&&(n=s.selection.getRng(),r=i.getParent(n.startContainer,"table"),r&&s.getBody().firstChild==r&&e(n,r)&&(n=i.createRng(),n.setStartBefore(r),n.setEndBefore(r),s.selection.setRng(n),t.preventDefault()))})}function u(){s.on("KeyDown SetContent VisualAid",function(){var e;for(e=s.getBody().lastChild;e;e=e.previousSibling)if(3==e.nodeType){if(e.nodeValue.length>0)break}else if(1==e.nodeType&&("BR"==e.tagName||!e.getAttribute("data-mce-bogus")))break;e&&"TABLE"==e.nodeName&&(s.settings.forced_root_block?s.dom.add(s.getBody(),s.settings.forced_root_block,s.settings.forced_root_block_attrs,n.ie&&n.ie<11?" ":'
'):s.dom.add(s.getBody(),"br",{"data-mce-bogus":"1"}))}),s.on("PreProcess",function(e){var t=e.node.lastChild;t&&("BR"==t.nodeName||1==t.childNodes.length&&("BR"==t.firstChild.nodeName||"\xa0"==t.firstChild.nodeValue))&&t.previousSibling&&"TABLE"==t.previousSibling.nodeName&&s.dom.remove(t)})}function d(){function e(e,t,n,r){var i,o,a,s=3,l=e.dom.getParent(t.startContainer,"TABLE");return l&&(i=l.parentNode),o=t.startContainer.nodeType==s&&0===t.startOffset&&0===t.endOffset&&r&&("TR"==n.nodeName||n==i),a=("TD"==n.nodeName||"TH"==n.nodeName)&&!r,o||a}function t(){var t=s.selection.getRng(),n=s.selection.getNode(),r=s.dom.getParent(t.startContainer,"TD,TH");if(e(s,t,n,r)){r||(r=n);for(var i=r.lastChild;i.lastChild;)i=i.lastChild;3==i.nodeType&&(t.setEnd(i,i.data.length),s.selection.setRng(t))}}s.on("KeyDown",function(){t()}),s.on("MouseDown",function(e){2!=e.button&&t()})}function f(){function t(e){s.selection.select(e,!0),s.selection.collapse(!0)}function n(e){s.$(e).empty(),i.paddCell(e)}s.on("keydown",function(i){if((i.keyCode==e.DELETE||i.keyCode==e.BACKSPACE)&&!i.isDefaultPrevented()){var o,a,l,c;if(o=s.dom.getParent(s.selection.getStart(),"table")){if(a=s.dom.select("td,th",o),l=r.grep(a,function(e){return s.dom.hasClass(e,"mce-item-selected")}),0===l.length)return c=s.dom.getParent(s.selection.getStart(),"td,th"),void(s.selection.isCollapsed()&&c&&s.dom.isEmpty(c)&&(i.preventDefault(),n(c),t(c)));i.preventDefault(),s.undoManager.transact(function(){a.length==l.length?s.execCommand("mceTableDelete"):(r.each(l,n),t(l[0]))})}}})}f(),n.webkit&&(l(),d()),n.gecko&&(c(),u()),n.ie>10&&(c(),u())}}),r("tinymce/tableplugin/CellSelection",["tinymce/tableplugin/TableGrid","tinymce/dom/TreeWalker","tinymce/util/Tools"],function(e,t,n){return function(r){function i(e){r.getBody().style.webkitUserSelect="",(e||d)&&(r.dom.removeClass(r.dom.select("td.mce-item-selected,th.mce-item-selected"),"mce-item-selected"),d=!1)}function o(t){var n,i,o=t.target;if(!c&&s&&(a||o!=s)&&("TD"==o.nodeName||"TH"==o.nodeName)){i=u.getParent(o,"table"),i==l&&(a||(a=new e(r,i),a.setStartCell(s),r.getBody().style.webkitUserSelect="none"),a.setEndCell(o),d=!0),n=r.selection.getSel();try{n.removeAllRanges?n.removeAllRanges():n.empty()}catch(f){}t.preventDefault()}}var a,s,l,c,u=r.dom,d=!0;return r.on("MouseDown",function(e){2==e.button||c||(i(),s=u.getParent(e.target,"td,th"),l=u.getParent(s,"table"))}),r.on("mouseover",o),r.on("remove",function(){u.unbind(r.getDoc(),"mouseover",o)}),r.on("MouseUp",function(){function e(e,r){var o=new t(e,e);do{if(3==e.nodeType&&0!==n.trim(e.nodeValue).length)return void(r?i.setStart(e,0):i.setEnd(e,e.nodeValue.length));if("BR"==e.nodeName)return void(r?i.setStartBefore(e):i.setEndBefore(e))}while(e=r?o.next():o.prev())}var i,o,c,d,f,p=r.selection;if(s){if(a&&(r.getBody().style.webkitUserSelect=""),o=u.select("td.mce-item-selected,th.mce-item-selected"),o.length>0){i=u.createRng(),d=o[0],i.setStartBefore(d),i.setEndAfter(d),e(d,1),c=new t(d,u.getParent(o[0],"table"));do if("TD"==d.nodeName||"TH"==d.nodeName){if(!u.hasClass(d,"mce-item-selected"))break;f=d}while(d=c.next());e(f),p.setRng(i)}r.nodeChanged(),s=a=l=null}}),r.on("KeyUp Drop SetContent",function(e){i("setcontent"==e.type),s=a=l=null,c=!1}),r.on("ObjectResizeStart ObjectResized",function(e){c="objectresized"!=e.type}),{clear:i}}}),r("tinymce/tableplugin/Dialogs",["tinymce/util/Tools","tinymce/Env"],function(e,t){var n=e.each;return function(r){function i(){var e=r.settings.color_picker_callback;return e?function(){var t=this;e.call(r,function(e){t.value(e).fire("change")},t.value())}:void 0}function o(e){return{title:"Advanced",type:"form",defaults:{onchange:function(){d(e,this.parents().reverse()[0],"style"==this.name())}},items:[{label:"Style",name:"style",type:"textbox"},{type:"form",padding:0,formItemDefaults:{layout:"grid",alignH:["start","right"]},defaults:{size:7},items:[{label:"Border color",type:"colorbox",name:"borderColor",onaction:i()},{label:"Background color",type:"colorbox",name:"backgroundColor",onaction:i()}]}]}}function a(e){return e?e.replace(/px$/,""):""}function s(e){return/^[0-9]+$/.test(e)&&(e+="px"),e}function l(e){n("left center right".split(" "),function(t){r.formatter.remove("align"+t,{},e)})}function c(e){n("top middle bottom".split(" "),function(t){r.formatter.remove("valign"+t,{},e)})}function u(t,n,r){function i(t,r){return r=r||[],e.each(t,function(e){var t={text:e.text||e.title};e.menu?t.menu=i(e.menu):(t.value=e.value,n&&n(t)),r.push(t)}),r}return i(t,r||[])}function d(e,t,n){var r=t.toJSON(),i=e.parseStyle(r.style);n?(t.find("#borderColor").value(i["border-color"]||"")[0].fire("change"),t.find("#backgroundColor").value(i["background-color"]||"")[0].fire("change")):(i["border-color"]=r.borderColor,i["background-color"]=r.backgroundColor),t.find("#style").value(e.serializeStyle(e.parseStyle(e.serializeStyle(i))))}function f(e,t,n){var r=e.parseStyle(e.getAttrib(n,"style"));r["border-color"]&&(t.borderColor=r["border-color"]),r["background-color"]&&(t.backgroundColor=r["background-color"]),t.style=e.serializeStyle(r)}function p(e,t,r){var i=e.parseStyle(e.getAttrib(t,"style"));n(r,function(e){i[e.name]=e.value}),e.setAttrib(t,"style",e.serializeStyle(e.parseStyle(e.serializeStyle(i))))}var m=this;m.tableProps=function(){m.table(!0)},m.table=function(i){function c(){function n(e,t,r){if("TD"===e.tagName||"TH"===e.tagName)C.setStyle(e,t,r);else if(e.children)for(var i=0;i',h.insertBefore(i,h.firstChild)),l(h),w.align&&r.formatter.apply("align"+w.align,{},h),r.focus(),r.addVisual()})}function m(e,t){function n(e,n){for(var r=0;rr;r++)n.push(r);return n}function S(e,t,n){for(var r,i=e(),o=0;o0?y(i,o,a):[],u=s.length>0?y(d,f,s):[];w(c,e.offsetWidth,l),N(u,e.offsetHeight,l)}function B(e,t,n,r){if(0>t||t>=e.length-1)return"";var i=e[t];if(i)i={value:i,delta:0};else for(var o=e.slice(0,t).reverse(),a=0;a0?i:o}function P(t,n,r){for(var i=T(t),o=e.map(i,function(e){return d(e.colIndex,e.element).x}),a=[],s=0;s1?B(o,s):M(i[s].element,n,r);c=c?c:Ce,a.push(c)}return a}function L(e){var t=D(e,"height"),n=parseInt(t,10);return V(t)&&(n=0),!isNaN(n)&&n>0?n:m(e,"height")}function H(t){for(var n=R(t),r=e.map(n,function(e){return i(e.rowIndex,e.element).y}),o=[],a=0;a1?B(r,a):L(n[a].element);l=l?l:we,o.push(l)}return o}function I(t,n,r,i,o){function a(t){return e.map(t,function(){return 0})}function s(){var e;if(o)e=[100-d[0]];else{var t=Math.max(i,d[0]+r);e=[t-d[0]]}return e}function l(e,t){var n,o=a(d.slice(0,e)),s=a(d.slice(t+1));if(r>=0){var l=Math.max(i,d[t]-r);n=o.concat([r,l-d[t]]).concat(s)}else{var c=Math.max(i,d[e]+r),u=d[e]-c;n=o.concat([c-d[e],u]).concat(s)}return n}function c(e,t){var n,o=a(d.slice(0,t));if(r>=0)n=o.concat([r]);else{var s=Math.max(i,d[t]+r);n=o.concat([s-d[t]])}return n}var u,d=t.slice(0);return u=0===t.length?[]:1===t.length?s():0===n?l(0,1):n>0&&ni;i++)r+=n[i];return r}function F(t,n){var r=t.getAllCells();return e.map(r,function(e){var t=O(e.colIndex,e.colIndex+e.colspan,n);return{element:e.element,width:t,colspan:e.colspan}})}function z(t,n){var r=t.getAllCells();return e.map(r,function(e){var t=O(e.rowIndex,e.rowIndex+e.rowspan,n);return{element:e.element,height:t,rowspan:e.rowspan}})}function W(t,n){var r=t.getAllRows();return e.map(r,function(e,t){return{element:e.element,height:n[t]}})}function V(e){return _e.test(e)}function U(e){return Ee.test(e)}function $(t,n,i){function o(t,n){e.each(t,function(e){r.dom.setStyle(e.element,"width",e.width+n),r.dom.setAttrib(e.element,"width",null)})}function a(){return in;n++){for(i+="",r=0;e>r;r++)i+=""+(s.ie?" ":"
")+"";i+=""}return i+="",o.undoManager.transact(function(){o.insertContent(i),a=o.dom.get("__mce"),o.dom.setAttrib(a,"id",null),o.dom.setAttribs(a,o.settings.table_default_attributes||{}),o.dom.setStyles(a,o.settings.table_default_styles||{})}),a}function c(e,t){function n(){e.disabled(!o.dom.getParent(o.selection.getStart(),t)),o.selection.selectorChanged(t,function(t){e.disabled(!t)})}o.initialized?n():o.on("init",n)}function d(){c(this,"table")}function f(){c(this,"td,th")}function p(){var e="";e='';for(var t=0;10>t;t++){e+="";for(var n=0;10>n;n++)e+='';e+=""}return e+="
",e+=''}function m(e,t,n){var r,i,a,s,l,c=n.getEl().getElementsByTagName("table")[0],u=n.isRtl()||"tl-tr"==n.parent().rel;for(c.nextSibling.innerHTML=e+1+" x "+(t+1),u&&(e=9-e),i=0;10>i;i++)for(r=0;10>r;r++)s=c.rows[i].childNodes[r].firstChild,l=(u?r>=e:e>=r)&&t>=i,o.dom.toggleClass(s,"mce-active",l),l&&(a=s);return a.parentNode}function h(){o.addButton("tableprops",{title:"Table properties",onclick:C.tableProps,icon:"table"}),o.addButton("tabledelete",{title:"Delete table",onclick:a("mceTableDelete")}),o.addButton("tablecellprops",{title:"Cell properties",onclick:a("mceTableCellProps")}),o.addButton("tablemergecells",{title:"Merge cells",onclick:a("mceTableMergeCells")}),o.addButton("tablesplitcells",{title:"Split cell",onclick:a("mceTableSplitCells")}),o.addButton("tableinsertrowbefore",{title:"Insert row before",onclick:a("mceTableInsertRowBefore")}),o.addButton("tableinsertrowafter",{title:"Insert row after",onclick:a("mceTableInsertRowAfter")}),o.addButton("tabledeleterow",{title:"Delete row",onclick:a("mceTableDeleteRow")}),o.addButton("tablerowprops",{title:"Row properties",onclick:a("mceTableRowProps")}),o.addButton("tablecutrow",{title:"Cut row",onclick:a("mceTableCutRow")}),o.addButton("tablecopyrow",{title:"Copy row",onclick:a("mceTableCopyRow")}),o.addButton("tablepasterowbefore",{title:"Paste row before",onclick:a("mceTablePasteRowBefore")}),o.addButton("tablepasterowafter",{title:"Paste row after",onclick:a("mceTablePasteRowAfter")}),o.addButton("tableinsertcolbefore",{title:"Insert column before",onclick:a("mceTableInsertColBefore")}),o.addButton("tableinsertcolafter",{title:"Insert column after",onclick:a("mceTableInsertColAfter")}),o.addButton("tabledeletecol",{title:"Delete column",onclick:a("mceTableDeleteCol")})}function g(e){var t=o.dom.is(e,"table")&&o.getBody().contains(e);return t}function v(){var e=o.settings.table_toolbar;""!==e&&e!==!1&&(e||(e="tableprops tabledelete | tableinsertrowbefore tableinsertrowafter tabledeleterow | tableinsertcolbefore tableinsertcolafter tabledeletecol"),o.addContextToolbar(g,e))}var y,b,x=this,C=new r(o);!o.settings.object_resizing||o.settings.object_resizing!==!0&&"table"!==o.settings.object_resizing||(b=i(o)),o.settings.table_grid===!1?o.addMenuItem("inserttable",{text:"Insert table",icon:"table",context:"table",onclick:C.table}):o.addMenuItem("inserttable",{text:"Insert table",icon:"table",context:"table",ariaHideMenu:!0,onclick:function(e){e.aria&&(this.parent().hideAll(),e.stopImmediatePropagation(),C.table())},onshow:function(){m(0,0,this.menu.items()[0])},onhide:function(){var e=this.menu.items()[0].getEl().getElementsByTagName("a");o.dom.removeClass(e,"mce-active"),o.dom.addClass(e[0],"mce-active")},menu:[{type:"container",html:p(),onPostRender:function(){this.lastX=this.lastY=0},onmousemove:function(e){var t,n,r=e.target;"A"==r.tagName.toUpperCase()&&(t=parseInt(r.getAttribute("data-mce-x"),10),n=parseInt(r.getAttribute("data-mce-y"),10),(this.isRtl()||"tl-tr"==this.parent().rel)&&(t=9-t),(t!==this.lastX||n!==this.lastY)&&(m(t,n,e.control),this.lastX=t,this.lastY=n))},onclick:function(e){var t=this;"A"==e.target.tagName.toUpperCase()&&(e.preventDefault(),e.stopPropagation(),t.parent().cancel(),o.undoManager.transact(function(){l(t.lastX+1,t.lastY+1)}),o.addVisual())}}]}),o.addMenuItem("tableprops",{text:"Table properties",context:"table",onPostRender:d,onclick:C.tableProps}),o.addMenuItem("deletetable",{text:"Delete table",context:"table",onPostRender:d,cmd:"mceTableDelete"}),o.addMenuItem("cell",{separator:"before",text:"Cell",context:"table",menu:[{text:"Cell properties",onclick:a("mceTableCellProps"),onPostRender:f},{text:"Merge cells",onclick:a("mceTableMergeCells"),onPostRender:f},{text:"Split cell",onclick:a("mceTableSplitCells"),onPostRender:f}]}),o.addMenuItem("row",{text:"Row",context:"table",menu:[{text:"Insert row before",onclick:a("mceTableInsertRowBefore"),onPostRender:f},{text:"Insert row after",onclick:a("mceTableInsertRowAfter"),onPostRender:f},{text:"Delete row",onclick:a("mceTableDeleteRow"),onPostRender:f},{text:"Row properties",onclick:a("mceTableRowProps"),onPostRender:f},{text:"-"},{text:"Cut row",onclick:a("mceTableCutRow"),onPostRender:f},{text:"Copy row",onclick:a("mceTableCopyRow"),onPostRender:f},{text:"Paste row before",onclick:a("mceTablePasteRowBefore"),onPostRender:f},{text:"Paste row after",onclick:a("mceTablePasteRowAfter"),onPostRender:f}]}),o.addMenuItem("column",{text:"Column",context:"table",menu:[{text:"Insert column before",onclick:a("mceTableInsertColBefore"),onPostRender:f},{text:"Insert column after",onclick:a("mceTableInsertColAfter"),onPostRender:f},{text:"Delete column",onclick:a("mceTableDeleteCol"),onPostRender:f}]});var w=[];u("inserttable tableprops deletetable | cell row column".split(" "),function(e){"|"==e?w.push({text:"-"}):w.push(o.menuItems[e])}),o.addButton("table",{type:"menubutton",title:"Table",menu:w}),s.isIE||o.on("click",function(e){e=e.target,"TABLE"===e.nodeName&&(o.selection.select(e),o.nodeChanged())}),x.quirks=new t(o),o.on("Init",function(){x.cellSelection=new n(o),x.resizeBars=b}),o.on("PreInit",function(){o.serializer.addAttributeFilter("data-mce-cell-padding,data-mce-border,data-mce-border-color",function(e,t){for(var n=e.length;n--;)e[n].attr(t,null)})}),u({mceTableSplitCells:function(e){e.split()},mceTableMergeCells:function(e){var t;t=o.dom.getParent(o.selection.getStart(),"th,td"),o.dom.select("td.mce-item-selected,th.mce-item-selected").length?e.merge():C.merge(e,t)},mceTableInsertRowBefore:function(e){e.insertRow(!0)},mceTableInsertRowAfter:function(e){e.insertRow()},mceTableInsertColBefore:function(e){e.insertCol(!0)},mceTableInsertColAfter:function(e){e.insertCol()},mceTableDeleteCol:function(e){e.deleteCols()},mceTableDeleteRow:function(e){e.deleteRows()},mceTableCutRow:function(e){y=e.cutRows()},mceTableCopyRow:function(e){y=e.copyRows()},mceTablePasteRowBefore:function(e){e.pasteRows(y,!0)},mceTablePasteRowAfter:function(e){e.pasteRows(y)},mceTableDelete:function(e){b&&b.clearBars(),e.deleteTable()}},function(t,n){o.addCommand(n,function(){var n=new e(o);n&&(t(n),o.execCommand("mceRepaint"),x.cellSelection.clear())})}),u({mceInsertTable:C.table,mceTableProps:function(){C.table(!0)},mceTableRowProps:C.row,mceTableCellProps:C.cell},function(e,t){o.addCommand(t,function(t,n){e(n)})}),h(),v(),o.settings.table_tab_navigation!==!1&&o.on("keydown",function(t){var n,r,i;9==t.keyCode&&(n=o.dom.getParent(o.selection.getStart(),"th,td"),n&&(t.preventDefault(),r=new e(o),i=t.shiftKey?-1:1,o.undoManager.transact(function(){!r.moveRelIdx(n,i)&&i>0&&(r.insertRow(),r.refresh(),r.moveRelIdx(n,i))})))}),x.insertTable=l}var u=o.each;l.add("table",c)})}(this); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/template/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/template/plugin.min.js new file mode 100644 index 0000000..53cc04c --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/template/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("template",function(e){function t(t){return function(){var n=e.settings.templates;"string"==typeof n?tinymce.util.XHR.send({url:n,success:function(e){t(tinymce.util.JSON.parse(e))}}):t(n)}}function n(t){function n(t){function n(t){if(-1==t.indexOf("")){var n="";tinymce.each(e.contentCSS,function(t){n+=''}),t=""+n+""+t+""}t=o(t,"template_preview_replace_values");var i=r.find("iframe")[0].getEl().contentWindow.document;i.open(),i.write(t),i.close()}var a=t.control.value();a.url?tinymce.util.XHR.send({url:a.url,success:function(e){i=e,n(i)}}):(i=a.content,n(i)),r.find("#description")[0].text(t.control.value().description)}var r,i,s=[];if(!t||0===t.length){var l=e.translate("No templates defined.");return void e.notificationManager.open({text:l,type:"info"})}tinymce.each(t,function(e){s.push({selected:!s.length,text:e.title,value:{url:e.url,content:e.content,description:e.description}})}),r=e.windowManager.open({title:"Insert template",layout:"flex",direction:"column",align:"stretch",padding:15,spacing:10,items:[{type:"form",flex:0,padding:0,items:[{type:"container",label:"Templates",items:{type:"listbox",label:"Templates",name:"template",values:s,onselect:n}}]},{type:"label",name:"description",label:"Description",text:"\xa0"},{type:"iframe",flex:1,border:1}],onsubmit:function(){a(!1,i)},width:e.getParam("template_popup_width",600),height:e.getParam("template_popup_height",500)}),r.find("listbox")[0].fire("select")}function r(t,n){function r(e,t){if(e=""+e,e.length0&&(l=u.create("div",null),l.appendChild(c[0].cloneNode(!0))),s(u.select("*",l),function(t){a(t,e.getParam("template_cdate_classes","cdate").replace(/\s+/g,"|"))&&(t.innerHTML=r(e.getParam("template_cdate_format",e.getLang("template.cdate_format")))),a(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=r(e.getParam("template_mdate_format",e.getLang("template.mdate_format")))),a(t,e.getParam("template_selected_content_classes","selcontent").replace(/\s+/g,"|"))&&(t.innerHTML=d)}),i(l),e.execCommand("mceInsertContent",!1,l.innerHTML),e.addVisual()}var s=tinymce.each;e.addCommand("mceInsertTemplate",a),e.addButton("template",{title:"Insert template",onclick:t(n)}),e.addMenuItem("template",{text:"Insert template",onclick:t(n),context:"insert"}),e.on("PreProcess",function(t){var n=e.dom;s(n.select("div",t.node),function(t){n.hasClass(t,"mceTmpl")&&(s(n.select("*",t),function(t){n.hasClass(t,e.getParam("template_mdate_classes","mdate").replace(/\s+/g,"|"))&&(t.innerHTML=r(e.getParam("template_mdate_format",e.getLang("template.mdate_format"))))}),i(t))})})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textcolor/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textcolor/plugin.min.js new file mode 100644 index 0000000..c762f75 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textcolor/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("textcolor",function(e){function t(t){var n;return e.dom.getParents(e.selection.getStart(),function(e){var r;(r=e.style["forecolor"==t?"color":"background-color"])&&(n=r)}),n}function n(){var t,n,r=[];for(n=e.settings.textcolor_map||["000000","Black","993300","Burnt orange","333300","Dark olive","003300","Dark green","003366","Dark azure","000080","Navy Blue","333399","Indigo","333333","Very dark gray","800000","Maroon","FF6600","Orange","808000","Olive","008000","Green","008080","Teal","0000FF","Blue","666699","Grayish blue","808080","Gray","FF0000","Red","FF9900","Amber","99CC00","Yellow green","339966","Sea green","33CCCC","Turquoise","3366FF","Royal blue","800080","Purple","999999","Medium gray","FF00FF","Magenta","FFCC00","Gold","FFFF00","Yellow","00FF00","Lime","00FFFF","Aqua","00CCFF","Sky blue","993366","Red violet","FFFFFF","White","FF99CC","Pink","FFCC99","Peach","FFFF99","Light yellow","CCFFCC","Pale green","CCFFFF","Pale cyan","99CCFF","Light sky blue","CC99FF","Plum"],t=0;t
'+(n?"×":"")+"
"}var r,i,o,a,s,u,d,f=this,p=f._id,m=0;for(r=n(),r.push({text:tinymce.translate("No color"),color:"transparent"}),o='',a=r.length-1,u=0;c>u;u++){for(o+="",s=0;l>s;s++)d=u*l+s,d>a?o+="":(i=r[d],o+=t(i.color,i.text));o+=""}if(e.settings.color_picker_callback){for(o+='",o+="",s=0;l>s;s++)o+=t("","Custom color");o+=""}return o+="
"}function i(t,n){e.undoManager.transact(function(){e.focus(),e.formatter.apply(t,{value:n}),e.nodeChanged()})}function o(t){e.undoManager.transact(function(){e.focus(),e.formatter.remove(t,{value:null},null,!0),e.nodeChanged()})}function a(n){function r(e){u.hidePanel(),u.color(e),i(u.settings.format,e)}function a(){u.hidePanel(),u.resetColor(),o(u.settings.format)}function s(e,t){e.style.background=t,e.setAttribute("data-mce-color",t)}var c,u=this.parent();tinymce.DOM.getParent(n.target,".mce-custom-color-btn")&&(u.hidePanel(),e.settings.color_picker_callback.call(e,function(e){var t,n,i,o=u.panel.getEl().getElementsByTagName("table")[0];for(t=tinymce.map(o.rows[o.rows.length-1].childNodes,function(e){return e.firstChild}),i=0;ii;i++)s(t[i],t[i+1].getAttribute("data-mce-color"));s(n,e),r(e)},t(u.settings.format))),c=n.target.getAttribute("data-mce-color"),c?(this.lastId&&document.getElementById(this.lastId).setAttribute("aria-selected",!1),n.target.setAttribute("aria-selected",!0),this.lastId=n.target.id,"transparent"==c?a():r(c)):null!==c&&u.hidePanel()}function s(){var e=this;e._color?i(e.settings.format,e._color):o(e.settings.format)}var l,c;c=e.settings.textcolor_rows||5,l=e.settings.textcolor_cols||8,e.addButton("forecolor",{type:"colorbutton",tooltip:"Text color",format:"forecolor",panel:{role:"application",ariaRemember:!0,html:r,onclick:a},onclick:s}),e.addButton("backcolor",{type:"colorbutton",tooltip:"Background color",format:"hilitecolor",panel:{role:"application",ariaRemember:!0,html:r,onclick:a},onclick:s})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textpattern/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textpattern/plugin.min.js new file mode 100644 index 0000000..5f2f45c --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/textpattern/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("textpattern",function(e){function t(){return c&&(l.sort(function(e,t){return e.start.length>t.start.length?-1:e.start.length'+e+"
"}function o(){var e,t="";for(e in p)t+=e;return new RegExp("["+t+"]","g")}function a(){var e,t="";for(e in p)t&&(t+=","),t+="span.mce-"+p[e];return t}var s,l,c,u,d,f,p,m,h=e.getBody(),g=e.selection;if(p={"\xa0":"nbsp","\xad":"shy"},r=!r,i.state=r,e.fire("VisualChars",{state:r}),m=o(),t&&(f=g.getBookmark()),r)for(l=[],tinymce.walk(h,function(e){3==e.nodeType&&e.nodeValue&&m.test(e.nodeValue)&&l.push(e)},"childNodes"),c=0;c=0;c--)e.dom.remove(l[c],1);g.moveToBookmark(f)}function n(){var t=this;e.on("VisualChars",function(e){t.active(e.state)})}var r,i=this;e.addCommand("mceVisualChars",t),e.addButton("visualchars",{title:"Show invisible characters",cmd:"mceVisualChars",onPostRender:n}),e.addMenuItem("visualchars",{text:"Show invisible characters",cmd:"mceVisualChars",onPostRender:n,selectable:!0,context:"view",prependToContext:!0}),e.on("beforegetcontent",function(e){r&&"raw"!=e.format&&!e.draft&&(r=!0,t(!1))})}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/wordcount/plugin.min.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/wordcount/plugin.min.js new file mode 100644 index 0000000..d31b926 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/wordcount/plugin.min.js @@ -0,0 +1 @@ +tinymce.PluginManager.add("wordcount",function(e){function t(){e.theme.panel.find("#wordcount").text(["Words: {0}",i.getCount()])}var n,r,i=this;n=e.getParam("wordcount_countregex",/[\w\u2019\x27\-\u00C0-\u1FFF]+/g),r=e.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$?\x27\x22_+=\\\/\-]*/g),e.on("init",function(){var n=e.theme.panel&&e.theme.panel.find("#statusbar")[0];n&&tinymce.util.Delay.setEditorTimeout(e,function(){n.insert({type:"label",name:"wordcount",text:["Words: {0}",i.getCount()],classes:"wordcount",disabled:e.settings.readonly},0),e.on("setcontent beforeaddundo",t),e.on("keyup",function(e){32==e.keyCode&&t()})},0)}),i.getCount=function(){var t=e.getContent({format:"raw"}),i=0;if(t){t=t.replace(/\.\.\./g," "),t=t.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," "),t=t.replace(/(\w+)(&#?[a-z0-9]+;)+(\w+)/i,"$1$3").replace(/&.+?;/g," "),t=t.replace(r,"");var o=t.match(n);o&&(i=o.length)}return i}}); \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/README.md b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/README.md new file mode 100644 index 0000000..951fbce --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/README.md @@ -0,0 +1,78 @@ +Plugin youtube for TinyMCE 4 +====================== + +Insert YouTube video W3C valid with optionnals (HD, similar vidéos) + + +Authors +------- + + * Gerits Aurelien (Author-Developer) contact[at]aurelien-gerits[point]be + +Official link in french : + +###Screenshot + +![tinyMCE plugin YouTube](http://blog.aurelien-gerits.be/wp-content/uploads/2013/09/youtube-tinymce-2.0.png "tinyMCE plugin YouTube") + +###Installation + * Download the dist/youtube.zip archive + * Unzip archive in tinyMCE plugin directory (tiny_mce/plugins/) + +###Configuration + ```html + +``` + +###Languages + * English + * French + * Russian + * Spanish + * German + * Italian + * Brazilian + * Hungarian + * Polish + + You can send me translations in other languages + +### Old Version + +[Plugin YouTube for tinyMCE 3](http://magix-cjquery.com/post/2012/05/11/plugin-youtube-v1.4-pour-tinyMCE) + +
+This file is part of tinyMCE.
+YouTube for tinyMCE
+Copyright (C) 2011 - 2013  Gerits Aurelien aurelien[at]magix-dev[dot]be - contact[at]aurelien-gerits[dot]be
+
+Redistributions of files must retain the above copyright notice.
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see .
+
+####DISCLAIMER
+
+Do not edit or add to this file if you wish to upgrade jimagine to newer
+versions in the future. If you wish to customize jimagine for your
+needs please refer to magix-dev.be for more information.
+
diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/css/styles.css b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/css/styles.css new file mode 100644 index 0000000..fa6367a --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/css/styles.css @@ -0,0 +1,9 @@ +/*! + * Bootstrap v3.0.0 + * + * Copyright 2013 Twitter, Inc + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Designed and built with all the love in the world by @mdo and @fat. + *//*! normalize.css v2.1.0 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden]{display:none}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}h1{margin:.67em 0}b,strong{font-weight:700}dfn{font-style:italic}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}mark{color:#000;background:#ff0}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}button,input,select,textarea{margin:0}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;box-sizing:border-box}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}@media print{*{color:#000!important;text-shadow:none!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:2cm .5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.428571429;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}button,input,select[multiple],textarea{background-image:none}a{color:#428bca;text-decoration:none}a:focus,a:hover{color:#2a6496;text-decoration:underline}a:focus{outline-offset:-2px}img{vertical-align:middle}.img-responsive{display:block;height:auto;max-width:100%}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);border:0}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16.1px;font-weight:200;line-height:1.4}@media(min-width:768px){.lead{font-size:21px}}small{font-size:85%}cite{font-style:normal}.text-muted{color:#999}.text-primary{color:#428bca}.text-warning{color:#c09853}.text-danger{color:#b94a48}.text-success{color:#468847}.text-info{color:#3a87ad}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-weight:500;line-height:1.1}.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:400;line-height:1;color:#999}h1,h2,h3{margin-top:20px;margin-bottom:10px}h4,h5,h6{margin-top:10px;margin-bottom:10px}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}.h1 small,h1 small{font-size:24px}.h2 small,h2 small{font-size:18px}.h3 small,.h4 small,h3 small,h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-inline,.list-unstyled{padding-left:0;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-bottom:20px}dd,dt{line-height:1.428571429}dt{font-weight:700}dd{margin-left:0}@media(min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}.dl-horizontal dd:after,.dl-horizontal dd:before{display:table;content:" "}.dl-horizontal dd:after{clear:both}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{font-size:17.5px;font-weight:300;line-height:1.25}blockquote p:last-child{margin-bottom:0}blockquote small{display:block;line-height:1.428571429;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}blockquote:after,blockquote:before,q:after,q:before{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:1.428571429}code,pre{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;white-space:nowrap;background-color:#f9f2f4;border-radius:4px}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.428571429;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container:after,.container:before{display:table;content:" "}.container:after{clear:both}.row{margin-right:-15px;margin-left:-15px}.row:after,.row:before{display:table;content:" "}.row:after{clear:both}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-1{width:8.333333333333332%}.col-xs-2{width:16.666666666666664%}.col-xs-3{width:25%}.col-xs-4{width:33.33333333333333%}.col-xs-5{width:41.66666666666667%}.col-xs-6{width:50%}.col-xs-7{width:58.333333333333336%}.col-xs-8{width:66.66666666666666%}.col-xs-9{width:75%}.col-xs-10{width:83.33333333333334%}.col-xs-11{width:91.66666666666666%}.col-xs-12{width:100%}@media(min-width:768px){.container{max-width:750px}.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-1{width:8.333333333333332%}.col-sm-2{width:16.666666666666664%}.col-sm-3{width:25%}.col-sm-4{width:33.33333333333333%}.col-sm-5{width:41.66666666666667%}.col-sm-6{width:50%}.col-sm-7{width:58.333333333333336%}.col-sm-8{width:66.66666666666666%}.col-sm-9{width:75%}.col-sm-10{width:83.33333333333334%}.col-sm-11{width:91.66666666666666%}.col-sm-12{width:100%}.col-sm-push-1{left:8.333333333333332%}.col-sm-push-2{left:16.666666666666664%}.col-sm-push-3{left:25%}.col-sm-push-4{left:33.33333333333333%}.col-sm-push-5{left:41.66666666666667%}.col-sm-push-6{left:50%}.col-sm-push-7{left:58.333333333333336%}.col-sm-push-8{left:66.66666666666666%}.col-sm-push-9{left:75%}.col-sm-push-10{left:83.33333333333334%}.col-sm-push-11{left:91.66666666666666%}.col-sm-pull-1{right:8.333333333333332%}.col-sm-pull-2{right:16.666666666666664%}.col-sm-pull-3{right:25%}.col-sm-pull-4{right:33.33333333333333%}.col-sm-pull-5{right:41.66666666666667%}.col-sm-pull-6{right:50%}.col-sm-pull-7{right:58.333333333333336%}.col-sm-pull-8{right:66.66666666666666%}.col-sm-pull-9{right:75%}.col-sm-pull-10{right:83.33333333333334%}.col-sm-pull-11{right:91.66666666666666%}.col-sm-offset-1{margin-left:8.333333333333332%}.col-sm-offset-2{margin-left:16.666666666666664%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333333333%}.col-sm-offset-5{margin-left:41.66666666666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.333333333333336%}.col-sm-offset-8{margin-left:66.66666666666666%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333333334%}.col-sm-offset-11{margin-left:91.66666666666666%}}@media(min-width:992px){.container{max-width:970px}.col-md-1,.col-md-10,.col-md-11,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-1{width:8.333333333333332%}.col-md-2{width:16.666666666666664%}.col-md-3{width:25%}.col-md-4{width:33.33333333333333%}.col-md-5{width:41.66666666666667%}.col-md-6{width:50%}.col-md-7{width:58.333333333333336%}.col-md-8{width:66.66666666666666%}.col-md-9{width:75%}.col-md-10{width:83.33333333333334%}.col-md-11{width:91.66666666666666%}.col-md-12{width:100%}.col-md-push-0{left:auto}.col-md-push-1{left:8.333333333333332%}.col-md-push-2{left:16.666666666666664%}.col-md-push-3{left:25%}.col-md-push-4{left:33.33333333333333%}.col-md-push-5{left:41.66666666666667%}.col-md-push-6{left:50%}.col-md-push-7{left:58.333333333333336%}.col-md-push-8{left:66.66666666666666%}.col-md-push-9{left:75%}.col-md-push-10{left:83.33333333333334%}.col-md-push-11{left:91.66666666666666%}.col-md-pull-0{right:auto}.col-md-pull-1{right:8.333333333333332%}.col-md-pull-2{right:16.666666666666664%}.col-md-pull-3{right:25%}.col-md-pull-4{right:33.33333333333333%}.col-md-pull-5{right:41.66666666666667%}.col-md-pull-6{right:50%}.col-md-pull-7{right:58.333333333333336%}.col-md-pull-8{right:66.66666666666666%}.col-md-pull-9{right:75%}.col-md-pull-10{right:83.33333333333334%}.col-md-pull-11{right:91.66666666666666%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.333333333333332%}.col-md-offset-2{margin-left:16.666666666666664%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333333333%}.col-md-offset-5{margin-left:41.66666666666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.333333333333336%}.col-md-offset-8{margin-left:66.66666666666666%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333333334%}.col-md-offset-11{margin-left:91.66666666666666%}}@media(min-width:1200px){.container{max-width:1170px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-1{width:8.333333333333332%}.col-lg-2{width:16.666666666666664%}.col-lg-3{width:25%}.col-lg-4{width:33.33333333333333%}.col-lg-5{width:41.66666666666667%}.col-lg-6{width:50%}.col-lg-7{width:58.333333333333336%}.col-lg-8{width:66.66666666666666%}.col-lg-9{width:75%}.col-lg-10{width:83.33333333333334%}.col-lg-11{width:91.66666666666666%}.col-lg-12{width:100%}.col-lg-push-0{left:auto}.col-lg-push-1{left:8.333333333333332%}.col-lg-push-2{left:16.666666666666664%}.col-lg-push-3{left:25%}.col-lg-push-4{left:33.33333333333333%}.col-lg-push-5{left:41.66666666666667%}.col-lg-push-6{left:50%}.col-lg-push-7{left:58.333333333333336%}.col-lg-push-8{left:66.66666666666666%}.col-lg-push-9{left:75%}.col-lg-push-10{left:83.33333333333334%}.col-lg-push-11{left:91.66666666666666%}.col-lg-pull-0{right:auto}.col-lg-pull-1{right:8.333333333333332%}.col-lg-pull-2{right:16.666666666666664%}.col-lg-pull-3{right:25%}.col-lg-pull-4{right:33.33333333333333%}.col-lg-pull-5{right:41.66666666666667%}.col-lg-pull-6{right:50%}.col-lg-pull-7{right:58.333333333333336%}.col-lg-pull-8{right:66.66666666666666%}.col-lg-pull-9{right:75%}.col-lg-pull-10{right:83.33333333333334%}.col-lg-pull-11{right:91.66666666666666%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.333333333333332%}.col-lg-offset-2{margin-left:16.666666666666664%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333333333%}.col-lg-offset-5{margin-left:41.66666666666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.333333333333336%}.col-lg-offset-8{margin-left:66.66666666666666%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333333334%}.col-lg-offset-11{margin-left:91.66666666666666%}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table tbody>tr>td,.table tbody>tr>th,.table tfoot>tr>td,.table tfoot>tr>th,.table thead>tr>td,.table thead>tr>th{padding:8px;line-height:1.428571429;vertical-align:top;border-top:1px solid #ddd}.table thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table caption+thead tr:first-child td,.table caption+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table thead:first-child tr:first-child td,.table thead:first-child tr:first-child th{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed tbody>tr>td,.table-condensed tbody>tr>th,.table-condensed tfoot>tr>td,.table-condensed tfoot>tr>th,.table-condensed thead>tr>td,.table-condensed thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{display:table-column;float:none}table td[class*=col-],table th[class*=col-]{display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8;border-color:#d6e9c6}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6;border-color:#c9e2b3}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede;border-color:#eed3d7}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc;border-color:#e6c1c7}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3;border-color:#fbeed5}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc;border-color:#f8e5be}@media(max-width:768px){.table-responsive{width:100%;margin-bottom:15px;overflow-x:scroll;overflow-y:hidden;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0;background-color:#fff}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>thead>tr:last-child>td,.table-responsive>.table-bordered>thead>tr:last-child>th{border-bottom:0}}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;line-height:normal}input[type=file]{display:block}select[multiple],select[size]{height:auto}select optgroup{font-family:inherit;font-size:inherit;font-style:inherit}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}.form-control:-moz-placeholder{color:#999}.form-control::-moz-placeholder{color:#999}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.428571429;color:#555;vertical-align:middle;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee}textarea.form-control{height:auto}.checkbox,.radio{display:block;min-height:20px;padding-left:20px;margin-top:10px;margin-bottom:10px;vertical-align:middle}.checkbox label,.radio label{display:inline;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{float:left;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline[disabled],.checkbox[disabled],.radio-inline[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm{height:auto}.input-lg{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:45px;line-height:45px}textarea.input-lg{height:auto}.has-warning .control-label,.has-warning .help-block{color:#c09853}.has-warning .form-control{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #dbc59e}.has-warning .input-group-addon{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.has-error .control-label,.has-error .help-block{color:#b94a48}.has-error .form-control{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #d59392}.has-error .input-group-addon{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.has-success .control-label,.has-success .help-block{color:#468847}.has-success .form-control{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #7aba7b}.has-success .input-group-addon{color:#468847;background-color:#dff0d8;border-color:#468847}.form-control-static{padding-top:7px;margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media(min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block}.form-inline .checkbox,.form-inline .radio{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{float:none;margin-left:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}.form-horizontal .form-group:after,.form-horizontal .form-group:before{display:table;content:" "}.form-horizontal .form-group:after{clear:both}@media(min-width:768px){.form-horizontal .control-label{text-align:right}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.428571429;text-align:center;white-space:nowrap;vertical-align:middle;cursor:pointer;border:1px solid transparent;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff}.btn-default.active,.btn-default:active,.btn-default:focus,.btn-default:hover,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default.active,.btn-default:active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-primary{color:#fff;background-color:#428bca}.btn-primary.active,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary.active,.btn-primary:active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#428bca;border-color:#357ebd}.btn-warning{color:#fff;background-color:#f0ad4e}.btn-warning.active,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-danger{color:#fff;background-color:#d9534f}.btn-danger.active,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-success{color:#fff;background-color:#5cb85c}.btn-success.active,.btn-success:active,.btn-success:focus,.btn-success:hover,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success.active,.btn-success:active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-info{color:#fff;background-color:#5bc0de}.btn-info.active,.btn-info:active,.btn-info:focus,.btn-info:hover,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info.active,.btn-info:active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-link{font-weight:400;color:#428bca;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#999;text-decoration:none}.btn-lg{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-xs{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs{padding:1px 5px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';-webkit-font-smoothing:antialiased;font-style:normal;font-weight:400;line-height:1}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-print:before{content:"\e045"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-briefcase:before{content:"\1f4bc"}.glyphicon-calendar:before{content:"\1f4c5"}.glyphicon-pushpin:before{content:"\1f4cc"}.glyphicon-paperclip:before{content:"\1f4ce"}.glyphicon-camera:before{content:"\1f4f7"}.glyphicon-lock:before{content:"\1f512"}.glyphicon-bell:before{content:"\1f514"}.glyphicon-bookmark:before{content:"\1f516"}.glyphicon-fire:before{content:"\1f525"}.glyphicon-wrench:before{content:"\1f527"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid #000;border-right:4px solid transparent;border-bottom:0 dotted;border-left:4px solid transparent;content:""}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.428571429;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#fff;text-decoration:none}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#999}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.428571429;color:#999}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0 dotted;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media(min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}}.btn-default .caret{border-top-color:#333}.btn-danger .caret,.btn-info .caret,.btn-primary .caret,.btn-success .caret,.btn-warning .caret{border-top-color:#fff}.dropup .btn-default .caret{border-bottom-color:#333}.dropup .btn-danger .caret,.dropup .btn-info .caret,.dropup .btn-primary .caret,.dropup .btn-success .caret,.dropup .btn-warning .caret{border-bottom-color:#fff}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group-vertical>.btn:focus,.btn-group>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar:after,.btn-toolbar:before{display:table;content:" "}.btn-toolbar:after{clear:both}.btn-toolbar .btn-group{float:left}.btn-toolbar>.btn+.btn,.btn-toolbar>.btn+.btn-group,.btn-toolbar>.btn-group+.btn,.btn-toolbar>.btn-group+.btn-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group-xs>.btn{padding:5px 10px;padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before{display:table;content:" "}.btn-group-vertical>.btn-group:after{clear:both}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-right-radius:0;border-bottom-left-radius:4px;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child>.btn:last-child,.btn-group-vertical>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;border-collapse:separate;table-layout:fixed}.btn-group-justified .btn{display:table-cell;float:none;width:1%}[data-toggle=buttons]>.btn>input[type=checkbox],[data-toggle=buttons]>.btn>input[type=radio]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group.col{float:none;padding-right:0;padding-left:0}.input-group .form-control{width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:45px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:45px;line-height:45px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-4px}.input-group-btn>.btn:active,.input-group-btn>.btn:hover{z-index:2}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav:after,.nav:before{display:table;content:" "}.nav:after{clear:both}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#999;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.428571429;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-right:0;border-bottom:1px solid #ddd}@media(min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}}.nav-tabs.nav-justified>.active>a{border-bottom-color:#fff}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:5px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center}@media(min-width:768px){.nav-justified>li{display:table-cell;width:1%}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-bottom:1px solid #ddd}.nav-tabs-justified>.active>a{border-bottom-color:#fff}.tabbable:after,.tabbable:before{display:table;content:" "}.tabbable:after{clear:both}.pill-content>.pill-pane,.tab-content>.tab-pane{display:none}.pill-content>.active,.tab-content>.active{display:block}.nav .caret{border-top-color:#428bca;border-bottom-color:#428bca}.nav a:hover .caret{border-top-color:#2a6496;border-bottom-color:#2a6496}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;z-index:1000;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar:after,.navbar:before{display:table;content:" "}.navbar:after{clear:both}@media(min-width:768px){.navbar{border-radius:4px}}.navbar-header:after,.navbar-header:before{display:table;content:" "}.navbar-header:after{clear:both}@media(min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse:after,.navbar-collapse:before{display:table;content:" "}.navbar-collapse:after{clear:both}.navbar-collapse.in{overflow-y:auto}@media(min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-collapse .navbar-nav.navbar-left:first-child{margin-left:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:-15px}.navbar-collapse .navbar-text:last-child{margin-right:0}}.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media(min-width:768px){.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{border-width:0 0 1px}@media(min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;border-width:0 0 1px}@media(min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;z-index:1030}.navbar-fixed-bottom{bottom:0;margin-bottom:0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}@media(min-width:768px){.navbar>.container .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media(min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media(max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media(min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}@media(min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media(min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;padding-left:0;margin-top:0;margin-bottom:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{float:none;margin-left:0}}@media(max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media(min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-nav.pull-right>li>.dropdown-menu,.navbar-nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-text{float:left;margin-top:15px;margin-bottom:15px}@media(min-width:768px){.navbar-text{margin-right:15px;margin-left:15px}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#ccc}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e6e6e6}.navbar-default .navbar-nav>.dropdown>a:focus .caret,.navbar-default .navbar-nav>.dropdown>a:hover .caret{border-top-color:#333;border-bottom-color:#333}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.open>a .caret,.navbar-default .navbar-nav>.open>a:focus .caret,.navbar-default .navbar-nav>.open>a:hover .caret{border-top-color:#555;border-bottom-color:#555}.navbar-default .navbar-nav>.dropdown>a .caret{border-top-color:#777;border-bottom-color:#777}@media(max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.dropdown>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-nav>.dropdown>a .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .navbar-nav>.open>a .caret,.navbar-inverse .navbar-nav>.open>a:focus .caret,.navbar-inverse .navbar-nav>.open>a:hover .caret{border-top-color:#fff;border-bottom-color:#fff}@media(max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.428571429;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{background-color:#eee}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span{color:#999;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager:after,.pager:before{display:table;content:" "}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:focus,.label[href]:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#999}.label-default[href]:focus,.label-default[href]:hover{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#999;border-radius:10px}.badge:empty{display:none}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.btn .badge{position:relative;top:-1px}.nav-pills>.active>a>.badge,a.list-group-item.active>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;font-size:21px;font-weight:200;line-height:2.1428571435;color:inherit;background-color:#eee}.jumbotron h1{line-height:1;color:inherit}.jumbotron p{line-height:1.4}.container .jumbotron{border-radius:6px}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-right:60px;padding-left:60px}.jumbotron h1{font-size:63px}}.thumbnail{display:inline-block;display:block;height:auto;max-width:100%;padding:4px;line-height:1.428571429;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img{display:block;height:auto;max-width:100%}a.thumbnail:focus,a.thumbnail:hover{border-color:#428bca}.thumbnail>img{margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#333}.alert{border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#468847;background-color:#dff0d8}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#356635}.alert-info{color:#3a87ad;background-color:#d9edf7}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#2d6987}.alert-warning{color:#c09853;background-color:#fcf8e3}.alert-warning hr{border-top-color:#f8e5be}.alert-warning .alert-link{color:#a47e3c}.alert-danger{color:#b94a48;background-color:#f2dede}.alert-danger hr{border-top-color:#e6c1c7}.alert-danger .alert-link{color:#953b39}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,.15)),color-stop(0.75,rgba(255,255,255,.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-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-image:-moz-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-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:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,.15)),color-stop(0.75,rgba(255,255,255,.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-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-image:-moz-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-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)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,.15)),color-stop(0.75,rgba(255,255,255,.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-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-image:-moz-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-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)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,.15)),color-stop(0.75,rgba(255,255,255,.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-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-image:-moz-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-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)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,.15)),color-stop(0.75,rgba(255,255,255,.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-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-image:-moz-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-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)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{text-decoration:none;background-color:#f5f5f5}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#e1edf7}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px}.panel-body{padding:15px}.panel-body:after,.panel-body:before{display:table;content:" "}.panel-body:after{clear:both}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0}.panel>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.list-group .list-group-item:last-child{border-bottom:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table{margin-bottom:0}.panel>.panel-body+.table{border-top:1px solid #ddd}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-title{margin-top:0;margin-bottom:0;font-size:16px}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-group .panel{margin-bottom:0;overflow:hidden;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-warning{border-color:#fbeed5}.panel-warning>.panel-heading{color:#c09853;background-color:#fcf8e3;border-color:#fbeed5}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#fbeed5}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#fbeed5}.panel-danger{border-color:#eed3d7}.panel-danger>.panel-heading{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#eed3d7}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#eed3d7}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal-open .navbar-fixed-bottom,.modal-open .navbar-fixed-top,body.modal-open{margin-right:15px}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:auto;overflow-y:scroll}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{z-index:1050;width:auto;padding:10px;margin-right:auto;margin-left:auto}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1030;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.428571429}.modal-body{position:relative;padding:20px}.modal-footer{padding:19px 20px 20px;margin-top:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer:after,.modal-footer:before{display:table;content:" "}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media screen and (min-width:768px){.modal-dialog{right:auto;left:50%;width:600px;padding-top:30px;padding-bottom:30px}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}}.tooltip{position:absolute;z-index:1030;display:block;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.top-right .tooltip-arrow{right:5px;bottom:0;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-bottom-color:#000;border-width:0 5px 5px}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;white-space:normal;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover .arrow,.popover .arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow{border-width:11px}.popover .arrow:after{border-width:10px;content:""}.popover.top .arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top .arrow:after{bottom:1px;margin-left:-10px;border-top-color:#fff;border-bottom-width:0;content:" "}.popover.right .arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right .arrow:after{bottom:-10px;left:1px;border-right-color:#fff;border-left-width:0;content:" "}.popover.bottom .arrow{top:-11px;left:50%;margin-left:-11px;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);border-top-width:0}.popover.bottom .arrow:after{top:1px;margin-left:-10px;border-bottom-color:#fff;border-top-width:0;content:" "}.popover.left .arrow{top:50%;right:-11px;margin-top:-11px;border-left-color:#999;border-left-color:rgba(0,0,0,.25);border-right-width:0}.popover.left .arrow:after{right:1px;bottom:-10px;border-left-color:#fff;border-right-width:0;content:" "}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{display:block;height:auto;max-width:100%;line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);opacity:.5;filter:alpha(opacity=50)}.carousel-control.left{background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5)0),color-stop(rgba(0,0,0,.0001)100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,.5)0,rgba(0,0,0,.0001)100%);background-image:linear-gradient(to right,rgba(0,0,0,.5)0,rgba(0,0,0,.0001)100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{right:0;left:auto;background-image:-webkit-gradient(linear,0 top,100% top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001)0),color-stop(rgba(0,0,0,.5)100%));background-image:-moz-linear-gradient(left,rgba(0,0,0,.0001)0,rgba(0,0,0,.5)100%);background-image:linear-gradient(to right,rgba(0,0,0,.0001)0,rgba(0,0,0,.5)100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;left:50%;z-index:5;display:inline-block}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:after,.clearfix:before{display:table;content:" "}.clearfix:after{clear:both}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.affix{position:fixed}@-ms-viewport{width:device-width}@media screen and (max-width:400px){@-ms-viewport{width:320px}}.hidden{display:none!important;visibility:hidden!important}.visible-lg,.visible-md,.visible-sm,.visible-xs,td.visible-lg,td.visible-md,td.visible-sm,td.visible-xs,th.visible-lg,th.visible-md,th.visible-sm,th.visible-xs,tr.visible-lg,tr.visible-md,tr.visible-sm,tr.visible-xs{display:none!important}@media(max-width:767px){.visible-xs{display:block!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-xs.visible-sm{display:block!important}tr.visible-xs.visible-sm{display:table-row!important}td.visible-xs.visible-sm,th.visible-xs.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-xs.visible-md{display:block!important}tr.visible-xs.visible-md{display:table-row!important}td.visible-xs.visible-md,th.visible-xs.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-xs.visible-lg{display:block!important}tr.visible-xs.visible-lg{display:table-row!important}td.visible-xs.visible-lg,th.visible-xs.visible-lg{display:table-cell!important}}@media(max-width:767px){.visible-sm.visible-xs{display:block!important}tr.visible-sm.visible-xs{display:table-row!important}td.visible-sm.visible-xs,th.visible-sm.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-sm{display:block!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-sm.visible-md{display:block!important}tr.visible-sm.visible-md{display:table-row!important}td.visible-sm.visible-md,th.visible-sm.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-sm.visible-lg{display:block!important}tr.visible-sm.visible-lg{display:table-row!important}td.visible-sm.visible-lg,th.visible-sm.visible-lg{display:table-cell!important}}@media(max-width:767px){.visible-md.visible-xs{display:block!important}tr.visible-md.visible-xs{display:table-row!important}td.visible-md.visible-xs,th.visible-md.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-md.visible-sm{display:block!important}tr.visible-md.visible-sm{display:table-row!important}td.visible-md.visible-sm,th.visible-md.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-md{display:block!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-md.visible-lg{display:block!important}tr.visible-md.visible-lg{display:table-row!important}td.visible-md.visible-lg,th.visible-md.visible-lg{display:table-cell!important}}@media(max-width:767px){.visible-lg.visible-xs{display:block!important}tr.visible-lg.visible-xs{display:table-row!important}td.visible-lg.visible-xs,th.visible-lg.visible-xs{display:table-cell!important}}@media(min-width:768px) and (max-width:991px){.visible-lg.visible-sm{display:block!important}tr.visible-lg.visible-sm{display:table-row!important}td.visible-lg.visible-sm,th.visible-lg.visible-sm{display:table-cell!important}}@media(min-width:992px) and (max-width:1199px){.visible-lg.visible-md{display:block!important}tr.visible-lg.visible-md{display:table-row!important}td.visible-lg.visible-md,th.visible-lg.visible-md{display:table-cell!important}}@media(min-width:1200px){.visible-lg{display:block!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}.hidden-xs{display:block!important}tr.hidden-xs{display:table-row!important}td.hidden-xs,th.hidden-xs{display:table-cell!important}@media(max-width:767px){.hidden-xs,td.hidden-xs,th.hidden-xs,tr.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-xs.hidden-sm,td.hidden-xs.hidden-sm,th.hidden-xs.hidden-sm,tr.hidden-xs.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-xs.hidden-md,td.hidden-xs.hidden-md,th.hidden-xs.hidden-md,tr.hidden-xs.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-xs.hidden-lg,td.hidden-xs.hidden-lg,th.hidden-xs.hidden-lg,tr.hidden-xs.hidden-lg{display:none!important}}.hidden-sm{display:block!important}tr.hidden-sm{display:table-row!important}td.hidden-sm,th.hidden-sm{display:table-cell!important}@media(max-width:767px){.hidden-sm.hidden-xs,td.hidden-sm.hidden-xs,th.hidden-sm.hidden-xs,tr.hidden-sm.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-sm,td.hidden-sm,th.hidden-sm,tr.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-sm.hidden-md,td.hidden-sm.hidden-md,th.hidden-sm.hidden-md,tr.hidden-sm.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-sm.hidden-lg,td.hidden-sm.hidden-lg,th.hidden-sm.hidden-lg,tr.hidden-sm.hidden-lg{display:none!important}}.hidden-md{display:block!important}tr.hidden-md{display:table-row!important}td.hidden-md,th.hidden-md{display:table-cell!important}@media(max-width:767px){.hidden-md.hidden-xs,td.hidden-md.hidden-xs,th.hidden-md.hidden-xs,tr.hidden-md.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-md.hidden-sm,td.hidden-md.hidden-sm,th.hidden-md.hidden-sm,tr.hidden-md.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-md,td.hidden-md,th.hidden-md,tr.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-md.hidden-lg,td.hidden-md.hidden-lg,th.hidden-md.hidden-lg,tr.hidden-md.hidden-lg{display:none!important}}.hidden-lg{display:block!important}tr.hidden-lg{display:table-row!important}td.hidden-lg,th.hidden-lg{display:table-cell!important}@media(max-width:767px){.hidden-lg.hidden-xs,td.hidden-lg.hidden-xs,th.hidden-lg.hidden-xs,tr.hidden-lg.hidden-xs{display:none!important}}@media(min-width:768px) and (max-width:991px){.hidden-lg.hidden-sm,td.hidden-lg.hidden-sm,th.hidden-lg.hidden-sm,tr.hidden-lg.hidden-sm{display:none!important}}@media(min-width:992px) and (max-width:1199px){.hidden-lg.hidden-md,td.hidden-lg.hidden-md,th.hidden-lg.hidden-md,tr.hidden-lg.hidden-md{display:none!important}}@media(min-width:1200px){.hidden-lg,td.hidden-lg,th.hidden-lg,tr.hidden-lg{display:none!important}}.visible-print,td.visible-print,th.visible-print,tr.visible-print{display:none!important}@media print{.visible-print{display:block!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}.hidden-print,td.hidden-print,th.hidden-print,tr.hidden-print{display:none!important}}.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,0,#e6e6e6,100%);background-image:-moz-linear-gradient(top,#fff 0,#e6e6e6 100%);background-image:linear-gradient(to bottom,#fff 0,#e6e6e6 100%);background-repeat:repeat-x;border-color:#e0e0e0;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0)}.btn-default.active,.btn-default:active{background-color:#e6e6e6;border-color:#e0e0e0}.btn-primary{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;border-color:#2d6ca2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.btn-primary.active,.btn-primary:active{background-color:#3071a9;border-color:#2d6ca2}.btn-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;border-color:#419641;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.btn-success.active,.btn-success:active{background-color:#449d44;border-color:#419641}.btn-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;border-color:#eb9316;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.btn-warning.active,.btn-warning:active{background-color:#ec971f;border-color:#eb9316}.btn-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;border-color:#c12e2a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.btn-danger.active,.btn-danger:active{background-color:#c9302c;border-color:#c12e2a}.btn-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;border-color:#2aabd2;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.btn-info.active,.btn-info:active{background-color:#31b0d5;border-color:#2aabd2}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover,.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#357ebd;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.navbar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fff),to(#f8f8f8));background-image:-webkit-linear-gradient(top,#fff,0,#f8f8f8,100%);background-image:-moz-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar .navbar-nav>.active>a{background-color:#f8f8f8}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-gradient(linear,left 0,left 100%,from(#3c3c3c),to(#222));background-image:-webkit-linear-gradient(top,#3c3c3c,0,#222,100%);background-image:-moz-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0)}.navbar-inverse .navbar-nav>.active>a{background-color:#222}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#c8e5bc));background-image:-webkit-linear-gradient(top,#dff0d8,0,#c8e5bc,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0)}.alert-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#b9def0));background-image:-webkit-linear-gradient(top,#d9edf7,0,#b9def0,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0)}.alert-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#f8efc0));background-image:-webkit-linear-gradient(top,#fcf8e3,0,#f8efc0,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0)}.alert-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#e7c3c3));background-image:-webkit-linear-gradient(top,#f2dede,0,#e7c3c3,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0)}.progress{background-image:-webkit-gradient(linear,left 0,left 100%,from(#ebebeb),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#ebebeb,0,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0)}.progress-bar{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3071a9));background-image:-webkit-linear-gradient(top,#428bca,0,#3071a9,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0)}.progress-bar-success{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5cb85c),to(#449d44));background-image:-webkit-linear-gradient(top,#5cb85c,0,#449d44,100%);background-image:-moz-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0)}.progress-bar-info{background-image:-webkit-gradient(linear,left 0,left 100%,from(#5bc0de),to(#31b0d5));background-image:-webkit-linear-gradient(top,#5bc0de,0,#31b0d5,100%);background-image:-moz-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0)}.progress-bar-warning{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f0ad4e),to(#ec971f));background-image:-webkit-linear-gradient(top,#f0ad4e,0,#ec971f,100%);background-image:-moz-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0)}.progress-bar-danger{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9534f),to(#c9302c));background-image:-webkit-linear-gradient(top,#d9534f,0,#c9302c,100%);background-image:-moz-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#3278b3));background-image:-webkit-linear-gradient(top,#428bca,0,#3278b3,100%);background-image:-moz-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f5f5f5),to(#e8e8e8));background-image:-webkit-linear-gradient(top,#f5f5f5,0,#e8e8e8,100%);background-image:-moz-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#428bca),to(#357ebd));background-image:-webkit-linear-gradient(top,#428bca,0,#357ebd,100%);background-image:-moz-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#dff0d8),to(#d0e9c6));background-image:-webkit-linear-gradient(top,#dff0d8,0,#d0e9c6,100%);background-image:-moz-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#d9edf7),to(#c4e3f3));background-image:-webkit-linear-gradient(top,#d9edf7,0,#c4e3f3,100%);background-image:-moz-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#fcf8e3),to(#faf2cc));background-image:-webkit-linear-gradient(top,#fcf8e3,0,#faf2cc,100%);background-image:-moz-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-gradient(linear,left 0,left 100%,from(#f2dede),to(#ebcccc));background-image:-webkit-linear-gradient(top,#f2dede,0,#ebcccc,100%);background-image:-moz-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0)}.well{background-image:-webkit-gradient(linear,left 0,left 100%,from(#e8e8e8),to(#f5f5f5));background-image:-webkit-linear-gradient(top,#e8e8e8,0,#f5f5f5,100%);background-image:-moz-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}body{padding:10px}a:active,a:focus{outline:0}a img{border:none}#preview{background-image:url(../img/Google-YouTube-128.png);background-repeat:no-repeat;background-position:center center;height:325px;width:430px}#insert-btn{margin-left:5px}.alert{margin-bottom:5px;padding:5px}.form-group{margin-bottom:0}.alert-info{font-size:12px} \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/Google-YouTube-128.png b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/Google-YouTube-128.png new file mode 100644 index 0000000..ef60bd0 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/Google-YouTube-128.png differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/youtube.gif b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/youtube.gif new file mode 100644 index 0000000..6d673d1 Binary files /dev/null and b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/img/youtube.gif differ diff --git a/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/js/main.js b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/js/main.js new file mode 100644 index 0000000..3f55be0 --- /dev/null +++ b/domokits/local/modules/Tinymce/Resources/js/tinymce/plugins/youtube/js/main.js @@ -0,0 +1,807 @@ +/*! + * bootstrap.js v3.0.0 by @fat and @mdo + * Copyright 2013 Twitter Inc. + * http://www.apache.org/licenses/LICENSE-2.0 + */ +if (!jQuery)throw new Error("Bootstrap requires jQuery"); ++function(t) { + "use strict"; + function e() { + var t = document.createElement("bootstrap"), e = {WebkitTransition: "webkitTransitionEnd", MozTransition: "transitionend", OTransition: "oTransitionEnd otransitionend", transition: "transitionend"}; + for (var i in e)if (void 0 !== t.style[i])return{end: e[i]} + } + + t.fn.emulateTransitionEnd = function(e) { + var i = !1, n = this; + t(this).one(t.support.transition.end, function() { + i = !0 + }); + var o = function() { + i || t(n).trigger(t.support.transition.end) + }; + return setTimeout(o, e), this + }, t(function() { + t.support.transition = e() + }) +}(window.jQuery), +function(t) { + "use strict"; + var e = '[data-dismiss="alert"]', i = function(i) { + t(i).on("click", e, this.close) + }; + i.prototype.close = function(e) { + function i() { + s.trigger("closed.bs.alert").remove() + } + + var n = t(this), o = n.attr("data-target"); + o || (o = n.attr("href"), o = o && o.replace(/.*(?=#[^\s]*$)/, "")); + var s = t(o); + e && e.preventDefault(), s.length || (s = n.hasClass("alert") ? n : n.parent()), s.trigger(e = t.Event("close.bs.alert")), e.isDefaultPrevented() || (s.removeClass("in"), t.support.transition && s.hasClass("fade") ? s.one(t.support.transition.end, i).emulateTransitionEnd(150) : i()) + }; + var n = t.fn.alert; + t.fn.alert = function(e) { + return this.each(function() { + var n = t(this), o = n.data("bs.alert"); + o || n.data("bs.alert", o = new i(this)), "string" == typeof e && o[e].call(n) + }) + }, t.fn.alert.Constructor = i, t.fn.alert.noConflict = function() { + return t.fn.alert = n, this + }, t(document).on("click.bs.alert.data-api", e, i.prototype.close) +}(window.jQuery), +function(t) { + "use strict"; + var e = function(i, n) { + this.$element = t(i), this.options = t.extend({}, e.DEFAULTS, n) + }; + e.DEFAULTS = {loadingText: "loading..."}, e.prototype.setState = function(t) { + var e = "disabled", i = this.$element, n = i.is("input") ? "val" : "html", o = i.data(); + t += "Text", o.resetText || i.data("resetText", i[n]()), i[n](o[t] || this.options[t]), setTimeout(function() { + "loadingText" == t ? i.addClass(e).attr(e, e) : i.removeClass(e).removeAttr(e) + }, 0) + }, e.prototype.toggle = function() { + var t = this.$element.closest('[data-toggle="buttons"]'); + if (t.length) { + var e = this.$element.find("input").prop("checked", !this.$element.hasClass("active")).trigger("change"); + "radio" === e.prop("type") && t.find(".active").removeClass("active") + } + this.$element.toggleClass("active") + }; + var i = t.fn.button; + t.fn.button = function(i) { + return this.each(function() { + var n = t(this), o = n.data("bs.button"), s = "object" == typeof i && i; + o || n.data("bs.button", o = new e(this, s)), "toggle" == i ? o.toggle() : i && o.setState(i) + }) + }, t.fn.button.Constructor = e, t.fn.button.noConflict = function() { + return t.fn.button = i, this + }, t(document).on("click.bs.button.data-api", "[data-toggle^=button]", function(e) { + var i = t(e.target); + i.hasClass("btn") || (i = i.closest(".btn")), i.button("toggle"), e.preventDefault() + }) +}(window.jQuery), +function(t) { + "use strict"; + var e = function(e, i) { + this.$element = t(e), this.$indicators = this.$element.find(".carousel-indicators"), this.options = i, this.paused = this.sliding = this.interval = this.$active = this.$items = null, "hover" == this.options.pause && this.$element.on("mouseenter", t.proxy(this.pause, this)).on("mouseleave", t.proxy(this.cycle, this)) + }; + e.DEFAULTS = {interval: 5e3, pause: "hover", wrap: !0}, e.prototype.cycle = function(e) { + return e || (this.paused = !1), this.interval && clearInterval(this.interval), this.options.interval && !this.paused && (this.interval = setInterval(t.proxy(this.next, this), this.options.interval)), this + }, e.prototype.getActiveIndex = function() { + return this.$active = this.$element.find(".item.active"), this.$items = this.$active.parent().children(), this.$items.index(this.$active) + }, e.prototype.to = function(e) { + var i = this, n = this.getActiveIndex(); + return e > this.$items.length - 1 || 0 > e ? void 0 : this.sliding ? this.$element.one("slid", function() { + i.to(e) + }) : n == e ? this.pause().cycle() : this.slide(e > n ? "next" : "prev", t(this.$items[e])) + }, e.prototype.pause = function(e) { + return e || (this.paused = !0), this.$element.find(".next, .prev").length && t.support.transition.end && (this.$element.trigger(t.support.transition.end), this.cycle(!0)), this.interval = clearInterval(this.interval), this + }, e.prototype.next = function() { + return this.sliding ? void 0 : this.slide("next") + }, e.prototype.prev = function() { + return this.sliding ? void 0 : this.slide("prev") + }, e.prototype.slide = function(e, i) { + var n = this.$element.find(".item.active"), o = i || n[e](), s = this.interval, a = "next" == e ? "left" : "right", r = "next" == e ? "first" : "last", l = this; + if (!o.length) { + if (!this.options.wrap)return; + o = this.$element.find(".item")[r]() + } + this.sliding = !0, s && this.pause(); + var h = t.Event("slide.bs.carousel", {relatedTarget: o[0], direction: a}); + if (!o.hasClass("active")) { + if (this.$indicators.length && (this.$indicators.find(".active").removeClass("active"), this.$element.one("slid", function() { + var e = t(l.$indicators.children()[l.getActiveIndex()]); + e && e.addClass("active") + })), t.support.transition && this.$element.hasClass("slide")) { + if (this.$element.trigger(h), h.isDefaultPrevented())return; + o.addClass(e), o[0].offsetWidth, n.addClass(a), o.addClass(a), n.one(t.support.transition.end,function() { + o.removeClass([e, a].join(" ")).addClass("active"), n.removeClass(["active", a].join(" ")), l.sliding = !1, setTimeout(function() { + l.$element.trigger("slid") + }, 0) + }).emulateTransitionEnd(600) + } else { + if (this.$element.trigger(h), h.isDefaultPrevented())return; + n.removeClass("active"), o.addClass("active"), this.sliding = !1, this.$element.trigger("slid") + } + return s && this.cycle(), this + } + }; + var i = t.fn.carousel; + t.fn.carousel = function(i) { + return this.each(function() { + var n = t(this), o = n.data("bs.carousel"), s = t.extend({}, e.DEFAULTS, n.data(), "object" == typeof i && i), a = "string" == typeof i ? i : s.slide; + o || n.data("bs.carousel", o = new e(this, s)), "number" == typeof i ? o.to(i) : a ? o[a]() : s.interval && o.pause().cycle() + }) + }, t.fn.carousel.Constructor = e, t.fn.carousel.noConflict = function() { + return t.fn.carousel = i, this + }, t(document).on("click.bs.carousel.data-api", "[data-slide], [data-slide-to]", function(e) { + var i, n = t(this), o = t(n.attr("data-target") || (i = n.attr("href")) && i.replace(/.*(?=#[^\s]+$)/, "")), s = t.extend({}, o.data(), n.data()), a = n.attr("data-slide-to"); + a && (s.interval = !1), o.carousel(s), (a = n.attr("data-slide-to")) && o.data("bs.carousel").to(a), e.preventDefault() + }), t(window).on("load", function() { + t('[data-ride="carousel"]').each(function() { + var e = t(this); + e.carousel(e.data()) + }) + }) +}(window.jQuery), +function(t) { + "use strict"; + var e = function(i, n) { + this.$element = t(i), this.options = t.extend({}, e.DEFAULTS, n), this.transitioning = null, this.options.parent && (this.$parent = t(this.options.parent)), this.options.toggle && this.toggle() + }; + e.DEFAULTS = {toggle: !0}, e.prototype.dimension = function() { + var t = this.$element.hasClass("width"); + return t ? "width" : "height" + }, e.prototype.show = function() { + if (!this.transitioning && !this.$element.hasClass("in")) { + var e = t.Event("show.bs.collapse"); + if (this.$element.trigger(e), !e.isDefaultPrevented()) { + var i = this.$parent && this.$parent.find("> .panel > .in"); + if (i && i.length) { + var n = i.data("bs.collapse"); + if (n && n.transitioning)return; + i.collapse("hide"), n || i.data("bs.collapse", null) + } + var o = this.dimension(); + this.$element.removeClass("collapse").addClass("collapsing")[o](0), this.transitioning = 1; + var s = function() { + this.$element.removeClass("collapsing").addClass("in")[o]("auto"), this.transitioning = 0, this.$element.trigger("shown.bs.collapse") + }; + if (!t.support.transition)return s.call(this); + var a = t.camelCase(["scroll", o].join("-")); + this.$element.one(t.support.transition.end, t.proxy(s, this)).emulateTransitionEnd(350)[o](this.$element[0][a]) + } + } + }, e.prototype.hide = function() { + if (!this.transitioning && this.$element.hasClass("in")) { + var e = t.Event("hide.bs.collapse"); + if (this.$element.trigger(e), !e.isDefaultPrevented()) { + var i = this.dimension(); + this.$element[i](this.$element[i]())[0].offsetHeight, this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"), this.transitioning = 1; + var n = function() { + this.transitioning = 0, this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse") + }; + return t.support.transition ? void this.$element[i](0).one(t.support.transition.end, t.proxy(n, this)).emulateTransitionEnd(350) : n.call(this) + } + } + }, e.prototype.toggle = function() { + this[this.$element.hasClass("in") ? "hide" : "show"]() + }; + var i = t.fn.collapse; + t.fn.collapse = function(i) { + return this.each(function() { + var n = t(this), o = n.data("bs.collapse"), s = t.extend({}, e.DEFAULTS, n.data(), "object" == typeof i && i); + o || n.data("bs.collapse", o = new e(this, s)), "string" == typeof i && o[i]() + }) + }, t.fn.collapse.Constructor = e, t.fn.collapse.noConflict = function() { + return t.fn.collapse = i, this + }, t(document).on("click.bs.collapse.data-api", "[data-toggle=collapse]", function(e) { + var i, n = t(this), o = n.attr("data-target") || e.preventDefault() || (i = n.attr("href")) && i.replace(/.*(?=#[^\s]+$)/, ""), s = t(o), a = s.data("bs.collapse"), r = a ? "toggle" : n.data(), l = n.attr("data-parent"), h = l && t(l); + a && a.transitioning || (h && h.find('[data-toggle=collapse][data-parent="' + l + '"]').not(n).addClass("collapsed"), n[s.hasClass("in") ? "addClass" : "removeClass"]("collapsed")), s.collapse(r) + }) +}(window.jQuery), +function(t) { + "use strict"; + function e() { + t(n).remove(), t(o).each(function(e) { + var n = i(t(this)); + n.hasClass("open") && (n.trigger(e = t.Event("hide.bs.dropdown")), e.isDefaultPrevented() || n.removeClass("open").trigger("hidden.bs.dropdown")) + }) + } + + function i(e) { + var i = e.attr("data-target"); + i || (i = e.attr("href"), i = i && /#/.test(i) && i.replace(/.*(?=#[^\s]*$)/, "")); + var n = i && t(i); + return n && n.length ? n : e.parent() + } + + var n = ".dropdown-backdrop", o = "[data-toggle=dropdown]", s = function(e) { + t(e).on("click.bs.dropdown", this.toggle) + }; + s.prototype.toggle = function(n) { + var o = t(this); + if (!o.is(".disabled, :disabled")) { + var s = i(o), a = s.hasClass("open"); + if (e(), !a) { + if ("ontouchstart"in document.documentElement && !s.closest(".navbar-nav").length && t(' + +
+
+
+ {intl l='List of the text area where the wysiwyg editor will be used' d='tinymce.bo.default'} +
+
+

+ {intl l='This is a critical data, to update it you have to inform the ids (#timymce_configuration-id-test_zone for example) or the classes (.wysiwyg for example) , separated with comas, of the text areas you want to have the wysiwyg editor directly in the data base.' d='tinymce.bo.default'} +

+
+ +
+ {render_form_field form=$form field="available_text_areas"} +
+
+
+ +
+
+ {if $form_error} +
{$form_error_message}
+ {/if} + +
+ {render_form_field form=$form field="editor_height"} + + {render_form_field form=$form field="force_pasting_as_text"} + {render_form_field form=$form field="set_images_as_responsive"} + {render_form_field form=$form field="show_menu_bar"} + {render_form_field form=$form field="custom_css"} +
+ +
+ {render_form_field form=$form field="test_zone" extra_class="wysiwyg"} +
+
+
+ + {/form} +
+ + + + \ No newline at end of file diff --git a/domokits/local/modules/Tinymce/templates/backOffice/default/tinymce_init.tpl b/domokits/local/modules/Tinymce/templates/backOffice/default/tinymce_init.tpl new file mode 100644 index 0000000..1b03d0d --- /dev/null +++ b/domokits/local/modules/Tinymce/templates/backOffice/default/tinymce_init.tpl @@ -0,0 +1,123 @@ + + + diff --git a/domokits/local/modules/UrlSanitizer/.github/workflows/release.yml b/domokits/local/modules/UrlSanitizer/.github/workflows/release.yml new file mode 100644 index 0000000..e880140 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/.github/workflows/release.yml @@ -0,0 +1,7 @@ +name: "Auto Release" +on: + push: + branches: [ master, main ] +jobs: + release: + uses: thelia-modules/ReusableWorkflow/.github/workflows/auto_release.yml@main diff --git a/domokits/local/modules/UrlSanitizer/Config/config.xml b/domokits/local/modules/UrlSanitizer/Config/config.xml new file mode 100644 index 0000000..8ac8854 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Config/config.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/domokits/local/modules/UrlSanitizer/Config/module.xml b/domokits/local/modules/UrlSanitizer/Config/module.xml new file mode 100644 index 0000000..64d0a58 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Config/module.xml @@ -0,0 +1,22 @@ + + + UrlSanitizer\UrlSanitizer + + Sanitize URLs + + + Nettoie les URLs + + + en_US + fr_FR + + 2.1.1 + classic + 2.5.0 + other + 0 + 0 + diff --git a/domokits/local/modules/UrlSanitizer/Config/routing.xml b/domokits/local/modules/UrlSanitizer/Config/routing.xml new file mode 100644 index 0000000..fc772b7 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Config/routing.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/domokits/local/modules/UrlSanitizer/Config/schema.xml b/domokits/local/modules/UrlSanitizer/Config/schema.xml new file mode 100644 index 0000000..08ba2de --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Config/schema.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/domokits/local/modules/UrlSanitizer/Controller/ConfigurationController.php b/domokits/local/modules/UrlSanitizer/Controller/ConfigurationController.php new file mode 100644 index 0000000..0bdfe53 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Controller/ConfigurationController.php @@ -0,0 +1,59 @@ +checkAuth([AdminResources::MODULE], ["UrlSanitizer"], AccessManager::UPDATE)) { + return $response; + } + + $urlSanitizerService->sanitizeAllExistingUrls(); + + return $this->generateRedirect(URL::getInstance()->absoluteUrl('/admin/module/UrlSanitizer', [])); + } + + #[Route('/configuration', name: 'configuration', methods: ['POST'])] + public function SaveConfiguration(ParserContext $parserContext): RedirectResponse|Response + { + $form = $this->createForm(ConfigurationForm::getName()); + + try { + $data = $this->validateForm($form)->getData(); + + UrlSanitizer::setConfigValue(UrlSanitizer::REMOVE_HTML_CONFIG_KEY, $data['remove_html']); + + return $this->generateSuccessRedirect($form); + } catch (FormValidationException $e) { + $error_message = $this->createStandardFormValidationErrorMessage($e); + } catch (Exception $e) { + $error_message = $e->getMessage(); + } + + $form->setErrorMessage($error_message); + + $parserContext + ->addForm($form) + ->setGeneralError($error_message); + + return $this->generateErrorRedirect($form); + } +} diff --git a/domokits/local/modules/UrlSanitizer/EventListener/RewriteUrlListener.php b/domokits/local/modules/UrlSanitizer/EventListener/RewriteUrlListener.php new file mode 100644 index 0000000..bd5ece0 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/EventListener/RewriteUrlListener.php @@ -0,0 +1,39 @@ +sanitizerService = $sanitizerService; + } + + public function sanitizeUrl(RewritingUrlEvent $event) + { + /** @var RewritingUrl $rewritingUrl */ + $rewritingUrl = $event->getModel(); + + $sanitizedUrl = $this->sanitizerService->unifyUrl( + $this->sanitizerService->sanitizeUrl($rewritingUrl->getUrl()), + $rewritingUrl + ); + + $rewritingUrl->setUrl($sanitizedUrl); + } + + public static function getSubscribedEvents() + { + return [ + RewritingUrlEvent::PRE_INSERT => ['sanitizeUrl', 64] + ]; + } +} diff --git a/domokits/local/modules/UrlSanitizer/Form/ConfigurationForm.php b/domokits/local/modules/UrlSanitizer/Form/ConfigurationForm.php new file mode 100644 index 0000000..66cec64 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Form/ConfigurationForm.php @@ -0,0 +1,21 @@ +formBuilder + ->add('remove_html', CheckboxType::class, [ + 'required' => false, + 'label' => Translator::getInstance()?->trans('Remove html extension from rewrite url ?', [], UrlSanitizer::DOMAIN_NAME), + 'data' => (bool)UrlSanitizer::getConfigValue(UrlSanitizer::REMOVE_HTML_CONFIG_KEY) + ]); + } +} \ No newline at end of file diff --git a/domokits/local/modules/UrlSanitizer/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/UrlSanitizer/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..a3af34c --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,9 @@ + 'Attention, cette opération est irréversible.', + 'If you want to sanitize the existing rewritten urls in your database, click on the button below.' => 'Pour effectuer cette opération sur les URL déjà existantes dans la base de données, cliquez sur le bouton ci-dessous.', + 'Once this module is activated, every rewritten url saved will be automatically sanitized.' => 'Lorsque ce module est activé, si une URL réécrite est sauvegardée elle sera automatiquement nettoyée.', + 'Sanitize existing URLs' => 'Nettoyer les URLs existantes', + 'This will create a sanitized url and redirect the old url to the new one.' => 'Ceci va créer une nouvelle url nettoyée et rediriger l\'ancienne vers la nouvelle.', +); diff --git a/domokits/local/modules/UrlSanitizer/Readme.md b/domokits/local/modules/UrlSanitizer/Readme.md new file mode 100644 index 0000000..01f4354 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Readme.md @@ -0,0 +1,19 @@ +# UrlSanitizer + +This module allows you to sanitize your urls. + +Only compatible with Thelia >= 2.4.0. + +For Thelia <= 2.4.0 you can install this module https://github.com/thelia-modules/UrlRemoveAccent +but he only replace accent and he override Thelia url sanitize. + +## Installation + +``` +composer require thelia/url-sanitizer-module ~1.0.0 +``` + +## Usage + +- If the module is activated, every rewritten url saved will be automatically sanitized. +- If you want to sanitize the existing urls in your database, go to the module configuration and follow the instructions. diff --git a/domokits/local/modules/UrlSanitizer/Service/UrlSanitizerService.php b/domokits/local/modules/UrlSanitizer/Service/UrlSanitizerService.php new file mode 100644 index 0000000..f935977 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/Service/UrlSanitizerService.php @@ -0,0 +1,223 @@ +filterByRedirected(null, Criteria::ISNULL) + ->find(); + + /** @var RewritingUrl $rewrittenUrl */ + foreach ($allDefaultRewrittenUrl as $rewrittenUrl) { + $baseUrl = $rewrittenUrl->getUrl(); + + $cleanUrl = $this->sanitizeUrl($baseUrl); + + if ("" == $cleanUrl) { + continue; + } + + // If url doesn't change do nothing + if ($baseUrl === $cleanUrl) { + continue; + } + + // If clean url already exist for the same view get it + $cleanRewritingUrl = RewritingUrlQuery::create() + ->filterByView($rewrittenUrl->getView()) + ->filterByViewId($rewrittenUrl->getViewId()) + ->filterByViewLocale($rewrittenUrl->getViewLocale()) + ->filterByUrl($cleanUrl) + ->findOne(); + + // Else create new url sanitized + if (!$cleanRewritingUrl) { + $cleanRewritingUrl = new RewritingUrl(); + + $uniqueUrl = $this->unifyUrl($cleanUrl, $rewrittenUrl); + + $cleanRewritingUrl->setUrl($uniqueUrl) + ->setView($rewrittenUrl->getView()) + ->setViewId($rewrittenUrl->getViewId()) + ->setViewLocale($rewrittenUrl->getViewLocale()) + ->save(); + } + + // Keep "Dirty" redirect before transfer + $oldRedirect = $rewrittenUrl->getRedirected(); + + // Redirect "Dirty" to "Clean" + $rewrittenUrl->setRedirected($cleanRewritingUrl->getId()) + ->save(); + + // Transfer "Dirty" Redirect to "Clean" + $cleanRewritingUrl->setRedirected($oldRedirect) + ->save(); + } + } + + public function sanitizeUrl($url) + { + $url = filter_var( + $this->replaceSpecialCaractere( + $this->replaceSpace( + $this->convertAccents( + $url + ) + ) + ), FILTER_SANITIZE_URL); + + if (UrlSanitizer::getConfigValue(UrlSanitizer::REMOVE_HTML_CONFIG_KEY)) { + $url = $this->removeHtmlExtension($url); + } + + return $url; + } + + public function unifyUrl($cleanedUrl, RewritingUrl $rewritingUrl) + { + $urlExist = RewritingUrlQuery::create() + ->filterByUrl($cleanedUrl) + ->filterById($rewritingUrl->getId(), Criteria::NOT_EQUAL) + ->findOne(); + + if (null === $urlExist) { + return $cleanedUrl; + } + + $urlWithPrefix = $rewritingUrl->getViewId()."-".$cleanedUrl; + + return $this->unifyUrl($urlWithPrefix, $rewritingUrl); + } + + protected function replaceSpace($string, $replacement = '-') + { + return preg_replace("/\s+/", $replacement, $string); + } + + protected function replaceSpecialCaractere($string, $replacement = '') + { + return preg_replace("/^[^a-zA-Z0-9]+/", $replacement, $string); + } + + protected function removeHtmlExtension($string, $replacement = '') + { + return str_replace('.html', $replacement, $string); + } + + protected function convertAccents($string) + { + if (!preg_match('/[\x80-\xff]/', $string)) { + return $string; + } + + $chars = array( + // Decompositions for Latin-1 Supplement + chr(195).chr(128) => 'A', chr(195).chr(129) => 'A', + chr(195).chr(130) => 'A', chr(195).chr(131) => 'A', + chr(195).chr(132) => 'A', chr(195).chr(133) => 'A', + chr(195).chr(135) => 'C', chr(195).chr(136) => 'E', + chr(195).chr(137) => 'E', chr(195).chr(138) => 'E', + chr(195).chr(139) => 'E', chr(195).chr(140) => 'I', + chr(195).chr(141) => 'I', chr(195).chr(142) => 'I', + chr(195).chr(143) => 'I', chr(195).chr(145) => 'N', + chr(195).chr(146) => 'O', chr(195).chr(147) => 'O', + chr(195).chr(148) => 'O', chr(195).chr(149) => 'O', + chr(195).chr(150) => 'O', chr(195).chr(153) => 'U', + chr(195).chr(154) => 'U', chr(195).chr(155) => 'U', + chr(195).chr(156) => 'U', chr(195).chr(157) => 'Y', + chr(195).chr(159) => 's', chr(195).chr(160) => 'a', + chr(195).chr(161) => 'a', chr(195).chr(162) => 'a', + chr(195).chr(163) => 'a', chr(195).chr(164) => 'a', + chr(195).chr(165) => 'a', chr(195).chr(167) => 'c', + chr(195).chr(168) => 'e', chr(195).chr(169) => 'e', + chr(195).chr(170) => 'e', chr(195).chr(171) => 'e', + chr(195).chr(172) => 'i', chr(195).chr(173) => 'i', + chr(195).chr(174) => 'i', chr(195).chr(175) => 'i', + chr(195).chr(177) => 'n', chr(195).chr(178) => 'o', + chr(195).chr(179) => 'o', chr(195).chr(180) => 'o', + chr(195).chr(181) => 'o', chr(195).chr(182) => 'o', + chr(195).chr(182) => 'o', chr(195).chr(185) => 'u', + chr(195).chr(186) => 'u', chr(195).chr(187) => 'u', + chr(195).chr(188) => 'u', chr(195).chr(189) => 'y', + chr(195).chr(191) => 'y', + // Decompositions for Latin Extended-A + chr(196).chr(128) => 'A', chr(196).chr(129) => 'a', + chr(196).chr(130) => 'A', chr(196).chr(131) => 'a', + chr(196).chr(132) => 'A', chr(196).chr(133) => 'a', + chr(196).chr(134) => 'C', chr(196).chr(135) => 'c', + chr(196).chr(136) => 'C', chr(196).chr(137) => 'c', + chr(196).chr(138) => 'C', chr(196).chr(139) => 'c', + chr(196).chr(140) => 'C', chr(196).chr(141) => 'c', + chr(196).chr(142) => 'D', chr(196).chr(143) => 'd', + chr(196).chr(144) => 'D', chr(196).chr(145) => 'd', + chr(196).chr(146) => 'E', chr(196).chr(147) => 'e', + chr(196).chr(148) => 'E', chr(196).chr(149) => 'e', + chr(196).chr(150) => 'E', chr(196).chr(151) => 'e', + chr(196).chr(152) => 'E', chr(196).chr(153) => 'e', + chr(196).chr(154) => 'E', chr(196).chr(155) => 'e', + chr(196).chr(156) => 'G', chr(196).chr(157) => 'g', + chr(196).chr(158) => 'G', chr(196).chr(159) => 'g', + chr(196).chr(160) => 'G', chr(196).chr(161) => 'g', + chr(196).chr(162) => 'G', chr(196).chr(163) => 'g', + chr(196).chr(164) => 'H', chr(196).chr(165) => 'h', + chr(196).chr(166) => 'H', chr(196).chr(167) => 'h', + chr(196).chr(168) => 'I', chr(196).chr(169) => 'i', + chr(196).chr(170) => 'I', chr(196).chr(171) => 'i', + chr(196).chr(172) => 'I', chr(196).chr(173) => 'i', + chr(196).chr(174) => 'I', chr(196).chr(175) => 'i', + chr(196).chr(176) => 'I', chr(196).chr(177) => 'i', + chr(196).chr(178) => 'IJ',chr(196).chr(179) => 'ij', + chr(196).chr(180) => 'J', chr(196).chr(181) => 'j', + chr(196).chr(182) => 'K', chr(196).chr(183) => 'k', + chr(196).chr(184) => 'k', chr(196).chr(185) => 'L', + chr(196).chr(186) => 'l', chr(196).chr(187) => 'L', + chr(196).chr(188) => 'l', chr(196).chr(189) => 'L', + chr(196).chr(190) => 'l', chr(196).chr(191) => 'L', + chr(197).chr(128) => 'l', chr(197).chr(129) => 'L', + chr(197).chr(130) => 'l', chr(197).chr(131) => 'N', + chr(197).chr(132) => 'n', chr(197).chr(133) => 'N', + chr(197).chr(134) => 'n', chr(197).chr(135) => 'N', + chr(197).chr(136) => 'n', chr(197).chr(137) => 'N', + chr(197).chr(138) => 'n', chr(197).chr(139) => 'N', + chr(197).chr(140) => 'O', chr(197).chr(141) => 'o', + chr(197).chr(142) => 'O', chr(197).chr(143) => 'o', + chr(197).chr(144) => 'O', chr(197).chr(145) => 'o', + chr(197).chr(146) => 'OE',chr(197).chr(147) => 'oe', + chr(197).chr(148) => 'R',chr(197).chr(149) => 'r', + chr(197).chr(150) => 'R',chr(197).chr(151) => 'r', + chr(197).chr(152) => 'R',chr(197).chr(153) => 'r', + chr(197).chr(154) => 'S',chr(197).chr(155) => 's', + chr(197).chr(156) => 'S',chr(197).chr(157) => 's', + chr(197).chr(158) => 'S',chr(197).chr(159) => 's', + chr(197).chr(160) => 'S', chr(197).chr(161) => 's', + chr(197).chr(162) => 'T', chr(197).chr(163) => 't', + chr(197).chr(164) => 'T', chr(197).chr(165) => 't', + chr(197).chr(166) => 'T', chr(197).chr(167) => 't', + chr(197).chr(168) => 'U', chr(197).chr(169) => 'u', + chr(197).chr(170) => 'U', chr(197).chr(171) => 'u', + chr(197).chr(172) => 'U', chr(197).chr(173) => 'u', + chr(197).chr(174) => 'U', chr(197).chr(175) => 'u', + chr(197).chr(176) => 'U', chr(197).chr(177) => 'u', + chr(197).chr(178) => 'U', chr(197).chr(179) => 'u', + chr(197).chr(180) => 'W', chr(197).chr(181) => 'w', + chr(197).chr(182) => 'Y', chr(197).chr(183) => 'y', + chr(197).chr(184) => 'Y', chr(197).chr(185) => 'Z', + chr(197).chr(186) => 'z', chr(197).chr(187) => 'Z', + chr(197).chr(188) => 'z', chr(197).chr(189) => 'Z', + chr(197).chr(190) => 'z', chr(197).chr(191) => 's' + ); + + $string = strtr($string, $chars); + + return $string; + } +} \ No newline at end of file diff --git a/domokits/local/modules/UrlSanitizer/UrlSanitizer.php b/domokits/local/modules/UrlSanitizer/UrlSanitizer.php new file mode 100644 index 0000000..1af5355 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/UrlSanitizer.php @@ -0,0 +1,25 @@ +load(self::getModuleCode().'\\', __DIR__) + ->exclude([THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*']) + ->autowire(true) + ->autoconfigure(true); + } +} diff --git a/domokits/local/modules/UrlSanitizer/composer.json b/domokits/local/modules/UrlSanitizer/composer.json new file mode 100644 index 0000000..3386286 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/composer.json @@ -0,0 +1,15 @@ +{ + "name": "thelia/url-sanitizer-module", + "description": "Sanitize Urls", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "replace": { + "thelia/url-remove-accent-module":"~1.0.0" + }, + "extra": { + "installer-name": "UrlSanitizer" + } +} diff --git a/domokits/local/modules/UrlSanitizer/templates/backOffice/default/UrlSanitizer/module-configuration.html b/domokits/local/modules/UrlSanitizer/templates/backOffice/default/UrlSanitizer/module-configuration.html new file mode 100644 index 0000000..f584339 --- /dev/null +++ b/domokits/local/modules/UrlSanitizer/templates/backOffice/default/UrlSanitizer/module-configuration.html @@ -0,0 +1,39 @@ +
+
+
+
+ {intl l="Once this module is activated, every rewritten url saved will be automatically sanitized." d="urlsanitizer.bo.default"} +
+ {intl l="If you want to sanitize the existing rewritten urls in your database, click on the button below." d="urlsanitizer.bo.default"} +
+ {intl l="This will create a sanitized url and redirect the old url to the new one." d="urlsanitizer.bo.default"} +
+ {intl l="Be careful ! This cannot be undone." d="urlsanitizer.bo.default"} +
+ {intl l="Sanitize existing URLs" d="urlsanitizer.bo.default"} + +


+ {form name="urlsanitizer_form_configuration_form"} +
+ {form_hidden_fields form=$form} + + {render_form_field field="success_url" value={url path='/admin/module/UrlSanitizer'}} + {render_form_field field="error_url" value={url path='/admin/module/UrlSanitizer'}} + + {if $form_error} +
+
+
{$form_error_message}
+
+
+ {/if} + + {render_form_field field='remove_html'} + +
+ +
+ {/form} +
+
+
diff --git a/domokits/local/modules/VirtualProductControl/Config/config.xml b/domokits/local/modules/VirtualProductControl/Config/config.xml new file mode 100644 index 0000000..689f790 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/Config/config.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + diff --git a/domokits/local/modules/VirtualProductControl/Config/module.xml b/domokits/local/modules/VirtualProductControl/Config/module.xml new file mode 100644 index 0000000..d797929 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/Config/module.xml @@ -0,0 +1,20 @@ + + + VirtualProductControl\VirtualProductControl + + Virtual Product Controller + Check if a virtual product delivery module is enabled if at least one product is virtual + + + Contrôle de produit virtuel + Vérifie qu'un module de livraison pour produit virtuel soit activé si des produits virtuels existent + + 2.5.4 + + Manuel Raynaud + manu@raynaud.io + + classic + 2.5.4 + alpha + diff --git a/domokits/local/modules/VirtualProductControl/Hook/VirtualProductHook.php b/domokits/local/modules/VirtualProductControl/Hook/VirtualProductHook.php new file mode 100644 index 0000000..f76f8c8 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/Hook/VirtualProductHook.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductControl\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; +use Thelia\Core\Security\AccessManager; +use Thelia\Core\Security\Resource\AdminResources; +use Thelia\Core\Security\SecurityContext; +use Thelia\Model\ModuleQuery; +use Thelia\Model\ProductQuery; + +/** + * Class VirtualProductHook. + * + * @author Manuel Raynaud + */ +class VirtualProductHook extends BaseHook +{ + /** + * @var SecurityContext + */ + protected $securityContext; + + public function __construct(SecurityContext $securityContext) + { + $this->securityContext = $securityContext; + } + + public function onMainBeforeContent(HookRenderEvent $event): void + { + if ($this->securityContext->isGranted( + ['ADMIN'], + [AdminResources::PRODUCT], + [], + [AccessManager::VIEW] + )) { + $products = ProductQuery::create() + ->filterByVirtual(1) + ->filterByVisible(1) + ->count(); + + if ($products > 0) { + $deliveryModule = ModuleQuery::create()->retrieveVirtualProductDelivery(); + + if (false === $deliveryModule) { + $event->add($this->render('virtual-delivery-warning.html')); + } + } + } + } +} diff --git a/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/de_DE.php b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/de_DE.php new file mode 100644 index 0000000..3b4be4e --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Mindestens ein virtuelles Produkt ist verfügbar, aber kein Liefermodul für virtuellen Produkte ist aktiviert', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/en_US.php b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/en_US.php new file mode 100644 index 0000000..a544616 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'At least one virtual product is online but no virtual product delivery module enabled', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/fr_FR.php b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/fr_FR.php new file mode 100644 index 0000000..50f2511 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Au moins un produit virtuel est en vente mais aucun module de livraison pour produit virtuel n\'est activé', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/it_IT.php b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/it_IT.php new file mode 100644 index 0000000..2f36aa7 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Almeno un prodotto virtuale è online, ma nessun modulo di consegna del prodotto virtuale è abilitato', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/tr_TR.php b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/tr_TR.php new file mode 100644 index 0000000..03149bc --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/backOffice/default/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'En az bir sanal ürün hiç sanal ürün teslim modülü etkin yayında', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/de_DE.php b/domokits/local/modules/VirtualProductControl/I18n/de_DE.php new file mode 100644 index 0000000..3b4be4e --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Mindestens ein virtuelles Produkt ist verfügbar, aber kein Liefermodul für virtuellen Produkte ist aktiviert', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/en_US.php b/domokits/local/modules/VirtualProductControl/I18n/en_US.php new file mode 100644 index 0000000..a544616 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'At least one virtual product is online but no virtual product delivery module enabled', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/fr_FR.php b/domokits/local/modules/VirtualProductControl/I18n/fr_FR.php new file mode 100644 index 0000000..50f2511 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Au moins un produit virtuel est en vente mais aucun module de livraison pour produit virtuel n\'est activé', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/it_IT.php b/domokits/local/modules/VirtualProductControl/I18n/it_IT.php new file mode 100644 index 0000000..2f36aa7 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Almeno un prodotto virtuale è online, ma nessun modulo di consegna del prodotto virtuale è abilitato', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/ru_RU.php b/domokits/local/modules/VirtualProductControl/I18n/ru_RU.php new file mode 100644 index 0000000..744f33e --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'Включен по крайне мере один виртуальный товар, но модуль доставки виртуальных товаров не активирован', +]; diff --git a/domokits/local/modules/VirtualProductControl/I18n/tr_TR.php b/domokits/local/modules/VirtualProductControl/I18n/tr_TR.php new file mode 100644 index 0000000..03149bc --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/I18n/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'warning-message' => 'En az bir sanal ürün hiç sanal ürün teslim modülü etkin yayında', +]; diff --git a/domokits/local/modules/VirtualProductControl/LICENSE.txt b/domokits/local/modules/VirtualProductControl/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/VirtualProductControl/VirtualProductControl.php b/domokits/local/modules/VirtualProductControl/VirtualProductControl.php new file mode 100644 index 0000000..cfe6f56 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/VirtualProductControl.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductControl; + +use Thelia\Module\BaseModule; + +class VirtualProductControl extends BaseModule +{ +} diff --git a/domokits/local/modules/VirtualProductControl/composer.json b/domokits/local/modules/VirtualProductControl/composer.json new file mode 100644 index 0000000..7bf1b28 --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/virtual-product-control-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "VirtualProductControl" + } +} diff --git a/domokits/local/modules/VirtualProductControl/templates/backOffice/default/virtual-delivery-warning.html b/domokits/local/modules/VirtualProductControl/templates/backOffice/default/virtual-delivery-warning.html new file mode 100644 index 0000000..cc5c13a --- /dev/null +++ b/domokits/local/modules/VirtualProductControl/templates/backOffice/default/virtual-delivery-warning.html @@ -0,0 +1,7 @@ +
+
+ +
+
\ No newline at end of file diff --git a/domokits/local/modules/VirtualProductDelivery/Config/config.xml b/domokits/local/modules/VirtualProductDelivery/Config/config.xml new file mode 100644 index 0000000..1b16d36 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/Config/config.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/VirtualProductDelivery/Config/module.xml b/domokits/local/modules/VirtualProductDelivery/Config/module.xml new file mode 100644 index 0000000..c3815f8 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/Config/module.xml @@ -0,0 +1,24 @@ + + + VirtualProductDelivery\VirtualProductDelivery + + Virtual Products Delivery + + + Livraison Produits Virtuels + + + en_US + fr_FR + + 2.5.4 + + Julien Chanséaume + jchanseaume@openstudio.fr + + delivery + 2.5.4 + alpha + diff --git a/domokits/local/modules/VirtualProductDelivery/EventListeners/SendMail.php b/domokits/local/modules/VirtualProductDelivery/EventListeners/SendMail.php new file mode 100755 index 0000000..b105262 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/EventListeners/SendMail.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductDelivery\EventListeners; + +use Propel\Runtime\ActiveQuery\Criteria; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Core\Event\Order\OrderEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Log\Tlog; +use Thelia\Mailer\MailerFactory; +use Thelia\Model\OrderProductQuery; +use VirtualProductDelivery\Events\VirtualProductDeliveryEvents; + +/** + * Class SendMail. + * + * @author Julien Chanséaume + */ +class SendMail implements EventSubscriberInterface +{ + /** @var MailerFactory */ + protected $mailer; + + /** @var EventDispatcherInterface */ + protected $eventDispatcher; + + public function __construct(MailerFactory $mailer, EventDispatcherInterface $eventDispatcher) + { + $this->mailer = $mailer; + $this->eventDispatcher = $eventDispatcher; + } + + public function updateStatus(OrderEvent $event): void + { + $order = $event->getOrder(); + + if ($order->hasVirtualProduct() && $order->isPaid(true)) { + $this->eventDispatcher->dispatch( + $event, + VirtualProductDeliveryEvents::ORDER_VIRTUAL_FILES_AVAILABLE + ); + } + } + + /** + * Send email to notify customer that files for virtual products are available. + * + * @throws \Exception + */ + public function sendEmail(OrderEvent $event): void + { + $order = $event->getOrder(); + + // Be sure that we have a document to download + $virtualProductCount = OrderProductQuery::create() + ->filterByOrderId($order->getId()) + ->filterByVirtual(true) + ->filterByVirtualDocument(null, Criteria::NOT_EQUAL) + ->count(); + + if ($virtualProductCount > 0) { + $customer = $order->getCustomer(); + + $this->mailer->sendEmailToCustomer( + 'mail_virtualproduct', + $customer, + [ + 'customer_id' => $customer->getId(), + 'order_id' => $order->getId(), + 'order_ref' => $order->getRef(), + 'order_date' => $order->getCreatedAt(), + 'update_date' => $order->getUpdatedAt(), + ] + ); + } else { + Tlog::getInstance()->warning( + "Virtual product download message not sent to customer: there's nothing to downnload" + ); + } + } + + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::ORDER_UPDATE_STATUS => ['updateStatus', 128], + VirtualProductDeliveryEvents::ORDER_VIRTUAL_FILES_AVAILABLE => ['sendEmail', 128], + ]; + } +} diff --git a/domokits/local/modules/VirtualProductDelivery/EventListeners/VirtualProductEvents.php b/domokits/local/modules/VirtualProductDelivery/EventListeners/VirtualProductEvents.php new file mode 100644 index 0000000..e2fc3ff --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/EventListeners/VirtualProductEvents.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductDelivery\EventListeners; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Thelia\Core\Event\Product\VirtualProductOrderDownloadResponseEvent; +use Thelia\Core\Event\Product\VirtualProductOrderHandleEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Core\Translation\Translator; +use Thelia\Model\ConfigQuery; +use Thelia\Model\MetaData as MetaDataModel; +use Thelia\Model\MetaDataQuery; +use Thelia\Model\ProductDocumentQuery; +use VirtualProductDelivery\VirtualProductDelivery; + +/** + * Class VirtualProductEvents. + * + * @author Julien Chanséaume + */ +class VirtualProductEvents implements EventSubscriberInterface +{ + public function handleOrder(VirtualProductOrderHandleEvent $event): void + { + $documentId = MetaDataQuery::getVal( + 'virtual', + MetaDataModel::PSE_KEY, + $event->getPseId() + ); + + if (null !== $documentId) { + $productDocument = ProductDocumentQuery::create()->findPk($documentId); + if (null !== $productDocument) { + $event->setPath($productDocument->getFile()); + } + } + } + + public function download(VirtualProductOrderDownloadResponseEvent $event): void + { + $orderProduct = $event->getOrderProduct(); + + if ($orderProduct->getVirtualDocument()) { + $baseSourceFilePath = ConfigQuery::read('documents_library_path'); + if ($baseSourceFilePath === null) { + $baseSourceFilePath = THELIA_LOCAL_DIR.'media'.DS.'documents'; + } else { + $baseSourceFilePath = THELIA_ROOT.$baseSourceFilePath; + } + + // try to get the file + $path = $baseSourceFilePath.DS.'product'.DS.$orderProduct->getVirtualDocument(); + + if (!is_file($path) || !is_readable($path)) { + throw new \ErrorException( + Translator::getInstance()->trans( + 'The file [%file] does not exist', + [ + '%file' => $orderProduct->getId(), + ], + VirtualProductDelivery::MESSAGE_DOMAIN + ) + ); + } + + $response = new BinaryFileResponse($path); + $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT); + $event->setResponse($response); + } + } + + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2')) + * + * @return array The event names to listen to + * + * @api + */ + public static function getSubscribedEvents() + { + return [ + TheliaEvents::VIRTUAL_PRODUCT_ORDER_HANDLE => ['handleOrder', 128], + TheliaEvents::VIRTUAL_PRODUCT_ORDER_DOWNLOAD_RESPONSE => ['download', 128], + ]; + } +} diff --git a/domokits/local/modules/VirtualProductDelivery/Events/VirtualProductDeliveryEvents.php b/domokits/local/modules/VirtualProductDelivery/Events/VirtualProductDeliveryEvents.php new file mode 100644 index 0000000..f65603c --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/Events/VirtualProductDeliveryEvents.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductDelivery\Events; + +use Thelia\Core\Event\ActionEvent; + +/** + * Class VirtualProductDeliveryEvents. + * + * @author Julien Chanséaume + */ +class VirtualProductDeliveryEvents extends ActionEvent +{ + public const ORDER_VIRTUAL_FILES_AVAILABLE = 'virtual_product_delivery.virtual_files_available'; +} diff --git a/domokits/local/modules/VirtualProductDelivery/Hook/HookManager.php b/domokits/local/modules/VirtualProductDelivery/Hook/HookManager.php new file mode 100644 index 0000000..359c7fd --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/Hook/HookManager.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductDelivery\Hook; + +use Thelia\Core\Event\Hook\HookRenderEvent; +use Thelia\Core\Hook\BaseHook; + +/** + * Class HookManager. + * + * @author Julien Chanséaume + */ +class HookManager extends BaseHook +{ + public function onAccountOrderAfterProducts(HookRenderEvent $event): void + { + $orderId = $event->getArgument('order'); + + if (null !== $orderId) { + $render = $this->render( + 'account-order-after-products.html', + [ + 'order_id' => $orderId, + ] + ); + $event->add($render); + } + } +} diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/de_DE.php b/domokits/local/modules/VirtualProductDelivery/I18n/de_DE.php new file mode 100644 index 0000000..ce73744 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/de_DE.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Bestellung {$order_ref} validiert. Laden Sie Ihre Dateien herunter.', + 'The file [%file] does not exist' => 'Die Datei [%file] existiert nicht', + 'This module cannot be used on the current cart.' => 'Dieses Modul kann nicht für diesen Warenkorb benutzt werden. ', + 'Virtual product download message' => 'Virtuelles Produkt Herunterladung Nachricht', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/email/default/en_US.php b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/en_US.php new file mode 100644 index 0000000..2e806f9 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/en_US.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Best Regards.' => 'Best Regards.', + 'Feel free to contact us for any further information.' => 'Feel free to contact us for any further information.', + 'Products:' => 'Products:', + 'You have to be logged in to your account to download this files.' => 'You have to be logged in to your account to download this files.', + 'Your order %ref has been validated. You can download your files.' => 'Your order %ref has been validated. You can download your files.', + 'have to be logged in to your account to download this files.' => 'have to be logged in to your account to download this files.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/email/default/fr_FR.php b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/fr_FR.php new file mode 100644 index 0000000..16e3553 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/fr_FR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Best Regards.' => 'Cordialement', + 'Feel free to contact us for any further information.' => 'N\'hésitez pas à nous contacter pour toute information complémentaire.', + 'Products:' => 'Articles à télécharger:', + 'You have to be logged in to your account to download this files.' => 'Vous devez être connecté à votre compte pour pouvoir télécharger le fichier.', + 'Your order %ref has been validated. You can download your files.' => 'Votre commande %ref a été validé. Vous pouvez désormais télécharger vos fichiers.', + 'have to be logged in to your account to download this files.' => 'Vous devez être connecté à votre compte pour pouvoir télécharger les fichiers.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/email/default/it_IT.php b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/it_IT.php new file mode 100644 index 0000000..373c73e --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/it_IT.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Best Regards.' => 'Distinti saluti.', + 'Feel free to contact us for any further information.' => 'Non esitate a contattarci per qualsiasi ulteriore informazione.', + 'Products:' => 'Prodotti:', + 'You have to be logged in to your account to download this files.' => 'Devi essere loggato al tuo account per poter scaricare questi file.', + 'Your order %ref has been validated. You can download your files.' => 'Il vostro ordine %ref è stato convalidato. È possibile scaricare i file.', + 'have to be logged in to your account to download this files.' => 'devi essere loggato al tuo account per poter scaricare questi file.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/email/default/ru_RU.php b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/ru_RU.php new file mode 100644 index 0000000..1c808aa --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/ru_RU.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Best Regards.' => 'С наилучшими пожеланиями.', + 'Feel free to contact us for any further information.' => 'Не стесняйтесь обращаться к нам за любой дополнительной информацией.', + 'Products:' => 'Товары:', + 'You have to be logged in to your account to download this files.' => 'Вы должны войти в ваш аккаунт, чтобы скачать эти файлы', + 'Your order %ref has been validated. You can download your files.' => 'Ваш заказ %ref подтвержден. Можете скачивать ваши файлы.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/email/default/tr_TR.php b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/tr_TR.php new file mode 100644 index 0000000..d010fb6 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/email/default/tr_TR.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Best Regards.' => 'Saygılarımızla,.', + 'Feel free to contact us for any further information.' => 'Daha fazla bilgi için bizimle temas kurmaktan çekinmeyin.', + 'Products:' => 'ürün:', + 'You have to be logged in to your account to download this files.' => 'Bu dosyaları karşıdan yüklemek için hesabınıza oturum açmış olmanız gerekir.', + 'Your order %ref has been validated. You can download your files.' => 'Sipariş %ref doğrulandı. Sen-ebilmek download senin eğe.', + 'have to be logged in to your account to download this files.' => 'Bu dosyaları karşıdan yüklemek için hesabınıza oturum açmış olmanız gerekir.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/en_US.php b/domokits/local/modules/VirtualProductDelivery/I18n/en_US.php new file mode 100755 index 0000000..963fd4b --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/en_US.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Order {$order_ref} validated. Download your files.', + 'The file [%file] does not exist' => 'The file [%file] does not exist', + 'This module cannot be used on the current cart.' => 'This module cannot be used on the current cart.', + 'Virtual product download message' => 'Virtual product download message', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/fr_FR.php b/domokits/local/modules/VirtualProductDelivery/I18n/fr_FR.php new file mode 100755 index 0000000..179b26a --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/fr_FR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Commande {$order_ref} validée. Téléchargez vos fichiers.', + 'The file [%file] does not exist' => 'le fichier [%file] n\'existe pas', + 'This module cannot be used on the current cart.' => 'Ce module ne peut pas être utilisé avec le panier actuel.', + 'Virtual product download message' => 'Message pour le téléchargement des produits virtuels', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/de_DE.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/de_DE.php new file mode 100644 index 0000000..fc50921 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/de_DE.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Lieferadresse', + 'Download' => 'Herunterladen', + 'File' => 'Datei', + 'List of downloadable files' => 'Liste der herunterladbaren Dateien', + 'No delivery address for this delivery method' => 'Keine Lieferadresse für diese Liefermethode', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/en_US.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/en_US.php new file mode 100755 index 0000000..43f5ce2 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/en_US.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Delivery address', + 'Download' => 'Download', + 'File' => 'File', + 'List of downloadable files' => 'List of downloadable files', + 'No delivery address for this delivery method' => 'No delivery address for this delivery method', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/fr_FR.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/fr_FR.php new file mode 100755 index 0000000..3c7de26 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/fr_FR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Adresse de livraison', + 'Download' => 'Télécharger', + 'File' => 'Fichier', + 'List of downloadable files' => 'Liste des fichiers téléchargeables', + 'No delivery address for this delivery method' => 'L\'adresse de livraison n\'est pas nécessaire pour cette méthode de livraison', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/it_IT.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/it_IT.php new file mode 100644 index 0000000..0fc49b5 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/it_IT.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Indirizzo di consegna', + 'File' => 'File', + 'List of downloadable files' => 'Elenco dei file scaricabili', + 'No delivery address for this delivery method' => 'Nessun indirizzo di consegna per questo metodo di consegna', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/ru_RU.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/ru_RU.php new file mode 100755 index 0000000..7f10712 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/ru_RU.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Адрес доставки', + 'Download' => 'Скачать', + 'File' => 'Файл', + 'List of downloadable files' => 'Список скачиваемых файлов', + 'No delivery address for this delivery method' => 'Нет адреса доставки для этого метода доставки', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/tr_TR.php b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/tr_TR.php new file mode 100644 index 0000000..1a06df9 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/frontOffice/default/tr_TR.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Delivery address' => 'Teslimat adresi', + 'Download' => 'İndir', + 'File' => 'Dosya', + 'List of downloadable files' => 'İndirilebilir dosyalar', + 'No delivery address for this delivery method' => 'Bu teslim yöntemi için hiçbir teslimat adresi', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/it_IT.php b/domokits/local/modules/VirtualProductDelivery/I18n/it_IT.php new file mode 100644 index 0000000..d68e2dc --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/it_IT.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Ordine {$order_ref} convalidato. Scarica i tuoi file.', + 'The file [%file] does not exist' => 'Il file [%file] non esiste', + 'This module cannot be used on the current cart.' => 'Questo modulo non può essere utilizzato sul carrello attuale.', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/de_DE.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/de_DE.php new file mode 100644 index 0000000..8e362d3 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/de_DE.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'Keine Lieferadresse für diese Liefermethode', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/en_US.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/en_US.php new file mode 100644 index 0000000..3256c53 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'No delivery address for this delivery method', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/fr_FR.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/fr_FR.php new file mode 100644 index 0000000..ba2dbaf --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'L\'adresse de livraison n\'est pas nécessaire pour cette méthode de livraison', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/it_IT.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/it_IT.php new file mode 100644 index 0000000..a925248 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/it_IT.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'Nessun indirizzo di consegna per questo metodo di consegna', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/ru_RU.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/ru_RU.php new file mode 100644 index 0000000..dd6c4ea --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/ru_RU.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'Нет адреса доставки для этого метода доставки', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/tr_TR.php b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/tr_TR.php new file mode 100644 index 0000000..6441d14 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/pdf/default/tr_TR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'No delivery address for this delivery method' => 'Bu teslim yöntemi için hiçbir teslimat adresi', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/ru_RU.php b/domokits/local/modules/VirtualProductDelivery/I18n/ru_RU.php new file mode 100755 index 0000000..6a70c30 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/ru_RU.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Заказ {$order_ref} подтвержден. Можете скачивать ваши файлы.', + 'The file [%file] does not exist' => 'Файл [%file] не найден', + 'This module cannot be used on the current cart.' => 'Этот модуль не может быть использован с текущей корзиной', + 'Virtual product download message' => 'Сообщения при скачивании виртуального товара', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/I18n/tr_TR.php b/domokits/local/modules/VirtualProductDelivery/I18n/tr_TR.php new file mode 100644 index 0000000..49e4b18 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/I18n/tr_TR.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + 'Order {$order_ref} validated. Download your files.' => 'Doğrulanmış {$order_ref} sipariş. Download senin eğe.', + 'The file [%file] does not exist' => '[%file] dosyası yok', + 'This module cannot be used on the current cart.' => 'Bu modül geçerli arabaya kullanılamaz.', + 'Virtual product download message' => 'Sanal ürün indir mesaj', +]; diff --git a/domokits/local/modules/VirtualProductDelivery/LICENSE.txt b/domokits/local/modules/VirtualProductDelivery/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/LICENSE.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/domokits/local/modules/VirtualProductDelivery/VirtualProductDelivery.php b/domokits/local/modules/VirtualProductDelivery/VirtualProductDelivery.php new file mode 100644 index 0000000..3a7b91c --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/VirtualProductDelivery.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualProductDelivery; + +use Propel\Runtime\Connection\ConnectionInterface; +use Thelia\Core\Translation\Translator; +use Thelia\Model\Country; +use Thelia\Model\LangQuery; +use Thelia\Model\Message; +use Thelia\Model\MessageQuery; +use Thelia\Model\State; +use Thelia\Module\AbstractDeliveryModuleWithState; +use Thelia\Module\Exception\DeliveryException; + +class VirtualProductDelivery extends AbstractDeliveryModuleWithState +{ + public const MESSAGE_DOMAIN = 'virtualproductdelivery'; + + /** @var Translator */ + protected $translator; + + /** + * The module is valid if the cart contains only virtual products. + * + * @throws \Propel\Runtime\Exception\PropelException + * + * @return bool true if there is only virtual products in cart elsewhere false + */ + public function isValidDelivery(Country $country, State $state = null) + { + return $this->getRequest()->getSession()->getSessionCart($this->getDispatcher())->isVirtual(); + } + + public function getPostage(Country $country, State $state = null) + { + if (!$this->isValidDelivery($country, $state)) { + throw new DeliveryException( + $this->trans('This module cannot be used on the current cart.') + ); + } + + return 0.0; + } + + /** + * This module manages virtual product delivery. + * + * @return bool + */ + public function handleVirtualProductDelivery() + { + return true; + } + + public function postActivation(ConnectionInterface $con = null): void + { + // create new message + if (null === MessageQuery::create()->findOneByName('mail_virtualproduct')) { + $message = new Message(); + $message + ->setName('mail_virtualproduct') + ->setHtmlTemplateFileName('virtual-product-download.html') + ->setHtmlLayoutFileName('') + ->setTextTemplateFileName('virtual-product-download.txt') + ->setTextLayoutFileName('') + ->setSecured(0); + + $languages = LangQuery::create()->find(); + + foreach ($languages as $language) { + $locale = $language->getLocale(); + + $message->setLocale($locale); + + $message->setSubject( + $this->trans('Order {$order_ref} validated. Download your files.', [], $locale) + ); + $message->setTitle( + $this->trans('Virtual product download message', [], $locale) + ); + } + + $message->save(); + } + } + + protected function trans($id, $parameters = [], $locale = null) + { + if (null === $this->translator) { + $this->translator = Translator::getInstance(); + } + + return $this->translator->trans($id, $parameters, self::MESSAGE_DOMAIN, $locale); + } +} diff --git a/domokits/local/modules/VirtualProductDelivery/composer.json b/domokits/local/modules/VirtualProductDelivery/composer.json new file mode 100644 index 0000000..77460d8 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/composer.json @@ -0,0 +1,11 @@ +{ + "name": "thelia/virtual-product-delivery-module", + "license": "LGPL-3.0+", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "VirtualProductDelivery" + } +} diff --git a/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.html b/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.html new file mode 100644 index 0000000..f7069b3 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.html @@ -0,0 +1,29 @@ +{default_translation_domain domain="virtualproductdelivery.email.default"} +{default_locale locale={$locale}} + +{loop name="order.invoice" type="order" id=$order_id customer="*" limit="1" backend_context="1"} +

+ {intl l="Your order %ref has been validated. You can download your files." ref={$REF}}

+ +

{intl l="Products:"}

+ +
    + {loop type="order_product" name="order-products" order=$ID virtual="1" backend_context="1"} +
  • + {$TITLE} : {url path="/account/download/$ID"} + {ifloop rel="combinations"} +
    + {loop type="order_product_attribute_combination" name="combinations" order_product=$ID} + {$ATTRIBUTE_TITLE} - {$ATTRIBUTE_AVAILABILITY_TITLE} + {/loop} + {/ifloop} +
  • + {/loop} +
+{/loop} + +

{intl l="You have to be logged in to your account to download this files."}

+ +

{intl l="Feel free to contact us for any further information."}

+ +

{intl l="Best Regards."}

\ No newline at end of file diff --git a/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.txt b/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.txt new file mode 100644 index 0000000..9fc5592 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/templates/email/default/virtual-product-download.txt @@ -0,0 +1,26 @@ +{default_translation_domain domain="virtualproductdelivery.email.default"} +{default_locale locale={$locale}} + +{loop name="order.invoice" type="order" id=$order_id customer="*" limit="1" backend_context="1"} + +{intl l="Your order %ref has been validated. You can download your files." ref={$REF}} + +---------------------------------------------------------------------- +{intl l="Products:"} +---------------------------------------------------------------------- +{loop type="order_product" name="order-products" order=$ID virtual="1" backend_context="1"} +{$TITLE} : {url path="/account/download/$ID"} +{ifloop rel="combinations"} +{loop type="order_product_attribute_combination" name="combinations" order_product=$ID} +{$ATTRIBUTE_TITLE} - {$ATTRIBUTE_AVAILABILITY_TITLE} +{/loop} +{/ifloop} +---------------------------------------------------------------------- +{/loop} +{/loop} + +{intl l="You have to be logged in to your account to download this files."} + +{intl l="Feel free to contact us for any further information."} + +{intl l="Best Regards."} \ No newline at end of file diff --git a/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/account-order-after-products.html b/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/account-order-after-products.html new file mode 100644 index 0000000..c4384e7 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/account-order-after-products.html @@ -0,0 +1,24 @@ +{loop type="order" name="virtual.order" id="$order_id" limit="1"} +{if $STATUS >=2 && $VIRTUAL} + + + + + + + + + {loop name="virtual.order.products" type="order_product" virtual="1" order={$ID}} + + + + + {/loop} + +
{intl l="File" d='virtualproductdelivery.fo.default'}{intl l="Download" d='virtualproductdelivery.fo.default'}
{$TITLE} + + {intl l="Download" d='virtualproductdelivery.fo.default'} + +
+{/if} +{/loop} diff --git a/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/delivery-address.html b/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/delivery-address.html new file mode 100644 index 0000000..ceb1e75 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/templates/frontOffice/default/delivery-address.html @@ -0,0 +1,6 @@ +
+
{intl l="Delivery address" d="virtualproductdelivery.fo.default"}
+
+ {intl l="No delivery address for this delivery method" d="virtualproductdelivery.fo.default"} +
+
\ No newline at end of file diff --git a/domokits/local/modules/VirtualProductDelivery/templates/pdf/default/delivery-address.html b/domokits/local/modules/VirtualProductDelivery/templates/pdf/default/delivery-address.html new file mode 100644 index 0000000..6a5a1f9 --- /dev/null +++ b/domokits/local/modules/VirtualProductDelivery/templates/pdf/default/delivery-address.html @@ -0,0 +1,3 @@ +

+ {intl l="No delivery address for this delivery method" d="virtualproductdelivery.pdf.default"} +

\ No newline at end of file diff --git a/domokits/local/modules/WebProfiler/Config/config.xml b/domokits/local/modules/WebProfiler/Config/config.xml new file mode 100644 index 0000000..d75e6f6 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/config.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/Config/config_dev.xml b/domokits/local/modules/WebProfiler/Config/config_dev.xml new file mode 100644 index 0000000..d75e6f6 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/config_dev.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/Config/config_prod.xml b/domokits/local/modules/WebProfiler/Config/config_prod.xml new file mode 100644 index 0000000..d75e6f6 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/config_prod.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/Config/config_test.xml b/domokits/local/modules/WebProfiler/Config/config_test.xml new file mode 100644 index 0000000..d75e6f6 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/config_test.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/Config/module.xml b/domokits/local/modules/WebProfiler/Config/module.xml new file mode 100644 index 0000000..07e5948 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/module.xml @@ -0,0 +1,43 @@ + + + WebProfiler\WebProfiler + + Add thelia specific data to web profiler + + + + Ajoute des données spécifique a thelia dans le web profiler + + + + + en_US + fr_FR + + 2.5.4 + + + + + + + classic + + 2.5.4 + other + 0 + 1 + diff --git a/domokits/local/modules/WebProfiler/Config/routing.xml b/domokits/local/modules/WebProfiler/Config/routing.xml new file mode 100644 index 0000000..f687f50 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/routing.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/domokits/local/modules/WebProfiler/Config/schema.xml b/domokits/local/modules/WebProfiler/Config/schema.xml new file mode 100644 index 0000000..793d486 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Config/schema.xml @@ -0,0 +1,25 @@ + + + + diff --git a/domokits/local/modules/WebProfiler/DataCollector/SmartyDataCollector.php b/domokits/local/modules/WebProfiler/DataCollector/SmartyDataCollector.php new file mode 100644 index 0000000..2dd4864 --- /dev/null +++ b/domokits/local/modules/WebProfiler/DataCollector/SmartyDataCollector.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WebProfiler\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use TheliaSmarty\Template\DataCollectorSmartyParser; + +class SmartyDataCollector extends DataCollector +{ + private $smartyParser; + + public function __construct(DataCollectorSmartyParser $smartyParser) + { + $this->smartyParser = $smartyParser; + } + + public function collect(Request $request, Response $response, \Throwable $exception = null): void + { + $this->data['templates'] = $this->smartyParser->getCollectedTemplates(); + } + + public function getTemplates() + { + return $this->data['templates']; + } + + public function getTemplateCount() + { + return \count($this->data['templates']); + } + + public function getTotalExecutionTime() + { + return array_reduce($this->data['templates'], function ($carry, $template) { return $carry + $template['executionTime']; }, 0); + } + + public function getName() + { + return 'smarty'; + } + + public function reset(): void + { + $this->data['templates'] = []; + } +} diff --git a/domokits/local/modules/WebProfiler/DataCollector/TheliaCollector.php b/domokits/local/modules/WebProfiler/DataCollector/TheliaCollector.php new file mode 100644 index 0000000..a0cff16 --- /dev/null +++ b/domokits/local/modules/WebProfiler/DataCollector/TheliaCollector.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WebProfiler\DataCollector; + +use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Thelia\Core\Thelia; + +class TheliaCollector extends AbstractDataCollector +{ + public function collect(Request $request, Response $response, \Throwable $exception = null): void + { + $this->data = [ + 'theliaVersion' => Thelia::THELIA_VERSION, + ]; + } + + public function getTheliaVersion() + { + return $this->data['theliaVersion']; + } + + public static function getTemplate(): ?string + { + return '@WebProfilerModule/debug/dataCollector/thelia.html.twig'; + } +} diff --git a/domokits/local/modules/WebProfiler/I18n/en_US.php b/domokits/local/modules/WebProfiler/I18n/en_US.php new file mode 100644 index 0000000..d391ee9 --- /dev/null +++ b/domokits/local/modules/WebProfiler/I18n/en_US.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // 'an english string' => 'The displayed english string', +]; diff --git a/domokits/local/modules/WebProfiler/I18n/fr_FR.php b/domokits/local/modules/WebProfiler/I18n/fr_FR.php new file mode 100644 index 0000000..ff066ca --- /dev/null +++ b/domokits/local/modules/WebProfiler/I18n/fr_FR.php @@ -0,0 +1,15 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + // 'an english string' => 'La traduction française de la chaine', +]; diff --git a/domokits/local/modules/WebProfiler/Readme.md b/domokits/local/modules/WebProfiler/Readme.md new file mode 100644 index 0000000..f07f8c3 --- /dev/null +++ b/domokits/local/modules/WebProfiler/Readme.md @@ -0,0 +1,55 @@ +# Web Profiler + +Add a short description here. You can also add a screenshot if needed. + +## Installation + +### Manually + +* Copy the module into ```/local/modules/``` directory and be sure that the name of the module is WebProfiler. +* Activate it in your thelia administration panel + +### Composer + +Add it in your main thelia composer.json file + +``` +composer require your-vendor/web-profiler-module:~1.0 +``` + +## Usage + +Explain here how to use your module, how to configure it, etc. + +## Hook + +If your module use one or more hook, fill this part. Explain which hooks are used. + + +## Loop + +If your module declare one or more loop, describe them here like this : + +[loop name] + +### Input arguments + +|Argument |Description | +|--- |--- | +|**arg1** | describe arg1 with an exemple. | +|**arg2** | describe arg2 with an exemple. | + +### Output arguments + +|Variable |Description | +|--- |--- | +|$VAR1 | describe $VAR1 variable | +|$VAR2 | describe $VAR2 variable | + +### Exemple + +Add a complete exemple of your loop + +## Other ? + +If you have other think to put, feel free to complete your readme as you want. diff --git a/domokits/local/modules/WebProfiler/WebProfiler.php b/domokits/local/modules/WebProfiler/WebProfiler.php new file mode 100644 index 0000000..0042bb3 --- /dev/null +++ b/domokits/local/modules/WebProfiler/WebProfiler.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace WebProfiler; + +use Symfony\Component\DependencyInjection\Loader\Configurator\ServicesConfigurator; +use Thelia\Module\BaseModule; +use TheliaSmarty\Template\DataCollectorSmartyParser; +use WebProfiler\DataCollector\SmartyDataCollector; + +use function Symfony\Component\DependencyInjection\Loader\Configurator\service; + +class WebProfiler extends BaseModule +{ + /** @var string */ + public const DOMAIN_NAME = 'webprofiler'; + + /* + * You may now override BaseModuleInterface methods, such as: + * install, destroy, preActivation, postActivation, preDeactivation, postDeactivation + * + * Have fun ! + */ + + /** + * Defines how services are loaded in your modules. + */ + public static function configureServices(ServicesConfigurator $servicesConfigurator): void + { + $servicesConfigurator->load(self::getModuleCode().'\\', __DIR__) + ->exclude( + [ + THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/I18n/*', + THELIA_MODULE_DIR.ucfirst(self::getModuleCode()).'/DataCollector/SmartyDataCollector', + ] + ) + ->autowire(true) + ->autoconfigure(true); + + $servicesConfigurator->set('data_collector.smarty', SmartyDataCollector::class) + ->args([ + service(DataCollectorSmartyParser::class)->ignoreOnInvalid(), + ]) + ->tag( + 'data_collector', + [ + 'template' => '@WebProfilerModule/debug/dataCollector/smarty.html.twig', + 'id' => 'smarty', + 'priority' => 42, + ] + ); + } +} diff --git a/domokits/local/modules/WebProfiler/composer.json b/domokits/local/modules/WebProfiler/composer.json new file mode 100644 index 0000000..97554a3 --- /dev/null +++ b/domokits/local/modules/WebProfiler/composer.json @@ -0,0 +1,12 @@ +{ + "name": "your-vendor/web-profiler-module", + "description": "WebProfiler module for Thelia", + "license": "LGPL-3.0-or-later", + "type": "thelia-module", + "require": { + "thelia/installer": "~1.1" + }, + "extra": { + "installer-name": "WebProfiler" + } +} diff --git a/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/base.html.twig b/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/base.html.twig new file mode 100644 index 0000000..538f7e1 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/base.html.twig @@ -0,0 +1,25 @@ + + + + + + + {% block title %}Thelia Profiler{% endblock %} + + {% block head %} + + {% endblock %} + + + + +{% block body '' %} + + diff --git a/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/header.html.twig b/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/header.html.twig new file mode 100644 index 0000000..3536442 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/bundles/WebProfilerBundle/Profiler/header.html.twig @@ -0,0 +1,5 @@ + diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smarty.svg b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smarty.svg new file mode 100644 index 0000000..efa16fe --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smarty.svg @@ -0,0 +1,48 @@ + +image/svg+xml + + diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smartyColor.svg b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smartyColor.svg new file mode 100644 index 0000000..6d0a989 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/smartyColor.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/thelia.svg b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/thelia.svg new file mode 100644 index 0000000..c0d85bd --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/thelia.svg @@ -0,0 +1,66 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/theliaWhite.svg b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/theliaWhite.svg new file mode 100644 index 0000000..7a2ca27 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/Icon/theliaWhite.svg @@ -0,0 +1,41 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/smarty.html.twig b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/smarty.html.twig new file mode 100644 index 0000000..93f3139 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/smarty.html.twig @@ -0,0 +1,94 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set icon %} + + {{ include('@WebProfilerModule/debug/dataCollector/Icon/smarty.svg') }} + + {{ collector.totalExecutionTime is defined ? collector.totalExecutionTime : 'n/a' }} + ms + + {% set text %} +
+ Render Time + {{ collector.totalExecutionTime is defined ? collector.totalExecutionTime : 'n/a' }} ms +
+
+ Template Calls + {{ collector.templatecount }} +
+ {% endset %} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: true, name: 'smarty', status: '', block_attrs: 'title=""' }) }} +{% endblock %} + +{% block menu %} + + {{ include('@WebProfilerModule/debug/dataCollector/Icon/smarty.svg') }} + Smarty + +{% endblock %} + +{% block panel %} + {% if collector.templatecount == 0 %} +

Smarty

+ +
+

No smarty templates were rendered for this request.

+
+ {% else %} +

Smarty Metrics

+ +
+
+ {{ collector.totalExecutionTime is defined ? collector.totalExecutionTime : 'n/a' }} ms + Render time +
+ +
+ {{ collector.templatecount }} + Template calls +
+
+ +

+ Render time includes sub-requests rendering time (if any). +

+ +

Rendered Templates

+ + + + + + + + + + {% for template in collector.templates %} + {%- set file = template.name -%} + {%- set link = file ? file|file_link(1) : false -%} + + + + + {% endfor %} + +
Template Name & PathRender time
+ {{ include('@WebProfilerModule/debug/dataCollector/Icon/smartyColor.svg') }} + {% if link %} + {{ template.name }} + + {% else %} + {{ template.name }} + {% endif %} + + {{ template.executionTime }} +
+ {% endif %} +{% endblock %} diff --git a/domokits/local/modules/WebProfiler/templates/debug/dataCollector/thelia.html.twig b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/thelia.html.twig new file mode 100644 index 0000000..f30e6c4 --- /dev/null +++ b/domokits/local/modules/WebProfiler/templates/debug/dataCollector/thelia.html.twig @@ -0,0 +1,12 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set icon %} + + {{ include('@WebProfilerModule/debug/dataCollector/Icon/thelia.svg') }} + + {{ collector.theliaVersion is defined ? collector.theliaVersion : 'n/a' }} + {% endset %} + + {{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: false, name: 'thelia', status: '', additional_classes: 'sf-toolbar-block-right', block_attrs: 'title=""' }) }} +{% endblock %} diff --git a/domokits/templates/frontOffice/custom/Gruntfile.js b/domokits/templates/frontOffice/custom/Gruntfile.js index a9db3ed..ff0bc87 100644 --- a/domokits/templates/frontOffice/custom/Gruntfile.js +++ b/domokits/templates/frontOffice/custom/Gruntfile.js @@ -38,7 +38,8 @@ module.exports = function (grunt) { cssmin: { target: { files: { - 'assets/dist/css/thelia.min.css': 'assets/src/css/thelia.css' + 'assets/dist/css/thelia.min.css': 'assets/src/css/thelia.css', + 'assets/dist/css/custom.min.css': 'assets/src/css/custom.css' } } }, @@ -177,7 +178,9 @@ module.exports = function (grunt) { dev: { src: [ 'assets/src/css/thelia.css', - 'assets/dist/css/thelia.min.css' + 'assets/dist/css/thelia.min.css', + 'assets/src/css/custom.css', + 'assets/dist/css/custom.min.css' ] } }, @@ -198,7 +201,7 @@ module.exports = function (grunt) { } }, cssmin: { - files: ['assets/src/css/thelia.css'], + files: ['assets/src/css/thelia.css', 'assets/src/css/custom.css'], tasks: ['autoprefixer', 'cssmin'], options: { spawn: false, diff --git a/domokits/templates/frontOffice/custom/assets/dist/css/thelia.min.css b/domokits/templates/frontOffice/custom/assets/dist/css/thelia.min.css index f3675a8..e5372b0 100644 --- a/domokits/templates/frontOffice/custom/assets/dist/css/thelia.min.css +++ b/domokits/templates/frontOffice/custom/assets/dist/css/thelia.min.css @@ -2,7 +2,7 @@ * Bootstrap v3.3.6 (http://getbootstrap.com) * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */@import url(//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800);hr,img{border:0}body,figure{margin:0}.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu{float:left}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{vertical-align:middle}svg:not(:root){overflow:hidden}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}.glyphicon,address{font-style:normal}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{blockquote,img,pre,tr{page-break-inside:avoid}*,:after,:before{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999}thead{display:table-header-group}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none}.img-thumbnail,body{background-color:#fff}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/bootstrap/glyphicons-halflings-regular.eot);src:url(../fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/bootstrap/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/bootstrap/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/bootstrap/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before,.glyphicon-btc:before,.glyphicon-xbt:before{content:"\e227"}.glyphicon-jpy:before,.glyphicon-yen:before{content:"\00a5"}.glyphicon-rub:before,.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*,:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{font-family:'Open Sans',sans-serif;font-size:14px;line-height:1.42857143;color:#7a7a7a}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#f49a17;text-decoration:none}a:focus,a:hover{color:#b66f09;text-decoration:underline}a:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:3px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#e5e5e5}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}dt,kbd kbd{font-weight:700}address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{padding-left:0;list-style:none}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#e5e5e5}.text-primary{color:#f49a17}a.text-primary:focus,a.text-primary:hover{color:#ce7e0a}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#f49a17}a.bg-primary:focus,a.bg-primary:hover{background-color:#ce7e0a}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}pre code,table{background-color:transparent}.page-header{padding-bottom:9px;margin:40px 0 20px}dl,ol,ul{margin-top:0}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}address,dl{margin-bottom:20px}ol,ul{margin-bottom:10px}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}legend,pre{display:block}dd{margin-left:0}@media (min-width:992px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #e5e5e5}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#e5e5e5}legend,pre{color:#7a7a7a}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}code,kbd{padding:2px 4px;font-size:90%;border-radius:3px}caption,th{text-align:left}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}code,kbd,pre,samp{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{color:#c7254e;background-color:#f9f2f4}kbd{color:#fff;background-color:#333;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;box-shadow:none}pre{padding:9.5px;margin:0 0 10px;font-size:13px;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:3px}.container,.container-fluid{margin-right:auto;margin-left:auto}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0}.container,.container-fluid{padding-left:15px;padding-right:15px}.pre-scrollable{overflow-y:scroll}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}caption{padding-top:8px;padding-bottom:8px;color:#e5e5e5}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset,legend{padding:0;border:0}fieldset{margin:0;min-width:0}legend{width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;border-bottom:1px solid transparent}label{display:inline-block;max-width:100%;margin-bottom:5px}input[type=search]{box-sizing:border-box;-webkit-appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}.form-control,output{font-size:14px;line-height:1.42857143;color:#555;display:block}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}output{padding-top:7px}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #e5e5e5;border-radius:3px;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#c7c7c7;opacity:1}.form-control:-ms-input-placeholder{color:#c7c7c7}.form-control::-webkit-input-placeholder{color:#c7c7c7}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.form-group-sm .form-control,.input-sm{padding:5px 10px;border-radius:3px;font-size:12px}.input-sm{height:30px;line-height:1.5}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;line-height:1.5}.form-group-lg .form-control,.input-lg{border-radius:6px;padding:10px 16px;font-size:18px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;line-height:1.3333333}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;line-height:1.3333333}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.collapsing,.dropdown,.dropup{position:relative}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .form-control{border-color:#3c763d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#bababa}@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block}.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#f49a17;text-decoration:none}.btn.active,.btn:active{outline:0}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#f49a17;background-color:#f7f7f7;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#f49a17;background-color:#dedede;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#f49a17;background-color:#dedede;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#f49a17;background-color:#ccc;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#f7f7f7;border-color:#ccc}.btn-default .badge{color:#f7f7f7;background-color:#f49a17}.btn-primary{color:#fff;background-color:#f49a17;border-color:#f49a17}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#ce7e0a;border-color:#855206}.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#ce7e0a;border-color:#c47809}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#ac6908;border-color:#855206}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#f49a17;border-color:#f49a17}.btn-primary .badge{color:#f49a17;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#f49a17;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#b66f09;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#e5e5e5;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{height:0;overflow:hidden;transition-property:height,visibility;transition-duration:.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;text-align:left;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:3px;background-clip:padding-box}.dropdown-menu-right,.dropdown-menu.pull-right{left:auto;right:0}.dropdown-header,.dropdown-menu>li>a{display:block;padding:3px 20px;line-height:1.42857143;white-space:nowrap}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{clear:both;font-weight:400;color:#7a7a7a}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#6d6d6d;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#f49a17}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#e5e5e5}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{font-size:12px;color:#e5e5e5}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:992px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn .caret,.btn-group>.btn:first-child{margin-left:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.block,.btn-group.open .dropdown-toggle.btn-link,.btn.active,.btn:active{box-shadow:none}.btn-lg .caret{border-width:5px 5px 0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:3px 3px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 3px 3px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.nav>li,.nav>li>a{display:block;position:relative}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #e5e5e5;border-radius:3px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li>a{padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#e5e5e5}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#e5e5e5;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#f49a17}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:3px 3px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:3px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-justified>li,.nav-stacked>li{float:none}.nav-pills>li>a{border-radius:3px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#f49a17}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:3px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:992px){.navbar{border-radius:3px}.navbar-header{float:left}.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0}}.embed-responsive,.modal,.modal-open,.progress{overflow:hidden}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}.navbar-static-top{z-index:1000;border-width:0 0 1px}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:992px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:3px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:991px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-danger,.progress-striped .progress-bar-info,.progress-striped .progress-bar-success,.progress-striped .progress-bar-warning{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)}@media (min-width:992px){.navbar-toggle{display:none}.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px}@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block}.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}.breadcrumb>li,.pagination{display:inline-block}.btn .badge,.btn .label{top:-1px;position:relative}@media (max-width:991px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:992px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;box-shadow:none}.navbar-text{float:left;margin-left:15px;margin-right:15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:3px 3px 0 0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:992px){.navbar-left{float:left!important;float:left}.navbar-right{float:right!important;float:right;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f5f5f5;border-color:#fff}.navbar-default .navbar-brand{color:#707070}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#575757;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#707070}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#fff;background-color:#f49a17}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#fff}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#f49a17;color:#fff}@media (max-width:991px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#707070}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:#f49a17}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#707070}.navbar-default .navbar-link:hover{color:#fff}.navbar-default .btn-link{color:#707070}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#fff}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{background-color:transparent;color:#fff}.navbar-inverse .navbar-brand{color:#fff}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#080808;color:#fff}@media (max-width:991px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .btn-link,.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover,.navbar-inverse .navbar-link,.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{margin-bottom:20px;list-style:none;background-color:#fff;border-radius:3px}.breadcrumb>li+li:before{padding:0 5px;color:#7a7a7a}.breadcrumb>.active{color:#7a7a7a}.pagination{padding-left:0;margin:20px 0;border-radius:3px}.pager li,.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;background-color:#f9f9f9;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#b66f09;background-color:transparent;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#f49a17;border-color:#f49a17;cursor:default}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#e5e5e5;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.badge,.label{font-weight:700;line-height:1;white-space:nowrap;text-align:center}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#f7f7f7;border:1px solid #ccc;border-radius:0}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:transparent}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#e5e5e5;background-color:#f7f7f7;cursor:not-allowed}a.badge:focus,a.badge:hover,a.label:focus,a.label:hover{color:#fff;cursor:pointer;text-decoration:none}.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff;vertical-align:baseline;border-radius:.25em}.label:empty{display:none}.label-default{background-color:#e5e5e5}.label-default[href]:focus,.label-default[href]:hover{background-color:#ccc}.label-primary{background-color:#f49a17}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#ce7e0a}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;color:#fff;vertical-align:middle;background-color:#e5e5e5;border-radius:10px}.badge:empty{display:none}.media-object,.thumbnail{display:block}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#f49a17;background-color:#fff}.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.alert,.thumbnail{margin-bottom:20px}.alert .alert-link,.close{font-weight:700}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:3px;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#f49a17}.thumbnail .caption{padding:9px;color:#7a7a7a}.alert{padding:15px;border:1px solid transparent;border-radius:3px}.alert h4{margin-top:0;color:inherit}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.modal,.modal-backdrop{top:0;right:0;bottom:0;left:0}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:3px;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#f49a17;box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-bar-info{background-color:#5bc0de}.progress-bar-warning{background-color:#f0ad4e}.progress-bar-danger{background-color:#d9534f}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#eee;color:#e5e5e5;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#e5e5e5}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#f49a17;border-color:#f49a17}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#fdefda}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:0;box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-title{margin-top:0;font-size:16px}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:-1;border-bottom-right-radius:-1}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#f5f5f5}.panel-default>.panel-heading{color:#7a7a7a;background-color:#f5f5f5;border-color:#f5f5f5}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f5f5f5}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#7a7a7a}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f5f5f5}.panel-primary{border-color:#f49a17}.panel-primary>.panel-heading{color:#fff;background-color:#f49a17;border-color:#f49a17}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f49a17}.panel-primary>.panel-heading .badge{color:#f49a17;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f49a17}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:3px;box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.popover,.tooltip{font-family:'Open Sans',sans-serif;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;text-decoration:none}.modal-title,.popover,.tooltip{line-height:1.42857143}.carousel-caption,.carousel-caption .btn,.carousel-control,.popover,.product-price .price-label,.text-hide,.tooltip{text-shadow:none}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-content,.popover{background-clip:padding-box}.modal{display:none;position:fixed;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0}.modal-backdrop{position:fixed;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;text-align:left;text-align:start;font-size:12px;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:3px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px}.tooltip.top-right .tooltip-arrow{left:5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{border-width:0 5px 5px;border-bottom-color:#000;top:0}.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px}.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px}.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;text-align:left;text-align:start;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2)}.carousel-caption,.carousel-control,.carousel-indicators,.fa-fw,.fa-li{text-align:center}#categories.block-nav .block-title,#filters>h3,#product #product-tabs .nav-tabs li,.availability .in-stock,.availability .out-of-stock,.filter .filter-heading,.panel-heading,.table-cart thead th,.table-order thead th{text-transform:uppercase}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.carousel,.carousel-inner{position:relative}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.left>.arrow:after,.popover.right>.arrow:after{content:" ";bottom:-10px}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{left:1px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff}.carousel-inner{overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);left:0}}.cart-warning:before,.fa{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:10%;opacity:.5;filter:alpha(opacity=50);font-size:30px;color:#ccc;background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:focus,.carousel-control:hover{outline:0;color:#ccc;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:45px;height:45px;margin-top:-15px;font-size:45px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.table-cart tbody td.product .name,.table-order tbody td.product .name,header .header .logo{margin-top:0}.block-thumbnail:after,.block-thumbnail:before,.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table}.block-thumbnail:after,.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;background-color:transparent;border:0}.fa-inverse,.footer-container .footer-info a,.footer-container .footer-info a:focus,.footer-container .footer-info a:hover{color:#fff}.affix{position:fixed}@-ms-viewport{width:device-width}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}.visible-xs-block{display:block!important}.visible-xs-inline{display:inline!important}.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}.visible-sm-block{display:block!important}.visible-sm-inline{display:inline!important}.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}.visible-md-block{display:block!important}.visible-md-inline{display:inline!important}.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}.visible-lg-block{display:block!important}.visible-lg-inline{display:inline!important}.visible-lg-inline-block{display:inline-block!important}.hidden-lg{display:none!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}.hidden-print{display:none!important}}/*! + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */@import url(//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800);.label,audio,canvas,progress,sub,sup,video{vertical-align:baseline}.btn,.btn-group,.btn-group-vertical,.caret,.checkbox-inline,.input-group-addon,.input-group-btn,.radio-inline,img{vertical-align:middle}.collapsing,.dropdown-menu .divider,.nav .nav-divider,.sr-only,svg:not(:root){overflow:hidden}.popover,.tooltip,button,select{text-transform:none}hr,img{border:0}.cart-warning:before,.fa,.glyphicon{-moz-osx-font-smoothing:grayscale}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{background:#ff0;color:#000}sub,sup{font-size:75%;line-height:0;position:relative}sup{top:-.5em}sub{bottom:-.25em}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}.glyphicon,.popover,.tooltip,address{font-style:normal}button{overflow:visible}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{blockquote,img,pre,tr{page-break-inside:avoid}*,:after,:before{background:0 0!important;color:#000!important;box-shadow:none!important;text-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999}thead{display:table-header-group}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}#product #product-gallery #product-thumbnails .carousel-control,.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-success,.open>.dropdown-toggle.btn-warning{background-image:none}.img-thumbnail,body{background-color:#fff}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/bootstrap/glyphicons-halflings-regular.eot);src:url(../fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/bootstrap/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/bootstrap/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/bootstrap/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-weight:400;line-height:1;-webkit-font-smoothing:antialiased}#products-new .overlay:after,#products-offer .overlay:after,#products-upsell .overlay:after,.popover,.tooltip,body{font-family:'Open Sans',sans-serif}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before,.glyphicon-btc:before,.glyphicon-xbt:before{content:"\e227"}.glyphicon-jpy:before,.glyphicon-yen:before{content:"\00a5"}.glyphicon-rub:before,.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*,:after,:before{box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:transparent}body{margin:0;font-size:14px;line-height:1.42857143;color:#7a7a7a}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#f49a17;text-decoration:none}a:focus,a:hover{color:#b66f09;text-decoration:underline}a:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}figure{margin:0}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:3px;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#e5e5e5}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,.label,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}dt,kbd kbd{font-weight:700}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{padding-left:0;list-style:none}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}#categories.block-nav .block-title,#filters>h3,#product #product-tabs .nav-tabs li,.availability .in-stock,.availability .out-of-stock,.filter .filter-heading,.initialism,.panel-heading,.table-cart tfoot th.total,.table-cart thead th,.table-order tfoot th.total,.table-order thead th{text-transform:uppercase}.text-muted{color:#e5e5e5}.text-primary{color:#f49a17}a.text-primary:focus,a.text-primary:hover{color:#ce7e0a}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#f49a17}a.bg-primary:focus,a.bg-primary:hover{background-color:#ce7e0a}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}pre code,table{background-color:transparent}.page-header{padding-bottom:9px;margin:40px 0 20px}dl,ol,ul{margin-top:0}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0}ol,ul{margin-bottom:10px}.list-inline{margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-bottom:20px}dd,dt{line-height:1.42857143}dd{margin-left:0}@media (min-width:992px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #e5e5e5}.initialism{font-size:90%}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#e5e5e5}legend,pre{color:#7a7a7a}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}code,kbd{padding:2px 4px;font-size:90%;border-radius:3px}.dropdown-menu,caption,th{text-align:left}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;line-height:1.42857143}code,kbd,pre,samp{font-family:Monaco,Menlo,Consolas,"Courier New",monospace}code{color:#c7254e;background-color:#f9f2f4}kbd{color:#fff;background-color:#333;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:3px}.container,.container-fluid{margin-right:auto;margin-left:auto}#payment-success.panel .panel-heading .payment-method,pre code{font-size:inherit}.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle),.btn-link,pre code{border-radius:0}pre code{padding:0;color:inherit;white-space:pre-wrap}.pre-scrollable{overflow-y:scroll}.container{padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}caption{padding-top:8px;padding-bottom:8px;color:#e5e5e5}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{overflow-x:auto;min-height:.01%}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset,legend{padding:0;border:0}fieldset{margin:0;min-width:0}legend{display:block;width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;border-bottom:1px solid transparent}label{display:inline-block;max-width:100%;margin-bottom:5px}input[type=search]{box-sizing:border-box;-webkit-appearance:none}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}.form-control,output{font-size:14px;line-height:1.42857143;display:block;color:#555}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}output{padding-top:7px}.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #e5e5e5;border-radius:3px;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#c7c7c7;opacity:1}.form-control:-ms-input-placeholder{color:#c7c7c7}.form-control::-webkit-input-placeholder{color:#c7c7c7}.form-control::-ms-expand{border:0;background-color:transparent}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-left:-20px;margin-top:4px\9}.checkbox-inline,.collapsing,.dropdown,.dropup,.has-feedback,.radio-inline{position:relative}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}.btn-block+.btn-block,.help-block{margin-top:5px}.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0;min-height:34px}.form-control-static.input-lg,.form-control-static.input-sm{padding-left:0;padding-right:0}.form-group-sm .form-control,.input-sm{padding:5px 10px;border-radius:3px;font-size:12px}.input-sm{height:30px;line-height:1.5}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;line-height:1.5}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.btn-group-lg>.btn,.btn-lg,.form-group-lg .form-control,.input-lg{padding:10px 16px;font-size:18px}.input-lg{height:46px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-bottom:10px;color:#bababa}@media (min-width:768px){.form-inline .form-control,.form-inline .form-control-static,.form-inline .form-group{display:inline-block}.form-inline .checkbox,.form-inline .control-label,.form-inline .form-group,.form-inline .radio{margin-bottom:0;vertical-align:middle}.form-inline .form-control{width:auto;vertical-align:middle}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}.form-horizontal .control-label{text-align:right;margin-bottom:0;padding-top:7px}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;-webkit-user-select:none;-ms-user-select:none;user-select:none}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:dotted thin;outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#f49a17;text-decoration:none}.btn.active,.btn:active{outline:0}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;opacity:.65;filter:alpha(opacity=65);box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#f49a17;background-color:#f7f7f7;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#f49a17;background-color:#dedede;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#f49a17;background-color:#dedede;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#f49a17;background-color:#ccc;border-color:#8c8c8c}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#f7f7f7;border-color:#ccc}.btn-default .badge{color:#f7f7f7;background-color:#f49a17}.btn-primary{color:#fff;background-color:#f49a17;border-color:#f49a17}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#ce7e0a;border-color:#855206}.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#ce7e0a;border-color:#c47809}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#ac6908;border-color:#855206}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#f49a17;border-color:#f49a17}.btn-primary .badge{color:#f49a17;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#f49a17}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#b66f09;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#e5e5e5;text-decoration:none}.btn-group-lg>.btn,.btn-lg{line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{height:0;transition-property:height,visibility;transition-duration:.35s;transition-timing-function:ease}.caret{display:inline-block;width:0;height:0;margin-left:2px;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:3px;background-clip:padding-box}.dropdown-menu-right,.dropdown-menu.pull-right{left:auto;right:0}.dropdown-header,.dropdown-menu>li>a{display:block;padding:3px 20px;line-height:1.42857143;white-space:nowrap}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-menu .divider{height:1px;margin:9px 0;background-color:#e5e5e5}.dropdown-menu>li>a{clear:both;font-weight:400;color:#7a7a7a}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{text-decoration:none;color:#6d6d6d;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;outline:0;background-color:#f49a17}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#e5e5e5}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{font-size:12px;color:#e5e5e5}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:992px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}#cart-address .panel,.block,.btn-group.open .dropdown-toggle.btn-link,.btn.active,.btn:active{box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.media-object.img-thumbnail,.nav>li>a>img{max-width:none}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:3px 3px 0 0}.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 3px 3px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group,.input-group .form-control,.input-group-btn,.input-group-btn>.btn,.nav>li,.nav>li>a,.navbar{position:relative}.input-group{display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #e5e5e5;border-radius:3px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{font-size:0;white-space:nowrap}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{display:block}.nav>li>a{display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#e5e5e5}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#e5e5e5;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#f49a17}.nav .nav-divider{height:1px;margin:9px 0;background-color:#e5e5e5}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:3px 3px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px;margin-right:0;border-radius:3px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-justified>li,.nav-stacked>li{float:none}.nav-pills>li>a{border-radius:3px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#f49a17}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li>a{text-align:center;margin-bottom:5px}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.alert,.breadcrumb,.navbar,.progress,.thumbnail{margin-bottom:20px}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:3px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{min-height:50px;border:1px solid transparent}.navbar-collapse{overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:992px){.navbar{border-radius:3px}.navbar-header{float:left}.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-left:0;padding-right:0}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}.navbar-static-top{border-radius:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:992px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:3px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:991px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:992px){.navbar-toggle{display:none}.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px}@media (min-width:768px){.navbar-form .form-control,.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block}.navbar-form .checkbox,.navbar-form .control-label,.navbar-form .form-group,.navbar-form .radio{margin-bottom:0;vertical-align:middle}.navbar-form .form-control{width:auto;vertical-align:middle}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}.breadcrumb>li,.pagination{display:inline-block}.btn .badge,.btn .label{top:-1px;position:relative}@media (max-width:991px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:992px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;box-shadow:none}.navbar-text{float:left;margin-left:15px;margin-right:15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:3px 3px 0 0}.breadcrumb,.pagination,.progress{border-radius:3px}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:992px){.navbar-left{float:left!important;float:left}.navbar-right{float:right!important;float:right;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f5f5f5;border-color:#fff}.navbar-default .navbar-brand{color:#707070}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#575757;background-color:transparent}.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#707070}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#fff;background-color:#f49a17}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#fff}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{background-color:#f49a17;color:#fff}@media (max-width:991px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#707070}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:#f49a17}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#707070}.navbar-default .navbar-link:hover{color:#fff}.navbar-default .btn-link{color:#707070}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#fff}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{background-color:transparent;color:#fff}.navbar-inverse .navbar-brand{color:#fff}.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#fff}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{background-color:#080808;color:#fff}@media (max-width:991px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#fff}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .btn-link,.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover,.navbar-inverse .navbar-link,.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{list-style:none;background-color:#fff}.breadcrumb>li+li:before{padding:0 5px;color:#7a7a7a}.breadcrumb>.active{color:#7a7a7a}.pagination{padding-left:0;margin:20px 0}.pager li,.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;background-color:#f9f9f9;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#b66f09;background-color:transparent;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;background-color:#f49a17;border-color:#f49a17;cursor:default}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#e5e5e5;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.badge,.label{font-weight:700;line-height:1;text-align:center;white-space:nowrap}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#f7f7f7;border:1px solid #ccc;border-radius:0}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:transparent}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.close,.list-group-item>.badge{float:right}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#e5e5e5;background-color:#f7f7f7;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;color:#fff;border-radius:.25em}.badge,.progress-bar,.tooltip{font-size:12px}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.label-default{background-color:#e5e5e5}.label-default[href]:focus,.label-default[href]:hover{background-color:#ccc}.label-primary{background-color:#f49a17}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#ce7e0a}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;color:#fff;vertical-align:middle;background-color:#e5e5e5;border-radius:10px}.badge:empty,.modal,.popover{display:none}.media-object,.thumbnail{display:block}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#f49a17;background-color:#fff}.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.alert .alert-link,.close{font-weight:700}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px;padding-left:15px;padding-right:15px}.list-group-item,.thumbnail{background-color:#fff;border:1px solid #ddd}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-left:60px;padding-right:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{padding:4px;line-height:1.42857143;border-radius:3px;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-left:auto;margin-right:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#f49a17}.thumbnail .caption{padding:9px;color:#7a7a7a}.alert,.panel-body{padding:15px}.alert{border:1px solid transparent;border-radius:3px}.alert h4{margin-top:0;color:inherit}.alert>p+p,.panel-group .panel+.panel{margin-top:5px}.alert>p,.alert>ul{margin-bottom:0}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;background-color:#f5f5f5;box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0%;height:100%;line-height:20px;color:#fff;text-align:center;background-color:#f49a17;box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{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:40px 40px}.progress-bar.active,.progress.active .progress-bar{animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{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)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{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)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{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)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{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)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{zoom:1;overflow:hidden}.media-body{width:10000px}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px}.list-group-item:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{text-decoration:none;color:#555;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{background-color:#eee;color:#e5e5e5;cursor:not-allowed}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#e5e5e5}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#f49a17;border-color:#f49a17}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#fdefda}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:0;box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive,.panel>.table-responsive>.table{margin-bottom:0}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:-1;border-top-left-radius:-1}.panel-title{margin-top:0;font-size:16px}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:-1;border-top-left-radius:-1}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-right-radius:0;border-top-left-radius:0}.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-right-radius:-1;border-top-left-radius:-1}.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-left:15px;padding-right:15px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:-1}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:-1}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:-1;border-bottom-left-radius:-1}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-left-radius:-1;border-bottom-right-radius:-1}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:-1}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:-1}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-responsive{border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:0}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#f5f5f5}.panel-default>.panel-heading{color:#7a7a7a;background-color:#f5f5f5;border-color:#f5f5f5}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f5f5f5}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#7a7a7a}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f5f5f5}.panel-primary{border-color:#f49a17}.panel-primary>.panel-heading{color:#fff;background-color:#f49a17;border-color:#f49a17}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#f49a17}.panel-primary>.panel-heading .badge{color:#f49a17;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#f49a17}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;left:0;bottom:0;height:100%;width:100%;border:0}.modal,.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:3px;box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.modal-title,.popover,.tooltip{line-height:1.42857143}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-content,.popover{background-clip:padding-box}.modal-open{overflow:hidden}.modal{overflow:hidden;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-ms-transform:translate(0,-25%);transform:translate(0,-25%);transition:transform .3s ease-out}.modal.in .modal-dialog{-ms-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0}.modal-backdrop{z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.carousel-control,.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-weight:400;letter-spacing:normal;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:3px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow,.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;border-width:5px 5px 0;border-top-color:#000}.tooltip.top .tooltip-arrow{left:50%;margin-left:-5px}.tooltip.top-left .tooltip-arrow{right:5px;margin-bottom:-5px}.tooltip.top-right .tooltip-arrow{left:5px;margin-bottom:-5px}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{border-width:0 5px 5px;border-bottom-color:#000;top:0}.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px}.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px}.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px}.popover{position:absolute;top:0;left:0;z-index:1060;max-width:276px;padding:1px;font-weight:400;letter-spacing:normal;line-break:auto;text-align:left;text-align:start;text-decoration:none;text-shadow:none;white-space:normal;word-break:normal;word-spacing:normal;word-wrap:normal;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.left>.arrow:after,.popover.right>.arrow:after{bottom:-10px;content:" "}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{left:1px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{transition:transform .6s ease-in-out;backface-visibility:hidden;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{transform:translate3d(100%,0,0);left:0}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{transform:translate3d(-100%,0,0);left:0}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{transform:translate3d(0,0,0);left:0}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:10%;font-size:30px;color:#ccc;text-align:center;text-shadow:none;background-color:rgba(0,0,0,0)}.carousel-control.left{background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:focus,.carousel-control:hover{outline:0;color:#ccc;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;margin-top:-10px;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;line-height:1;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:none}.affix,.loader{position:fixed}.product-price .price-label,.text-hide{color:transparent;border:0;text-shadow:none}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:45px;height:45px;margin-top:-15px;font-size:45px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.table-cart tbody td.product .name,.table-order tbody td.product .name,header .header .logo{margin-top:0}.block-thumbnail:after,.block-thumbnail:before,.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{content:" ";display:table}.block-thumbnail:after,.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;background-color:transparent}@-ms-viewport{width:device-width}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}.visible-xs-block{display:block!important}.visible-xs-inline{display:inline!important}.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}.visible-sm-block{display:block!important}.visible-sm-inline{display:inline!important}.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}.visible-md-block{display:block!important}.visible-md-inline{display:inline!important}.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}.visible-lg-block{display:block!important}.visible-lg-inline{display:inline!important}.visible-lg-inline-block{display:inline-block!important}.hidden-lg{display:none!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}.hidden-print{display:none!important}}/*! * Font Awesome 4.3.0 by @davegandy - http://fontawesome.io - @fontawesome * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(../fonts/fontawesome/fontawesome-webfont.eot?v=4.3.0);src:url(../fonts/fontawesome/fontawesome-webfont.eot?#iefix&v=4.3.0) format('embedded-opentype'),url(../fonts/fontawesome/fontawesome-webfont.woff2?v=4.3.0) format('woff2'),url(../fonts/fontawesome/fontawesome-webfont.woff?v=4.3.0) format('woff'),url(../fonts/fontawesome/fontawesome-webfont.ttf?v=4.3.0) format('truetype'),url(../fonts/fontawesome/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular) format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1,1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1,-1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{-webkit-filter:none;filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before,.fa-genderless:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}header .header{margin-bottom:20px}header .header .language-container .search-container{margin-bottom:10px}header .header .language-container .search-container .form-control{width:auto}header .header .language-container .currency-switch,header .header .language-container .language-switch{display:inline-block;position:relative;vertical-align:middle}header .header .language-container .currency-switch .dropdown-label,header .header .language-container .language-switch .dropdown-label{display:inline-block;float:left;margin-left:1em;margin-right:.4em}header .header .language-container .currency-switch .current,header .header .language-container .language-switch .current{display:inline-block;float:left;position:relative}#payment-method.panel .radio,.account-info .email,.account-info .mobile,.account-info .tel,.js .group-qty .form-inline .form-group{display:block}header .header .language-container .currency-switch .select,header .header .language-container .language-switch .select{left:auto;right:0;min-width:80px}.footer-container .footer-banner .banner .col{padding:10px 0}.footer-container .footer-block .blocks,.footer-container .footer-info .info{padding:20px 0}.footer-container .footer-info .info .nav-footer ul li+li:before{margin-right:10px}.account-info address{margin-bottom:0}.account-info li{margin-bottom:20px}.list-payment,.table-order tbody td.qty .group-qty{margin-bottom:0}.table-order-total td{width:50%}#delivery-address .panel-heading{position:relative}.checkout-progress{margin-bottom:20px;width:100%}.cart-warning,.table-cart tbody td.qty .group-qty,.table-cart-mini{margin-bottom:0}.cart-empty{margin:0;padding:40px}.table-cart-total td{width:50%}.cart-warning{clear:both}.pagination>li>a:focus,.pagination>li>span:focus{z-index:3}@media (min-width:992px){.navbar .navbar-cart .dropdown>a:after,.navbar .navbar-customer .dropdown>a:after{padding-left:.3em;display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f078";float:none}}@media (min-width:992px) and (min-width:992px){.navbar .navbar-cart .dropdown>a:after,.navbar .navbar-customer .dropdown>a:after{float:none}}.navbar .navbar-cart .dropdown-menu,.navbar .navbar-customer .dropdown-menu{margin:0;padding:20px}@media (max-width:992px){.navbar .navbar-cart .dropdown-menu,.navbar .navbar-customer .dropdown-menu{display:none}}.navbar .navbar-cart .dropdown-menu.cart-content,.navbar .navbar-customer .dropdown-menu.cart-content{width:350px}.navbar .navbar-cart .dropdown-menu.cart-content>p,.navbar .navbar-customer .dropdown-menu.cart-content>p{margin:0}.navbar .navbar-cart .cart-not-empty .cart-content,.navbar .navbar-customer .cart-not-empty .cart-content{border-top:none;padding:0}.navbar .full-width{position:static}.navbar .full-width .dropdown-menu{width:100%;left:0;right:0}.navbar .full-width .dropdown-menu .dropdown-content .dropdown-subheading{display:block}.js .dropdown-toggle:after{float:right;padding-left:.3em}@media (min-width:992px){.navbar-collapse .navbar-nav.navbar-right:first-child{margin-right:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:0}.js .dropdown-toggle:after{float:none}}#form-forgotpassword,#form-login{padding:45px}#form-forgotpassword legend,#form-login legend{margin-bottom:10px}#filters,.filter{margin-bottom:20px}.filter{padding:0 15px}.grid .products-content>ul .item .product-price,.grid .products-content>ul .item>article .product-image{padding:0}.filter .filter-heading{margin:0 0 4px}.toolbar{margin-bottom:20px}.toolbar .sorter-container .amount{float:left}.grid .products-content>ul .item>article .product-image,.grid .products-content>ul .item>article .product-info,.grid .products-content>ul .item>article .product-price,.list .products-content>ul .item{width:100%;float:none}.toolbar .sorter-container .sort-by,.toolbar .sorter-container .view-mode{margin-left:40px}.toolbar .pagination-container>.pagination{margin:15px 0 0}.products-content>ul .item .product-info .short-description,.products-content>ul .item .product-price .price-container{display:block;margin-bottom:5px}.grid .products-content>ul .item{margin-bottom:20px}.grid .products-content>ul .item>article{margin:0}.grid .products-content>ul .item>article .name{margin:4px 0}.grid .products-content>ul .item .description{display:none!important}@media (max-width:767px){.grid .products-content>ul .item .description{display:block!important}table.grid .products-content>ul .item .description{display:table!important}tr.grid .products-content>ul .item .description{display:table-row!important}td.grid .products-content>ul .item .description,th.grid .products-content>ul .item .description{display:table-cell!important}}.list .products-content>ul .item+.item{padding-top:15px}.list .products-content>ul .item>article{margin-left:0}.list .products-content>ul .item>article .product-image{margin-bottom:15px;padding:0}.list .products-content>ul .item>article .product-info .name{margin-top:0}.option{margin-bottom:20px;padding:0}.option .option-heading{display:block;margin:0 0 5px}#product #product-gallery .product-image,#product>section{margin-bottom:20px}#product #product-gallery #product-thumbnails .carousel-inner{margin:0 auto;width:90%}#brands .brands>ul .item>article,#folder-contents .contents>ul .item>article,.contents-list .item>article{margin-left:0}#product #product-gallery #product-thumbnails .carousel-control{background-image:none;display:none;width:4%;margin-top:-4px}#brands .brands>ul .item>article .brand-info .name,#folder-contents .contents>ul .item>article .content-info .name,#product #product-details .name,.contents-list .item>article .content-info .name,.page-header,.table-address .radio,.table-delivery .radio{margin-top:0}#product #product-gallery #product-thumbnails ul{margin:0}#product #product-gallery #product-thumbnails ul>li{margin:0;padding:0;width:19%}#product #product-details .product-price,#product #product-tabs{margin-bottom:20px}#folder-contents .contents>ul .item>article .content-image>img,.contents-list .item>article .content-image>img{width:100%}#product #product-details .product-cart{margin-bottom:20px;padding:0}#product #product-tabs .nav-tabs{margin-bottom:-1px}.folder-description{margin-bottom:20px}.contents-list .item{padding-bottom:15px}.contents-list .item+.item{padding-top:15px}.contents-list .item>article .content-image{margin-bottom:15px;padding:0}.brand-description{margin-bottom:20px}#brands .brands>ul .item{padding-bottom:15px}#brands .brands>ul .item+.item{padding-top:15px}#brands .brands>ul .item>article .brand-image{margin-bottom:15px;padding:0}header .header .logo a{text-decoration:none}header .header .language-container{text-align:right}header .header .language-container .currency-switch .dropdown-label,header .header .language-container .language-switch .dropdown-label{font-size:1em;font-weight:300}.footer-container .footer-banner{background-color:#e8e8e8;font-size:19px}.footer-container .footer-banner .banner i{display:block;font-size:2em}.footer-container .footer-banner .banner small{font-size:.65em;display:block;font-style:italic;font-weight:400}.footer-container .footer-banner .banner .col{text-align:center}.footer-container .footer-banner .banner .col+.col{border-top:1px solid #d6d6d6}@media (min-width:768px){.footer-container .footer-banner .banner .col+.col{border-left:1px solid #d6d6d6;border-top:none}}.footer-container .footer-block{background-color:#f5f5f5}.footer-container .footer-info{background-color:#444;color:#fff;font-size:12px}.footer-container .footer-info .info .nav-footer ul li+li:before{content:'-'}.footer-container .footer-info .info .copyright{font-weight:300;text-align:right}#payment-method.panel .panel-body,.cart-warning{text-align:center}.footer-container .footer-info .info .copyright>a{font-weight:700}.cart-warning>a{color:inherit}.cart-warning:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f071";display:block;font-size:2.2em}.breadcrumb>li+li:before,.js .dropdown-toggle:after{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}#cart-address .panel{box-shadow:none;border:none}#payment-method.panel .radio label>img{border:1px solid #ddd;border-radius:3px;opacity:.4;filter:alpha(opacity=40)}#payment-method.panel .radio label>img:focus,#payment-method.panel .radio label>img:hover{opacity:1;filter:alpha(opacity=100);transition:opacity .2s ease-in-out}.btn,a{transition:all .3s ease-in-out}#payment-method .list-group-item{border:none}.js #payment-method .radio .active>img,.js #payment-method .radio input:checked+img{opacity:1;filter:alpha(opacity=100)}.checkout-progress .btn-step{padding:16px 24px;background:#eee;color:#555}.checkout-progress .btn-step+.btn-step{border-left:1px solid #555}.checkout-progress .btn-step .step-nb{border-right:1px solid #7a7a7a;font-size:30px;line-height:0;font-weight:600;padding-right:6px;vertical-align:middle}.checkout-progress .btn-step .step-label{font-size:20px;font-weight:100;min-width:250px;padding-left:6px;vertical-align:middle}.checkout-progress .btn-step.active,.checkout-progress .btn-step:active,.checkout-progress .btn-step:focus,.checkout-progress .btn-step:hover{color:#fff;background:#f49a17}.checkout-progress .btn-step.active .step-nb,.checkout-progress .btn-step:active .step-nb,.checkout-progress .btn-step:focus .step-nb,.checkout-progress .btn-step:hover .step-nb{border-right:1px solid #fff}.checkout-progress .btn-step.active{background:#f49a17;cursor:default;display:inherit;pointer-events:none}.price{color:#f49a17;font-size:20px;font-weight:700;font-style:italic;white-space:nowrap}.old-price .price{color:#7a7a7a;font-size:16px;font-weight:600;text-decoration:line-through}#folder-contents .contents>ul .item{padding-bottom:15px}#folder-contents .contents>ul .item+.item{padding-top:15px;border-top:1px solid #ededed}#folder-contents .contents>ul .item>article .content-image{margin-bottom:15px;padding:0}.contents-list .item+.item{border-top:1px solid #ededed}.breadcrumb{padding:0}.breadcrumb>li+li:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f105"}.btn{border-radius:0;text-align:left;font-weight:600}.btn-primary{font-style:italic;border-left:3px solid #f9c478}.btn-primary:focus,.btn-primary:hover{background-color:#f49a17;color:#b66f09}.btn-default{border-left:3px solid #ccc}.btn-default:focus,.btn-default:hover{background-color:#f7f7f7}.btn-default.active,.btn-default.active:hover,.btn-default:active,.btn-default:active:hover,.btn-primary.active,.btn-primary.active:hover,.btn-primary:active,.btn-primary:active:hover{background-color:#d5d5d5;border-color:#6f6f6f;color:#fff}.btn-link{font-weight:400}.form-control:focus::-moz-placeholder{color:#eee;opacity:1}.form-control:focus:-ms-input-placeholder{color:#eee}.form-control:focus::-webkit-input-placeholder{color:#eee}#form-login-mini{width:200px}#form-login-mini .mini-forgot-password{font-size:12px}#form-forgotpassword,#form-login{background:#f5f5f5}#form-forgotpassword legend,#form-login legend{font-size:14px;font-weight:700}.fn,.table-address .radio label,.table-delivery .radio label{font-weight:600}#form-forgotpassword .btn-login,#form-login .btn-login{display:block;width:100%}@media (min-width:768px){#form-forgotpassword .group-btn,#form-login .group-btn{text-align:right}#form-forgotpassword .group-btn .btn-login,#form-login .group-btn .btn-login{display:inline-block;width:auto}}@media (min-width:992px){.btn{padding:2px 15px 2px 5px}#form-forgotpassword,#form-login{width:45%}}.no-js .collapse{display:block!important}.loader,.no-js #carousel .carousel-control{display:none}.loader{position:fixed;background:url(../img/ajax-loader.gif) center center no-repeat #fff;background-color:rgba(255,255,255,.5);left:0;top:0;width:100%;height:100%;z-index:100}.oldie{position:absolute}.thumbnail.active{border-color:#7a7a7a}.main{margin-bottom:20px}.fn{display:block}.adr,.org{font-size:12px}.table-address .group-btn,.table-delivery .group-btn{text-align:right}.table-address tbody>tr>td,.table-address tbody>tr>th,.table-address tfoot>tr>td,.table-address tfoot>tr>th,.table-address thead>tr>td,.table-address thead>tr>th,.table-delivery tbody>tr>td,.table-delivery tbody>tr>th,.table-delivery tfoot>tr>td,.table-delivery tfoot>tr>th,.table-delivery thead>tr>td,.table-delivery thead>tr>th{border-color:#f5f5f5;padding:10px 10px 0}@media (min-width:768px){.table-address tbody>tr>td,.table-address tbody>tr>th,.table-address tfoot>tr>td,.table-address tfoot>tr>th,.table-address thead>tr>td,.table-address thead>tr>th,.table-delivery tbody>tr>td,.table-delivery tbody>tr>th,.table-delivery tfoot>tr>td,.table-delivery tfoot>tr>th,.table-delivery thead>tr>td,.table-delivery thead>tr>th{padding:30px 30px 0}}.modal-dialog td{vertical-align:middle}.modal-dialog .close{margin:10px;position:relative;z-index:10}.modal-dialog .btn{margin-left:10px}@media screen and (min-width:768px){.modal-dialog{width:800px}}.navbar.navbar-secondary{z-index:1001}@media (min-width:992px){.navbar .list-subnav{background-color:#f49a17;border:1px solid #f49a17;border-radius:0;box-shadow:none}.navbar .list-subnav>li>a{color:#fff;padding:3px 12px}.navbar .list-subnav>.active>a,.navbar .list-subnav>.active>a:focus,.navbar .list-subnav>.active>a:hover,.navbar .list-subnav>li>a:focus,.navbar .list-subnav>li>a:hover{background-color:#fff;color:#f49a17}}.table-cart tfoot td.empty,.table-cart-total td.empty,.table-order tfoot td.empty,.table-order-total td.empty{border-bottom-color:transparent;border-left-color:transparent}.navbar .full-width .dropdown-menu .dropdown-content{padding:20px}.navbar .full-width .dropdown-menu .dropdown-content .dropdown-subheading{font-weight:700}.js .dropdown-toggle:after{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f078"}.alert-warning:before,.block-default .block-content li:before{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}#account .panel-heading{padding:0}#account .panel-heading .panel-title>a{background:#f49a17;color:#fff;display:block;padding:12px 15px;text-decoration:none}#account .panel-heading .panel-title>a.collapsed{background:0 0;color:inherit}#account .panel-heading .panel-title>a.collapsed:focus,#account .panel-heading .panel-title>a.collapsed:hover{background:#f49a17;color:#fff}#account .panel-body{padding:25px}.table-cart tbody>tr>td,.table-cart tbody>tr>th,.table-cart tfoot>tr>td,.table-cart tfoot>tr>th,.table-cart thead>tr>td,.table-cart thead>tr>th,.table-order tbody>tr>td,.table-order tbody>tr>th,.table-order tfoot>tr>td,.table-order tfoot>tr>th,.table-order thead>tr>td,.table-order thead>tr>th{padding:14px;text-align:center;vertical-align:middle}.table-cart tbody>tr>td.product,.table-cart tbody>tr>th.product,.table-cart tfoot>tr>td.product,.table-cart tfoot>tr>th.product,.table-cart thead>tr>td.product,.table-cart thead>tr>th.product,.table-order tbody>tr>td.product,.table-order tbody>tr>th.product,.table-order tfoot>tr>td.product,.table-order tfoot>tr>th.product,.table-order thead>tr>td.product,.table-order thead>tr>th.product{text-align:left}.table-cart tbody>tr>td.image,.table-cart tbody>tr>th.image,.table-cart tfoot>tr>td.image,.table-cart tfoot>tr>th.image,.table-cart thead>tr>td.image,.table-cart thead>tr>th.image,.table-order tbody>tr>td.image,.table-order tbody>tr>th.image,.table-order tfoot>tr>td.image,.table-order tfoot>tr>th.image,.table-order thead>tr>td.image,.table-order thead>tr>th.image{border-right-color:transparent}.table-cart thead th,.table-order thead th{background-color:#f5f5f5;border-bottom-width:1px}.table-cart thead th.subprice,.table-order thead th.subprice{color:#f49a17}.table-cart tbody td.price,.table-cart tbody td.qty,.table-cart tbody td.subprice,.table-order tbody td.price,.table-order tbody td.qty,.table-order tbody td.subprice{padding:35px 10px}.table-cart tbody td.unitprice .price,.table-order tbody td.unitprice .price{color:#7a7a7a}.table-cart tbody td.unitprice .old-price .price,.table-order tbody td.unitprice .old-price .price{font-size:14px}.table-cart tbody td.unitprice .secondary-price .price,.table-order tbody td.unitprice .secondary-price .price{font-size:14px;font-weight:400}.table-cart tbody td.subprice .price,.table-order tbody td.subprice .price{color:#f49a17}.table-cart tfoot td,.table-cart tfoot th,.table-order tfoot td,.table-order tfoot th{background-color:#f5f5f5}.table-cart tfoot td.empty,.table-cart tfoot th.empty,.table-order tfoot td.empty,.table-order tfoot th.empty{background:0 0}.table-cart tfoot td.total,.table-cart tfoot th.total,.table-order tfoot td.total,.table-order tfoot th.total{background-color:#666;color:#fff}.table-cart tfoot td.total .price,.table-cart tfoot th.total .price,.table-order tfoot td.total .price,.table-order tfoot th.total .price{color:inherit}.table-cart tfoot td.shipping .price,.table-order tfoot td.shipping .price{color:#7a7a7a;font-size:19px}.table-cart tfoot td.total .price,.table-order tfoot td.total .price{font-size:19px}.table-cart tfoot th.total,.table-order tfoot th.total{text-transform:uppercase;font-weight:100;font-size:16px}.table-cart-total td.total .price,.table-order-total td.total .price{font-size:19px}.alert-warning{clear:both;margin-bottom:0;text-align:center}.alert-warning>a{color:inherit}.alert-warning:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f071";display:block;font-size:2.2em}.block{background:0 0;border:1px solid transparent;border-radius:0}.block .block-heading{background:0 0;border-bottom:1px solid #dfdfdf;color:#888;margin:0 0 6px;padding-bottom:6px}.block .block-title{font-size:21px;margin-top:0;margin-bottom:0}.block .block-title>a{color:inherit}.block .block-content{font-size:12px;margin-bottom:20px}.block .block-content ul{padding-left:0;list-style:none}.block .block-content .block-subtitle{color:#f49a17;font-size:16px;font-weight:300;margin:0 0 6px}.block-default .block-content li{margin-left:15px;padding-top:6px}.block-default .block-content li a{color:#747474}.block-default .block-content li a:focus,.block-default .block-content li a:hover{color:#b66f09}.block-default .block-content li:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f054";color:#f49a17;margin-left:-15px;margin-right:5px}.block-nav .block-content li a.accordion-toggle:after,.has-error .help-block:before{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}.block-links .block-content li a,.block-nav .block-content li a{color:#747474;display:block;font-weight:400;position:relative;font-size:12px}.block-links .block-content li+li a{border-top:1px solid #fff}.block-links .block-content li a{background-color:transparent;padding:10px 3px}.block-links .block-content li a:focus,.block-links .block-content li a:hover{text-decoration:none;background-color:#ebebeb}.block-links .block-content li a>p,.block-nav .block-heading{margin-bottom:0}.block-nav .block-content li a{background-color:transparent;padding:10px 60px 10px 3px}.block-nav .block-content li a:focus,.block-nav .block-content li a:hover{text-decoration:none;background-color:#f7f7f7}.block-nav .block-content li a.accordion-toggle:after{color:#f49a17;display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f068"}.block-nav .block-content li a.accordion-toggle.collapsed:after{content:"\f067"}.block-nav .block-content ul a{padding-left:15px}.block-nav .block-content ul ul a{padding-left:30px}.block-nav .block-content ul ul ul a{padding-left:45px}.block-thumbnail{margin-left:-15px;margin-right:-15px}.block-thumbnail.block-thumbnail-2 li{max-width:50%}.block-thumbnail.block-thumbnail-3 li{max-width:33.33333333%}.block-thumbnail.block-thumbnail-4 li{max-width:25%}.block-thumbnail .block-content li{float:left;padding-right:7.5px;padding-bottom:7.5px;position:relative;max-width:33.33333333%}.block-social .block-content li{display:inline-block;font-size:18px}.block-social .block-content li>a{color:#888}.block-social .block-content li>a:focus,.block-social .block-content li>a:hover{color:#b66f09}.block-newsletter .block-content form .btn-subscribe{padding:6px}.block-contact .block-content li{clear:both;margin-bottom:5px}.block-carousel{margin-bottom:30px}.block-carousel .carousel-indicators{bottom:auto}.block-carousel .block-carousel-control{float:right!important;float:right}.block-carousel .block-carousel-control .carousel-control{background:#efefef;color:#000;display:block;float:left;font-size:24px;margin-left:3px;position:relative;top:1px;left:auto;bottom:auto;width:28px;height:28px;transition:background-color .3s ease-in-out}.label-delivered,.label-new,.label-sale{padding:.2em .6em .3em;font-size:75%;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em;color:#fff;font-weight:700}.btn .label-delivered,.btn .label-new,.btn .label-sale{top:-1px;position:relative}#brands .brands>ul .item>article .brand-image>img,.grid .item .product-image>img,.list .item>article .product-image>img{width:100%}.block-carousel .block-carousel-control .carousel-control:focus,.block-carousel .block-carousel-control .carousel-control:hover{background-color:#000;color:#fff}.label-new{display:inline;background-color:#5bc0de}a.label-new:focus,a.label-new:hover{color:#fff;text-decoration:none;cursor:pointer}.label-new:empty{display:none}.label-new[href]:focus,.label-new[href]:hover{background-color:#31b0d5}.label-sale{display:inline;background-color:#d9534f}a.label-sale:focus,a.label-sale:hover{color:#fff;text-decoration:none;cursor:pointer}.label-sale:empty{display:none}.label-sale[href]:focus,.label-sale[href]:hover{background-color:#c9302c}.label-delivered{display:inline;background-color:#5cb85c}a.label-delivered:focus,a.label-delivered:hover{color:#fff;text-decoration:none;cursor:pointer}.grid .btn-grid,.list .btn-list{cursor:default;pointer-events:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.label-delivered:empty{display:none}.label-delivered[href]:focus,.label-delivered[href]:hover{background-color:#449d44}.products-heading .btn-all{float:right}.products-heading h3{top:-14px!important;margin:0}.availability .in-stock{color:#5cb85c;font-style:italic;font-weight:700}.availability .in-stock .in{display:block}.availability .in-stock .out,.availability .out-of-stock .in{display:none}.availability .in-stock .quantity{font-style:italic}.availability .out-of-stock{color:#f0ad4e;font-style:italic;font-weight:700}.availability .out-of-stock .out{display:block}#brands .brands>ul .item>article .brand-image.overlay:after,.no-js .toolbar .limiter,.no-js .toolbar .sort-by{display:none}.option{background:#fff;border:1px solid transparent;border-radius:0}.option .option-heading{border-bottom:1px solid transparent;color:#7a7a7a;font-size:14px;font-weight:700}.option .option-content .checkbox label,.option .option-content .radio label{font-weight:100}#product #product-gallery{border-right:1px solid #f5f5f5;padding-right:20px}#product #product-details .name{font-size:21px;font-weight:400}#product #product-details .product-cart{background:#fff;border:1px solid transparent;border-radius:0}#product #product-tabs .nav-tabs{border-bottom:1px solid #ddd}#product #product-tabs .tab-content{border:1px solid #ddd;border-radius:0 0 3px 3px;padding:30px 15px;min-height:180px;height:auto!important;height:180px}.list .item+.item{border-top:1px solid #ededed}.list .item>article .product-price{text-align:right}.filter{background:#f5f5f5;border:1px solid #f5f5f5;border-radius:0}.filter .filter-heading{border-bottom:1px solid #dfdfdf;color:#888;font-size:19px;font-weight:100}.filter .filter-content .checkbox label,.filter .filter-content .radio label{font-weight:100}.toolbar{line-height:50px}.toolbar .pagination-container,.toolbar .sorter-container{overflow:hidden;height:50px}.toolbar .sorter-container{background-color:#fff;border-radius:0;padding:0;text-align:right}.overlay:after,.page-home #carousel .item,.toolbar .pagination-container{text-align:center}.toolbar .sorter-container .view-mode>.view-mode-btn{font-size:24px}.toolbar .sorter-container .view-mode>.view-mode-btn a{padding:0 6px;font-size:21px;text-decoration:none}#brands .brands>ul .item+.item{border-top:1px solid #ededed}.page-404 .main{padding:10px 0 100px}.page-404 #main-label{color:#f49a17;font-size:9em;font-weight:700;text-align:center}.page-404 #main-label span{color:#CCC;display:block;font-size:15px;font-weight:400}.page-home #carousel{margin-bottom:20px}@media screen and (min-width:768px){.page-home #carousel .carousel-control .fa-caret-left,.page-home #carousel .carousel-control .fa-caret-right{font-size:80px;margin-top:-40px;margin-left:-40px;width:80px;height:80px}}.page-header{border:none;font-weight:100;font-size:30px}.form-control{box-shadow:none}.form-control:invalid:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.dropdown-menu,.modal-content,.popover{box-shadow:none}.has-error .help-block:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f00d";margin-right:.3em}label{font-weight:600}.popover{border-radius:3px}.overlay{display:block;overflow:hidden;position:relative;font-size:40px}.overlay:after,.overlay:before{display:block;width:100%;height:100%;visibility:hidden;position:absolute;top:0;left:0;right:0;opacity:0;filter:alpha(opacity=0);transition:all .3s ease-in-out 50ms}.overlay:before{content:'';overflow:visible;background-color:#f49a17;background-color:rgba(244,154,23,.4)}.overlay:after{font-family:FontAwesome;content:"\f002";color:#fff;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);line-height:0}.overlay:focus:after,.overlay:focus:before,.overlay:hover:after,.overlay:hover:before{visibility:visible;opacity:1;filter:alpha(opacity=100)}.overlay:focus:after,.overlay:hover:after{-webkit-transform:translate(0,50%);-ms-transform:translate(0,50%);transform:translate(0,50%)}.navbar li>a.home:before,.navbar li>a.login:before{font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);margin-right:.5em;display:inline-block}.navbar li>a.home:before{-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f015";color:#c9c9c9;font-size:26px;line-height:0;position:relative;top:3px}.navbar li>a.login:before{-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f007";color:#f49a17;font-size:19px;line-height:0}#product-details .product-promo .sale-saving:before,.navbar li.cart-not-empty>a.cart:before{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);text-rendering:auto;-moz-osx-font-smoothing:grayscale}.navbar li>a.cart:focus>.badge,.navbar li>a.cart:hover>.badge{background-color:#fff;color:#f49a17}.navbar li.cart-not-empty>a.cart{background-color:#f49a17;color:#fff}.navbar li.cart-not-empty>a.cart>.badge{background-color:#fff;color:#f49a17}.navbar li.cart-not-empty>a.cart:focus,.navbar li.cart-not-empty>a.cart:hover{background-color:#f49a17;color:#fff}.navbar li.cart-not-empty>a.cart:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f07a";color:#fff;font-size:24px;line-height:0;margin-right:.4em}@media (min-width:992px){.navbar .navbar-nav .list-subnav>li+li{border-top:1px solid #e28a0b}.navbar .navbar-nav .list-subnav>li>a{font-weight:100}}.navbar .navbar-nav>li>a:focus:before,.navbar .navbar-nav>li>a:hover:before{color:#fff}.navbar .navbar-nav>.active>a:focus,.navbar .navbar-nav>.active>a:hover{background-color:#f49a17;color:#fff}.navbar .navbar-nav>.active:after{background:#f49a17;content:"";display:block;position:absolute;bottom:0;width:100%;height:2px;z-index:100}.navbar .navbar-nav>.open>a,.navbar .navbar-nav>.open>a:focus,.navbar .navbar-nav>.open>a:hover{background-color:#f49a17;color:#fff}.navbar .navbar-nav>.open>a:before,.navbar .navbar-nav>.open>a:focus:before,.navbar .navbar-nav>.open>a:hover:before{color:#fff}.container>.navbar-collapse{margin-left:-15px;margin-right:-15px}header .header .logo{float:none}.page-home #carousel .carousel-control{background-image:none}.products-heading h2{color:#7a7a7a;font-size:18px;font-weight:700}.products-heading .btn-all,.products-heading .btn-all:focus,.products-heading .btn-all:hover{color:#7a7a7a;font-size:16px;font-style:italic;font-weight:600}.products-heading .short-description{background-color:#f5f5f5;margin-bottom:10px;padding:10px}.product-options dl{font-size:.85em;margin-bottom:10px}.product-options dl>dt{text-align:left}.product-info .name,td.product .name{font-size:16px;font-weight:600}.product-info .name>a,td.product .name>a{color:#7a7a7a;text-decoration:none}.product-info .name>a:focus,.product-info .name>a:hover,td.product .name>a:focus,td.product .name>a:hover{color:#b66f09}.product-price .price-label{font:0/0 a;color:transparent;background-color:transparent;border:0;display:block}.product-price .regular-price .price,.product-price .special-price .price{display:block;font-size:14px;line-height:25px;font-style:normal;font-weight:400}.product-price .old-price .price{display:block;font-size:14px;line-height:25px;font-style:italic;font-weight:400;text-decoration:line-through}#products-new .products-grid .overlay:after{-webkit-transform:translate(0,40%);-ms-transform:translate(0,40%);transform:translate(0,40%)}#products-new .products-grid .item>article{border-bottom:4px solid #f49a17;border-bottom-right-radius:3px;border-bottom-left-radius:3px;overflow:hidden;position:relative}#products-new .products-grid .item>article .product-info{background-color:#f6af48;color:#fff;display:block;padding:6px 12px;position:relative;text-decoration:none!important}#products-new .products-grid .item>article .product-info:focus,#products-new .products-grid .item>article .product-info:hover{background-color:#f49a17}#products-new .products-grid .item>article .product-info .name{min-height:40px;height:auto!important;height:40px}#products-new .products-grid .item>article .product-info .name:after{content:'+';font-size:45px;line-height:0;font-style:normal;font-weight:100;position:absolute;top:16px;right:4px;-webkit-font-smoothing:antialiased}#products-new .products-grid .item>article .product-info .short-description{font-size:11px;line-height:1.1}#products-new .products-grid .item>article .product-price .price{color:#fff;font-size:22px;font-weight:700}@media (min-width:992px){#products-new .products-grid .item>article .product-image{padding-bottom:40px}#products-new .products-grid .item>article .product-info{transition:height .3s linear;position:absolute;bottom:0;width:100%;height:50px}#products-new .products-grid .item>article .product-info h3{margin-top:2px;padding-right:20px}#products-new .products-grid .item>article .product-info h3 span{height:2em;overflow:hidden;display:block}#products-new .products-grid .item>article .product-info:focus,#products-new .products-grid .item>article .product-info:hover{cursor:pointer;height:140px}}#products-upsell{margin-top:40px;position:relative}#products-upsell .products-heading{border-bottom:1px solid #e5e5e5;margin:20px 0}#products-upsell .products-heading h3{background:#fff;color:#f49a17;padding-right:15px;position:absolute;top:-24px}#products-offer .products-grid .item>article,#products-related .products-grid .item>article,#products-upsell .products-grid .item>article{border-radius:3px;transition:background-color .3s ease-in-out;padding:6px}#products-offer .products-grid .item>article .product-info,#products-related .products-grid .item>article .product-info,#products-upsell .products-grid .item>article .product-info{padding:0}#products-offer .products-grid .item>article .product-info .short-description,#products-related .products-grid .item>article .product-info .short-description,#products-upsell .products-grid .item>article .product-info .short-description{font-size:11px}@media (min-width:768px){#products-offer .products-grid .item:hover article,#products-related .products-grid .item:hover article,#products-upsell .products-grid .item:hover article{background-color:#f6f6f6}}#products-new .overlay:after,#products-offer .overlay:after,#products-upsell .overlay:after{content:'+';font-family:'Open Sans',sans-serif;font-size:80px;font-weight:100;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#products-new .overlay:before{border-radius:3px 3px 0 0}#category-products .item>article .product-info .description{font-size:.83em;line-height:1.3}#category-products .item>article .product-price .price-label{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;display:block}#category-products .item>article .product-price .price-container{margin-bottom:10px}#category-products .item>article .product-price .price-container .price{margin-left:4px}#category-products .item>article .product-price .product-btn{min-height:26px}.grid #category-products .item{border-right:1px solid #e8e8e8;margin:0;padding:10px}.grid #category-products .item>article .product-info{padding:3px}.grid #category-products .item>article .product-info .name{margin:4px;height:2em;overflow:hidden}.grid #category-products .item>article .product-info .description{margin-left:4px}.list #category-products .item>article .product-price .price-container{margin-bottom:20px}.list #category-products .item>article .product-price .price-container .old-price,.list #category-products .item>article .product-price .price-container .regular-price,.list #category-products .item>article .product-price .price-container .special-price{display:block;width:100%}#product-details .product-info{border-bottom:1px solid #e5e5e5;margin-bottom:15px}#product-details .product-info .sku{color:#e5e5e5;display:block;font-size:14px;margin-top:-8px;margin-bottom:20px}#product-details .product-info .pse-name{color:#555;font-size:14px}#product-details .product-options .option{margin-bottom:10px}#product-details .product-cart{background-color:#f5f5f5!important;margin-bottom:20px;padding:10px!important}#product-details .product-promo{background-color:#f5f5f5;margin-bottom:15px;padding:10px}#product-details .product-promo .sale-label{font-weight:300;line-height:1.4;font-size:21px}#product-details .product-promo .sale-saving{color:#f49a17}#product-details .product-promo .sale-saving:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f005"}#product-details .product-promo .sale-period{font-style:italic;font-size:90%}#product-thumbnails .carousel-control{width:17px!important}#product-thumbnails .carousel-control .fa{position:absolute;top:50%}#product-thumbnails .carousel-control.left{border-right:7px solid #ccc;color:#ccc;text-align:left}#product-thumbnails .carousel-control.left>.fa-caret-left{left:0;margin-left:0;margin-top:-15px}#product-thumbnails .carousel-control.left>.fa-caret-left:before{color:inherit}#product-thumbnails .carousel-control.right{border-left:7px solid #ccc;text-align:right}#product-thumbnails .carousel-control.right>.fa-caret-right{left:auto;right:0;margin-left:0;margin-top:-15px}@media (min-width:768px){#product #product-gallery{border-right:1px solid #eee;padding-right:20px}#product #product-details .group-qty .form-control{display:inline-block;margin-right:1em;margin-left:.4em;width:100px}}#product-gallery .product-image{margin-bottom:20px}#product-gallery .product-thumbnails li{width:20%}#filters{background:#f5f5f5}#filters>h3{background:#e5e5e5;box-shadow:inset 0 -4px 10px rgba(0,0,0,.125);margin:0 0 15px;padding:10px 15px;font-size:18px;font-weight:700}#filters>h3>span{display:block;font-size:.75em;font-weight:100;text-transform:lowercase}#filters>h3:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f002";font-size:30px;float:left;margin-right:.5em}#filters .filter{margin-bottom:10px}.block.block-links .block-content ul>li+li a{border-top:none}.block.block-links .block-content ul>li+li:before{background:#fff;content:"";display:block;margin:0 auto;text-align:center;width:65%;height:2px}.block.block-contact .block-content ul>li.contact-address:before,.block.block-newsletter .block-content form .form-group:before{display:inline-block;text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}.block.block-newsletter .block-content form .form-group{position:relative}@media (min-width:1200px){.block.block-newsletter .block-content form .form-group{width:176px}}.block.block-newsletter .block-content form .form-group .form-control{background-color:#e6e6e6;font-size:12px;padding-left:35px;width:inherit;box-shadow:inset 1px 1px 1px rgba(0,0,0,.075)}.block.block-newsletter .block-content form .form-group .form-control::-moz-placeholder{color:#888;opacity:1}.block.block-newsletter .block-content form .form-group .form-control:-ms-input-placeholder{color:#888}.block.block-newsletter .block-content form .form-group .form-control::-webkit-input-placeholder{color:#888}.block.block-newsletter .block-content form .form-group .form-control:focus::-moz-placeholder{color:#c8c8c8;opacity:1}.block.block-newsletter .block-content form .form-group .form-control:focus:-ms-input-placeholder{color:#c8c8c8}.block.block-newsletter .block-content form .form-group .form-control:focus::-webkit-input-placeholder{color:#c8c8c8}.block.block-newsletter .block-content form .form-group:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f0e0";color:#8b8b8b;font-size:18px;position:absolute;top:8px;left:9px}.block.block-newsletter .block-content form .btn-subscribe{padding:6px}.block.block-social .block-content ul>li>a:hover.facebook{color:#3d5fa6}.block.block-social .block-content ul>li>a:hover.twitter{color:#53b1f0}.block.block-social .block-content ul>li>a:hover.rss{color:#fac200}.block.block-social .block-content ul>li>a:hover.instagram{color:#425E75}.block.block-social .block-content ul>li>a:hover.google-plus{color:#fac200}.block.block-social .block-content ul>li>a:hover.youtube{color:#e82a20}.block.block-contact .block-content ul>li{clear:both;margin-bottom:5px}.block.block-contact .block-content ul>li.contact-address:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f041";font-size:34px}.block.block-contact .block-content ul>li.contact-email:before,.block.block-contact .block-content ul>li.contact-phone:before{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);display:inline-block}.block.block-contact .block-content ul>li.contact-phone:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f10b";font-size:30px;margin-top:-8px;margin-left:3px}.block.block-contact .block-content ul>li.contact-email:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f0e0";font-size:17px;margin-left:2px}.block.block-contact .block-content ul>li.contact-contact:before,.js #payment-method .radio .active:after{text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}.block.block-contact .block-content ul>li:before{color:#f49a17;float:left;line-height:1;margin-right:.4em}.block.block-contact .block-content ul>li.contact-contact:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f1d8";font-size:17px}#categories.block-nav .block-content{border-top:1px solid #aeaeae}#categories.block-nav .block-content .amount{font-weight:700}#categories.block-nav .block-content li{border-top:1px solid #eee;position:relative}#categories.block-nav .block-content li .accordion-toggle{position:absolute;top:0;right:0;padding-right:10px;padding-left:5px}#categories.block-nav .block-content li .accordion-toggle:focus,#categories.block-nav .block-content li .accordion-toggle:hover{background:0 0}#categories.block-nav .block-content li .accordion-toggle:focus:after,#categories.block-nav .block-content li .accordion-toggle:hover:after{border-color:#b66f09;color:#b66f09}#categories.block-nav .block-content li .accordion-toggle:after{border:1px solid #f49a17;border-radius:10px;line-height:17px;text-align:center;width:19px;height:19px}.toolbar.toolbar-top{margin-top:-20px;border-bottom:1px solid #eee}.toolbar.toolbar-bottom .sorter-container,.toolbar.toolbar-top .pagination-container{display:none}.toolbar .amount{color:#f49a17;font-size:22px;font-weight:400}.toolbar .view-mode>.view-mode-btn a{background-color:#fff;border:0!important;color:#7a7a7a}.toolbar .view-mode>.view-mode-btn a:focus,.toolbar .view-mode>.view-mode-btn a:hover{background-color:#efefef;color:#474747}.toolbar .view-mode>.view-mode-btn a:active{color:#fff}.pagination>li>a,.pagination>li>span{box-shadow:2px 1px 1px rgba(0,0,0,.1);transition:all .2s ease-in-out;background-image:linear-gradient(to bottom,#fff 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff9f9f9', GradientType=0);color:#7a7a7a;font-weight:700}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{background:0 0}.pagination>li>a:focus:active,.pagination>li>a:hover:active,.pagination>li>span:focus:active,.pagination>li>span:hover:active{background-color:#f49a17;border-color:#f49a17;color:#fff}.pagination>li:first-child>a,.pagination>li:first-child>span{border-bottom-left-radius:30px;border-top-left-radius:30px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:30px;border-top-right-radius:30px}.pagination>.active>a,.pagination>.active>span{background-image:none}#form-forgotpassword .group-email label,#form-forgotpassword legend,#form-login .group-email label,#form-login legend{font-size:16px;font-weight:600}#form-forgotpassword .radio-account1,#form-login .radio-account1{margin-top:10px}#form-forgotpassword .forgot-password,#form-login .forgot-password{color:#7a7a7a;font-size:12px;font-style:italic}@media (min-width:768px){#form-forgotpassword .radio-account1,#form-login .radio-account1{float:left}#form-forgotpassword .group-password,#form-login .group-password{float:right;margin-top:5px;width:50%}}#delivery-address.panel .panel-body,#delivery-method.panel .panel-body{padding:0}#delivery-method.panel .radio{display:block;margin-top:0}#delivery-method.panel .radio+.radio{border-top:1px solid #f5f5f5}#delivery-method.panel .price{text-align:right}#delivery-method.panel .image{text-align:center}#account .panel-title,#payment-success.panel .panel-heading{text-align:left}.js #payment-method .radio{padding-left:0;position:relative}.js #payment-method .radio .active:after{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f077";color:#f49a17;display:block;font-size:1.5em;line-height:0;position:absolute;bottom:-8px;left:40%}#account .panel-title>a:before,#account-info .list-info .mobile:before{display:inline-block;text-rendering:auto;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0)}#payment-success.panel .panel-heading .payment-method{font-size:inherit}#payment-success.panel .panel-body{padding:20px 40px}#account-address .panel-body,#account-orders .panel-body{padding-left:0;padding-right:0}#payment-success.panel .panel-body>h3{color:#f49a17}#account .panel{box-shadow:none;border-color:#fff}#account .panel-title>a:before{font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f078";float:left;width:20px}#account .panel-title>a.collapsed:before{content:"\f054"}#account-info .fn{font-size:16px;font-weight:600}#account-info .list-info .email:before,#account-info .list-info .mobile:before,#account-info .list-info .tel:before{color:#f49a17;line-height:1;margin-right:.4em;vertical-align:middle}#account-info .list-info .mobile:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f10b";font-size:30px}#account-info .list-info .email:before,#account-info .list-info .tel:before{display:inline-block;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);text-rendering:auto;-moz-osx-font-smoothing:grayscale}#account-info .list-info .tel:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f095";font-size:22px}#account-info .list-info .email:before{font:normal normal normal 14px/1 FontAwesome;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f0e0";font-size:18px}#account-info .group-btn a{color:#7a7a7a;margin-bottom:4px;padding:0}#account-info .group-btn a>i{color:#f49a17;font-size:20px;line-height:1;margin-right:.3em;vertical-align:middle}#account-info .group-btn a:focus,#account-info .group-btn a:hover{color:#b66f09}#account-address .panel-body{padding-top:10px}#account-address .table-address{border:1px solid #f5f5f5;margin-bottom:0}#account-orders .table-orders tbody>tr>td,#account-orders .table-orders tbody>tr>th,#account-orders .table-orders thead>tr>td,#account-orders .table-orders thead>tr>th{padding:14px;text-align:center}#account-orders .table-orders thead>tr>th{background-color:#f5f5f5;border-bottom-width:1px}#account-orders .table-order-products tbody>tr>td,#account-orders .table-order-products tbody>tr>th,#account-orders .table-order-products thead>tr>td,#account-orders .table-order-products thead>tr>th{padding:5px;text-align:center}.table-cart-mini tbody>tr>td,.table-cart-mini tbody>tr>th,.table-cart-mini tfoot>tr>td,.table-cart-mini tfoot>tr>th,.table-cart-mini thead>tr>td,.table-cart-mini thead>tr>th{vertical-align:middle}#google-map{border:none;display:block;margin-bottom:20px;width:100%;height:350px;-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%);-ms-filter:grayscale(100%);-o-filter:grayscale(100%);filter:grayscale(100%)}#sale-details .sale-discount-information{background-color:#f5f5f5;margin-bottom:10px;padding:10px}#sale-details .sale-discount-information .sale-saving{font-size:120%;color:#f49a17}#sale-details .sale-discount-information .sale-saving:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0);content:"\f005"}#sale-details .sale-discount-information .sale-period{font-style:italic;font-size:90%}#sale-details .sale-information{margin-bottom:30px}#sale-details .sale-information .chapo,#sale-details .sale-information .description{margin-bottom:10px} \ No newline at end of file + */@font-face{font-family:FontAwesome;src:url(../fonts/fontawesome/fontawesome-webfont.eot?v=4.3.0);src:url(../fonts/fontawesome/fontawesome-webfont.eot?#iefix&v=4.3.0) format('embedded-opentype'),url(../fonts/fontawesome/fontawesome-webfont.woff2?v=4.3.0) format('woff2'),url(../fonts/fontawesome/fontawesome-webfont.woff?v=4.3.0) format('woff'),url(../fonts/fontawesome/fontawesome-webfont.ttf?v=4.3.0) format('truetype'),url(../fonts/fontawesome/fontawesome-webfont.svg?v=4.3.0#fontawesomeregular) format('svg');font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0)}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0)}100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-ms-transform:scale(-1,1);transform:scale(-1,1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-ms-transform:scale(1,-1);transform:scale(1,-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-rotate-90{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-close:before,.fa-remove:before,.fa-times:before,.has-error .help-block:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-repeat:before,.fa-rotate-right:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.block-default .block-content li:before,.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.cart-warning:before,.fa-exclamation-triangle:before,.fa-warning:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-floppy-o:before,.fa-save:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-bolt:before,.fa-flash:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-chain-broken:before,.fa-unlink:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\f150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\f151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\f152"}.fa-eur:before,.fa-euro:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-inr:before,.fa-rupee:before{content:"\f156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\f158"}.fa-krw:before,.fa-won:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-try:before,.fa-turkish-lira:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\f19c"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\f1c5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\f1c6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-empire:before,.fa-ge:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-paper-plane:before,.fa-send:before{content:"\f1d8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before,.fa-genderless:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-bed:before,.fa-hotel:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}header .header{margin-bottom:20px}header .header .language-container .search-container{margin-bottom:10px}header .header .language-container .search-container .form-control{width:auto}header .header .language-container .currency-switch,header .header .language-container .language-switch{display:inline-block;position:relative;vertical-align:middle}header .header .language-container .currency-switch .dropdown-label,header .header .language-container .language-switch .dropdown-label{display:inline-block;float:left;margin-left:1em;margin-right:.4em}header .header .language-container .currency-switch .current,header .header .language-container .language-switch .current{display:inline-block;float:left;position:relative}#payment-method.panel .radio,.account-info .email,.account-info .mobile,.account-info .tel,.js .group-qty .form-inline .form-group{display:block}header .header .language-container .currency-switch .select,header .header .language-container .language-switch .select{left:auto;right:0;min-width:80px}.footer-container .footer-banner .banner .col{padding:10px 0}.footer-container .footer-block .blocks,.footer-container .footer-info .info{padding:20px 0}.footer-container .footer-info .info .nav-footer ul li+li:before{margin-right:10px}.account-info address{margin-bottom:0}.account-info li{margin-bottom:20px}.list-payment,.table-order tbody td.qty .group-qty{margin-bottom:0}.table-order-total td{width:50%}#delivery-address .panel-heading{position:relative}.checkout-progress{margin-bottom:20px;width:100%}.alert-warning,.cart-warning,.table-cart tbody td.qty .group-qty,.table-cart-mini{margin-bottom:0}.cart-empty{margin:0;padding:40px}.table-cart-total td{width:50%}.cart-warning{clear:both}.pagination>li>a:focus,.pagination>li>span:focus{z-index:3}@media (min-width:992px){.navbar .navbar-cart .dropdown>a:after,.navbar .navbar-customer .dropdown>a:after{padding-left:.3em;display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f078";float:none}}@media (min-width:992px) and (min-width:992px){.navbar .navbar-cart .dropdown>a:after,.navbar .navbar-customer .dropdown>a:after{float:none}}.navbar .navbar-cart .dropdown-menu,.navbar .navbar-customer .dropdown-menu{margin:0;padding:20px}@media (max-width:992px){.navbar .navbar-cart .dropdown-menu,.navbar .navbar-customer .dropdown-menu{display:none}}.navbar .navbar-cart .dropdown-menu.cart-content,.navbar .navbar-customer .dropdown-menu.cart-content{width:350px}.grid .products-content>ul .item>article .product-image,.grid .products-content>ul .item>article .product-info,.grid .products-content>ul .item>article .product-price,.list .products-content>ul .item{width:100%;float:none}.navbar .navbar-cart .dropdown-menu.cart-content>p,.navbar .navbar-customer .dropdown-menu.cart-content>p{margin:0}.navbar .navbar-cart .cart-not-empty .cart-content,.navbar .navbar-customer .cart-not-empty .cart-content{border-top:none;padding:0}.navbar .full-width{position:static}.navbar .full-width .dropdown-menu{width:100%;left:0;right:0}.navbar .full-width .dropdown-menu .dropdown-content .dropdown-subheading{display:block}.js .dropdown-toggle:after{float:right;padding-left:.3em}@media (min-width:992px){.navbar-collapse .navbar-nav.navbar-right:first-child{margin-right:-15px}.navbar-collapse .navbar-nav.navbar-right:last-child{margin-right:0}.js .dropdown-toggle:after{float:none}}#form-forgotpassword,#form-login{padding:45px}#form-forgotpassword legend,#form-login legend{margin-bottom:10px}#filters,.filter{margin-bottom:20px}.filter{padding:0 15px}.filter .filter-heading{margin:0 0 4px}.toolbar{margin-bottom:20px}.toolbar .sorter-container .amount{float:left}.toolbar .sorter-container .sort-by,.toolbar .sorter-container .view-mode{margin-left:40px}.toolbar .pagination-container>.pagination{margin:15px 0 0}.products-content>ul .item .product-info .short-description,.products-content>ul .item .product-price .price-container{display:block;margin-bottom:5px}.grid .products-content>ul .item{margin-bottom:20px}.grid .products-content>ul .item>article{margin:0}.grid .products-content>ul .item>article .product-image{padding:0}.grid .products-content>ul .item>article .name{margin:4px 0}.grid .products-content>ul .item .description{display:none!important}@media (max-width:767px){.grid .products-content>ul .item .description{display:block!important}table.grid .products-content>ul .item .description{display:table!important}tr.grid .products-content>ul .item .description{display:table-row!important}td.grid .products-content>ul .item .description,th.grid .products-content>ul .item .description{display:table-cell!important}}.grid .products-content>ul .item .product-price{padding:0}.list .products-content>ul .item+.item{padding-top:15px}.list .products-content>ul .item>article{margin-left:0}.list .products-content>ul .item>article .product-image{margin-bottom:15px;padding:0}.list .products-content>ul .item>article .product-info .name{margin-top:0}.option{margin-bottom:20px;padding:0}.option .option-heading{display:block;margin:0 0 5px}#product #product-gallery .product-image,#product>section{margin-bottom:20px}#product #product-gallery #product-thumbnails .carousel-inner{margin:0 auto;width:90%}#brands .brands>ul .item>article,#folder-contents .contents>ul .item>article,.contents-list .item>article{margin-left:0}#product #product-gallery #product-thumbnails .carousel-control{display:none;width:4%;margin-top:-4px}#brands .brands>ul .item>article .brand-info .name,#folder-contents .contents>ul .item>article .content-info .name,#product #product-details .name,.contents-list .item>article .content-info .name,.page-header,.table-address .radio,.table-delivery .radio{margin-top:0}#product #product-gallery #product-thumbnails ul{margin:0}#product #product-gallery #product-thumbnails ul>li{margin:0;padding:0;width:19%}#folder-contents .contents>ul .item>article .content-image>img,.contents-list .item>article .content-image>img{width:100%}#product #product-details .product-price{margin-bottom:20px}#product #product-details .product-cart{margin-bottom:20px;padding:0}#product #product-tabs{margin-bottom:20px}#product #product-tabs .nav-tabs{margin-bottom:-1px}.folder-description{margin-bottom:20px}.contents-list .item{padding-bottom:15px}.contents-list .item+.item{padding-top:15px}.contents-list .item>article .content-image{margin-bottom:15px;padding:0}.brand-description,.main{margin-bottom:20px}#brands .brands>ul .item{padding-bottom:15px}#brands .brands>ul .item+.item{padding-top:15px}#brands .brands>ul .item>article .brand-image{margin-bottom:15px;padding:0}header .header .logo a{text-decoration:none}header .header .language-container{text-align:right}header .header .language-container .currency-switch .dropdown-label,header .header .language-container .language-switch .dropdown-label{font-size:1em;font-weight:300}.footer-container .footer-banner{background-color:#e8e8e8;font-size:19px}.footer-container .footer-banner .banner i{display:block;font-size:2em}.footer-container .footer-banner .banner small{font-size:.65em;display:block;font-style:italic;font-weight:400}.footer-container .footer-banner .banner .col{text-align:center}.footer-container .footer-banner .banner .col+.col{border-top:1px solid #d6d6d6}@media (min-width:768px){.footer-container .footer-banner .banner .col+.col{border-left:1px solid #d6d6d6;border-top:none}}.footer-container .footer-block{background-color:#f5f5f5}.footer-container .footer-info{background-color:#444;color:#fff;font-size:12px}.footer-container .footer-info a,.footer-container .footer-info a:focus,.footer-container .footer-info a:hover{color:#fff}.footer-container .footer-info .info .nav-footer ul li+li:before{content:'-'}.footer-container .footer-info .info .copyright{font-weight:300;text-align:right}#payment-method.panel .panel-body,.cart-warning{text-align:center}.footer-container .footer-info .info .copyright>a{font-weight:700}.alert-warning:before,.breadcrumb>li+li:before,.cart-warning:before,.js .dropdown-toggle:after{font:normal normal normal 14px/1 FontAwesome}.cart-warning>a{color:inherit}.cart-warning:before{text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);display:block;font-size:2.2em}.breadcrumb>li+li:before,.js .dropdown-toggle:after{-ms-transform:translate(0,0);text-rendering:auto;-moz-osx-font-smoothing:grayscale}#cart-address .panel{border:none}#payment-method.panel .radio label>img{border:1px solid #ddd;border-radius:3px;opacity:.4;filter:alpha(opacity=40)}#payment-method.panel .radio label>img:focus,#payment-method.panel .radio label>img:hover{opacity:1;filter:alpha(opacity=100);transition:opacity .2s ease-in-out}.btn,a{transition:all .3s ease-in-out}#payment-method .list-group-item{border:none}.js #payment-method .radio .active>img,.js #payment-method .radio input:checked+img{opacity:1;filter:alpha(opacity=100)}.checkout-progress .btn-step{padding:16px 24px;background:#eee;color:#555}.checkout-progress .btn-step+.btn-step{border-left:1px solid #555}.checkout-progress .btn-step .step-nb{border-right:1px solid #7a7a7a;font-size:30px;line-height:0;font-weight:600;padding-right:6px;vertical-align:middle}.checkout-progress .btn-step .step-label{font-size:20px;font-weight:100;min-width:250px;padding-left:6px;vertical-align:middle}.checkout-progress .btn-step.active,.checkout-progress .btn-step:active,.checkout-progress .btn-step:focus,.checkout-progress .btn-step:hover{color:#fff;background:#f49a17}.checkout-progress .btn-step.active .step-nb,.checkout-progress .btn-step:active .step-nb,.checkout-progress .btn-step:focus .step-nb,.checkout-progress .btn-step:hover .step-nb{border-right:1px solid #fff}.checkout-progress .btn-step.active{background:#f49a17;cursor:default;display:inherit;pointer-events:none}.price{color:#f49a17;font-size:20px;font-weight:700;font-style:italic;white-space:nowrap}.old-price .price{color:#7a7a7a;font-size:16px;font-weight:600;text-decoration:line-through}#folder-contents .contents>ul .item{padding-bottom:15px}#folder-contents .contents>ul .item+.item{padding-top:15px;border-top:1px solid #ededed}#folder-contents .contents>ul .item>article .content-image{margin-bottom:15px;padding:0}.contents-list .item+.item{border-top:1px solid #ededed}.breadcrumb{padding:0}.breadcrumb>li+li:before{display:inline-block;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f105"}.btn{border-radius:0;text-align:left;font-weight:600}.btn-primary{font-style:italic;border-left:3px solid #f9c478}.btn-primary:focus,.btn-primary:hover{background-color:#f49a17;color:#b66f09}.btn-default{border-left:3px solid #ccc}.btn-default:focus,.btn-default:hover{background-color:#f7f7f7}.btn-default.active,.btn-default.active:hover,.btn-default:active,.btn-default:active:hover,.btn-primary.active,.btn-primary.active:hover,.btn-primary:active,.btn-primary:active:hover{background-color:#d5d5d5;border-color:#6f6f6f;color:#fff}.btn-link{font-weight:400}.form-control:focus::-moz-placeholder{color:#eee;opacity:1}.form-control:focus:-ms-input-placeholder{color:#eee}.form-control:focus::-webkit-input-placeholder{color:#eee}#form-login-mini{width:200px}#form-login-mini .mini-forgot-password{font-size:12px}#form-forgotpassword,#form-login{background:#f5f5f5}#form-forgotpassword legend,#form-login legend{font-size:14px;font-weight:700}.fn,.table-address .radio label,.table-delivery .radio label{font-weight:600}#form-forgotpassword .btn-login,#form-login .btn-login{display:block;width:100%}@media (min-width:768px){#form-forgotpassword .group-btn,#form-login .group-btn{text-align:right}#form-forgotpassword .group-btn .btn-login,#form-login .group-btn .btn-login{display:inline-block;width:auto}}@media (min-width:992px){.btn{padding:2px 15px 2px 5px}#form-forgotpassword,#form-login{width:45%}}#brands .brands>ul .item>article .brand-image>img,.grid .item .product-image>img,.list .item>article .product-image>img,.loader{width:100%}.no-js .collapse{display:block!important}.loader,.no-js #carousel .carousel-control{display:none}.loader{background:url(../img/ajax-loader.gif) center center no-repeat #fff;background-color:rgba(255,255,255,.5);left:0;top:0;height:100%;z-index:100}.oldie{position:absolute}.thumbnail.active{border-color:#7a7a7a}.fn{display:block}.adr,.org{font-size:12px}.table-address .group-btn,.table-delivery .group-btn{text-align:right}.table-address tbody>tr>td,.table-address tbody>tr>th,.table-address tfoot>tr>td,.table-address tfoot>tr>th,.table-address thead>tr>td,.table-address thead>tr>th,.table-delivery tbody>tr>td,.table-delivery tbody>tr>th,.table-delivery tfoot>tr>td,.table-delivery tfoot>tr>th,.table-delivery thead>tr>td,.table-delivery thead>tr>th{border-color:#f5f5f5;padding:10px 10px 0}@media (min-width:768px){.table-address tbody>tr>td,.table-address tbody>tr>th,.table-address tfoot>tr>td,.table-address tfoot>tr>th,.table-address thead>tr>td,.table-address thead>tr>th,.table-delivery tbody>tr>td,.table-delivery tbody>tr>th,.table-delivery tfoot>tr>td,.table-delivery tfoot>tr>th,.table-delivery thead>tr>td,.table-delivery thead>tr>th{padding:30px 30px 0}}.modal-dialog td{vertical-align:middle}.modal-dialog .close{margin:10px;position:relative;z-index:10}.modal-dialog .btn{margin-left:10px}@media screen and (min-width:768px){.modal-dialog{width:800px}}.navbar.navbar-secondary{z-index:1001}@media (min-width:992px){.navbar .list-subnav{background-color:#f49a17;border:1px solid #f49a17;border-radius:0;box-shadow:none}.navbar .list-subnav>li>a{color:#fff;padding:3px 12px}.navbar .list-subnav>.active>a,.navbar .list-subnav>.active>a:focus,.navbar .list-subnav>.active>a:hover,.navbar .list-subnav>li>a:focus,.navbar .list-subnav>li>a:hover{background-color:#fff;color:#f49a17}}.navbar .full-width .dropdown-menu .dropdown-content{padding:20px}.navbar .full-width .dropdown-menu .dropdown-content .dropdown-subheading{font-weight:700}.js .dropdown-toggle:after{display:inline-block;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f078"}#account .panel-heading{padding:0}#account .panel-heading .panel-title>a{background:#f49a17;color:#fff;display:block;padding:12px 15px;text-decoration:none}#account .panel-heading .panel-title>a.collapsed{background:0 0;color:inherit}#account .panel-heading .panel-title>a.collapsed:focus,#account .panel-heading .panel-title>a.collapsed:hover{background:#f49a17;color:#fff}#account .panel-body{padding:25px}.table-cart tbody>tr>td,.table-cart tbody>tr>th,.table-cart tfoot>tr>td,.table-cart tfoot>tr>th,.table-cart thead>tr>td,.table-cart thead>tr>th,.table-order tbody>tr>td,.table-order tbody>tr>th,.table-order tfoot>tr>td,.table-order tfoot>tr>th,.table-order thead>tr>td,.table-order thead>tr>th{padding:14px;text-align:center;vertical-align:middle}.table-cart tbody>tr>td.product,.table-cart tbody>tr>th.product,.table-cart tfoot>tr>td.product,.table-cart tfoot>tr>th.product,.table-cart thead>tr>td.product,.table-cart thead>tr>th.product,.table-order tbody>tr>td.product,.table-order tbody>tr>th.product,.table-order tfoot>tr>td.product,.table-order tfoot>tr>th.product,.table-order thead>tr>td.product,.table-order thead>tr>th.product{text-align:left}.table-cart tbody>tr>td.image,.table-cart tbody>tr>th.image,.table-cart tfoot>tr>td.image,.table-cart tfoot>tr>th.image,.table-cart thead>tr>td.image,.table-cart thead>tr>th.image,.table-order tbody>tr>td.image,.table-order tbody>tr>th.image,.table-order tfoot>tr>td.image,.table-order tfoot>tr>th.image,.table-order thead>tr>td.image,.table-order thead>tr>th.image{border-right-color:transparent}.table-cart thead th,.table-order thead th{background-color:#f5f5f5;border-bottom-width:1px}.table-cart thead th.subprice,.table-order thead th.subprice{color:#f49a17}.table-cart tbody td.price,.table-cart tbody td.qty,.table-cart tbody td.subprice,.table-order tbody td.price,.table-order tbody td.qty,.table-order tbody td.subprice{padding:35px 10px}.table-cart tbody td.unitprice .price,.table-order tbody td.unitprice .price{color:#7a7a7a}.table-cart tbody td.unitprice .old-price .price,.table-order tbody td.unitprice .old-price .price{font-size:14px}.table-cart tbody td.unitprice .secondary-price .price,.table-order tbody td.unitprice .secondary-price .price{font-size:14px;font-weight:400}.table-cart tbody td.subprice .price,.table-order tbody td.subprice .price{color:#f49a17}.table-cart tfoot td,.table-cart tfoot th,.table-order tfoot td,.table-order tfoot th{background-color:#f5f5f5}.table-cart tfoot td.empty,.table-cart tfoot th.empty,.table-order tfoot td.empty,.table-order tfoot th.empty{background:0 0}.table-cart tfoot td.total,.table-cart tfoot th.total,.table-order tfoot td.total,.table-order tfoot th.total{background-color:#666;color:#fff}.table-cart tfoot td.total .price,.table-cart tfoot th.total .price,.table-order tfoot td.total .price,.table-order tfoot th.total .price{color:inherit}.table-cart tfoot td.shipping .price,.table-order tfoot td.shipping .price{color:#7a7a7a;font-size:19px}.table-cart tfoot td.total .price,.table-order tfoot td.total .price{font-size:19px}.table-cart tfoot td.empty,.table-order tfoot td.empty{border-bottom-color:transparent;border-left-color:transparent}.table-cart tfoot th.total,.table-order tfoot th.total{font-weight:100;font-size:16px}.table-cart-total td.total .price,.table-order-total td.total .price{font-size:19px}.table-cart-total td.empty,.table-order-total td.empty{border-bottom-color:transparent;border-left-color:transparent}.alert-warning{clear:both;text-align:center}.alert-warning>a{color:inherit}.alert-warning:before{text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f071";display:block;font-size:2.2em}.block{background:0 0;border:1px solid transparent;border-radius:0}.block .block-heading{background:0 0;border-bottom:1px solid #dfdfdf;color:#888;margin:0 0 6px;padding-bottom:6px}.block .block-title{font-size:21px;margin-top:0;margin-bottom:0}.block .block-title>a{color:inherit}.block .block-content{font-size:12px;margin-bottom:20px}.block .block-content ul{padding-left:0;list-style:none}.block .block-content .block-subtitle{color:#f49a17;font-size:16px;font-weight:300;margin:0 0 6px}.block-default .block-content li:before,.block-nav .block-content li a.accordion-toggle:after{font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-moz-osx-font-smoothing:grayscale}.block-default .block-content li{margin-left:15px;padding-top:6px}.block-default .block-content li a{color:#747474}.block-default .block-content li a:focus,.block-default .block-content li a:hover{color:#b66f09}.block-default .block-content li:before{display:inline-block;font-size:inherit;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);color:#f49a17;margin-left:-15px;margin-right:5px}.block-links .block-content li a,.block-nav .block-content li a{color:#747474;display:block;font-weight:400;position:relative;font-size:12px}.block-links .block-content li+li a{border-top:1px solid #fff}.block-links .block-content li a{background-color:transparent;padding:10px 3px}.block-links .block-content li a:focus,.block-links .block-content li a:hover{text-decoration:none;background-color:#ebebeb}.block-links .block-content li a>p,.block-nav .block-heading{margin-bottom:0}.block-nav .block-content li a{background-color:transparent;padding:10px 60px 10px 3px}.block-nav .block-content li a:focus,.block-nav .block-content li a:hover{text-decoration:none;background-color:#f7f7f7}.block-nav .block-content li a.accordion-toggle:after{color:#f49a17;display:inline-block;font-size:inherit;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f068"}.block-nav .block-content li a.accordion-toggle.collapsed:after{content:"\f067"}.block-nav .block-content ul a{padding-left:15px}.block-nav .block-content ul ul a{padding-left:30px}.block-nav .block-content ul ul ul a{padding-left:45px}.block-thumbnail{margin-left:-15px;margin-right:-15px}.block-thumbnail.block-thumbnail-2 li{max-width:50%}.block-thumbnail.block-thumbnail-3 li{max-width:33.33333333%}.block-thumbnail.block-thumbnail-4 li{max-width:25%}.block-thumbnail .block-content li{float:left;padding-right:7.5px;padding-bottom:7.5px;position:relative;max-width:33.33333333%}.block-social .block-content li{display:inline-block;font-size:18px}.block-social .block-content li>a{color:#888}.block-social .block-content li>a:focus,.block-social .block-content li>a:hover{color:#b66f09}.block-newsletter .block-content form .btn-subscribe{padding:6px}.block-contact .block-content li{clear:both;margin-bottom:5px}.block-carousel{margin-bottom:30px}.block-carousel .carousel-indicators{bottom:auto}.block-carousel .block-carousel-control{float:right!important;float:right}.block-carousel .block-carousel-control .carousel-control{background:#efefef;color:#000;display:block;float:left;font-size:24px;margin-left:3px;position:relative;top:1px;left:auto;bottom:auto;width:28px;height:28px;transition:background-color .3s ease-in-out}.label-delivered,.label-new,.label-sale{padding:.2em .6em .3em;font-size:75%;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em;color:#fff}.btn .label-delivered,.btn .label-new,.btn .label-sale{top:-1px;position:relative}.block-carousel .block-carousel-control .carousel-control:focus,.block-carousel .block-carousel-control .carousel-control:hover{background-color:#000;color:#fff}.label-new{display:inline;font-weight:700;background-color:#5bc0de}a.label-new:focus,a.label-new:hover{color:#fff;text-decoration:none;cursor:pointer}.label-new:empty{display:none}.label-new[href]:focus,.label-new[href]:hover{background-color:#31b0d5}.label-sale{display:inline;font-weight:700;background-color:#d9534f}a.label-sale:focus,a.label-sale:hover{color:#fff;text-decoration:none;cursor:pointer}.label-sale:empty{display:none}.label-sale[href]:focus,.label-sale[href]:hover{background-color:#c9302c}.label-delivered{display:inline;font-weight:700;background-color:#5cb85c}a.label-delivered:focus,a.label-delivered:hover{color:#fff;text-decoration:none;cursor:pointer}.grid .btn-grid,.list .btn-list{cursor:default;pointer-events:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.label-delivered:empty{display:none}.label-delivered[href]:focus,.label-delivered[href]:hover{background-color:#449d44}.products-heading .btn-all{float:right}.products-heading h3{top:-14px!important;margin:0}.availability .in-stock{color:#5cb85c;font-style:italic;font-weight:700}.availability .in-stock .in{display:block}.availability .in-stock .out,.availability .out-of-stock .in{display:none}.availability .in-stock .quantity{font-style:italic}.availability .out-of-stock{color:#f0ad4e;font-style:italic;font-weight:700}.availability .out-of-stock .out{display:block}#brands .brands>ul .item>article .brand-image.overlay:after,.no-js .toolbar .limiter,.no-js .toolbar .sort-by{display:none}.option{background:#fff;border:1px solid transparent;border-radius:0}.option .option-heading{border-bottom:1px solid transparent;color:#7a7a7a;font-size:14px;font-weight:700}.option .option-content .checkbox label,.option .option-content .radio label{font-weight:100}#product #product-gallery{border-right:1px solid #f5f5f5;padding-right:20px}#product #product-details .name{font-size:21px;font-weight:400}#product #product-details .product-cart{background:#fff;border:1px solid transparent;border-radius:0}#product #product-tabs .nav-tabs{border-bottom:1px solid #ddd}#product #product-tabs .tab-content{border:1px solid #ddd;border-radius:0 0 3px 3px;padding:30px 15px;min-height:180px;height:auto!important;height:180px}.list .item+.item{border-top:1px solid #ededed}.list .item>article .product-price{text-align:right}.filter{background:#f5f5f5;border:1px solid #f5f5f5;border-radius:0}.filter .filter-heading{border-bottom:1px solid #dfdfdf;color:#888;font-size:19px;font-weight:100}.filter .filter-content .checkbox label,.filter .filter-content .radio label{font-weight:100}.toolbar{line-height:50px}.toolbar .pagination-container,.toolbar .sorter-container{overflow:hidden;height:50px}.toolbar .sorter-container{background-color:#fff;border-radius:0;padding:0;text-align:right}.toolbar .sorter-container .view-mode>.view-mode-btn{font-size:24px}.toolbar .sorter-container .view-mode>.view-mode-btn a{padding:0 6px;font-size:21px;text-decoration:none}.toolbar .pagination-container{text-align:center}#brands .brands>ul .item+.item{border-top:1px solid #ededed}.page-404 .main{padding:10px 0 100px}.page-404 #main-label{color:#f49a17;font-size:9em;font-weight:700;text-align:center}.page-404 #main-label span{color:#CCC;display:block;font-size:15px;font-weight:400}.page-home #carousel{margin-bottom:20px}.page-home #carousel .item{text-align:center}@media screen and (min-width:768px){.page-home #carousel .carousel-control .fa-caret-left,.page-home #carousel .carousel-control .fa-caret-right{font-size:80px;margin-top:-40px;margin-left:-40px;width:80px;height:80px}}.page-header{border:none;font-weight:100;font-size:30px}.has-error .help-block:before,.navbar li.cart-not-empty>a.cart:before,.navbar li>a.home:before,.navbar li>a.login:before{font:normal normal normal 14px/1 FontAwesome}.form-control{box-shadow:none}.form-control:invalid:focus{border-color:#843534;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}#account .panel,.dropdown-menu,.modal-content,.popover{box-shadow:none}.has-error .help-block:before{display:inline-block;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);margin-right:.3em}label{font-weight:600}.popover{border-radius:3px}.overlay{display:block;overflow:hidden;position:relative;font-size:40px}.overlay:after,.overlay:before{display:block;width:100%;height:100%;visibility:hidden;position:absolute;top:0;left:0;right:0;opacity:0;filter:alpha(opacity=0);transition:all .3s ease-in-out 50ms}.overlay:before{content:'';overflow:visible;background-color:#f49a17;background-color:rgba(244,154,23,.4)}#filters>h3:before,.overlay:after{content:"\f002"}.overlay:after{font-family:FontAwesome;color:#fff;text-align:center;-ms-transform:translate(0,0);transform:translate(0,0);line-height:0}.overlay:focus:after,.overlay:focus:before,.overlay:hover:after,.overlay:hover:before{visibility:visible;opacity:1;filter:alpha(opacity=100)}.overlay:focus:after,.overlay:hover:after{-ms-transform:translate(0,50%);transform:translate(0,50%)}.navbar li>a.home:before{display:inline-block;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f015";color:#c9c9c9;font-size:26px;line-height:0;margin-right:.5em;position:relative;top:3px}#product-details .product-promo .sale-saving:before,.navbar li.cart-not-empty>a.cart:before,.navbar li>a.login:before{display:inline-block;text-rendering:auto;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0)}.navbar li>a.login:before{-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f007";color:#f49a17;font-size:19px;line-height:0;margin-right:.5em}.navbar li>a.cart:focus>.badge,.navbar li>a.cart:hover>.badge{background-color:#fff;color:#f49a17}.navbar li.cart-not-empty>a.cart{background-color:#f49a17;color:#fff}.navbar li.cart-not-empty>a.cart>.badge{background-color:#fff;color:#f49a17}.navbar li.cart-not-empty>a.cart:focus,.navbar li.cart-not-empty>a.cart:hover{background-color:#f49a17;color:#fff}.navbar li.cart-not-empty>a.cart:before{-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f07a";color:#fff;font-size:24px;line-height:0;margin-right:.4em}@media (min-width:992px){.navbar .navbar-nav .list-subnav>li+li{border-top:1px solid #e28a0b}.navbar .navbar-nav .list-subnav>li>a{font-weight:100}}.navbar .navbar-nav>li>a:focus:before,.navbar .navbar-nav>li>a:hover:before{color:#fff}.navbar .navbar-nav>.active>a:focus,.navbar .navbar-nav>.active>a:hover{background-color:#f49a17;color:#fff}.navbar .navbar-nav>.active:after{background:#f49a17;content:"";display:block;position:absolute;bottom:0;width:100%;height:2px;z-index:100}.navbar .navbar-nav>.open>a,.navbar .navbar-nav>.open>a:focus,.navbar .navbar-nav>.open>a:hover{background-color:#f49a17;color:#fff}.navbar .navbar-nav>.open>a:before,.navbar .navbar-nav>.open>a:focus:before,.navbar .navbar-nav>.open>a:hover:before{color:#fff}.container>.navbar-collapse{margin-left:-15px;margin-right:-15px}header .header .logo{float:none}.page-home #carousel .carousel-control{background-image:none}.products-heading h2{color:#7a7a7a;font-size:18px;font-weight:700}.products-heading .btn-all,.products-heading .btn-all:focus,.products-heading .btn-all:hover{color:#7a7a7a;font-size:16px;font-style:italic;font-weight:600}.products-heading .short-description{background-color:#f5f5f5;margin-bottom:10px;padding:10px}.product-options dl{font-size:.85em;margin-bottom:10px}.product-options dl>dt{text-align:left}.product-info .name,td.product .name{font-size:16px;font-weight:600}.product-info .name>a,td.product .name>a{color:#7a7a7a;text-decoration:none}.product-info .name>a:focus,.product-info .name>a:hover,td.product .name>a:focus,td.product .name>a:hover{color:#b66f09}.product-price .price-label{font:0/0 a;background-color:transparent;display:block}.product-price .regular-price .price,.product-price .special-price .price{display:block;font-size:14px;line-height:25px;font-style:normal;font-weight:400}.product-price .old-price .price{display:block;font-size:14px;line-height:25px;font-style:italic;font-weight:400;text-decoration:line-through}#products-new .products-grid .overlay:after{-ms-transform:translate(0,40%);transform:translate(0,40%)}#products-new .products-grid .item>article{border-bottom:4px solid #f49a17;border-bottom-right-radius:3px;border-bottom-left-radius:3px;overflow:hidden;position:relative}#products-new .products-grid .item>article .product-info{background-color:#f6af48;color:#fff;display:block;padding:6px 12px;position:relative;text-decoration:none!important}#products-new .products-grid .item>article .product-info:focus,#products-new .products-grid .item>article .product-info:hover{background-color:#f49a17}#products-new .products-grid .item>article .product-info .name{min-height:40px;height:auto!important;height:40px}#products-new .products-grid .item>article .product-info .name:after{content:'+';font-size:45px;line-height:0;font-style:normal;font-weight:100;position:absolute;top:16px;right:4px;-webkit-font-smoothing:antialiased}#products-new .products-grid .item>article .product-info .short-description{font-size:11px;line-height:1.1}#products-new .products-grid .item>article .product-price .price{color:#fff;font-size:22px;font-weight:700}@media (min-width:992px){#products-new .products-grid .item>article .product-image{padding-bottom:40px}#products-new .products-grid .item>article .product-info{transition:height .3s linear;position:absolute;bottom:0;width:100%;height:50px}#products-new .products-grid .item>article .product-info h3{margin-top:2px;padding-right:20px}#products-new .products-grid .item>article .product-info h3 span{height:2em;overflow:hidden;display:block}#products-new .products-grid .item>article .product-info:focus,#products-new .products-grid .item>article .product-info:hover{cursor:pointer;height:140px}}#products-upsell{margin-top:40px;position:relative}#products-upsell .products-heading{border-bottom:1px solid #e5e5e5;margin:20px 0}#products-upsell .products-heading h3{background:#fff;color:#f49a17;padding-right:15px;position:absolute;top:-24px}#products-offer .products-grid .item>article,#products-related .products-grid .item>article,#products-upsell .products-grid .item>article{border-radius:3px;transition:background-color .3s ease-in-out;padding:6px}#delivery-address.panel .panel-body,#delivery-method.panel .panel-body,#products-offer .products-grid .item>article .product-info,#products-related .products-grid .item>article .product-info,#products-upsell .products-grid .item>article .product-info{padding:0}#products-offer .products-grid .item>article .product-info .short-description,#products-related .products-grid .item>article .product-info .short-description,#products-upsell .products-grid .item>article .product-info .short-description{font-size:11px}@media (min-width:768px){#products-offer .products-grid .item:hover article,#products-related .products-grid .item:hover article,#products-upsell .products-grid .item:hover article{background-color:#f6f6f6}}#products-new .overlay:after,#products-offer .overlay:after,#products-upsell .overlay:after{content:'+';font-size:80px;font-weight:100;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#products-new .overlay:before{border-radius:3px 3px 0 0}#category-products .item>article .product-info .description{font-size:.83em;line-height:1.3}#category-products .item>article .product-price .price-label{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0;display:block}#category-products .item>article .product-price .price-container{margin-bottom:10px}#category-products .item>article .product-price .price-container .price{margin-left:4px}#category-products .item>article .product-price .product-btn{min-height:26px}.grid #category-products .item{border-right:1px solid #e8e8e8;margin:0;padding:10px}.grid #category-products .item>article .product-info{padding:3px}.grid #category-products .item>article .product-info .name{margin:4px;height:2em;overflow:hidden}.grid #category-products .item>article .product-info .description{margin-left:4px}.list #category-products .item>article .product-price .price-container{margin-bottom:20px}.list #category-products .item>article .product-price .price-container .old-price,.list #category-products .item>article .product-price .price-container .regular-price,.list #category-products .item>article .product-price .price-container .special-price{display:block;width:100%}#product-details .product-info{border-bottom:1px solid #e5e5e5;margin-bottom:15px}#product-details .product-info .sku{color:#e5e5e5;display:block;font-size:14px;margin-top:-8px;margin-bottom:20px}#product-details .product-info .pse-name{color:#555;font-size:14px}#product-details .product-options .option{margin-bottom:10px}#product-details .product-cart{background-color:#f5f5f5!important;margin-bottom:20px;padding:10px!important}#product-details .product-promo{background-color:#f5f5f5;margin-bottom:15px;padding:10px}#product-details .product-promo .sale-label{font-weight:300;line-height:1.4;font-size:21px}#product-details .product-promo .sale-saving{color:#f49a17}#product-details .product-promo .sale-saving:before{font:normal normal normal 14px/1 FontAwesome;font-size:inherit;-webkit-font-smoothing:antialiased;transform:translate(0,0);content:"\f005"}#product-details .product-promo .sale-period{font-style:italic;font-size:90%}#product-thumbnails .carousel-control{width:17px!important}#product-thumbnails .carousel-control .fa{position:absolute;top:50%}#product-thumbnails .carousel-control.left{border-right:7px solid #ccc;color:#ccc;text-align:left}#product-thumbnails .carousel-control.left>.fa-caret-left{left:0;margin-left:0;margin-top:-15px}#product-thumbnails .carousel-control.left>.fa-caret-left:before{color:inherit}#product-thumbnails .carousel-control.right{border-left:7px solid #ccc;text-align:right}#product-thumbnails .carousel-control.right>.fa-caret-right{left:auto;right:0;margin-left:0;margin-top:-15px}@media (min-width:768px){#product #product-gallery{border-right:1px solid #eee;padding-right:20px}#product #product-details .group-qty .form-control{display:inline-block;margin-right:1em;margin-left:.4em;width:100px}}#product-gallery .product-image{margin-bottom:20px}#product-gallery .product-thumbnails li{width:20%}#filters{background:#f5f5f5}#filters>h3{background:#e5e5e5;box-shadow:inset 0 -4px 10px rgba(0,0,0,.125);margin:0 0 15px;padding:10px 15px;font-size:18px;font-weight:700}#filters>h3>span{display:block;font-size:.75em;font-weight:100;text-transform:lowercase}#filters>h3:before,.block.block-newsletter .block-content form .form-group:before{font:normal normal normal 14px/1 FontAwesome;-moz-osx-font-smoothing:grayscale;text-rendering:auto}#filters>h3:before{display:inline-block;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);font-size:30px;float:left;margin-right:.5em}#filters .filter{margin-bottom:10px}.block.block-links .block-content ul>li+li a{border-top:none}.block.block-links .block-content ul>li+li:before{background:#fff;content:"";display:block;margin:0 auto;text-align:center;width:65%;height:2px}.block.block-newsletter .block-content form .form-group{position:relative}@media (min-width:1200px){.block.block-newsletter .block-content form .form-group{width:176px}}.block.block-newsletter .block-content form .form-group .form-control{background-color:#e6e6e6;font-size:12px;padding-left:35px;width:inherit;box-shadow:inset 1px 1px 1px rgba(0,0,0,.075)}.block.block-newsletter .block-content form .form-group .form-control::-moz-placeholder{color:#888;opacity:1}.block.block-newsletter .block-content form .form-group .form-control:-ms-input-placeholder{color:#888}.block.block-newsletter .block-content form .form-group .form-control::-webkit-input-placeholder{color:#888}.block.block-newsletter .block-content form .form-group .form-control:focus::-moz-placeholder{color:#c8c8c8;opacity:1}.block.block-newsletter .block-content form .form-group .form-control:focus:-ms-input-placeholder{color:#c8c8c8}.block.block-newsletter .block-content form .form-group .form-control:focus::-webkit-input-placeholder{color:#c8c8c8}.block.block-newsletter .block-content form .form-group:before{display:inline-block;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f0e0";color:#8b8b8b;font-size:18px;position:absolute;top:8px;left:9px}.block.block-newsletter .block-content form .btn-subscribe{padding:6px}.block.block-social .block-content ul>li>a:hover.facebook{color:#3d5fa6}.block.block-social .block-content ul>li>a:hover.twitter{color:#53b1f0}.block.block-social .block-content ul>li>a:hover.rss{color:#fac200}.block.block-social .block-content ul>li>a:hover.instagram{color:#425E75}.block.block-social .block-content ul>li>a:hover.google-plus{color:#fac200}.block.block-social .block-content ul>li>a:hover.youtube{color:#e82a20}.block.block-contact .block-content ul>li{clear:both;margin-bottom:5px}.block.block-contact .block-content ul>li.contact-address:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f041";font-size:34px}.block.block-contact .block-content ul>li.contact-phone:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f10b";font-size:30px;margin-top:-8px;margin-left:3px}.block.block-contact .block-content ul>li.contact-email:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f0e0";font-size:17px;margin-left:2px}.block.block-contact .block-content ul>li:before{color:#f49a17;float:left;line-height:1;margin-right:.4em}.block.block-contact .block-content ul>li.contact-contact:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f1d8";font-size:17px}#categories.block-nav .block-content{border-top:1px solid #aeaeae}#categories.block-nav .block-content .amount{font-weight:700}#categories.block-nav .block-content li{border-top:1px solid #eee;position:relative}#categories.block-nav .block-content li .accordion-toggle{position:absolute;top:0;right:0;padding-right:10px;padding-left:5px}#categories.block-nav .block-content li .accordion-toggle:focus,#categories.block-nav .block-content li .accordion-toggle:hover{background:0 0}#categories.block-nav .block-content li .accordion-toggle:focus:after,#categories.block-nav .block-content li .accordion-toggle:hover:after{border-color:#b66f09;color:#b66f09}#categories.block-nav .block-content li .accordion-toggle:after{border:1px solid #f49a17;border-radius:10px;line-height:17px;text-align:center;width:19px;height:19px}.toolbar.toolbar-top{margin-top:-20px;border-bottom:1px solid #eee}.toolbar.toolbar-bottom .sorter-container,.toolbar.toolbar-top .pagination-container{display:none}.toolbar .amount{color:#f49a17;font-size:22px;font-weight:400}.toolbar .view-mode>.view-mode-btn a{background-color:#fff;border:0!important;color:#7a7a7a}.toolbar .view-mode>.view-mode-btn a:focus,.toolbar .view-mode>.view-mode-btn a:hover{background-color:#efefef;color:#474747}.toolbar .view-mode>.view-mode-btn a:active{color:#fff}.pagination>li>a,.pagination>li>span{box-shadow:2px 1px 1px rgba(0,0,0,.1);transition:all .2s ease-in-out;background-image:linear-gradient(to bottom,#fff 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff9f9f9', GradientType=0);color:#7a7a7a;font-weight:700}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{background:0 0}.pagination>li>a:focus:active,.pagination>li>a:hover:active,.pagination>li>span:focus:active,.pagination>li>span:hover:active{background-color:#f49a17;border-color:#f49a17;color:#fff}.pagination>li:first-child>a,.pagination>li:first-child>span{border-bottom-left-radius:30px;border-top-left-radius:30px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:30px;border-top-right-radius:30px}.pagination>.active>a,.pagination>.active>span{background-image:none}#form-forgotpassword .group-email label,#form-forgotpassword legend,#form-login .group-email label,#form-login legend{font-size:16px;font-weight:600}#form-forgotpassword .radio-account1,#form-login .radio-account1{margin-top:10px}#form-forgotpassword .forgot-password,#form-login .forgot-password{color:#7a7a7a;font-size:12px;font-style:italic}@media (min-width:768px){#form-forgotpassword .radio-account1,#form-login .radio-account1{float:left}#form-forgotpassword .group-password,#form-login .group-password{float:right;margin-top:5px;width:50%}}#delivery-method.panel .radio{display:block;margin-top:0}#delivery-method.panel .radio+.radio{border-top:1px solid #f5f5f5}#delivery-method.panel .price{text-align:right}#delivery-method.panel .image{text-align:center}#account .panel-title,#payment-success.panel .panel-heading{text-align:left}.js #payment-method .radio{padding-left:0;position:relative}.js #payment-method .radio .active:after{font:normal normal normal 14px/1 FontAwesome;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f077";color:#f49a17;display:block;font-size:1.5em;line-height:0;position:absolute;bottom:-8px;left:40%}#account .panel-title>a:before,#account-info .list-info .email:before,#account-info .list-info .mobile:before,#account-info .list-info .tel:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;-moz-osx-font-smoothing:grayscale}#payment-success.panel .panel-body{padding:20px 40px}#payment-success.panel .panel-body>h3{color:#f49a17}#account .panel{border-color:#fff}#account .panel-title>a:before{font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f078";float:left;width:20px}#account .panel-title>a.collapsed:before{content:"\f054"}#account-info .fn{font-size:16px;font-weight:600}#account-info .list-info .email:before,#account-info .list-info .mobile:before,#account-info .list-info .tel:before{color:#f49a17;line-height:1;margin-right:.4em;vertical-align:middle}#account-info .list-info .mobile:before{text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f10b";font-size:30px}#account-info .list-info .tel:before{text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f095";font-size:22px}#account-info .list-info .email:before{text-rendering:auto;-webkit-font-smoothing:antialiased;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f0e0";font-size:18px}#account-info .group-btn a{color:#7a7a7a;margin-bottom:4px;padding:0}#account-info .group-btn a>i{color:#f49a17;font-size:20px;line-height:1;margin-right:.3em;vertical-align:middle}#account-info .group-btn a:focus,#account-info .group-btn a:hover{color:#b66f09}#account-address .panel-body{padding-left:0;padding-right:0;padding-top:10px}#account-address .table-address{border:1px solid #f5f5f5;margin-bottom:0}#account-orders .panel-body{padding-left:0;padding-right:0}#account-orders .table-orders tbody>tr>td,#account-orders .table-orders tbody>tr>th,#account-orders .table-orders thead>tr>td,#account-orders .table-orders thead>tr>th{padding:14px;text-align:center}#account-orders .table-orders thead>tr>th{background-color:#f5f5f5;border-bottom-width:1px}#account-orders .table-order-products tbody>tr>td,#account-orders .table-order-products tbody>tr>th,#account-orders .table-order-products thead>tr>td,#account-orders .table-order-products thead>tr>th{padding:5px;text-align:center}.table-cart-mini tbody>tr>td,.table-cart-mini tbody>tr>th,.table-cart-mini tfoot>tr>td,.table-cart-mini tfoot>tr>th,.table-cart-mini thead>tr>td,.table-cart-mini thead>tr>th{vertical-align:middle}#google-map{border:none;display:block;margin-bottom:20px;width:100%;height:350px;-moz-filter:grayscale(100%);-ms-filter:grayscale(100%);-o-filter:grayscale(100%);filter:grayscale(100%)}#sale-details .sale-discount-information{background-color:#f5f5f5;margin-bottom:10px;padding:10px}#sale-details .sale-discount-information .sale-saving{font-size:120%;color:#f49a17}#sale-details .sale-discount-information .sale-saving:before{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-ms-transform:translate(0,0);transform:translate(0,0);content:"\f005"}#sale-details .sale-discount-information .sale-period{font-style:italic;font-size:90%}#sale-details .sale-information{margin-bottom:30px}#sale-details .sale-information .chapo,#sale-details .sale-information .description{margin-bottom:10px} \ No newline at end of file diff --git a/domokits/templates/frontOffice/custom/assets/src/css/custom.css b/domokits/templates/frontOffice/custom/assets/src/css/custom.css new file mode 100644 index 0000000..72e83e9 --- /dev/null +++ b/domokits/templates/frontOffice/custom/assets/src/css/custom.css @@ -0,0 +1,11 @@ +div.container { + width: 90% !important; +} + +.container { + width: 95% +} + +header .header .logo { + background-color: #EC8C39; +} diff --git a/domokits/templates/frontOffice/custom/assets/src/css/thelia.css b/domokits/templates/frontOffice/custom/assets/src/css/thelia.css index 6bfb3b7..7c46b4c 100644 --- a/domokits/templates/frontOffice/custom/assets/src/css/thelia.css +++ b/domokits/templates/frontOffice/custom/assets/src/css/thelia.css @@ -2989,7 +2989,6 @@ select[multiple].input-lg { line-height: 1.42857143; border-radius: 3px; -webkit-user-select: none; - -moz-user-select: none; -ms-user-select: none; user-select: none; } @@ -5052,14 +5051,6 @@ a.thumbnail.active { .alert-danger .alert-link { color: #843534; } -@-webkit-keyframes progress-bar-stripes { - from { - background-position: 40px 0; - } - to { - background-position: 0 0; - } -} @keyframes progress-bar-stripes { from { background-position: 40px 0; @@ -5095,7 +5086,6 @@ a.thumbnail.active { } .progress.active .progress-bar, .progress-bar.active { - -webkit-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } .progress-bar-success { @@ -5806,14 +5796,11 @@ button.close { outline: 0; } .modal.fade .modal-dialog { - -webkit-transform: translate(0, -25%); -ms-transform: translate(0, -25%); transform: translate(0, -25%); - transition: -webkit-transform 0.3s ease-out; transition: transform 0.3s ease-out; } .modal.in .modal-dialog { - -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); transform: translate(0, 0); } @@ -6170,29 +6157,23 @@ button.close { } @media all and (transform-3d), (-webkit-transform-3d) { .carousel-inner > .item { - transition: -webkit-transform 0.6s ease-in-out; transition: transform 0.6s ease-in-out; - -webkit-backface-visibility: hidden; backface-visibility: hidden; - -webkit-perspective: 1000px; perspective: 1000px; } .carousel-inner > .item.next, .carousel-inner > .item.active.right { - -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); left: 0; } .carousel-inner > .item.prev, .carousel-inner > .item.active.left { - -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); left: 0; } .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right, .carousel-inner > .item.active { - -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); left: 0; } @@ -6684,9 +6665,8 @@ button.close { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); } /* makes the font 33% larger relative to the icon container */ .fa-lg { @@ -6746,60 +6726,41 @@ button.close { margin-left: .3em; } .fa-spin { - -webkit-animation: fa-spin 2s infinite linear; animation: fa-spin 2s infinite linear; } .fa-pulse { - -webkit-animation: fa-spin 1s infinite steps(8); animation: fa-spin 1s infinite steps(8); } -@-webkit-keyframes fa-spin { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} @keyframes fa-spin { 0% { - -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { - -webkit-transform: rotate(359deg); transform: rotate(359deg); } } .fa-rotate-90 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); - -webkit-transform: rotate(90deg); -ms-transform: rotate(90deg); transform: rotate(90deg); } .fa-rotate-180 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); - -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); transform: rotate(180deg); } .fa-rotate-270 { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); - -webkit-transform: rotate(270deg); -ms-transform: rotate(270deg); transform: rotate(270deg); } .fa-flip-horizontal { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); - -webkit-transform: scale(-1, 1); -ms-transform: scale(-1, 1); transform: scale(-1, 1); } .fa-flip-vertical { filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); - -webkit-transform: scale(1, -1); -ms-transform: scale(1, -1); transform: scale(1, -1); } @@ -6808,8 +6769,7 @@ button.close { :root .fa-rotate-270, :root .fa-flip-horizontal, :root .fa-flip-vertical { - -webkit-filter: none; - filter: none; + filter: none; } .fa-stack { position: relative; @@ -8595,9 +8555,8 @@ header .header .language-container .currency-switch .select { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f078"; } .navbar .navbar-cart .dropdown > a:after, @@ -8957,9 +8916,8 @@ header .header .language-container .currency-switch .select { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f071"; display: block; font-size: 2.2em; @@ -9087,9 +9045,8 @@ a { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f105"; } .btn { @@ -9320,9 +9277,8 @@ a { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f078"; } .panel-heading { @@ -9498,9 +9454,8 @@ a { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f071"; display: block; font-size: 2.2em; @@ -9558,9 +9513,8 @@ a { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f054"; color: #f49a17; margin-left: -15px; @@ -9613,9 +9567,8 @@ a { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f068"; } .block-nav .block-content li a.accordion-toggle.collapsed:after { @@ -9998,9 +9951,8 @@ a.label-delivered:focus { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f00d"; margin-right: .3em; } @@ -10048,7 +10000,6 @@ label { content: "\f002"; color: #fff; text-align: center; - -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); transform: translate(0, 0); line-height: 0; @@ -10063,7 +10014,6 @@ label { } .overlay:hover:after, .overlay:focus:after { - -webkit-transform: translate(0, 50%); -ms-transform: translate(0, 50%); transform: translate(0, 50%); } @@ -10074,9 +10024,8 @@ label { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f015"; color: #c9c9c9; font-size: 26px; @@ -10092,9 +10041,8 @@ label { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f007"; color: #f49a17; font-size: 19px; @@ -10126,9 +10074,8 @@ label { text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f07a"; color: #fff; font-size: 24px; @@ -10249,7 +10196,6 @@ td.product .name > a:focus, text-decoration: line-through; } #products-new .products-grid .overlay:after { - -webkit-transform: translate(0, 40%); -ms-transform: translate(0, 40%); transform: translate(0, 40%); } @@ -10465,9 +10411,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f005"; } #product-details .product-promo .sale-period { @@ -10547,9 +10492,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f002"; font-size: 30px; float: left; @@ -10617,9 +10561,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f0e0"; color: #8b8b8b; font-size: 18px; @@ -10659,9 +10602,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f041"; font-size: 34px; } @@ -10672,9 +10614,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f10b"; font-size: 30px; margin-top: -8px; @@ -10687,9 +10628,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f0e0"; font-size: 17px; margin-left: 2px; @@ -10707,9 +10647,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f1d8"; font-size: 17px; } @@ -10875,9 +10814,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f077"; color: #f49a17; display: block; @@ -10913,9 +10851,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f078"; float: left; width: 20px; @@ -10942,9 +10879,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f10b"; font-size: 30px; } @@ -10955,9 +10891,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f095"; font-size: 22px; } @@ -10968,9 +10903,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f0e0"; font-size: 18px; } @@ -11035,7 +10969,6 @@ td.product .name > a:focus, margin-bottom: 20px; width: 100%; height: 350px; - -webkit-filter: grayscale(100%); -moz-filter: grayscale(100%); -ms-filter: grayscale(100%); -o-filter: grayscale(100%); @@ -11057,9 +10990,8 @@ td.product .name > a:focus, text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-transform: translate(0, 0); - -ms-transform: translate(0, 0); - transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); content: "\f005"; } #sale-details .sale-discount-information .sale-period { diff --git a/domokits/templates/frontOffice/custom/category.html b/domokits/templates/frontOffice/custom/category.html index 1872542..5a8c4a6 100644 --- a/domokits/templates/frontOffice/custom/category.html +++ b/domokits/templates/frontOffice/custom/category.html @@ -56,7 +56,8 @@ {hook name="category.main-top" category="$category_id"} -
+ +
{hook name="category.content-top" category="$category_id"} @@ -125,9 +126,11 @@ {hook name="category.content-bottom" category="$category_id"}
+ {hook name="category.main-bottom" category="$category_id"} diff --git a/domokits/templates/frontOffice/custom/layout.tpl b/domokits/templates/frontOffice/custom/layout.tpl index 2be3627..c6a3473 100644 --- a/domokits/templates/frontOffice/custom/layout.tpl +++ b/domokits/templates/frontOffice/custom/layout.tpl @@ -20,7 +20,7 @@ GNU General Public License : http://www.gnu.org/licenses/ {* Declare assets directory, relative to template base directory *} {declare_assets directory='assets/dist'} {* Set the default translation domain, that will be used by {intl} when the 'd' parameter is not set *} -{default_translation_domain domain='fo.default'} +{default_translation_domain domain='fo.custom'} {* -- Define some stuff for Smarty ------------------------------------------ *} {config_load file='variables.conf'} @@ -73,6 +73,9 @@ GNU General Public License : http://www.gnu.org/licenses/ {stylesheets file='assets/dist/css/thelia.min.css'} {/stylesheets} + {stylesheets file='assets/dist/css/custom.min.css'} + + {/stylesheets} {* If you want to generate the CSS assets on the fly, just replace the stylesheet inclusion above by the following. Then, in your back-office, go to Configuration -> System Variables and set process_assets to 1.