Commit 1bc8243f by Van

feat(all): Done

parent 1b8d7fcc
Elloha Extension Elloha Extension
====================== ======================
#### Requires installation of the translation extension bolt before installing this extension:
animal/translate
## Installation ## Installation
### Composer.json edition ### Composer.json edition
...@@ -33,4 +29,50 @@ Go to the base dir with terminal and run this following lines : ...@@ -33,4 +29,50 @@ Go to the base dir with terminal and run this following lines :
``` ```
cd extensions cd extensions
composer require appolo/elloha composer require appolo/elloha
``` ```
\ No newline at end of file
## Configuration
### Config File
When using for the first time this extension, you must remplace the IdBookingEngine by the provided by Elloha
````
IdBookEngine : "b68c6848-b7d9-4083-91f0-3de6f065280e" ==> It's the key test you must replace
````
Fill in the content types on which you want to search for the reservations
````
searchable_contenttypes:
- HCO
- HLO
- HOT
````
They're in resa_types the types of housing defined by Elloha. You can remove those you don't want.
````
resa_types:
Hotel:
label: Hôtels
BedAndBreakfast:
label: Chambres d'hôtes
ApartmentVillaOrCottage:
label: Appartements, villas et gîtes
TouristHome:
label: Résidence de tourisme
GroupAccommodation:
label: Hébergements Collectifs
Camping:
label: Campings
HolidayVillage:
label: Villages vacances
Stay:
label: Séjours
````
### Twig Filter
To view the form reservation in your template you can use this twig filter
{{ resaForm('resaform')}}
![](docs/ResaForm.png)
\ No newline at end of file
IdBookEngine : "b68c6848-b7d9-4083-91f0-3de6f065280e"
searchable_contenttypes:
- HCO
- HLO
- HOT
resa_types:
Hotel:
label: Hôtels
BedAndBreakfast:
label: Chambres d'hôtes
ApartmentVillaOrCottage:
label: Appartements, villas et gîtes
TouristHome:
label: Résidence de tourisme
GroupAccommodation:
label: Hébergements Collectifs
Camping:
label: Campings
HolidayVillage:
label: Villages vacances
Stay:
label: Séjours
\ No newline at end of file
<?php
namespace Bolt\Extension\Appolo\Elloha\Controller\Frontend;
use Bolt\Controller\Base;
use Bolt\Extension\Appolo\Elloha\Request\EllohaRequest;
use Silex\ControllerCollection;
class SearchController extends Base
{
/**
* @var array
*/
private $config;
/**
* @var EllohaRequest
*/
private $ellohaRequest;
/** @var string */
private $prefix;
/**
* NewsletterController constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->config = $config;
$this->ellohaRequest = new EllohaRequest($this->config);
}
/**
* @param ControllerCollection $c
*/
protected function addRoutes(ControllerCollection $c)
{
$c->match('/', 'search')->bind('elloha_search');
}
/**
* @return \Bolt\Response\TemplateResponse|\Bolt\Response\TemplateView
*/
public function search()
{
$formData = $this->app['request']->request->get('resa');
$this->_saveBaseFormData();
try {
$this->ellohaRequest
->setData($formData)
->launchApiSearch()
;
} finally {
$idsProduct = $this->ellohaRequest->getIdProducts();
$results = $this->_searchReservableContents($idsProduct);
return $this->render('@elloha/Frontend/search/results.html.twig', [
'records' => [
'hotels' => $results['hotels'],
'locatifs_meubles' => $results['locatifs-meubles'],
'hebergements_collectifs' => $results['hebergements-collectifs']
]
]);
}
}
/**
* @param array $idsProduct
* @return array
* Search foreach contenttypes records where idsProduct is in the array
*/
private function _searchReservableContents(array $idsProduct = [])
{
$results = [];
$ids = implode(' || ', $idsProduct);
foreach ($this->_getSearchableContents() as $content) {
$records = $this->app['storage']->getContent($content['slug'], ['resacode' => $ids, 'returnsingle' => false]);
$results[$content['slug']]= $records;
}
return $results;
}
/**
* @return array
* Get the define searchable table
*/
protected function _getSearchableTables(){
$tables = [];
foreach($this->config['searchable_contenttypes'] as $contenttype) {
$tables[] = $this->getContenttypeTablename($contenttype);
}
return $tables;
}
/**
* @return array
* Get contents searchable
*/
protected function _getSearchableContents(){
$contents = [];
foreach($this->config['searchable_contenttypes'] as $contenttype) {
$contents[] = $this->getContenttype($contenttype);
}
return $contents;
}
/**
* Save in session data form
*/
protected function _saveBaseFormData()
{
$formData = $this->app['request']->request->get('resa');
$this->session()->set('searchData', $formData);
}
/**
* Get the table name with prefix from a given $name.
*
* @param string $name
*
* @return string
*
* @Internal
*/
protected function getTablename($name)
{
if ($this->prefix === null) {
$this->prefix = $this->app['config']->get('general/database/prefix', 'bolt_');
}
$name = str_replace('-', '_', $this->app['slugify']->slugify($name));
return sprintf('%s%s', $this->prefix, $name);
}
/**
* Get the tablename with prefix from a given Contenttype.
*
* @param string|array $contenttype
*
* @return string
*
* @Internal
*/
protected function getContenttypeTablename($contenttype)
{
if (is_string($contenttype)) {
$contenttype = $this->getContentType($contenttype);
}
return $this->getTablename($contenttype['tablename']);
}
}
\ No newline at end of file
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
namespace Bolt\Extension\Appolo\Elloha; namespace Bolt\Extension\Appolo\Elloha;
use Bolt\Asset\File\JavaScript;
use Bolt\Asset\File\Stylesheet;
use Bolt\Controller\Zone;
use Bolt\Extension\Appolo\Elloha\Form\ResaType;
use Bolt\Extension\SimpleExtension; use Bolt\Extension\SimpleExtension;
/** /**
...@@ -10,4 +16,109 @@ use Bolt\Extension\SimpleExtension; ...@@ -10,4 +16,109 @@ use Bolt\Extension\SimpleExtension;
*/ */
class EllohaExtension extends SimpleExtension class EllohaExtension extends SimpleExtension
{ {
/**
* @var
*/
protected $config;
/**
* @return array|\Bolt\Asset\AssetInterface[]
*/
protected function registerAssets()
{
return [
Stylesheet::create()
->setFileName('extensions/vendor/appolo/elloha/css/elloha.css')
->setZone(Zone::FRONTEND),
Stylesheet::create()
->setFileName('//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css')
->setZone(Zone::FRONTEND),
Stylesheet::create()
->setFileName('https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.6/chosen.min.css')
->setZone(Zone::FRONTEND),
JavaScript::create()
->setFileName('https://code.jquery.com/jquery-1.12.4.js')
->setZone(Zone::FRONTEND)
->setPriority(1)
->setLate(true),
JavaScript::create()
->setFileName('https://code.jquery.com/ui/1.12.1/jquery-ui.js')
->setZone(Zone::FRONTEND)
->setPriority(2)
->setLate(true),
JavaScript::create()
->setFileName('https://cdnjs.cloudflare.com/ajax/libs/chosen/1.8.6/chosen.jquery.min.js')
->setZone(Zone::FRONTEND)
->setPriority(3)
->setLate(true),
JavaScript::create()
->setFileName('extensions/vendor/appolo/elloha/js/elloha.js')
->setZone(Zone::FRONTEND)
->setPriority(4)
->setLate(true),
];
}
/**
* Register Controller
* {@inheritdoc}
*/
protected function registerFrontendControllers()
{
return [
'/resa/search' => new Controller\Frontend\SearchController($this->getConfig()),
];
}
/**
* Register twig paths for application
*/
protected function registerTwigPaths()
{
return [
'templates' => ['namespace' => 'elloha']
];
}
/**
* {@inheritdoc}
*/
protected function registerTwigFunctions()
{
return [
'resaForm' => ['resaForm', ['is_safe' => ['html']]],
];
}
/**
* @return string
*/
public function resaForm()
{
$session = $this->getContainer()['session'];
$formData = ($session->has('searchData')) ? $session->get('searchData') : [];
$form = $this->getContainer()['form.factory']->createBuilder(ResaType::class, $formData, [
'resaTypes' => $this->_getResaTypesForOptions(),
])->getForm();
return $this->renderTemplate('@elloha/Frontend/generateForm.html.twig', [
'form' => $form->createView()
]);
}
/**
* @return array
*/
protected function _getResaTypesForOptions()
{
$types = [];
foreach ($this->getConfig()['resa_types'] as $key => $value) {
$types[$key] = (isset($value['label'])) ? $value['label'] : 'Non défini';
}
return $types;
}
} }
<?php
namespace Bolt\Extension\Appolo\Elloha\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ResaType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'Type',
ChoiceType::class,
[
'label' => 'Type de logement',
'required' => false,
'attr' => [
'class' => 'input-resa'
],
'label_attr' => [
'class' => 'main control-label col-xs-12'
],
'choices' => array_merge([null => 'Tous les hébergements'], $options['resaTypes'])
])
->add(
'StartDate',
TextType::class,
[
'label' => 'Date d\'arrivée',
'required' => false,
'attr' => [
'class' => 'input-resa',
'autocomplete' =>"off"
],
'label_attr' => [
'class' => 'main control-label col-xs-12'
]
])
->add(
'EndDate',
TextType::class,
[
'label' => 'Date de départ',
'required' => false,
'attr' => [
'class' => 'input-resa',
'autocomplete' =>"off"
],
'label_attr' => [
'class' => 'main control-label col-xs-12'
]
])
->add(
'AdultNumber',
IntegerType::class,
[
'label' => 'Nombre de personnes',
'required' => false,
'attr' => [
'class' => 'input-resa',
'min' => 1,
],
'label_attr' => [
'class' => 'main control-label col-xs-12'
],
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'resaTypes' => null
]);
}
}
\ No newline at end of file
<?php
namespace Bolt\Extension\Appolo\Elloha\Request;
use GuzzleHttp\Client;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class EllohaRequest
{
use GetterSetterTrait;
/**
* @var Client
*/
private $client;
/**
* @var array
*/
private $config;
/** @var int */
private $key;
/**
* @var array
*/
private $dataResults = [];
/**
* Request constructor.
* @param array $config
*/
public function __construct(array $config = [])
{
$this->client = new Client([
'base_uri' => 'https://api.elloha.com/',
'timeout' => 60,
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'culture' => 'fr-FR'
]
]);
$this->config = $config;
$this->key = (!empty($this->config['IdBookEngine'])) ? $this->config['IdBookEngine'] : 0;
}
/**
* @param array $data
* @return $this
*/
public function setData(array $data = [])
{
$this
->setType($data['Type'])
->setStartDate(($data['StartDate']))
->setEndDate($data['EndDate'])
->setAdultNumber($data['AdultNumber'])
;
return $this;
}
/**
* @return array
*/
public function getData() {
$data = [];
if($this->getType()) {
$data['Type'] = $this->getType();
}
if($this->getStartDate()) {
$data['StartDate'] = $this->getStartDate();
}
if($this->getEndDate()) {
$data['EndDate'] = $this->getEndDate();
}
if($this->getAdultNumber()) {
$data['AdultNumber'] = $this->getAdultNumber();
}
return $data;
}
/**
* Call to the API with form data
*/
public function launchApiSearch()
{
try {
$response = $this->client->post('Availabilities', [
'body' => \GuzzleHttp\json_encode(
array_merge(
[
'IdBookingEngine' => $this->key
],
$this->getData()
)
)
]);
$result = \GuzzleHttp\json_decode($response->getBody()->getContents());
$this->dataResults = json_decode(json_encode($result), TRUE);
}
catch(NotFoundHttpException $e)
{
$this->dataResults = [];
throw new $e;
}
}
/**
* @return array
* Tableau d'Ids de Produits correspondant à la recherche
*/
public function getIdProducts()
{
$idsProducts = [];
foreach ($this->dataResults as $item){
$idProduct = $item['IdProduct'];
array_push($idsProducts, $idProduct);
}
return $idsProducts;
}
}
\ No newline at end of file
<?php
namespace Bolt\Extension\Appolo\Elloha\Request;
trait GetterSetterTrait
{
/**
* @var string
*/
private $type;
/**
* @var integer
*/
private $startDate;
/**
* @var integer
*/
private $endDate;
/**
* @var integer
*/
private $adultNumber;
/**
* @var integer
*/
private $key;
public function setStartDate($startDate)
{
$this->startDate = $startDate;
return $this;
}
/**
* @return int
*/
public function getStartDate()
{
return $this->startDate;
}
/**
* @param int $endDate
* @return GetterSetterTrait
*/
public function setEndDate($endDate)
{
$this->endDate = $endDate;
return $this;
}
/**
* @return int
*/
public function getEndDate()
{
return $this->endDate;
}
/**
* @param int $key
* @return self
*/
public function setKey($key)
{
$this->key = $key;
return $this;
}
/**
* @return int
*/
public function getKey()
{
return $this->key;
}
/**
* @param string $type
* @return GetterSetterTrait
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* @return string
*/
public function getType()
{
return $this->type;
}
/**
* @param int $adultNumber
* @return GetterSetterTrait
*/
public function setAdultNumber($adultNumber)
{
$this->adultNumber = $adultNumber;
return $this;
}
/**
* @return int
*/
public function getAdultNumber()
{
return $this->adultNumber;
}
}
\ No newline at end of file
<?php
namespace Bolt\Extension\Appolo\Elloha\Storage;
use Silex\Application;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class QueryBuilder
{
/**
* @var Application
*/
private $app;
/**
* QueryBuilder constructor.
* @param Application $application
*/
public function __construct(Application $application)
{
$this->app = $application;
}
/**
* @param $contentType
* @param array $fields
* @return false|mixed
*/
public function getQueriesResults($contentType, array $fields = [])
{
if(empty($contentType)) {
throw new NotFoundHttpException('');
}
$records = [];
$storage = $this->app['storage'];
$query = $this->_buildQuery($contentType, $fields);
dump($query);
if(!empty($query))
{
$rows = $this->app['db']->fetchAll($query);
$ids = array_map(function($rows) {
return $rows['id'];
}, $rows);
$contents = $storage->getContent($contentType, ['id' => implode(' || ', $ids), 'paging' => true, 'limit' => 99999]);
if(is_array($contents)) {
$records = array_merge($records, $contents);
} else {
$records = array_merge($records, [$contents]);
}
}
return $records;
}
/**
* @param array $fields
* @return string
*/
protected function _buildQuery($contentType, array $fields = [])
{
if(!$contentType) {
return '';
}
$storage = $this->app['storage'];
$tableName = $storage->getContenttypeTablename($contentType);
if(!$tableName) {
return '';
}
$query = 'SELECT id FROM '.$tableName;
$where = $this->_buildWhere($fields);
if($where) {
$query .= $where;
}
return $query;
}
/**
* @param array $fields
* @return bool
*/
protected function _buildWhere(array $fields = [])
{
$where = ' WHERE';
foreach ($fields as $field => $value) {
if (!empty($value)) {
if(is_array($value)) {
foreach ($value as $item) {
$where .= " AND $field LIKE '%$item%'";
}
} else {
$where .= " $field LIKE '%$value%'";
}
}
}
return $where;
}
}
\ No newline at end of file
{% use "bootstrap_3_layout.html.twig" %}
{{ form_start(form, {'action' : path('elloha_search'), 'method': 'POST'}) }}
<div class="notification is-primary">
<div class="row">
<div class="col-3" style="text-align: center">
{{ form_label(form.Type) }}
<div class="col-xs-12">
{{ form_widget(form.Type) }}
{{ form_errors(form.Type) }}
</div>
</div>
</div>
<div class="row">
<div class="col-12" style="text-align: center">
{{ form_label(form.StartDate) }}
<div class="col-xs-12">
{{ form_widget(form.StartDate) }}
{{ form_errors(form.StartDate) }}
</div>
</div>
</div>
<div class="row">
<div class="col-12" style="text-align: center">
{{ form_label(form.EndDate) }}
<div class="col-xs-12">
{{ form_widget(form.EndDate) }}
{{ form_errors(form.EndDate) }}
</div>
</div>
</div>
<div class="row">
<div class="col-12" style="text-align: center">
{{ form_label(form.AdultNumber) }}
<div class="col-xs-12">
{{ form_widget(form.AdultNumber) }}
{{ form_errors(form.AdultNumber) }}
</div>
</div>
</div>
<div id="form-builder" class="row"></div>
<div class="row">
<div class="col-12" style="text-align: center">
<button type="submit" class="button btn-primary"> Rechercher </button>
</div>
</div>
</div>
{{ form_end(form) }}
\ No newline at end of file
.input-resa{
width: 50%;
height: 2em;
text-align: center;
margin-bottom: 1em;
margin-top: 1em;
font-size: 1em;
}
\ No newline at end of file
$(document).ready(function () {
// DatePicker
$('#resa_StartDate').datepicker({ dateFormat: 'yy-mm-dd' });
$('#resa_EndDate').datepicker();
setAdultNumber();
$(".choosen").chosen();
/**
* Set min adultNumber at 1
*/
function setAdultNumber() {
let adultNumber = $('#resa_AdultNumber').val();
if(adultNumber < 1) {
$('#resa_AdultNumber').val(1);
}
}
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment