diff --git a/src/Bundle/ChillMain/.gitignore b/src/Bundle/ChillMain/.gitignore new file mode 100644 index 000000000..eda88ae1f --- /dev/null +++ b/src/Bundle/ChillMain/.gitignore @@ -0,0 +1,34 @@ +# MacOS +.DS_Store + +# Bootstrap +app/bootstrap* + +# Symfony directories +vendor/* +*/logs/* +*/cache/* +web/uploads/* +web/bundles/* + +# Configuration files +app/config/parameters.ini +app/config/parameters.yml +Tests/Fixtures/App/config/parameters.yml + +#composer +composer.lock +#sass-cache +Resources/assets/gumpy/.sass-cache +Resources/public/stylesheets/sass +*~ + +Resources/.sass-cache/ +Resources/bower_components/ +Resources/node_modules/ + +/nbproject/private/ + +# Yarn lock +Resources/package-lock.json +Resources/yarn.lock diff --git a/src/Bundle/ChillMain/.gitlab-ci.yml b/src/Bundle/ChillMain/.gitlab-ci.yml new file mode 100644 index 000000000..4f26b8f29 --- /dev/null +++ b/src/Bundle/ChillMain/.gitlab-ci.yml @@ -0,0 +1,65 @@ +.test_definition: &test_definition + services: + - chill/database:latest + before_script: + - composer config github-oauth.github.com $GITHUB_TOKEN + - composer install + - cp Resources/test/Fixtures/App/app/config/parameters.gitlab-ci.yml Resources/test/Fixtures/App/app/config/parameters.yml + - php Resources/test/Fixtures/App/app/console --env=test cache:warmup + - php Resources/test/Fixtures/App/app/console doctrine:migrations:migrate --env=test --no-interaction + - php Resources/test/Fixtures/App/app/console doctrine:fixtures:load --env=test --no-interaction + + +stages: + - deploy + - test + - build-doc + - deploy-doc + +test:php-7.2: + stage: test + <<: *test_definition + image: chill/ci-image:php-7.2 + script: APP_ENV=test vendor/bin/phpunit + +# deploy documentation +api-doc-build: + stage: build-doc + environment: api-doc + image: chill/ci-image:php-7.2 + before_script: + - mkdir api-doc + script: apigen generate --destination api-doc/$CI_BUILD_REF_NAME/$CI_PROJECT_NAME + artifacts: + paths: + - "api-doc/" + name: api + expire_in: '2h' + only: + - master + - tags + +api-doc-deploy: + stage: deploy-doc + image: pallet/swiftclient:latest + before_script: + # test that CONTAINER_API variable is set + - if [ -z ${CONTAINER_API+x} ]; then echo "Please set CONTAINER_API variable"; exit -1; fi + # go to api-doc to have and url with PROJECT/BUILD + - cd api-doc + # upload, and keep files during 1 year + script: "swift upload --header \"X-Delete-After: 31536000\" $CONTAINER_API $CI_BUILD_REF_NAME/$CI_PROJECT_NAME" + only: + - master + - tags + +deploy-packagist: + stage: deploy + image: chill/ci-image:php-7.2 + before_script: + # test that PACKAGIST USERNAME and PACKAGIST_TOKEN variable are set + - if [ -z ${PACKAGIST_USERNAME+x} ]; then echo "Please set PACKAGIST_USERNAME variable"; exit -1; fi + - if [ -z ${PACKAGIST_TOKEN+x} ]; then echo "Please set PACKAGIST_TOKEN variable"; exit -1; fi + script: + - STATUSCODE=$(curl -XPOST -H'content-type:application/json' "https://packagist.org/api/update-package?username=$PACKAGIST_USERNAME&apiToken=$PACKAGIST_TOKEN" -d"{\"repository\":{\"url\":\"$CI_PROJECT_URL.git\"}}" --silent --output /dev/stderr --write-out "%{http_code}") + - if [ $STATUSCODE = "202" ]; then exit 0; else exit $STATUSCODE; fi diff --git a/src/Bundle/ChillMain/CHANGELOG.md b/src/Bundle/ChillMain/CHANGELOG.md new file mode 100644 index 000000000..34cff4fa9 --- /dev/null +++ b/src/Bundle/ChillMain/CHANGELOG.md @@ -0,0 +1,157 @@ + + +Version 1.5.1 +============= + +- add email to users on fixtures ; +- spare ressource with recursive trigger on inserting user ; +- fix error when no flags are used during edit and creation of permission group ; + +Version 1.5.2 +============= + +- allow to filters users shown by `UserPickerType` based on flags. This flags do an additional filter based on the flags assigned in permissions groups; +- add a method to filters users by permissions groups flags in `UserRepository` + +Version 1.5.3 +============= + +- fix error when interval is hour only + +Version 1.5.4 +============= + +- layout of page "list exports" +- create function "SIMILARITY" (see [posgtgresql documentation](https://www.postgresql.org/docs/9.6/static/pgtrgm.html)) +- create function "OVERLAPSI", which will detect period of date overlapping, replacing NULL date by infinity or -infinity (see [postgresql page for date time function and operators](https://www.postgresql.org/docs/9.6/static/functions-datetime.html)) +- add repository for Center class + +Version 1.5.5 +============= + +- add margin of 0.5rem beyond buttons ; +- add a spreadsheet formatter (format xlsx, ods, csv) for lists +- add possibility to generate DirectExport: exports without formatters, filters and aggregators ; +- add api for grouping centers ; +- select centers as grouped on step "pick centers" in exports ; + +Version 1.5.6 +============= + +- fix long url in report download. The exports parameters are now stored in redis. +- add an option to allow address to be empty if street or postcode is not set. Used for embedding address in another form, when address is not required. + +Version 1.5.7 +============= + +- insert the title of the export inside the "download" page ; +- add twig helper "print_or_message" ; +- add twig helper for routing ; +- add css layout for boxes ; +- collect supplementary query parameters in SearchController ; + +Version 1.5.8 +============= + +- allow to remove interval units from DateInterval + +Version 1.5.9 +============= + +- add optionnal impersonate feature (if firewall option switch_user is true) ; + +Version 1.5.10 +============== + +- allow to group export in UI + +Version 1.5.11 +============== + +- improve return path functions and filters ; + +Version 1.5.12 +============== + +- make the redirection to admin temporarily: some admin experienced cache problems (403 error) when they switched from one admin account to a non-admin one ; + +Version 1.5.13 +============== + +- allow to customize logo on login screen and main layout ; +- remove desert background image on page, handle it from cache in login screen; + +Version 1.5.14 +============== + +- fix errors in pagination +- fix search: usage of parenthesis +- add DQL function REPLACE for replacing in strings: "REPLACE(string, 'from', 'to')" +- add function to format phonenumber +- improve `chill_print_or_message` to support date time; +- add a module `show_hide` for javascript; +- load assets using functions ; +- load a `runtime.js` assets for objects shared by webpack ; + +Version 1.5.15 +============== + +- create an api for rendering entities +- css: render the placeholder in expanded choice item as italic (the "no specified" choice") +- css: add an extra space around choices expanded widget +- add Tabs parametric feature to easily render tabs panels +- css: add a margin on the button "delete entry" in collection +- module `show_hide`: add the possibility to launch a show hide manually and not on page loading. Useful when show/hide occurs in collection. +- module `show_hide`: add events to module +- [phonenumber validation] allow to validate against mobile **or** landline/voip phonenumbers; +- [phonenumber validation & format] format and validation does not make the app fail when network is not available; + +Version 1.5.16 +============== + +- [translation] in french, replace "Modifier" by "Enregistrer" in the edit form +- [entity render] do not throw an exception when null element are passed to `chill_entity_render_box` and `chill_entity_render_string` + +Version 1.5.17 +============== + +- [chill entity render] fix error when fallback to default entity render (usage of `__toString()`) +- [CRUD] add step delete +- [CRUD] check that action exists before inserting them in edit and view template +- [CRUD] fix error when no crud are created + +Version 1.5.18 +============== + +- [webpack] add namespace for import sass ; +- [activity] move activity.scss to own bundle ; + +Version 1.5.19 +============== + +- [address] add a "homeless" characteristic to addresses ; + +Version 1.5.20 +============== + +- [CRUD] make index query more abstract. + + Improve build and count query in default index action to be customized + in one dedicated method. + +Version 1.5.21 +============== + +- [Export list] improve alignment of last line +- [CRUD] Forward query parameters when pushing button "save and new" in "create" page; +- [Show/hide] Take selects input into account; + +Version 1.5.23 +============== + +- [address] allow to add custom fields to addresses + +Version 1.5.24 +============== + +- [bugfix] add missing migration files diff --git a/src/Bundle/ChillMain/CRUD/Controller/CRUDController.php b/src/Bundle/ChillMain/CRUD/Controller/CRUDController.php new file mode 100644 index 000000000..cbd4d55fd --- /dev/null +++ b/src/Bundle/ChillMain/CRUD/Controller/CRUDController.php @@ -0,0 +1,1060 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Pagination\PaginatorFactory; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Chill\MainBundle\CRUD\Resolver\Resolver; +use Chill\MainBundle\Pagination\PaginatorInterface; +use Chill\MainBundle\CRUD\Form\CRUDDeleteEntityForm; + +/** + * + * + */ +class CRUDController extends AbstractController +{ + /** + * The crud configuration + * + * This configuration si defined by `chill_main['crud']`. + * + * @var array + */ + protected $crudConfig; + + public function setCrudConfig(array $config) + { + $this->crudConfig = $config; + } + + public function delete(Request $request, $id) + { + return $this->deleteAction('delete', $request, $id); + } + + protected function deleteAction(string $action, Request $request, $id, $formClass = null) + { + $this->onPreDelete($action, $request, $id); + + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found"), $this->getCrudName(), $id); + } + + $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { + return $response; + } + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPreRemove($action, $entity, $form, $request); + $this->removeEntity($action, $entity, $form, $request); + $this->onPostRemove($action, $entity, $form, $request); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + protected function onPreDelete(string $action, Request $request) {} + + protected function onPreRemove(string $action, $entity, FormInterface $form, Request $request) {} + + protected function onPostRemove(string $action, $entity, FormInterface $form, Request $request) {} + + protected function removeEntity(string $action, $entity, FormInterface $form, Request $request) + { + $this->getDoctrine() + ->getManager() + ->remove($entity); + } + + /** + * Base method called by index action. + * + * @param Request $request + * @return type + */ + public function index(Request $request) + { + return $this->indexEntityAction('index', $request); + } + + /** + * Build an index page. + * + * Some steps may be overriden during this process of rendering. + * + * This method: + * + * 1. Launch `onPreIndex` + * x. check acl. If it does return a response instance, return it + * x. launch `onPostCheckACL`. If it does return a response instance, return it + * 1. count the items, using `countEntities` + * 2. build a paginator element from the the number of entities ; + * 3. Launch `onPreIndexQuery`. If it does return a response instance, return it + * 3. build a query, using `queryEntities` + * x. fetch the results, using `getQueryResult` + * x. Launch `onPostIndexFetchQuery`. If it does return a response instance, return it + * 4. create default parameters: + * + * The default parameters are: + * + * * entities: the list en entities ; + * * crud_name: the name of the crud ; + * * paginator: a paginator element ; + * 5. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * + * @param string $action + * @param Request $request + * @return type + */ + protected function indexEntityAction($action, Request $request) + { + $this->onPreIndex($action, $request); + + $response = $this->checkACL($action, null); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { + return $response; + } + + $totalItems = $this->countEntities($action, $request); + $paginator = $this->getPaginatorFactory()->create($totalItems); + + $response = $this->onPreIndexBuildQuery($action, $request, $totalItems, + $paginator); + + if ($response instanceof Response) { + return $response; + } + + $query = $this->queryEntities($action, $request, $paginator); + + $response = $this->onPostIndexBuildQuery($action, $request, $totalItems, + $paginator, $query); + + if ($response instanceof Response) { + return $response; + } + + $entities = $this->getQueryResult($action, $request, $totalItems, $paginator, $query); + + $response = $this->onPostIndexFetchQuery($action, $request, $totalItems, + $paginator, $entities); + + if ($response instanceof Response) { + return $response; + } + + $defaultTemplateParameters = [ + 'entities' => $entities, + 'crud_name' => $this->getCrudName(), + 'paginator' => $paginator + ]; + + return $this->render( + $this->getTemplateFor($action, $entities, $request), + $this->generateTemplateParameter($action, $entities, $request, $defaultTemplateParameters) + ); + } + + /** + * + * @param string $action + * @param Request $request + */ + protected function onPreIndex(string $action, Request $request) { } + + /** + * method used by indexAction + * + * @param string $action + * @param Request $request + * @param int $totalItems + * @param PaginatorInterface $paginator + */ + protected function onPreIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator) { } + + /** + * method used by indexAction + * + * @param string $action + * @param Request $request + * @param int $totalItems + * @param PaginatorInterface $paginator + * @param mixed $query + */ + protected function onPostIndexBuildQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) { } + + /** + * method used by indexAction + * + * @param string $action + * @param Request $request + * @param int $totalItems + * @param PaginatorInterface $paginator + * @param mixed $entities + */ + protected function onPostIndexFetchQuery(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $entities) { } + + /** + * Build the base query for listing all entities, normally use in a listing + * page. + * + * This base query does not contains any `WHERE` or `SELECT` clauses. Those + * are added by other methods, like `queryEntities` and `countQueries`. + * + * @param string $action + * @param Request $request + * @return QueryBuilder + */ + protected function buildQueryEntities(string $action, Request $request) + { + return $this->getDoctrine()->getManager() + ->createQueryBuilder() + ->select('e') + ->from($this->getEntityClass(), 'e') + ; + } + + /** + * Query the entity. + * + * By default, get all entities. + * + * The method `orderEntity` is called internally to order entities. + * + * It returns, by default, a query builder. + * + * @param string $action + * @param Request $request + * @param PaginatorInterface $paginator + * @return type + */ + protected function queryEntities(string $action, Request $request, PaginatorInterface $paginator) + { + $query = $this->buildQueryEntities($action, $request) + ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber()) + ->setMaxResults($paginator->getItemsPerPage()); + + // allow to order queries and return the new query + return $this->orderQuery($action, $query, $request, $paginator); + } + + + /** + * Add ordering fields in the query build by self::queryEntities + * + * @param string $action + * @param QueryBuilder|mixed $query by default, an instance of QueryBuilder + * @param Request $request + * @param PaginatorInterface $paginator + * @return QueryBuilder|mixed + */ + protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator) + { + return $query; + } + + /** + * Get the result of the query + * + * @param string $action + * @param Request $request + * @param int $totalItems + * @param PaginatorInterface $paginator + * @param mixed $query + * @return mixed + */ + protected function getQueryResult(string $action, Request $request, int $totalItems, PaginatorInterface $paginator, $query) + { + return $query->getQuery()->getResult(); + } + + /** + * Count the number of entities + * + * @param string $action + * @param Request $request + * @return int + */ + protected function countEntities(string $action, Request $request): int + { + return $this->buildQueryEntities($action, $request) + ->select('COUNT(e)') + ->getQuery() + ->getSingleScalarResult() + ; + } + + /** + * BAse method for edit action + * + * IMplemented by the method formEditAction, with action as 'edit' + * + * @param Request $request + * @param mixed $id + * @return Response + */ + public function edit(Request $request, $id): Response + { + return $this->formEditAction('edit', $request, $id); + } + + /** + * Base method for new action + * + * Implemented by the method formNewAction, with action as 'new' + * + * @param Request $request + * @return Response + */ + public function new(Request $request): Response + { + return $this->formCreateAction('new', $request); + } + + /** + * Base method for the view action. + * + * Implemented by the method viewAction, with action as 'view' + * + * @param Request $request + * @param mixed $id + * @return Response + */ + public function view(Request $request, $id): Response + { + return $this->viewAction('view', $request, $id); + } + + /** + * The view action. + * + * Some steps may be overriden during this process of rendering: + * + * This method: + * + * 1. fetch the entity, using `getEntity` + * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, + * this response is returned. + * 2. throw an HttpNotFoundException if entity is null + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. generate default template parameters: + * + * * `entity`: the fetched entity ; + * * `crud_name`: the crud name + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * + * @param string $action + * @param Request $request + * @param mixed $id + * @return Response + */ + protected function viewAction(string $action, Request $request, $id) + { + $entity = $this->getEntity($action, $id, $request); + + $postFetch = $this->onPostFetchEntity($action, $request, $entity); + + if ($postFetch instanceof Response) { + return $postFetch; + } + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found"), $this->getCrudName(), $id); + } + + $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { + return $response; + } + + $defaultTemplateParameters = [ + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + /** + * The edit action. + * + * Some steps may be overriden during this process of rendering: + * + * This method: + * + * 1. fetch the entity, using `getEntity` + * 2. launch `onPostFetchEntity`. If postfetch is an instance of Response, + * this response is returned. + * 2. throw an HttpNotFoundException if entity is null + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. generate a form using `createFormFor`, and handle request on this form; + * + * If the form is valid, the entity is stored and flushed, and a redirection + * is returned. + * + * In this case, those hooks are available: + * + * * onFormValid + * * onPreFlush + * * onPostFlush + * * onBeforeRedirectAfterSubmission. If this method return an instance of + * Response, this response is returned. + * + * 5. generate default template parameters: + * + * * `entity`: the fetched entity ; + * * `crud_name`: the crud name ; + * * `form`: the formview instance. + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * + * @param string $action + * @param Request $request + * @param mixed $id + * @param string $formClass + * @param array $formOptions + * @return Response + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException + */ + protected function formEditAction(string $action, Request $request, $id, string $formClass = null, array $formOptions = []): Response + { + $entity = $this->getEntity($action, $id, $request); + + if (NULL === $entity) { + throw $this->createNotFoundException(sprintf("The %s with id %s " + . "is not found"), $this->getCrudName(), $id); + } + + $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { + return $response; + } + + $form = $this->createFormFor($action, $entity, $formClass, $formOptions); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + /** + * The new (or creation) action. + * + * Some steps may be overriden during this process of rendering: + * + * This method: + * + * 1. Create or duplicate an entity: + * + * If the `duplicate` parameter is present, the entity is duplicated + * using the `duplicate` method. + * + * If not, the entity is created using the `create` method. + * 3. check ACL using `checkACL` ; + * 4. launch `onPostCheckACL`. If the result is an instance of Response, + * this response is returned ; + * 5. generate a form using `createFormFor`, and handle request on this form; + * + * If the form is valid, the entity is stored and flushed, and a redirection + * is returned. + * + * In this case, those hooks are available: + * + * * onFormValid + * * onPreFlush + * * onPostFlush + * * onBeforeRedirectAfterSubmission. If this method return an instance of + * Response, this response is returned. + * + * 5. generate default template parameters: + * + * * `entity`: the fetched entity ; + * * `crud_name`: the crud name ; + * * `form`: the formview instance. + * + * 6. Launch rendering, the parameter is fetch using `getTemplateFor` + * The parameters may be personnalized using `generateTemplateParameter`. + * + * @param string $action + * @param Request $request + * @param type $formClass + * @return Response + */ + protected function formCreateAction(string $action, Request $request, $formClass = null): Response + { + if ($request->query->has('duplicate')) { + $entity = $this->duplicateEntity($action, $request); + } else { + $entity = $this->createEntity($action, $request); + } + + $response = $this->checkACL($action, $entity); + if ($response instanceof Response) { + return $response; + } + + $response = $this->onPostCheckACL($action, $request, $entity); + if ($response instanceof Response) { + return $response; + } + + $form = $this->createFormFor($action, $entity, $formClass); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $this->onFormValid($entity, $form, $request); + $em = $this->getDoctrine()->getManager(); + + $this->onPrePersist($action, $entity, $form, $request); + $em->persist($entity); + $this->onPostPersist($action, $entity, $form, $request); + + $this->onPreFlush($action, $entity, $form, $request); + $em->flush(); + $this->onPostFlush($action, $entity, $form, $request); + $this->getPaginatorFactory(); + $this->addFlash('success', $this->generateFormSuccessMessage($action, $entity)); + + $result = $this->onBeforeRedirectAfterSubmission($action, $entity, $form, $request); + + if ($result instanceof Response) { + return $result; + } + + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', ['id' => $entity->getId()]); + + } elseif ($form->isSubmitted()) { + $this->addFlash('error', $this->generateFormErrorMessage($action, $form)); + } + + $defaultTemplateParameters = [ + 'form' => $form->createView(), + 'entity' => $entity, + 'crud_name' => $this->getCrudName() + ]; + + return $this->render( + $this->getTemplateFor($action, $entity, $request), + $this->generateTemplateParameter($action, $entity, $request, $defaultTemplateParameters) + ); + } + + /** + * get the instance of the entity with the given id + * + * @param string $id + * @return object + */ + protected function getEntity($action, $id, Request $request): ?object + { + return $this->getDoctrine() + ->getRepository($this->getEntityClass()) + ->find($id); + } + + /** + * Duplicate an entity + * + * @param string $action + * @param Request $request + * @return mixed + */ + protected function duplicateEntity(string $action, Request $request) + { + $id = $request->query->get('duplicate_id', 0); + $originalEntity = $this->getEntity($action, $id, $request); + + $this->getDoctrine()->getManager() + ->detach($originalEntity); + + return $originalEntity; + } + + /** + * + * @return string the complete fqdn of the class + */ + protected function getEntityClass(): string + { + return $this->crudConfig['class']; + } + + /** + * + * @return string the crud name + */ + protected function getCrudName(): string + { + return $this->crudConfig['name']; + } + + /** + * check the acl. Called by every action. + * + * By default, check the role given by `getRoleFor` for the value given in + * $entity. + * + * Throw an \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + * if not accessible. + * + * @param string $action + * @param mixed $entity + * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException + */ + protected function checkACL(string $action, $entity) + { + $this->denyAccessUnlessGranted($this->getRoleFor($action), $entity); + } + + /** + * get the role given from the config. + * + * @param string $action + * @return string + */ + protected function getRoleFor($action) + { + if (NULL !== ($this->getActionConfig($action)['role'])) { + return $this->getActionConfig($action)['role']; + } + + return $this->buildDefaultRole($action); + } + + /** + * build a default role name, using the crud resolver. + * + * This method should not be overriden. Override `getRoleFor` instead. + * + * @param string $action + * @return string + */ + protected function buildDefaultRole($action) + { + return $this->getCrudResolver()->buildDefaultRole($this->getCrudName(), + $action); + } + + /** + * get the default form class from config + * + * @param string $action + * @return string the FQDN of the form class + */ + protected function getFormClassFor($action) + { + if ($action === 'delete') { + return $this->crudConfig[$action]['form_class'] + ?? $this->getDefaultDeleteFormClass($action); + } + + return $this->crudConfig[$action]['form_class'] + ?? $this->crudConfig['form_class']; + } + + protected function getDefaultDeleteFormClass($action) + { + return CRUDDeleteEntityForm::class; + } + + /** + * Create a form + * + * use the method `getFormClassFor` + * + * A hook is available: `customizeForm` allow you to customize the form + * if needed. + * + * It is preferable to override customizeForm instead of overriding + * this method. + * + * @param string $action + * @param mixed $entity + * @param string $formClass + * @param array $formOptions + * @return FormInterface + */ + protected function createFormFor(string $action, $entity, string $formClass = null, array $formOptions = []): FormInterface + { + $formClass = $formClass ?? $this->getFormClassFor($action); + + $form = $this->createForm($formClass, $entity, $formOptions); + + $this->customizeForm($action, $form); + + return $form; + } + + /** + * Customize the form created by createFormFor. + * + * @param string $action + * @param FormInterface $form + */ + protected function customizeForm(string $action, FormInterface $form) + { + + } + + /** + * Generate a message which explains an error about the form. + * + * Used in form actions + * + * @param string $action + * @param FormInterface $form + * @return string + */ + protected function generateFormErrorMessage(string $action, FormInterface $form): string + { + $msg = 'This form contains errors'; + + return $this->getTranslator()->trans($msg); + } + + /** + * Generate a success message when a form could be flushed successfully + * + * @param string $action + * @param mixed $entity + * @return string + */ + protected function generateFormSuccessMessage($action, $entity): string + { + switch ($action) { + case 'edit': + $msg = "crud.edit.success"; + break; + case 'new': + $msg = "crud.new.success"; + break; + case 'delete': + $msg = "crud.delete.success"; + break; + default: + $msg = "crud.default.success"; + } + + return $this->getTranslator()->trans($msg); + } + + /** + * Customize template parameters. + * + * @param string $action + * @param mixed $entity + * @param Request $request + * @param array $defaultTemplateParameters + * @return array + */ + protected function generateTemplateParameter( + string $action, + $entity, + Request $request, + array $defaultTemplateParameters = [] + ) { + return $defaultTemplateParameters; + } + + /** + * Create an entity. + * + * @param string $action + * @param Request $request + * @return object + */ + protected function createEntity(string $action, Request $request): object + { + $type = $this->getEntityClass(); + + return new $type; + } + + /** + * Get the template for the current crud. + * + * This template may be defined in configuration. If any template are + * defined, return the default template for the actions new, edit, index, + * and view. + * + * @param string $action + * @param mixed $entity the entity for the current request, or an array of entities + * @param Request $request + * @return string the path to the template + * @throws \LogicException if no template are available + */ + protected function getTemplateFor($action, $entity, Request $request) + { + if ($this->hasCustomTemplate($action, $entity, $request)) { + return $this->getActionConfig($action)['template']; + } + + switch ($action) { + case 'new': + return '@ChillMain/CRUD/new.html.twig'; + case 'edit': + return '@ChillMain/CRUD/edit.html.twig'; + case 'index': + return '@ChillMain/CRUD/index.html.twig'; + case 'view': + return '@ChillMain/CRUD/view.html.twig'; + case 'delete': + return '@ChillMain/CRUD/delete.html.twig'; + default: + throw new \LogicException("the view for action $action is not " + . "defined. You should override ".__METHOD__." to add this " + . "action"); + } + } + + protected function hasCustomTemplate($action, $entity, Request $request): bool + { + return !empty($this->getActionConfig($action)['template']); + } + + protected function onPreFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostFlush(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPrePersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostPersist(string $action, $entity, FormInterface $form, Request $request) + { + } + + protected function onPostFetchEntity($action, Request $request, $entity): ?Response + { + return null; + } + + protected function onPostCheckACL($action, Request $request, $entity): ?Response + { + return null; + } + + protected function onFormValid(object $entity, FormInterface $form, Request $request) + { + } + + /** + * Return a redirect response depending on the value of submit button. + * + * The handled values are : + * + * * save-and-close: return to index of current crud ; + * * save-and-new: return to new page of current crud ; + * * save-and-view: return to view page of current crud ; + * + * @param string $action + * @param mixed $entity + * @param FormInterface $form + * @param Request $request + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + protected function onBeforeRedirectAfterSubmission(string $action, $entity, FormInterface $form, Request $request) + { + $next = $request->request->get("submit", "save-and-close"); + + switch ($next) { + case "save-and-close": + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_index'); + case "save-and-new": + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_new', $request->query->all()); + default: + return $this->redirectToRoute('chill_crud_'.$this->getCrudName().'_view', [ + 'id' => $entity->getId() + ]); + } + } + + protected function getActionConfig(string $action) + { + return $this->crudConfig['actions'][$action]; + } + + protected function getPaginatorFactory(): PaginatorFactory + { + return $this->get(PaginatorFactory::class); + } + + protected function getTranslator(): TranslatorInterface + { + return $this->container->get('translator'); + } + + protected function getAuthorizationHelper(): AuthorizationHelper + { + return $this->container->get(AuthorizationHelper::class); + } + + protected function getReachableCenters(Role $role, Scope $scope = null) + { + return $this->getAuthorizationHelper() + ->getReachableCenters($this->getUser(), $role, $scope) + ; + } + + protected function getEventDispatcher(): EventDispatcherInterface + { + return $this->get(EventDispatcherInterface::class); + } + + protected function getCrudResolver(): Resolver + { + return $this->get(Resolver::class); + } + + public static function getSubscribedServices() + { + return \array_merge( + parent::getSubscribedServices(), + [ + PaginatorFactory::class => PaginatorFactory::class, + 'translator' => TranslatorInterface::class, + AuthorizationHelper::class => AuthorizationHelper::class, + EventDispatcherInterface::class => EventDispatcherInterface::class, + Resolver::class => Resolver::class, + ] + ); + } +} diff --git a/src/Bundle/ChillMain/CRUD/Form/CRUDDeleteEntityForm.php b/src/Bundle/ChillMain/CRUD/Form/CRUDDeleteEntityForm.php new file mode 100644 index 000000000..609b56f58 --- /dev/null +++ b/src/Bundle/ChillMain/CRUD/Form/CRUDDeleteEntityForm.php @@ -0,0 +1,33 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\CRUD\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; + + +/** + * + * + */ +class CRUDDeleteEntityForm extends AbstractType +{ +} diff --git a/src/Bundle/ChillMain/CRUD/Resolver/Resolver.php b/src/Bundle/ChillMain/CRUD/Resolver/Resolver.php new file mode 100644 index 000000000..64aeb61bc --- /dev/null +++ b/src/Bundle/ChillMain/CRUD/Resolver/Resolver.php @@ -0,0 +1,102 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Resolver; + +use Doctrine\ORM\EntityManagerInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; + +/** + * + * + */ +class Resolver +{ + /** + * + * @var EntityManagerInterface + */ + protected $em; + + /** + * + * @var \Symfony\Component\PropertyAccess\PropertyAccessor + */ + protected $propertyAccess; + + /** + * + * @var array + */ + protected $crudConfig; + + /** + * @deprecated + */ + const ROLE_VIEW = 'role.view'; + + /** + * @deprecated + */ + const ROLE_EDIT = 'role.edit'; + + /** + * The key to get the role necessary for the action + */ + const ROLE = 'role'; + + function __construct(EntityManagerInterface $em, array $crudConfig) + { + $this->em = $em; + + foreach($crudConfig as $conf) { + $this->crudConfig[$conf['name']] = $conf; + } + } + + public function getConfigValue($key, $crudName, $action = null) + { + $config = $this->crudConfig[$crudName]; + + switch ($key) { + case self::ROLE: + return $config['actions'][$action]['role'] ?? $this->buildDefaultRole($crudName, $action); + } + } + + public function buildDefaultRole($crudName, $action) + { + if (empty($this->crudConfig[$crudName]['base_role'])) { + throw new \LogicException(sprintf("the base role is not defined. You must define " + . "on or override %s or %s methods", __METHOD__, "getRoleFor")); + } + + return \strtoupper( + $this->crudConfig[$crudName]['base_role']. + '_'. + $action); + } + + public function hasAction($crudName, $action) + { + return \array_key_exists($action, + $this->crudConfig[$crudName]['actions']); + } +} diff --git a/src/Bundle/ChillMain/CRUD/Routing/CRUDRoutesLoader.php b/src/Bundle/ChillMain/CRUD/Routing/CRUDRoutesLoader.php new file mode 100644 index 000000000..12f261d21 --- /dev/null +++ b/src/Bundle/ChillMain/CRUD/Routing/CRUDRoutesLoader.php @@ -0,0 +1,78 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Routing; + +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + + +/** + * Load the route for CRUD + * + */ +class CRUDRoutesLoader +{ + protected $config = []; + + public function __construct($config) + { + $this->config = $config; + } + + public function load() + { + $collection = new RouteCollection(); + + foreach ($this->config as $config) { + $collection->addCollection($this->loadConfig($config)); + } + + return $collection; + } + + protected function loadConfig($config): RouteCollection + { + $collection = new RouteCollection(); + foreach ($config['actions'] as $name => $action) { + $defaults = [ + '_controller' => 'cscrud_'.$config['name'].'_controller'.':'.($action['controller_action'] ?? $name) + ]; + + if ($name === 'index') { + $path = "{_locale}".$config['base_path']; + $route = new Route($path, $defaults); + } elseif ($name === 'new') { + $path = "{_locale}".$config['base_path'].'/'.$name; + $route = new Route($path, $defaults); + } else { + $path = "{_locale}".$config['base_path'].($action['path'] ?? '/{id}/'.$name); + $requirements = $action['requirements'] ?? [ + '{id}' => '\d+' + ]; + $route = new Route($path, $defaults, $requirements); + } + + $collection->add('chill_crud_'.$config['name'].'_'.$name, $route); + } + + return $collection; + } +} diff --git a/src/Bundle/ChillMain/CRUD/Templating/TwigCRUDResolver.php b/src/Bundle/ChillMain/CRUD/Templating/TwigCRUDResolver.php new file mode 100644 index 000000000..a593b08b0 --- /dev/null +++ b/src/Bundle/ChillMain/CRUD/Templating/TwigCRUDResolver.php @@ -0,0 +1,66 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\CRUD\Templating; + +use Chill\MainBundle\CRUD\Resolver\Resolver; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\Extension\AbstractExtension; +use Twig\Environment; + +/** + * Twig filters to display data in crud template + * + */ +class TwigCRUDResolver extends AbstractExtension +{ + /** + * + * @var Resolver + */ + protected $resolver; + + function __construct(Resolver $resolver) + { + $this->resolver = $resolver; + } + + public function getFunctions() + { + return [ + new TwigFunction('chill_crud_config', [$this, 'getConfig'], + ['is_safe' => 'html']), + new TwigFunction('chill_crud_action_exists', [$this, 'hasAction'], + []), + ]; + } + + public function getConfig($configKey, $crudName, $action = null) + { + return $this->resolver->getConfigValue($configKey, $crudName, $action); + } + + public function hasAction($crudName, $action) + { + return $this->resolver->hasAction($crudName, $action); + } + +} diff --git a/src/Bundle/ChillMain/Center/GroupingCenterInterface.php b/src/Bundle/ChillMain/Center/GroupingCenterInterface.php new file mode 100644 index 000000000..5f10d35c8 --- /dev/null +++ b/src/Bundle/ChillMain/Center/GroupingCenterInterface.php @@ -0,0 +1,27 @@ +addCompilerPass(new SearchableServicesCompilerPass()); + $container->addCompilerPass(new ConfigConsistencyCompilerPass()); + $container->addCompilerPass(new TimelineCompilerClass()); + $container->addCompilerPass(new RoleProvidersCompilerPass()); + $container->addCompilerPass(new ExportsCompilerPass()); + $container->addCompilerPass(new WidgetsCompilerPass()); + $container->addCompilerPass(new NotificationCounterCompilerPass()); + $container->addCompilerPass(new MenuCompilerPass()); + $container->addCompilerPass(new ACLFlagsCompilerPass()); + $container->addCompilerPass(new GroupingCenterCompilerPass()); + $container->addCompilerPass(new RenderEntityCompilerPass()); + } +} diff --git a/src/Bundle/ChillMain/Command/ChillImportUsersCommand.php b/src/Bundle/ChillMain/Command/ChillImportUsersCommand.php new file mode 100644 index 000000000..38da1daf4 --- /dev/null +++ b/src/Bundle/ChillMain/Command/ChillImportUsersCommand.php @@ -0,0 +1,448 @@ +em = $em; + $this->passwordEncoder = $passwordEncoder; + $this->validator = $validator; + $this->logger = $logger; + + + $this->userRepository = $em->getRepository(User::class); + + parent::__construct('chill:main:import-users'); + } + + + + protected function configure() + { + $this + ->setDescription('Import users from csv file') + ->setHelp("Import users from a csv file. Users are added to centers contained in the file. Headers are used to detect columns. Adding to multiple centers can be done by using a `grouping centers` file, which will group multiple centers into a signle alias, used in 'centers' column.") + ->addArgument('csvfile', InputArgument::REQUIRED, 'Path to the csv file. Columns are: `username`, `email`, `center` (can contain alias), `permission group`') + ->addOption('grouping-centers', null, InputOption::VALUE_OPTIONAL, 'Path to a csv file to aggregate multiple centers into a single alias') + ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Do not commit the changes') + ->addOption('csv-dump', null, InputOption::VALUE_REQUIRED, 'A path to dump a summary of the created file') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->tempOutput = $output; + $this->tempInput = $input; + + if ($input->getOption('dry-run')) { + $this->doChanges = false; + } + + $this->prepareWriter(); + + if ($input->hasOption('grouping-centers')) { + $this->prepareGroupingCenters(); + } + + try { + $this->loadUsers(); + } + catch(\Exception $e) { + throw $e; + } + } + + protected function prepareWriter() + { + $this->output = $output = Writer::createFromPath($this->tempInput + ->getOption('csv-dump'), 'a+'); + + $output->insertOne([ + 'email', + 'username', + 'id' + ]); + } + + protected function appendUserToFile(User $user) + { + $this->output->insertOne( [ + $user->getEmail(), + $user->getUsername(), + $user->getId() + ]); + } + + protected function loadUsers() + { + $reader = Reader::createFromPath($this->tempInput->getArgument('csvfile')); + $reader->setHeaderOffset(0); + + foreach ($reader->getRecords() as $line => $r) { + $this->logger->debug("starting handling new line", [ + 'line' => $line + ]); + + if ($this->doesUserExists($r)) { + $this->tempOutput->writeln(sprintf("User with username '%s' already " + . "exists, skipping", $r["username"])); + + $this->logger->info("One user already exists, skipping creation", [ + 'username_in_file' => $r['username'], + 'email_in_file' => $r['email'], + 'line' => $line + ]); + + continue; + } + + $user = $this->createUser($line, $r); + $this->appendUserToFile($user); + } + } + + protected function doesUserExists($data) + { + if ($this->userRepository->countByUsernameOrEmail($data['username']) > 0) { + return true; + } + + if ($this->userRepository->countByUsernameOrEmail($data['email']) > 0) { + return true; + } + + return false; + } + + protected function createUser($offset, $data) + { + $user = new User(); + $user + ->setEmail(\trim($data['email'])) + ->setUsername(\trim($data['username'])) + ->setEnabled(true) + ->setPassword($this->passwordEncoder->encodePassword($user, + \bin2hex(\random_bytes(32)))) + ; + + $errors = $this->validator->validate($user); + + if ($errors->count() > 0) { + $errorMessages = $this->concatenateViolations($errors); + + $this->tempOutput->writeln(sprintf("%d errors found with user with username \"%s\" at line %d", $errors->count(), $data['username'], $offset)); + $this->tempOutput->writeln($errorMessages); + + throw new \RuntimeException("Found errors while creating an user. " + . "Watch messages in command output"); + } + + $pgs = $this->getPermissionGroup($data['permission group']); + $centers = $this->getCenters($data['center']); + + foreach($pgs as $pg) { + foreach ($centers as $center) { + $groupcenter = $this->createOrGetGroupCenter($center, $pg); + + if (FALSE === $user->getGroupCenters()->contains($groupcenter)) { + $user->addGroupCenter($groupcenter); + } + } + } + + + if ($this->doChanges) { + $this->em->persist($user); + $this->em->flush(); + } + + $this->logger->notice("Create user", [ + 'username' => $user->getUsername(), + 'id' => $user->getId(), + 'nb_of_groupCenters' => $user->getGroupCenters()->count() + ]); + + return $user; + } + + protected function getPermissionGroup($alias) + { + if (\array_key_exists($alias, $this->permissionGroups)) { + return $this->permissionGroups[$alias]; + } + + $permissionGroupsByName = []; + + foreach($this->em->getRepository(PermissionsGroup::class) + ->findAll() as $permissionGroup) { + $permissionGroupsByName[$permissionGroup->getName()] = $permissionGroup; + } + + if (count($permissionGroupsByName) === 0) { + throw new \RuntimeException("no permission groups found. Create them " + . "before importing users"); + } + + $question = new ChoiceQuestion("To which permission groups associate with \"$alias\" ?", + \array_keys($permissionGroupsByName)); + $question + ->setMultiselect(true) + ->setAutocompleterValues(\array_keys($permissionGroupsByName)) + ->setNormalizer(function($value) { + if (NULL === $value) { return ''; } + + return \trim($value); + }) + ; + $helper = $this->getHelper('question'); + + $keys = $helper->ask($this->tempInput, $this->tempOutput, $question); + + $this->tempOutput->writeln("You have chosen ".\implode(", ", $keys)); + + if ($helper->ask($this->tempInput, $this->tempOutput, + new ConfirmationQuestion("Are you sure ?", true))) { + + foreach ($keys as $key) { + $this->permissionGroups[$alias][] = $permissionGroupsByName[$key]; + } + + return $this->permissionGroups[$alias]; + } else { + $this->logger->error("Error while responding to a a question"); + + $this->tempOutput("Ok, I accept, but I do not know what to do. Please try again."); + + throw new \RuntimeException("Error while responding to a question"); + } + } + + /** + * + * @param Center $center + * @param \Chill\MainBundle\Command\PermissionGroup $pg + * @return GroupCenter + */ + protected function createOrGetGroupCenter(Center $center, PermissionsGroup $pg): GroupCenter + { + if (\array_key_exists($center->getId(), $this->groupCenters)) { + if (\array_key_exists($pg->getId(), $this->groupCenters[$center->getId()])) { + return $this->groupCenters[$center->getId()][$pg->getId()]; + } + } + + $repository = $this->em->getRepository(GroupCenter::class); + + $groupCenter = $repository->findOneBy(array( + 'center' => $center, + 'permissionsGroup' => $pg + )); + + if ($groupCenter === NULL) { + $groupCenter = new GroupCenter(); + $groupCenter + ->setCenter($center) + ->setPermissionsGroup($pg) + ; + + $this->em->persist($groupCenter); + } + + $this->groupCenters[$center->getId()][$pg->getId()] = $groupCenter; + + return $groupCenter; + } + + protected function prepareGroupingCenters() + { + $reader = Reader::createFromPath($this->tempInput->getOption('grouping-centers')); + $reader->setHeaderOffset(0); + + foreach ($reader->getRecords() as $r) { + $this->centers[$r['alias']] = + \array_merge( + $this->centers[$r['alias']] ?? [], + $this->getCenters($r['center'] + ) + ); + } + } + + /** + * return a list of centers matching the name of alias. + * + * If the name match one center, this center is returned in an array. + * + * If the name match an alias, the centers corresponding to the alias are + * returned in an array. + * + * If the center is not found or alias is not created, a new center is created + * and suggested to user + * + * @param string $name the name of the center or the alias regrouping center + * @return Center[] + */ + protected function getCenters($name) + { + // sanitize + $name = \trim($name); + + if (\array_key_exists($name, $this->centers)) { + return $this->centers[$name]; + } + + // search for a center with given name + $center = $this->em->getRepository(Center::class) + ->findOneByName($name); + + if ($center instanceof Center) { + $this->centers[$name] = [$center]; + + return $this->centers[$name]; + } + + // suggest and create + $center = (new Center()) + ->setName($name); + + $this->tempOutput->writeln("Center with name \"$name\" not found."); + $qFormatter = $this->getHelper('question'); + $question = new ConfirmationQuestion("Create a center with name \"$name\" ?", true); + + if ($qFormatter->ask($this->tempInput, $this->tempOutput, $question)) { + $this->centers[$name] = [ $center ]; + + $errors = $this->validator->validate($center); + + if ($errors->count() > 0) { + $errorMessages = $this->concatenateViolations($errors); + + $this->tempOutput->writeln(sprintf("%d errors found with center with name \"%s\"", $errors->count(), $name)); + $this->tempOutput->writeln($errorMessages); + + throw new \RuntimeException("Found errors while creating one center. " + . "Watch messages in command output"); + } + + $this->em->persist($center); + + return $this->centers[$name]; + } + + return null; + } + + protected function concatenateViolations(ConstraintViolationListInterface $list) + { + $str = []; + + foreach ($list as $e) { + /* @var $e \Symfony\Component\Validator\ConstraintViolationInterface */ + $str[] = $e->getMessage(); + } + + return \implode(";", $str); + } + +} diff --git a/src/Bundle/ChillMain/Command/ChillUserSendRenewPasswordCodeCommand.php b/src/Bundle/ChillMain/Command/ChillUserSendRenewPasswordCodeCommand.php new file mode 100644 index 000000000..69089ecf4 --- /dev/null +++ b/src/Bundle/ChillMain/Command/ChillUserSendRenewPasswordCodeCommand.php @@ -0,0 +1,198 @@ +logger = $logger; + $this->em = $em; + $this->recoverPasswordHelper = $recoverPasswordHelper; + $this->eventDispatcher = $eventDispatcher; + + parent::__construct(); + } + + + + protected function configure() + { + $this + ->setName('chill:user:send-password-recover-code') + ->setDescription('Send a message with code to recover password') + ->addArgument('csvfile', InputArgument::REQUIRED, 'CSV file with the list of users') + ->addOption('template', null, InputOption::VALUE_REQUIRED, 'Template for email') + ->addOption('expiration', null, InputOption::VALUE_REQUIRED, 'Expiration of the link, as an unix timestamp') + ->addOption('subject', null, InputOption::VALUE_REQUIRED, 'Subject of the email', 'Recover your password') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + + $reader = $this->getReader(); + + foreach($reader->getRecords() as $offset => $r) { + $user = $this->getUser($r); + + if ($user === null) { + $this->onUserNotFound($r, $offset); + continue; + } + + $this->sendRecoverCode($user); + } + } + + protected function sendRecoverCode(User $user) + { + if (empty($user->getEmail())) { + $this->logger->alert("User without email", [ + 'user_id' => $user->getId(), + 'username' => $user->getUsername() + ]); + + return; + } + + $template = $this->input->getOption('template'); + $expiration = \DateTime::createFromFormat('U', + $this->input->getOption('expiration')); + + $this->recoverPasswordHelper + ->sendRecoverEmail( + $user, + $expiration, + $template, + [ 'expiration' => $expiration], + false, + [ '_locale' => 'fr' ], + $this->input->getOption('subject') + ); + } + + protected function onUserNotFound($row, $offset) + { + $this->logger->alert('User not found', \array_merge([ + 'offset' => $offset + ], $row)); + } + + protected function getUser($row) + { + /* @var $userRepository \Chill\MainBundle\Repository\UserRepository */ + $userRepository = $this->em->getRepository(User::class); + + try { + if (\array_key_exists('email', $row)) { + return $userRepository->findOneByUsernameOrEmail(\trim($row['email'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + // continue, we will try username + } + + try { + if (\array_key_exists('username', $row)) { + return $userRepository->findOneByUsernameOrEmail(\trim($row['username'])); + } + } catch (\Doctrine\ORM\NoResultException $e) { + return null; + } + } + + /** + * + * @return Reader + * @throws \Exception + */ + protected function getReader() + { + try { + $reader = Reader::createFromPath($this->input->getArgument('csvfile')); + } catch (\Exception $e) { + $this->logger->error("The csv file could not be read", [ + 'path' => $this->input->getArgument('csvfile') + ]); + + throw $e; + } + + $reader->setHeaderOffset(0); + + $headers = $reader->getHeader(); + + if (FALSE === \in_array('username', $headers) + && FALSE === \in_array('email', $headers)) { + throw new \InvalidArgumentException("The csv file does not have an " + . "username or email header"); + } + + return $reader; + } + +} diff --git a/src/Bundle/ChillMain/Command/LoadAndUpdateLanguagesCommand.php b/src/Bundle/ChillMain/Command/LoadAndUpdateLanguagesCommand.php new file mode 100644 index 000000000..aa01085da --- /dev/null +++ b/src/Bundle/ChillMain/Command/LoadAndUpdateLanguagesCommand.php @@ -0,0 +1,124 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/* + * Load or update the languages entities command + */ +class LoadAndUpdateLanguagesCommand extends ContainerAwareCommand +{ + // The regional version of language are language with _ in the code + // This array contains regional code to not exclude + private $regionalVersionToInclude = ["ro_MD"]; + + // Array of ancien languages (to exclude) + private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga", + "dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"]; + + const INCLUDE_REGIONAL_VERSION = 'include_regional'; + const INCLUDE_ANCIENT = 'include_ancient'; + + /* + * (non-PHPdoc) + * @see \Symfony\Component\Console\Command\Command::configure() + */ + protected function configure() + { + $this + ->setName('chill:main:languages:populate') + ->setDescription('Load or update languages in db. This command does not delete existing '. + 'languages, but will update names according to available languages') + ->addOption( + self::INCLUDE_REGIONAL_VERSION, + null, + InputOption::VALUE_NONE, + 'Include the regional languages. The regional languages are languages with code containing _ excepted ' + . implode(',', $this->regionalVersionToInclude) . '.') + ->addOption( + self::INCLUDE_ANCIENT, + null, + InputOption::VALUE_NONE, + 'Include the ancient languages that are languages with code ' + . implode(', ', $this->ancientToExclude) . '.') + ; + } + + /* + * (non-PHPdoc) + * @see \Symfony\Component\Console\Command\Command::execute() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $chillAvailableLanguages = $this->getContainer()->getParameter('chill_main.available_languages'); + $languageBundle = Intl::getLanguageBundle(); + $languages = array(); + + foreach ($chillAvailableLanguages as $avLang) { + $languages[$avLang] = $languageBundle->getLanguageNames($avLang); + } + + $languageCodes = array_keys($languages[$chillAvailableLanguages[0]]); + + foreach ($languageCodes as $code) { + $excludeCode = ( + ( + ! $input->getOption(self::INCLUDE_REGIONAL_VERSION) + and strpos($code, '_') + and !in_array($code, $this->regionalVersionToInclude) + ) or ( + ! $input->getOption(self::INCLUDE_ANCIENT) + and in_array($code, $this->ancientToExclude) + ) + ); + + $langageDB = $em->getRepository('ChillMainBundle:Language')->find($code); + + if(! $excludeCode) { + if (! $langageDB) { + $langageDB = new \Chill\MainBundle\Entity\Language(); + $langageDB->setId($code); + $em->persist($langageDB); + } + + $avLangNames = array(); + + foreach ($chillAvailableLanguages as $avLang) { + $avLangNames[$avLang] = $languages[$avLang][$code]; + } + + $langageDB->setName($avLangNames); + } else { + if($langageDB) { + $em->remove($langageDB); + } + echo "Code excluded : ".$code." - ".$languageBundle->getLanguageName($code)."\n"; + } + } + + $em->flush(); + } +} diff --git a/src/Bundle/ChillMain/Command/LoadCountriesCommand.php b/src/Bundle/ChillMain/Command/LoadCountriesCommand.php new file mode 100644 index 000000000..2062c5667 --- /dev/null +++ b/src/Bundle/ChillMain/Command/LoadCountriesCommand.php @@ -0,0 +1,75 @@ +setName('chill:main:countries:populate') + ->setDescription('Load or update countries in db. This command does not delete existing countries, '. + 'but will update names according to available languages'); + } + + /* + * (non-PHPdoc) + * @see \Symfony\Component\Console\Command\Command::execute() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $countries = static::prepareCountryList($this->getContainer()->getParameter('chill_main.available_languages')); + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + + foreach($countries as $country) { + $countryStored = $em->getRepository('ChillMainBundle:Country') + ->findOneBy(array('countryCode' => $country->getCountryCode())); + + if (NULL === $countryStored) { + $em->persist($country); + } else { + $countryStored->setName($country->getName()); + } + } + + $em->flush(); + } + + public static function prepareCountryList($languages) + { + $regionBundle = Intl::getRegionBundle(); + + foreach ($languages as $language) { + $countries[$language] = $regionBundle->getCountryNames($language); + } + + $countryEntities = array(); + + foreach ($countries[$languages[0]] as $countryCode => $name) { + $names = array(); + + foreach ($languages as $language) { + $names[$language] = $countries[$language][$countryCode]; + } + + $country = new \Chill\MainBundle\Entity\Country(); + $country->setName($names)->setCountryCode($countryCode); + $countryEntities[] = $country; + } + + return $countryEntities; + } +} diff --git a/src/Bundle/ChillMain/Command/LoadPostalCodesCommand.php b/src/Bundle/ChillMain/Command/LoadPostalCodesCommand.php new file mode 100644 index 000000000..2fa96d32e --- /dev/null +++ b/src/Bundle/ChillMain/Command/LoadPostalCodesCommand.php @@ -0,0 +1,199 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Filesystem\Filesystem; +use Chill\MainBundle\Entity\PostalCode; + +/** + * + * + * @author Julien Fastré + */ +class LoadPostalCodesCommand extends ContainerAwareCommand +{ + protected function configure() + { + $this->setName('chill:main:postal-code:populate') + ->setDescription("Add the postal code from a csv file.") + ->setHelp("This script will try to avoid existing postal code " + . "using the postal code and name. \n" + . "The CSV file must have the following columns: " + . "postal code, label, country code." + . "The CSV file should not have any header row.") + ->addArgument('csv_file', InputArgument::REQUIRED, "the path to " + . "the csv file. See the help for specifications.") + ->addOption( + 'delimiter', + 'd', + InputOption::VALUE_OPTIONAL, + "The delimiter character of the csv file", + ",") + ->addOption( + 'enclosure', + null, + InputOption::VALUE_OPTIONAL, + "The enclosure character of the csv file", + '"' + ) + ->addOption( + 'escape', + null, + InputOption::VALUE_OPTIONAL, + "The escape character of the csv file", + "\\" + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + try { + $csv = $this->getCSVResource($input); + } catch (\RuntimeException $e) { + $output->writeln('Error during opening the csv file : '. + $e->getMessage().''); + } + + if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERY_VERBOSE) { + $output->writeln('The content of the file is ...'); + $output->write(file_get_contents($input->getArgument('csv_file'))); + } + + $num = 0; + $line = 0; + + while (($row = fgetcsv( + $csv, + 0, + $input->getOption('delimiter'), + $input->getOption('enclosure'), + $input->getOption('escape'))) !== false) { + + try{ + $this->addPostalCode($row, $output); + $num++; + } catch (ExistingPostalCodeException $ex) { + $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + } catch (CountryCodeNotFoundException $ex) { + $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + } catch (PostalCodeNotValidException $ex) { + $output->writeln(' on line '.$line.' : '.$ex->getMessage().''); + } + $line ++; + } + + $this->getContainer()->get('doctrine.orm.entity_manager')->flush(); + + $output->writeln(''.$num.' were added !'); + } + + private function getCSVResource(InputInterface $input) + { + $fs = new Filesystem(); + $filename = $input->getArgument('csv_file'); + + if (!$fs->exists($filename)) { + throw new \RuntimeException("The file does not exists or you do not " + . "have the right to read it."); + } + + $resource = fopen($filename, 'r'); + + if ($resource == FALSE) { + throw new \RuntimeException("The file '$filename' could not be opened."); + } + + return $resource; + } + + private function addPostalCode($row, OutputInterface $output) + { + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln('handling row: '. $row[0].' | '. $row[1].' | '. $row[2]); + } + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + $country = $em + ->getRepository('ChillMainBundle:Country') + ->findOneBy(array('countryCode' => $row[2])); + + if ($country === NULL) { + throw new CountryCodeNotFoundException(sprintf("The country with code %s is not found. Aborting to insert postal code with %s - %s", + $row[2], $row[0], $row[1])); + } + + // try to find an existing postal code + $existingPC = $em + ->getRepository('ChillMainBundle:PostalCode') + ->findBy(array('code' => $row[0], 'name' => $row[1])); + + if (count($existingPC) > 0) { + throw new ExistingPostalCodeException(sprintf("A postal code with code : %s and name : %s already exists, skipping", + $row[0], $row[1])); + } + + $postalCode = (new PostalCode()) + ->setCode($row[0]) + ->setName($row[1]) + ->setCountry($country) + ; + + $errors = $this->getContainer()->get('validator')->validate($postalCode); + + if ($errors->count() == 0) { + $em->persist($postalCode); + } else { + $msg = ""; + foreach ($errors as $error) { + $msg .= " ".$error->getMessage(); + } + + throw new PostalCodeNotValidException($msg); + } + + + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { + $output->writeln(sprintf('Creating postal code with code: %s, name: %s, countryCode: %s', + $postalCode->getCode(), $postalCode->getName(), $postalCode->getCountry()->getCountryCode())); + } + } +} + + +class ExistingPostalCodeException extends \Exception +{ + +} + +class CountryCodeNotFoundException extends \Exception +{ + +} + +class PostalCodeNotValidException extends \Exception +{ + +} diff --git a/src/Bundle/ChillMain/Command/SetPasswordCommand.php b/src/Bundle/ChillMain/Command/SetPasswordCommand.php new file mode 100644 index 000000000..c6741b524 --- /dev/null +++ b/src/Bundle/ChillMain/Command/SetPasswordCommand.php @@ -0,0 +1,80 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Command; + +use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Chill\MainBundle\Entity\User; + +/** + * Description of SetPasswordCommand + * + * @author Julien Fastré + */ +class SetPasswordCommand extends ContainerAwareCommand +{ + public function configure() + { + $this->setName('chill:user:set_password') + ->setDescription('set a password to user') + ->addArgument('username', InputArgument::REQUIRED, 'the user\'s ' + . 'username you want to change password') + ->addArgument('password', InputArgument::OPTIONAL, 'the new password') + ; + } + + public function execute(InputInterface $input, OutputInterface $output) + { + $user = $this->_getUser($input->getArgument('username')); + + if ($user === NULL) { + throw new \LogicException("The user with username '". + $input->getArgument('username')."' is not found"); + } + + $password = $input->getArgument('password'); + if ($password === NULL) { + $dialog = $this->getHelperSet()->get('dialog'); + $password = $dialog->askHiddenResponse($output, "the new password :" + . ""); + } + + $this->_setPassword($user, $password); + } + + public function _getUser($username) + { + $em = $this->getContainer()->get('doctrine.orm.entity_manager'); + return $em->getRepository('ChillMainBundle:User') + ->findOneBy(array('username' => $username)); + } + + public function _setPassword(User $user, $password) + { + $encoder = $this->getContainer()->get('security.encoder_factory') + ->getEncoder($user); + $user->setPassword($encoder->encodePassword($password, $user->getSalt())); + $this->getContainer()->get('doctrine.orm.entity_manager') + ->flush($user); + } +} diff --git a/src/Bundle/ChillMain/Controller/AdminController.php b/src/Bundle/ChillMain/Controller/AdminController.php new file mode 100644 index 000000000..9852be7ab --- /dev/null +++ b/src/Bundle/ChillMain/Controller/AdminController.php @@ -0,0 +1,52 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; + +/** + * + * + * @author julien.fastre@champs-libres.coop + * @author marc@champs-libres.coop + */ +class AdminController extends Controller { + + public function indexAction($menu = 'admin', + $header_title = 'views.Main.admin.index.header_title', + $page_title = 'views.Main.admin.index.page_title') { + return $this->render('ChillMainBundle:Admin:layout.html.twig'); + } + + public function indexPermissionsAction() + { + return $this->render('ChillMainBundle:Admin:layout_permissions.html.twig'); + } + + public function configurationWarningsAction() + { + $alertManager = $this->get('chill_main.configuration_alert_manager'); + + + } + +} diff --git a/src/Bundle/ChillMain/Controller/AdminCountryCRUDController.php b/src/Bundle/ChillMain/Controller/AdminCountryCRUDController.php new file mode 100644 index 000000000..310a36c60 --- /dev/null +++ b/src/Bundle/ChillMain/Controller/AdminCountryCRUDController.php @@ -0,0 +1,20 @@ +paginatorFactory = $paginator; + } +} diff --git a/src/Bundle/ChillMain/Controller/CenterController.php b/src/Bundle/ChillMain/Controller/CenterController.php new file mode 100644 index 000000000..9fabb50aa --- /dev/null +++ b/src/Bundle/ChillMain/Controller/CenterController.php @@ -0,0 +1,178 @@ +getDoctrine()->getManager(); + + $entities = $em->getRepository('ChillMainBundle:Center')->findAll(); + + return $this->render('ChillMainBundle:Center:index.html.twig', array( + 'entities' => $entities, + )); + } + /** + * Creates a new Center entity. + * + */ + public function createAction(Request $request) + { + $center = new Center(); + $form = $this->createCreateForm($center); + $form->handleRequest($request); + + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($center); + $em->flush(); + + return $this->redirect($this->generateUrl('admin_center_show', array('id' => $center->getId()))); + } + + return $this->render('ChillMainBundle:Center:new.html.twig', array( + 'entity' => $center, + 'form' => $form->createView(), + )); + } + + /** + * Creates a form to create a Center entity. + * + * @param Center $center The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(Center $center) + { + $form = $this->createForm(CenterType::class, $center, array( + 'action' => $this->generateUrl('admin_center_create'), + 'method' => 'POST', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Create')); + + return $form; + } + + /** + * Displays a form to create a new Center entity. + * + */ + public function newAction() + { + $center = new Center(); + $form = $this->createCreateForm($center); + + return $this->render('ChillMainBundle:Center:new.html.twig', array( + 'entity' => $center, + 'form' => $form->createView(), + )); + } + + /** + * Finds and displays a Center entity. + * + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $center = $em->getRepository('ChillMainBundle:Center')->find($id); + + if (!$center) { + throw $this->createNotFoundException('Unable to find Center entity.'); + } + + return $this->render('ChillMainBundle:Center:show.html.twig', array( + 'entity' => $center + )); + } + + /** + * Displays a form to edit an existing Center entity. + * + */ + public function editAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $center = $em->getRepository('ChillMainBundle:Center')->find($id); + + if (!$center) { + throw $this->createNotFoundException('Unable to find Center entity.'); + } + + $editForm = $this->createEditForm($center); + return $this->render('ChillMainBundle:Center:edit.html.twig', array( + 'entity' => $center, + 'edit_form' => $editForm->createView() + )); + } + + /** + * Creates a form to edit a Center entity. + * + * @param Center $center The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(Center $center) + { + $form = $this->createForm(CenterType::class, $center, array( + 'action' => $this->generateUrl('admin_center_update', array('id' => $center->getId())), + 'method' => 'PUT', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Update')); + + return $form; + } + /** + * Edits an existing Center entity. + * + */ + public function updateAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $center = $em->getRepository('ChillMainBundle:Center')->find($id); + + if (!$center) { + throw $this->createNotFoundException('Unable to find Center entity.'); + } + + $editForm = $this->createEditForm($center); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + $em->flush(); + + return $this->redirect($this->generateUrl('admin_center_edit', array('id' => $id))); + } + + return $this->render('ChillMainBundle:Center:edit.html.twig', array( + 'entity' => $center, + 'edit_form' => $editForm->createView() + )); + } +} diff --git a/src/Bundle/ChillMain/Controller/DefaultController.php b/src/Bundle/ChillMain/Controller/DefaultController.php new file mode 100644 index 000000000..5909f5f1c --- /dev/null +++ b/src/Bundle/ChillMain/Controller/DefaultController.php @@ -0,0 +1,81 @@ +isGranted('ROLE_ADMIN')) { + + return $this->redirectToRoute('chill_main_admin_central', [], 302); + } + + return $this->render('ChillMainBundle::layout.html.twig'); + } + + public function indexWithoutLocaleAction() + { + return $this->redirect($this->generateUrl('chill_main_homepage')); + } + + public function testAction() + { + return $this->render('ChillMainBundle:Tabs:index.html.twig', [ + 'tabs' => [ + 'test1' => [ + [ + 'name' => "Link 1", + 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + ], + [ + 'name' => "Link 2", + 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + ], + [ + 'name' => "Link 3", + 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + ], + [ + 'name' => "Link 4", + 'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", + ], + [ + 'name' => "Link 5", + 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", + ] + ], + 'test2' => [ + [ + 'name' => "Link 1", + 'link' => "http://localhost", + 'content' => "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vitae auctor eu augue ut. Elementum nisi quis eleifend quam. Faucibus purus in massa tempor nec. Turpis massa sed elementum tempus egestas sed sed risus. Etiam sit amet nisl purus in mollis nunc sed id. Enim nunc faucibus a pellentesque sit amet porttitor eget. Risus nec feugiat in fermentum posuere. Augue mauris augue neque gravida. Sollicitudin aliquam ultrices sagittis orci a scelerisque purus semper eget. Id leo in vitae turpis massa sed elementum tempus egestas. Mauris commodo quis imperdiet massa. Fames ac turpis egestas integer eget aliquet nibh praesent. Urna porttitor rhoncus dolor purus non enim praesent elementum. Donec enim diam vulputate ut pharetra sit. Auctor neque vitae tempus quam. Mattis rhoncus urna neque viverra justo nec ultrices.", + ], + [ + 'name' => "Link 2", + //'link' => "http://localhost", + 'content' => "Dui sapien eget mi proin sed libero. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Turpis nunc eget lorem dolor. Phasellus egestas tellus rutrum tellus. Diam sit amet nisl suscipit adipiscing bibendum est ultricies integer. Duis ultricies lacus sed turpis tincidunt id. Nisl suscipit adipiscing bibendum est ultricies integer. Elementum nibh tellus molestie nunc non blandit massa enim. Faucibus in ornare quam viverra orci sagittis eu. Neque volutpat ac tincidunt vitae semper quis lectus nulla. Accumsan sit amet nulla facilisi morbi. Leo vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Amet est placerat in egestas erat imperdiet sed euismod. Quis auctor elit sed vulputate mi. Mauris nunc congue nisi vitae suscipit tellus mauris a diam. At volutpat diam ut venenatis. Facilisis gravida neque convallis a cras semper.", + ], + [ + 'name' => "Link 3", + //'link' => "http://localhost", + 'content' => "In ornare quam viverra orci sagittis eu volutpat. Ac tincidunt vitae semper quis lectus nulla at volutpat. Placerat duis ultricies lacus sed turpis tincidunt. Augue interdum velit euismod in pellentesque. Felis eget nunc lobortis mattis aliquam. Volutpat lacus laoreet non curabitur gravida arcu. Gravida cum sociis natoque penatibus et magnis dis parturient montes. Nisl pretium fusce id velit ut tortor. Nunc scelerisque viverra mauris in aliquam sem fringilla ut. Magna eget est lorem ipsum dolor sit. Non consectetur a erat nam at lectus urna. Eget est lorem ipsum dolor sit amet consectetur adipiscing elit. Sed velit dignissim sodales ut.", + ], + [ + 'name' => "Link 4", + 'link' => "http://localhost", + //'content' => "Ut tellus elementum sagittis vitae et. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Hendrerit gravida rutrum quisque non tellus orci ac auctor augue. Eleifend quam adipiscing vitae proin sagittis nisl rhoncus mattis rhoncus. Dictumst quisque sagittis purus sit. Suspendisse sed nisi lacus sed viverra. Pretium quam vulputate dignissim suspendisse in est ante. Id eu nisl nunc mi ipsum. Ut venenatis tellus in metus vulputate. Ut morbi tincidunt augue interdum velit euismod.", + ], + [ + 'name' => "Link 5", + //'link' => "http://localhost", + 'content' => "Vel elit scelerisque mauris pellentesque pulvinar. Ornare suspendisse sed nisi lacus sed viverra tellus. Massa tincidunt dui ut ornare lectus sit. Congue nisi vitae suscipit tellus mauris a diam. At auctor urna nunc id cursus metus aliquam. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Mattis aliquam faucibus purus in massa tempor nec feugiat. Et leo duis ut diam quam. Auctor augue mauris augue neque. Purus ut faucibus pulvinar elementum integer enim neque volutpat. Scelerisque felis imperdiet proin fermentum leo. Diam sit amet nisl suscipit adipiscing bibendum est ultricies. Consectetur libero id faucibus nisl tincidunt. Vel fringilla est ullamcorper eget nulla facilisi. Pharetra diam sit amet nisl suscipit adipiscing. Dignissim diam quis enim lobortis. Auctor eu augue ut lectus arcu bibendum at varius.", + ] + ] + ] + ]); + } + +} diff --git a/src/Bundle/ChillMain/Controller/ExportController.php b/src/Bundle/ChillMain/Controller/ExportController.php new file mode 100644 index 000000000..ec8d9ee7a --- /dev/null +++ b/src/Bundle/ChillMain/Controller/ExportController.php @@ -0,0 +1,548 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\Form\Type\Export\ExportType; +use Chill\MainBundle\Form\Type\Export\FormatterType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Chill\MainBundle\Form\Type\Export\PickCenterType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Chill\MainBundle\Export\ExportManager; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Form\FormFactoryInterface; +use Chill\MainBundle\Redis\ChillRedis; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * ExportController is the controller use for exporting data. + * + * + */ +class ExportController extends Controller +{ + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var LoggerInterface + */ + protected $logger; + + /** + * + * @var SessionInterface + */ + protected $session; + + /** + * + * @var FormFactoryInterface + */ + protected $formFactory; + + /** + * + * @var ChillRedis + */ + protected $redis; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + public function __construct( + ChillRedis $chillRedis, + ExportManager $exportManager, + FormFactoryInterface $formFactory, + LoggerInterface $logger, + SessionInterface $session, + TranslatorInterface $translator + ) { + $this->redis = $chillRedis; + $this->exportManager = $exportManager; + $this->formFactory = $formFactory; + $this->logger = $logger; + $this->session = $session; + $this->translator = $translator; + } + + + /** + * Render the list of available exports + * + * @param Request $request + * @return \Symfony\Component\HttpFoundation\Response + */ + public function indexAction(Request $request) + { + $exportManager = $this->exportManager; + + $exports = $exportManager->getExportsGrouped(true); + + return $this->render('ChillMainBundle:Export:layout.html.twig', array( + 'grouped_exports' => $exports + )); + } + + /** + * handle the step to build a query for an export + * + * This action has three steps : + * + * 1.'export', the export form. When the form is posted, the data is stored + * in the session (if valid), and then a redirection is done to next step. + * 2. 'formatter', the formatter form. When the form is posted, the data is + * stored in the session (if valid), and then a redirection is done to next step. + * 3. 'generate': gather data from session from the previous steps, and + * make a redirection to the "generate" action with data in query (HTTP GET) + * + * @param string $request + * @param Request $alias + * @return \Symfony\Component\HttpFoundation\Response + */ + public function newAction(Request $request, $alias) + { + // first check for ACL + $exportManager = $this->exportManager; + $export = $exportManager->getExport($alias); + + if ($exportManager->isGrantedForElement($export) === FALSE) { + throw $this->createAccessDeniedException('The user does not have access to this export'); + } + + $step = $request->query->getAlpha('step', 'centers'); + + switch ($step) { + case 'centers': + return $this->selectCentersStep($request, $export, $alias); + case 'export': + return $this->exportFormStep($request, $export, $alias); + break; + case 'formatter': + return $this->formatterFormStep($request, $export, $alias); + break; + case 'generate': + return $this->forwardToGenerate($request, $export, $alias); + break; + default: + throw $this->createNotFoundException("The given step '$step' is invalid"); + } + } + + /** + * + * @param Request $request + * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param string $alias + * @return Response + * @throws type + */ + protected function selectCentersStep(Request $request, $export, $alias) + { + /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + $exportManager = $this->exportManager; + + $form = $this->createCreateFormExport($alias, 'centers'); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + if ($form->isValid()) { + $this->logger->debug('form centers is valid', array( + 'location' => __METHOD__)); + + $data = $form->getData(); + + // check ACL + if ($exportManager->isGrantedForElement($export, NULL, + $exportManager->getPickedCenters($data['centers'])) === FALSE) { + throw $this->createAccessDeniedException('you do not have ' + . 'access to this export for those centers'); + } + + $this->session->set('centers_step_raw', + $request->request->all()); + $this->session->set('centers_step', $data); + + return $this->redirectToRoute('chill_main_export_new', array( + 'step' => $this->getNextStep('centers', $export), + 'alias' => $alias + )); + + } + } + + return $this->render('ChillMainBundle:Export:new_centers_step.html.twig', + array( + 'form' => $form->createView(), + 'export' => $export + )); + } + + /** + * Render the export form + * + * When the method is POST, the form is stored if valid, and a redirection + * is done to next step. + * + * @param string $alias + * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function exportFormStep(Request $request, $export, $alias) + { + $exportManager = $this->exportManager; + + // check we have data from the previous step (export step) + $data = $this->session->get('centers_step', null); + + if ($data === null) { + + return $this->redirectToRoute('chill_main_export_new', array( + 'step' => $this->getNextStep('export', $export, true), + 'alias' => $alias + )); + } + + $export = $exportManager->getExport($alias); + + $form = $this->createCreateFormExport($alias, 'export', $data); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + if ($form->isValid()) { + + $this->logger->debug('form export is valid', array( + 'location' => __METHOD__)); + + // store data for reusing in next steps + $data = $form->getData(); + $this->session->set('export_step_raw', + $request->request->all()); + $this->session->set('export_step', $data); + + //redirect to next step + return $this->redirect( + $this->generateUrl('chill_main_export_new', array( + 'step' => $this->getNextStep('export', $export), + 'alias' => $alias + ))); + } else { + $this->logger->debug('form export is invalid', array( + 'location' => __METHOD__)); + } + } + + return $this->render('ChillMainBundle:Export:new.html.twig', array( + 'form' => $form->createView(), + 'export_alias' => $alias, + 'export' => $export + )); + } + + /** + * create a form to show on different steps. + * + * @param string $alias + * @param string $step, can either be 'export', 'formatter', 'generate_export' or 'generate_formatter' (last two are used by generate action) + * @param array $data the data from previous step. Required for steps 'formatter' and 'generate_formatter' + * @return \Symfony\Component\Form\Form + */ + protected function createCreateFormExport($alias, $step, $data = array()) + { + /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + $exportManager = $this->exportManager; + $isGenerate = strpos($step, 'generate_') === 0; + + $builder = $this->formFactory + ->createNamedBuilder(null, FormType::class, array(), array( + 'method' => $isGenerate ? 'GET' : 'POST', + 'csrf_protection' => $isGenerate ? false : true, + )); + + if ($step === 'centers' or $step === 'generate_centers') { + $builder->add('centers', PickCenterType::class, array( + 'export_alias' => $alias + )); + } + + if ($step === 'export' or $step === 'generate_export') { + $builder->add('export', ExportType::class, array( + 'export_alias' => $alias, + 'picked_centers' => $exportManager->getPickedCenters($data['centers']) + )); + } + + if ($step === 'formatter' or $step === 'generate_formatter') { + $builder->add('formatter', FormatterType::class, array( + 'formatter_alias' => $exportManager + ->getFormatterAlias($data['export']), + 'export_alias' => $alias, + 'aggregator_aliases' => $exportManager + ->getUsedAggregatorsAliases($data['export']) + )); + } + + $builder->add('submit', SubmitType::class, array( + 'label' => 'Generate' + )); + + return $builder->getForm(); + } + + /** + * get the next step. If $reverse === true, the previous step is returned. + * + * This method provides a centralized way of handling next/previous step. + * + * @param string $step the current step + * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param boolean $reverse set to true to get the previous step + * @return string the next/current step + * @throws \LogicException if there is no step before or after the given step + */ + private function getNextStep($step, $export, $reverse = false) + { + switch($step) { + case 'centers': + if ($reverse !== false) { + throw new \LogicException("there is no step before 'export'"); + } + return 'export'; + case 'export': + if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { + return $reverse ? 'centers' : 'formatter'; + } elseif ($export instanceof \Chill\MainBundle\Export\DirectExportInterface) { + return $reverse ? 'centers' : 'generate'; + } + + case 'formatter' : + return $reverse ? 'export' : 'generate'; + case 'generate' : + if ($reverse === false) { + throw new \LogicException("there is no step after 'generate'"); + } + return 'formatter'; + + default: + throw new \LogicException("the step $step is not defined."); + } + } + + /** + * Render the form for formatter. + * + * If the form is posted and valid, store the data in session and + * redirect to the next step. + * + * @param Request $request + * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param string $alias + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function formatterFormStep(Request $request, $export, $alias) + { + + // check we have data from the previous step (export step) + $data = $this->session->get('export_step', null); + + if ($data === null) { + + return $this->redirectToRoute('chill_main_export_new', array( + 'step' => $this->getNextStep('formatter', $export, true), + 'alias' => $alias + )); + } + + $form = $this->createCreateFormExport($alias, 'formatter', $data); + + if ($request->getMethod() === 'POST') { + $form->handleRequest($request); + + if ($form->isValid()) { + $dataFormatter = $form->getData(); + $this->session->set('formatter_step', $dataFormatter); + $this->session->set('formatter_step_raw', + $request->request->all()); + + //redirect to next step + return $this->redirect($this->generateUrl('chill_main_export_new', + array( + 'alias' => $alias, + 'step' => $this->getNextStep('formatter', $export) + ))); + } + } + + return $this->render('ChillMainBundle:Export:new_formatter_step.html.twig', + array( + 'form' => $form->createView(), + 'export' => $export + )); + } + + /** + * Gather data stored in session from previous steps, store it inside redis + * and redirect to the `generate` action. + * + * The data from previous steps is removed from session. + * + * @param Request $request + * @param \Chill\MainBundle\Export\ExportInterface|\Chill\MainBundle\Export\DirectExportInterface $export + * @param string $alias + * @return \Symfony\Component\HttpFoundation\RedirectResponse + */ + protected function forwardToGenerate(Request $request, $export, $alias) + { + $dataCenters = $this->session->get('centers_step_raw', null); + $dataFormatter = $this->session->get('formatter_step_raw', null); + $dataExport = $this->session->get('export_step_raw', null); + + if ($dataFormatter === NULL and $export instanceof \Chill\MainBundle\Export\ExportInterface) { + return $this->redirectToRoute('chill_main_export_new', array( + 'alias' => $alias, 'step' => $this->getNextStep('generate', $export, true) + )); + } + + $parameters = [ + 'formatter' => $dataFormatter ?? [], + 'export' => $dataExport ?? [], + 'centers' => $dataCenters ?? [], + 'alias' => $alias + ]; + unset($parameters['_token']); + $key = md5(uniqid(rand(), false)); + + $this->redis->setEx($key, 3600, \serialize($parameters)); + + // remove data from session + $this->session->remove('export_step_raw'); + $this->session->remove('export_step'); + $this->session->remove('formatter_step_raw'); + $this->session->remove('formatter_step'); + + return $this->redirectToRoute('chill_main_export_download', [ 'key' => $key, 'alias' => $alias ]); + } + + /** + * Generate a report. + * + * This action must work with GET queries. + * + * @param Request $request + * @param string $alias + * @return \Symfony\Component\HttpFoundation\Response + */ + public function generateAction(Request $request, $alias) + { + /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + $exportManager = $this->exportManager; + $key = $request->query->get('key', null); + + list($dataCenters, $dataExport, $dataFormatter) = $this->rebuildData($key); + + $r = $exportManager->generate( + $alias, + $dataCenters['centers'], + $dataExport['export'], + $dataFormatter !== NULL ? $dataFormatter['formatter'] : [] + ); + + return $r; + } + + protected function rebuildData($key) + { + if ($key === NULL) { + throw $this->createNotFoundException("key does not exists"); + } + + if ($this->redis->exists($key) !== 1) { + $this->addFlash('error', $this->translator->trans("This report is not available any more")); + throw $this->createNotFoundException("key does not exists"); + } + + $serialized = $this->redis->get($key); + + if ($serialized === false) { + throw new \LogicException("the key could not be reached from redis"); + } + + $rawData = \unserialize($serialized); + $alias = $rawData['alias']; + + $formCenters = $this->createCreateFormExport($alias, 'generate_centers'); + $formCenters->submit($rawData['centers']); + $dataCenters = $formCenters->getData(); + + $formExport = $this->createCreateFormExport($alias, 'generate_export', $dataCenters); + $formExport->submit($rawData['export']); + $dataExport = $formExport->getData(); + + if (count($rawData['formatter']) > 0) { + $formFormatter = $this->createCreateFormExport($alias, 'generate_formatter', + $dataExport); + $formFormatter->submit($rawData['formatter']); + $dataFormatter = $formFormatter->getData(); + } + + return [$dataCenters, $dataExport, $dataFormatter ?? null]; + } + + public function downloadResultAction(Request $request, $alias) + { + /* @var $exportManager \Chill\MainBundle\Export\ExportManager */ + $exportManager = $this->exportManager; + $key = $request->query->get('key', null); + + list($dataCenters, $dataExport, $dataFormatter) = $this->rebuildData($key); + + $formatterAlias = $exportManager->getFormatterAlias($dataExport['export']); + if ($formatterAlias !== null) { + $formater = $exportManager->getFormatter($formatterAlias); + } else { + $formater = null; + } + + $viewVariables = [ + 'alias' => $alias, + 'export' => $exportManager->getExport($alias) + ]; + + if ($formater instanceof \Chill\MainBundle\Export\Formatter\CSVListFormatter) { + // due to a bug in php, we add the mime type in the download view + $viewVariables['mime_type'] = 'text/csv'; + } + + return $this->render("ChillMainBundle:Export:download.html.twig", $viewVariables); + } +} diff --git a/src/Bundle/ChillMain/Controller/LoginController.php b/src/Bundle/ChillMain/Controller/LoginController.php new file mode 100644 index 000000000..046b78678 --- /dev/null +++ b/src/Bundle/ChillMain/Controller/LoginController.php @@ -0,0 +1,43 @@ +helper = $helper; + } + + /** + * Show a login form + * + * @param Request $request + * @return Response + */ + public function loginAction(Request $request) + { + + return $this->render('ChillMainBundle:Login:login.html.twig', array( + 'last_username' => $this->helper->getLastUsername(), + 'error' => $this->helper->getLastAuthenticationError() + )); + } + + public function LoginCheckAction(Request $request) + { + + } + +} diff --git a/src/Bundle/ChillMain/Controller/MenuController.php b/src/Bundle/ChillMain/Controller/MenuController.php new file mode 100644 index 000000000..4402bff6e --- /dev/null +++ b/src/Bundle/ChillMain/Controller/MenuController.php @@ -0,0 +1,18 @@ +render($layout, array( + 'menu_composer' => $this->get('chill.main.menu_composer'), + 'menu' => $menu, + 'args' => $args, + 'activeRouteKey' => $activeRouteKey + )); + } +} diff --git a/src/Bundle/ChillMain/Controller/PasswordController.php b/src/Bundle/ChillMain/Controller/PasswordController.php new file mode 100644 index 000000000..dea72180d --- /dev/null +++ b/src/Bundle/ChillMain/Controller/PasswordController.php @@ -0,0 +1,311 @@ +chillLogger = $chillLogger; + $this->passwordEncoder = $passwordEncoder; + $this->translator = $translator; + $this->tokenManager = $tokenManager; + $this->recoverPasswordHelper = $recoverPasswordHelper; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * + * @param Request $request + * @return Response + */ + public function UserPasswordAction(Request $request) + { + // get authentified user + $user = $this->getUser(); + + // create a form for password_encoder + $form = $this->passwordForm($user); + + // process the form + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $password = $form->get('new_password')->getData(); + + // logging for prod + $this + ->chillLogger + ->notice( + 'update password for an user', + array( + 'method' => $request->getMethod(), + 'user' => $user->getUsername() + ) + ); + + $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); + + $em = $this->getDoctrine()->getManager(); + $em->flush(); + + $this->addFlash('success', $this->translator->trans('Password successfully updated!')); + + return $this->redirectToRoute('change_my_password'); + + } + + // render into a template + return $this->render('ChillMainBundle:Password:password.html.twig', array( + 'form' => $form->createView() + )); + + } + + /** + * + * + * @param User $user + * @return \Symfony\Component\Form\Form + */ + private function passwordForm(User $user) + { + return $this + ->createForm( + UserPasswordType::class, + [], + [ 'user' => $user ] + ) + ->add('submit', SubmitType::class, array('label' => 'Change password')) + ; + } + + + public function recoverAction(Request $request) + { + if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { + return (new Response($this->translator->trans("You are not allowed " + . "to try to recover password, due to mitigating possible " + . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); + } + + $query = $request->query; + $username = $query->get(TokenManager::USERNAME_CANONICAL); + $hash = $query->getAlnum(TokenManager::HASH); + $token = $query->getAlnum(TokenManager::TOKEN); + $timestamp = $query->getInt(TokenManager::TIMESTAMP); + $user = $this->getDoctrine()->getRepository(User::class) + ->findOneByUsernameCanonical($username); + + if (NULL === $user) { + $this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN, + new PasswordRecoverEvent($token, null, $request->getClientIp())); + + throw $this->createNotFoundException(sprintf('User %s not found', $username)); + } + + if (TRUE !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) { + $this->eventDispatcher->dispatch(PasswordRecoverEvent::INVALID_TOKEN, + new PasswordRecoverEvent($token, $user, $request->getClientIp())); + + return new Response("Invalid token", Response::HTTP_FORBIDDEN); + } + + $form = $this->passwordForm($user); + $form->remove('actual_password'); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + $password = $form->get('new_password')->getData(); + $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); + // logging for prod + $this + ->chillLogger + ->notice( + 'setting new password for user', + array( + 'user' => $user->getUsername() + ) + ); + + $this->getDoctrine()->getManager()->flush(); + + return $this->redirectToRoute('password_request_recover_changed'); + } + + return $this->render('@ChillMain/Password/recover_password_form.html.twig', [ + 'form' => $form->createView() + ]); + + } + + public function changeConfirmedAction() + { + return $this->render('@ChillMain/Password/recover_password_changed.html.twig'); + } + + public function requestRecoverAction(Request $request) + { + if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { + return (new Response($this->translator->trans("You are not allowed " + . "to try to recover password, due to mitigating possible " + . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); + } + + $form = $this->requestRecoverForm(); + + $form->handleRequest($request); + + if ($form->isSubmitted() && $form->isValid()) { + /* @var $qb \Doctrine\ORM\QueryBuilder */ + $qb = $this->getDoctrine()->getManager() + ->createQueryBuilder(); + $qb->select('u') + ->from(User::class, 'u') + ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) + ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' )) + ->setParameter('pattern', $form->get('username_or_email')->getData()) + ; + + $user = $qb->getQuery()->getSingleResult(); + + if (empty($user->getEmail())) { + $this->addFlash('error', $this->translator->trans('This account does not have an email address. ' + . 'Please ask your administrator to renew your password.')); + } else { + if (FALSE === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) { + return (new Response($this->translator->trans("You are not allowed " + . "to try to recover password, due to mitigating possible " + . "attack. Try to contact your system administrator"), Response::HTTP_FORBIDDEN)); + } + + $this->recoverPasswordHelper->sendRecoverEmail($user, + (new \DateTimeImmutable('now'))->add(new \DateInterval('PT30M'))); + + // logging for prod + $this + ->chillLogger + ->notice( + 'Sending an email for password recovering', + array( + 'user' => $user->getUsername() + ) + ); + + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::ASK_TOKEN_SUCCESS, + new PasswordRecoverEvent(null, $user, $request->getClientIp()) + ); + + return $this->redirectToRoute('password_request_recover_confirm'); + } + } elseif ($form->isSubmitted() && FALSE === $form->isValid()) { + $this->eventDispatcher->dispatch( + PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM, + new PasswordRecoverEvent(null, null, $request->getClientIp()) + ); + } + + return $this->render('@ChillMain/Password/request_recover_password.html.twig', [ + 'form' => $form->createView() + ]); + } + + public function requestRecoverConfirmAction() + { + return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig'); + } + + protected function requestRecoverForm() + { + $builder = $this->createFormBuilder(); + $builder + ->add('username_or_email', TextType::class, [ + 'label' => 'Username or email', + 'constraints' => [ + new Callback([ + 'callback' => function($pattern, ExecutionContextInterface $context, $payload) { + $qb = $this->getDoctrine()->getManager() + ->createQueryBuilder(); + $qb->select('COUNT(u)') + ->from(User::class, 'u') + ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) + ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))' )) + ->setParameter('pattern', $pattern) + ; + + if ((int) $qb->getQuery()->getSingleScalarResult() !== 1) { + $context->addViolation('This username or email does not exists'); + } + } + ]) + ] + ]) + ->add('submit', SubmitType::class, [ + 'label' => 'Request recover' + ]); + + return $builder->getForm(); + } +} diff --git a/src/Bundle/ChillMain/Controller/PermissionsGroupController.php b/src/Bundle/ChillMain/Controller/PermissionsGroupController.php new file mode 100644 index 000000000..4c828c6aa --- /dev/null +++ b/src/Bundle/ChillMain/Controller/PermissionsGroupController.php @@ -0,0 +1,509 @@ +getDoctrine()->getManager(); + + $entities = $em->getRepository('ChillMainBundle:PermissionsGroup')->findAll(); + + return $this->render('ChillMainBundle:PermissionsGroup:index.html.twig', array( + 'entities' => $entities, + )); + } + + /** + * Creates a new PermissionsGroup entity. + * + */ + public function createAction(Request $request) + { + $permissionsGroup = new PermissionsGroup(); + $form = $this->createCreateForm($permissionsGroup); + $form->handleRequest($request); + + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($permissionsGroup); + $em->flush(); + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', + array('id' => $permissionsGroup->getId()))); + } + + return $this->render('ChillMainBundle:PermissionsGroup:new.html.twig', array( + 'entity' => $permissionsGroup, + 'form' => $form->createView(), + )); + } + + /** + * Creates a form to create a PermissionsGroup entity. + * + * @param PermissionsGroup $permissionsGroup The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(PermissionsGroup $permissionsGroup) + { + $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array( + 'action' => $this->generateUrl('admin_permissionsgroup_create'), + 'method' => 'POST', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Create')); + + return $form; + } + + /** + * Displays a form to create a new PermissionsGroup entity. + * + */ + public function newAction() + { + $permissionsGroup = new PermissionsGroup(); + $form = $this->createCreateForm($permissionsGroup); + + return $this->render('ChillMainBundle:PermissionsGroup:new.html.twig', array( + 'entity' => $permissionsGroup, + 'form' => $form->createView(), + )); + } + + /** + * Finds and displays a PermissionsGroup entity. + * + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + $translatableStringHelper = $this->get('chill.main.helper.translatable_string'); + $roleScopes = $permissionsGroup->getRoleScopes()->toArray(); + + // sort $roleScopes by name + usort($roleScopes, + function(RoleScope $a, RoleScope $b) use ($translatableStringHelper) { + if ($a->getScope() === NULL) { + return 1; + } + if ($b->getScope() === NULL) { + return +1; + } + + return strcmp( + $translatableStringHelper->localize($a->getScope()->getName()), + $translatableStringHelper->localize($b->getScope()->getName()) + ); + }); + + // sort role scope by title + /* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */ + $roleProvider = $this->get('chill.main.role_provider'); + $roleScopesSorted = array(); + foreach($roleScopes as $roleScope) { + /* @var $roleScope RoleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('ChillMainBundle:PermissionsGroup:show.html.twig', array( + 'entity' => $permissionsGroup, + 'role_scopes_sorted' => $roleScopesSorted, + 'expanded_roles' => $this->getExpandedRoles($roleScopes) + )); + } + + /** + * expand roleScopes to be easily shown in template + * + * @param array $roleScopes + * @return array + */ + private function getExpandedRoles(array $roleScopes) + { + $expandedRoles = array(); + foreach ($roleScopes as $roleScope) { + if (!array_key_exists($roleScope->getRole(), $expandedRoles)) { + $expandedRoles[$roleScope->getRole()] = + array_map( + function(RoleInterface $role) { + + return $role->getRole(); + }, + $this->get('security.role_hierarchy') + ->getReachableRoles( + array(new Role($roleScope->getRole())) + ) + ); + } + } + return $expandedRoles; + } + + /** + * Displays a form to edit an existing PermissionsGroup entity. + * + */ + public function editAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + // create all the forms + $editForm = $this->createEditForm($permissionsGroup); + + $deleteRoleScopesForm = array(); + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { + $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( + $permissionsGroup, $roleScope); + } + + $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); + + // sort role scope by title + /* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */ + $roleProvider = $this->get('chill.main.role_provider'); + $roleScopesSorted = array(); + foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /* @var $roleScope RoleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array( + 'entity' => $permissionsGroup, + 'role_scopes_sorted' => $roleScopesSorted, + 'edit_form' => $editForm->createView(), + 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), + 'delete_role_scopes_form' => array_map( function($form) { + + return $form->createView(); + }, $deleteRoleScopesForm), + 'add_role_scopes_form' => $addRoleScopesForm->createView() + )); + } + + /** + * Creates a form to edit a PermissionsGroup entity. + * + * @param PermissionsGroup $permissionsGroup The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(PermissionsGroup $permissionsGroup) + { + $form = $this->createForm(PermissionsGroupType::class, $permissionsGroup, array( + 'action' => $this->generateUrl('admin_permissionsgroup_update', array('id' => $permissionsGroup->getId())), + 'method' => 'PUT', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Update')); + + return $form; + } + + /** + * Edits an existing PermissionsGroup entity. + * + */ + public function updateAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em + ->getRepository('ChillMainBundle:PermissionsGroup') + ->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find Permissions' + . 'Group entity.'); + } + + $editForm = $this->createEditForm($permissionsGroup); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + + $em->flush(); + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', array('id' => $id))); + } + + $deleteRoleScopesForm = array(); + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { + $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( + $permissionsGroup, $roleScope); + } + + $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); + + // sort role scope by title + /* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */ + $roleProvider = $this->get('chill.main.role_provider'); + $roleScopesSorted = array(); + foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /* @var $roleScope RoleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array( + 'entity' => $permissionsGroup, + 'role_scopes_sorted' => $roleScopesSorted, + 'edit_form' => $editForm->createView(), + 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), + 'delete_role_scopes_form' => array_map( function($form) { + + return $form->createView(); + }, $deleteRoleScopesForm), + 'add_role_scopes_form' => $addRoleScopesForm->createView() + )); + } + + /** + * get a role scope by his parameters. The role scope is persisted if it + * doesn't exists in database. + * + * @param Scope $scope + * @param string $role + * @return RoleScope + */ + protected function getPersistentRoleScopeBy($role, Scope $scope = null) + { + $em = $this->getDoctrine()->getManager(); + + $roleScope = $em->getRepository('ChillMainBundle:RoleScope') + ->findOneBy(array('role' => $role, 'scope' => $scope)); + + if ($roleScope === NULL) { + $roleScope = (new RoleScope()) + ->setRole($role) + ->setScope($scope) + ; + + $em->persist($roleScope); + } + + return $roleScope; + } + + /** + * remove an association between permissionsGroup and roleScope + * + * @param int $pgid permissionsGroup id + * @param int $rsid roleScope id + * @return redirection to edit form + */ + public function deleteLinkRoleScopeAction($pgid, $rsid) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($pgid); + $roleScope = $em->getRepository('ChillMainBundle:RoleScope')->find($rsid); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + if (!$roleScope) { + throw $this->createNotFoundException('Unable to find RoleScope entity'); + } + + try { + $permissionsGroup->removeRoleScope($roleScope); + } catch (\RuntimeException $ex) { + $this->addFlash('notice', + $this->get('translator')->trans("The role '%role%' and circle " + . "'%scope%' is not associated with this permission group", array( + '%role%' => $this->get('translator')->trans($roleScope->getRole()), + '%scope%' => $this->get('chill.main.helper.translatable_string') + ->localize($roleScope->getScope()->getName()) + ))); + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', + array('id' => $pgid))); + } + + $em->flush(); + + if ($roleScope->getScope() !== NULL ) { + $this->addFlash('notice', + $this->get('translator')->trans("The role '%role%' on circle " + . "'%scope%' has been removed", array( + '%role%' => $this->get('translator')->trans($roleScope->getRole()), + '%scope%' => $this->get('chill.main.helper.translatable_string') + ->localize($roleScope->getScope()->getName()) + ))); + } else { + $this->addFlash('notice', + $this->get('translator')->trans("The role '%role%' has been removed", array( + '%role%' => $this->get('translator')->trans($roleScope->getRole()) + ))); + } + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', + array('id' => $pgid))); + } + + /** + * + * @param Request $request + * @param int $id + * @return Respon + * @throws type + */ + public function addLinkRoleScopeAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $permissionsGroup = $em->getRepository('ChillMainBundle:PermissionsGroup')->find($id); + + if (!$permissionsGroup) { + throw $this->createNotFoundException('Unable to find PermissionsGroup entity.'); + } + + $form = $this->createAddRoleScopeForm($permissionsGroup); + $form->handleRequest($request); + + if ($form->isValid()) { + $roleScope = $this->getPersistentRoleScopeBy( + $form['composed_role_scope']->getData()->getRole(), + $form['composed_role_scope']->getData()->getScope() + ); + + $permissionsGroup->addRoleScope($roleScope); + $violations = $this->get('validator')->validate($permissionsGroup); + + if ($violations->count() === 0) { + $em->flush(); + + $this->addFlash('notice', + $this->get('translator')->trans("The permissions have been added")); + + return $this->redirect($this->generateUrl('admin_permissionsgroup_edit', + array('id' => $id))); + } else { + foreach($violations as $error) { + $this->addFlash('error', $error->getMessage()); + } + } + + } else { + foreach ($form->getErrors() as $error) { + $this->addFlash('error', $error->getMessage()); + } + } + + $editForm = $this->createEditForm($permissionsGroup); + + $deleteRoleScopesForm = array(); + foreach ($permissionsGroup->getRoleScopes() as $roleScope) { + $deleteRoleScopesForm[$roleScope->getId()] = $this->createDeleteRoleScopeForm( + $permissionsGroup, $roleScope); + } + + $addRoleScopesForm = $this->createAddRoleScopeForm($permissionsGroup); + + // sort role scope by title + /* @var $roleProvider \Chill\MainBundle\Security\RoleProvider */ + $roleProvider = $this->get('chill.main.role_provider'); + $roleScopesSorted = array(); + foreach($permissionsGroup->getRoleScopes()->toArray() as $roleScope) { + /* @var $roleScope RoleScope */ + $title = $roleProvider->getRoleTitle($roleScope->getRole()); + $roleScopesSorted[$title][] = $roleScope; + } + ksort($roleScopesSorted); + + return $this->render('ChillMainBundle:PermissionsGroup:edit.html.twig', array( + 'entity' => $permissionsGroup, + 'edit_form' => $editForm->createView(), + 'role_scopes_sorted' => $roleScopesSorted, + 'expanded_roles' => $this->getExpandedRoles($permissionsGroup->getRoleScopes()->toArray()), + 'delete_role_scopes_form' => array_map( function($form) { + + return $form->createView(); + }, $deleteRoleScopesForm), + 'add_role_scopes_form' => $addRoleScopesForm->createView() + )); + + } + + /** + * Creates a form to delete a link to roleScope. + * + * @param mixed $permissionsGroup The entity id + * + * @return \Symfony\Component\Form\Form The form + */ + private function createDeleteRoleScopeForm(PermissionsGroup $permissionsGroup, + RoleScope $roleScope) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl('admin_permissionsgroup_delete_role_scope', + array('pgid' => $permissionsGroup->getId(), 'rsid' => $roleScope->getId()))) + ->setMethod('DELETE') + ->add('submit', SubmitType::class, array('label' => 'Delete')) + ->getForm() + ; + } + + /** + * creates a form to add a role scope to permissionsgroup + * + * @param PermissionsGroup $permissionsGroup + * @return \Symfony\Component\Form\Form The form + */ + private function createAddRoleScopeForm(PermissionsGroup $permissionsGroup) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl('admin_permissionsgroup_add_role_scope', + array('id' => $permissionsGroup->getId()))) + ->setMethod('PUT') + ->add('composed_role_scope', ComposedRoleScopeType::class) + ->add('submit', SubmitType::class, array('label' => 'Add permission')) + ->getForm() + ; + } + + +} diff --git a/src/Bundle/ChillMain/Controller/PostalCodeController.php b/src/Bundle/ChillMain/Controller/PostalCodeController.php new file mode 100644 index 000000000..cd7dd9ace --- /dev/null +++ b/src/Bundle/ChillMain/Controller/PostalCodeController.php @@ -0,0 +1,89 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; +use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Chill\MainBundle\Entity\PostalCode; +use Symfony\Component\HttpFoundation\JsonResponse; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\ORM\Query; + +/** + * + * + * @author Julien Fastré + */ +class PostalCodeController extends Controller +{ + /** + * + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + + public function __construct(TranslatableStringHelper $translatableStringHelper) + { + $this->translatableStringHelper = $translatableStringHelper; + } + + + /** + * + * @Route( + * "{_locale}/postalcode/search" + * ) + * @param Request $request + * @return JsonResponse + */ + public function searchAction(Request $request) + { + $pattern = $request->query->getAlnum('q', ''); + + if (empty($pattern)) { + return new JsonResponse(["results" => [], "pagination" => [ "more" => false]]); + } + + $query = $this->getDoctrine()->getManager() + ->createQuery(sprintf( + "SELECT p.id AS id, p.name AS name, p.code AS code, " + . "country.name AS country_name, " + . "country.countryCode AS country_code " + . "FROM %s p " + . "JOIN p.country country " + . "WHERE LOWER(p.name) LIKE LOWER(:pattern) OR LOWER(p.code) LIKE LOWER(:pattern) " + . "ORDER BY code" + , PostalCode::class) + ) + ->setParameter('pattern', '%'.$pattern.'%') + ->setMaxResults(30) + ; + + $results = \array_map(function($row) { + $row['country_name'] = $this->translatableStringHelper->localize($row['country_name']); + $row['text'] = $row['code']." ".$row["name"]." (".$row['country_name'].")"; + + return $row; + }, $query->getResult(Query::HYDRATE_ARRAY)); + + return new JsonResponse([ 'results' => $results, "pagination" => [ "more" => false ] ]); + } +} diff --git a/src/Bundle/ChillMain/Controller/ScopeController.php b/src/Bundle/ChillMain/Controller/ScopeController.php new file mode 100644 index 000000000..a999a0b3b --- /dev/null +++ b/src/Bundle/ChillMain/Controller/ScopeController.php @@ -0,0 +1,179 @@ +getDoctrine()->getManager(); + + $entities = $em->getRepository('ChillMainBundle:Scope')->findAll(); + + return $this->render('ChillMainBundle:Scope:index.html.twig', array( + 'entities' => $entities, + )); + } + /** + * Creates a new Scope entity. + * + */ + public function createAction(Request $request) + { + $scope = new Scope(); + $form = $this->createCreateForm($scope); + $form->handleRequest($request); + + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + $em->persist($scope); + $em->flush(); + + return $this->redirect($this->generateUrl('admin_scope_show', array('id' => $scope->getId()))); + } + + return $this->render('ChillMainBundle:Scope:new.html.twig', array( + 'entity' => $scope, + 'form' => $form->createView(), + )); + } + + /** + * Creates a form to create a Scope entity. + * + * @param Scope $scope The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(Scope $scope) + { + $form = $this->createForm(ScopeType::class, $scope, array( + 'action' => $this->generateUrl('admin_scope_create'), + 'method' => 'POST', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Create')); + + return $form; + } + + /** + * Displays a form to create a new Scope entity. + * + */ + public function newAction() + { + $scope = new Scope(); + $form = $this->createCreateForm($scope); + + return $this->render('ChillMainBundle:Scope:new.html.twig', array( + 'entity' => $scope, + 'form' => $form->createView(), + )); + } + + /** + * Finds and displays a Scope entity. + * + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $scope = $em->getRepository('ChillMainBundle:Scope')->find($id); + + if (!$scope) { + throw $this->createNotFoundException('Unable to find Scope entity.'); + } + + return $this->render('ChillMainBundle:Scope:show.html.twig', array( + 'entity' => $scope + )); + } + + /** + * Displays a form to edit an existing Scope entity. + * + */ + public function editAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $scope = $em->getRepository('ChillMainBundle:Scope')->find($id); + + if (!$scope) { + throw $this->createNotFoundException('Unable to find Scope entity.'); + } + + $editForm = $this->createEditForm($scope); + + return $this->render('ChillMainBundle:Scope:edit.html.twig', array( + 'entity' => $scope, + 'edit_form' => $editForm->createView(), + )); + } + + /** + * Creates a form to edit a Scope entity. + * + * @param Scope $scope The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(Scope $scope) + { + $form = $this->createForm(ScopeType::class, $scope, array( + 'action' => $this->generateUrl('admin_scope_update', array('id' => $scope->getId())), + 'method' => 'PUT', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Update')); + + return $form; + } + /** + * Edits an existing Scope entity. + * + */ + public function updateAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $scope = $em->getRepository('ChillMainBundle:Scope')->find($id); + + if (!$scope) { + throw $this->createNotFoundException('Unable to find Scope entity.'); + } + + $editForm = $this->createEditForm($scope); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + $em->flush(); + + return $this->redirect($this->generateUrl('admin_scope_edit', array('id' => $id))); + } + + return $this->render('ChillMainBundle:Scope:edit.html.twig', array( + 'entity' => $scope, + 'edit_form' => $editForm->createView() + )); + } +} diff --git a/src/Bundle/ChillMain/Controller/SearchController.php b/src/Bundle/ChillMain/Controller/SearchController.php new file mode 100644 index 000000000..a56b49d69 --- /dev/null +++ b/src/Bundle/ChillMain/Controller/SearchController.php @@ -0,0 +1,215 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; +use Chill\MainBundle\Search\UnknowSearchDomainException; +use Chill\MainBundle\Search\UnknowSearchNameException; +use Chill\MainBundle\Search\ParsingException; +use Chill\MainBundle\Search\SearchInterface; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\HttpFoundation\JsonResponse; + +/** + * + * + * @author julien.fastre@champs-libres.coop + * @author marc@champs-libres.coop + */ +class SearchController extends Controller +{ + + public function searchAction(Request $request, $_format) + { + $pattern = $request->query->get('q', ''); + + if ($pattern === ''){ + switch($_format) { + case 'html': + return $this->render('ChillMainBundle:Search:error.html.twig', + array( + 'message' => $this->get('translator')->trans("Your search is empty. " + . "Please provide search terms."), + 'pattern' => $pattern + )); + case 'json': + return new JsonResponse([ + 'results' => [], + 'pagination' => [ 'more' => false ] + ]); + } + } + + $name = $request->query->get('name', NULL); + + try { + if ($name === NULL) { + if ($_format === 'json') { + return new JsonResponse('Currently, we still do not aggregate results ' + . 'from different providers', JsonResponse::HTTP_BAD_REQUEST); + } + + // no specific search selected. Rendering result in "preview" mode + $results = $this->get('chill.main.search_provider') + ->getSearchResults( + $pattern, + 0, + 5, + array(SearchInterface::SEARCH_PREVIEW_OPTION => true) + ); + } else { + // we want search on a specific search provider. Display full results. + + // create a paginator to get the startPage and stopPage + /* @var $paginatorFactory \Chill\MainBundle\Pagination\PaginatorFactory */ + $paginatorFactory = $this->get('chill_main.paginator_factory'); + + $results = [$this->get('chill.main.search_provider') + ->getResultByName( + $pattern, + $name, + $paginatorFactory->getCurrentPageFirstItemNumber(), + $paginatorFactory->getCurrentItemsPerPage(), + array( + SearchInterface::SEARCH_PREVIEW_OPTION => false, + SearchInterface::REQUEST_QUERY_PARAMETERS => $request + ->get(SearchInterface::REQUEST_QUERY_KEY_ADD_PARAMETERS, []) + ), + $_format + )]; + + if ($_format === 'json') { + return new JsonResponse(\reset($results)); + } + } + } catch (UnknowSearchDomainException $ex) { + return $this->render('ChillMainBundle:Search:error.html.twig', + array( + "message" => $this->get('translator')->trans("The domain %domain% " + . "is unknow. Please check your search.", array('%domain%' => $ex->getDomain())), + 'pattern' => $pattern + )); + } catch (UnknowSearchNameException $ex) { + throw $this->createNotFoundException("The name ".$ex->getName()." is not found"); + } catch (ParsingException $ex) { + return $this->render('ChillMainBundle:Search:error.html.twig', + array( + "message" => $this->get('translator')->trans('Invalid terms'). + ": ".$this->get('translator')->trans($ex->getMessage()), + 'pattern' => $pattern + )); + } + + + return $this->render('ChillMainBundle:Search:list.html.twig', + array('results' => $results, 'pattern' => $pattern) + ); + } + + public function advancedSearchListAction(Request $request) + { + /* @var $variable Chill\MainBundle\Search\SearchProvider */ + $searchProvider = $this->get('chill.main.search_provider'); + $advancedSearchProviders = $searchProvider + ->getHasAdvancedFormSearchServices(); + + if(\count($advancedSearchProviders) === 1) { + \reset($advancedSearchProviders); + + return $this->redirectToRoute('chill_main_advanced_search', [ + 'name' => \key($advancedSearchProviders) + ]); + } + + return $this->render('ChillMainBundle:Search:choose_list.html.twig'); + } + + public function advancedSearchAction($name, Request $request) + { + try { + /* @var $variable Chill\MainBundle\Search\SearchProvider */ + $searchProvider = $this->get('chill.main.search_provider'); + /* @var $variable Chill\MainBundle\Search\HasAdvancedSearchFormInterface */ + $search = $this->get('chill.main.search_provider') + ->getHasAdvancedFormByName($name); + + } catch (\Chill\MainBundle\Search\UnknowSearchNameException $e) { + throw $this->createNotFoundException("no advanced search for " + . "$name"); + } + + if ($request->query->has('q')) { + $data = $search->convertTermsToFormData($searchProvider->parse( + $request->query->get('q'))); + } + + $form = $this->createAdvancedSearchForm($name, $data ?? []); + + if ($request->isMethod(Request::METHOD_POST)) { + $form->handleRequest($request); + + if ($form->isValid()) { + $pattern = $this->get('chill.main.search_provider') + ->getHasAdvancedFormByName($name) + ->convertFormDataToQuery($form->getData()); + + return $this->redirectToRoute('chill_main_search', [ + 'q' => $pattern, 'name' => $name + ]); + } + } + + return $this->render('ChillMainBundle:Search:advanced_search.html.twig', + [ + 'form' => $form->createView(), + 'name' => $name, + 'title' => $search->getAdvancedSearchTitle() + ]); + } + + protected function createAdvancedSearchForm($name, array $data = []) + { + $builder = $this + ->get('form.factory') + ->createNamedBuilder( + null, + FormType::class, + $data, + [ 'method' => Request::METHOD_POST ] + ); + + $this->get('chill.main.search_provider') + ->getHasAdvancedFormByName($name) + ->buildForm($builder) + ; + + $builder->add('submit', SubmitType::class, [ + 'label' => 'Search' + ]); + + return $builder->getForm(); + } + +} diff --git a/src/Bundle/ChillMain/Controller/UIController.php b/src/Bundle/ChillMain/Controller/UIController.php new file mode 100644 index 000000000..07b3694f8 --- /dev/null +++ b/src/Bundle/ChillMain/Controller/UIController.php @@ -0,0 +1,39 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Controller; + +use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Chill\MainBundle\Templating\UI\CountNotificationUser; + +/** + * + * + * @author Julien Fastré + */ +class UIController extends Controller +{ + public function showNotificationUserCounterAction( + CountNotificationUser $counter + ) { + $nb = $counter->getSumNotification($this->getUser()); + + return $this->render('ChillMainBundle:UI:notification_user_counter.html.twig', [ + 'nb' => $nb + ]); + } +} diff --git a/src/Bundle/ChillMain/Controller/UserController.php b/src/Bundle/ChillMain/Controller/UserController.php new file mode 100644 index 000000000..83465366a --- /dev/null +++ b/src/Bundle/ChillMain/Controller/UserController.php @@ -0,0 +1,435 @@ +getDoctrine()->getManager(); + + $entities = $em->createQuery('SELECT u FROM ChillMainBundle:User u ' + . 'ORDER BY u.username') + ->getResult(); + + return $this->render('ChillMainBundle:User:index.html.twig', array( + 'entities' => $entities, + )); + } + /** + * Creates a new User entity. + * + */ + public function createAction(Request $request) + { + $user = new User(); + $form = $this->createCreateForm($user); + $form->handleRequest($request); + + if ($form->isValid()) { + $em = $this->getDoctrine()->getManager(); + + $user->setPassword($this->get('security.password_encoder') + ->encodePassword($user, $form['plainPassword']->getData())); + + $em->persist($user); + $em->flush(); + + return $this->redirect($this->generateUrl('admin_user_show', array('id' => $user->getId()))); + } + + return $this->render('ChillMainBundle:User:new.html.twig', array( + 'entity' => $user, + 'form' => $form->createView(), + )); + } + + /** + * Creates a form to create a User entity. + * + * @param User $entity The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createCreateForm(User $entity) + { + $form = $this->createForm(UserType::class, $entity, array( + 'action' => $this->generateUrl('admin_user_create'), + 'method' => 'POST', + 'is_creation' => true + )); + + $form->add('submit', SubmitType::class, array('label' => 'Create')); + + return $form; + } + + /** + * Displays a form to create a new User entity. + * + */ + public function newAction() + { + $user = new User(); + $form = $this->createCreateForm($user); + + return $this->render('ChillMainBundle:User:new.html.twig', array( + 'entity' => $user, + 'form' => $form->createView(), + )); + } + + /** + * Finds and displays a User entity. + * + */ + public function showAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($id); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + return $this->render('ChillMainBundle:User:show.html.twig', array( + 'entity' => $user, + )); + } + + /** + * Displays a form to edit an existing User entity. + * + */ + public function editAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($id); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $editForm = $this->createEditForm($user); + + return $this->render('ChillMainBundle:User:edit.html.twig', array( + 'entity' => $user, + 'edit_form' => $editForm->createView(), + 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(), + 'delete_groupcenter_form' => array_map( + function(\Symfony\Component\Form\Form $form) { + return $form->createView(); + + }, + iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true)) + )); + } + + /** + * Displays a form to edit the user password. + * + */ + public function editPasswordAction($id) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($id); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $editForm = $this->createEditPasswordForm($user); + + return $this->render('ChillMainBundle:User:edit_password.html.twig', array( + 'entity' => $user, + 'edit_form' => $editForm->createView() + )); + } + + /** + * + * + * @param User $user + * @return \Symfony\Component\Form\Form + */ + private function createEditPasswordForm(User $user) + { + return $this->createForm(UserPasswordType::class, null, array( + 'action' => + $this->generateUrl('admin_user_update_password', array('id' => $user->getId())), + 'method' => 'PUT', + 'user' => $user + )) + ->add('submit', SubmitType::class, array('label' => 'Change password')) + ->remove('actual_password') + ; + } + + public function deleteLinkGroupCenterAction($uid, $gcid) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($uid); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $groupCenter = $em->getRepository('ChillMainBundle:GroupCenter') + ->find($gcid); + + if (!$groupCenter) { + throw $this->createNotFoundException('Unable to find groupCenter entity'); + } + + try { + $user->removeGroupCenter($groupCenter); + } catch (\RuntimeException $ex) { + $this->addFlash('error', $this->get('translator')->trans($ex->getMessage())); + + return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $uid))); + } + + $em->flush(); + + $this->addFlash('success', $this->get('translator') + ->trans('The permissions where removed.')); + + return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $uid))); + + } + + public function addLinkGroupCenterAction(Request $request, $uid) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($uid); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $form = $this->createAddLinkGroupCenterForm($user); + $form->handleRequest($request); + + if ($form->isValid()) { + $groupCenter = $this->getPersistedGroupCenter( + $form[self::FORM_GROUP_CENTER_COMPOSED]->getData()); + $user->addGroupCenter($groupCenter); + + if ($this->get('validator')->validate($user)->count() === 0) { + $em->flush(); + + $this->addFlash('success', $this->get('translator')->trans('The ' + . 'permissions have been successfully added to the user')); + + return $this->redirect($this->generateUrl('admin_user_edit', + array('id' => $uid))); + } else { + foreach($this->get('validator')->validate($user) as $error) + $this->addFlash('error', $error->getMessage()); + } + } + + return $this->render('ChillMainBundle:User:edit.html.twig', array( + 'entity' => $user, + 'edit_form' => $this->createEditForm($user)->createView(), + 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(), + 'delete_groupcenter_form' => array_map( + function(\Symfony\Component\Form\Form $form) { + return $form->createView(); + + }, + iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true)) + )); + } + + private function getPersistedGroupCenter(GroupCenter $groupCenter) + { + $em = $this->getDoctrine()->getManager(); + + $groupCenterManaged = $em->getRepository('ChillMainBundle:GroupCenter') + ->findOneBy(array( + 'center' => $groupCenter->getCenter(), + 'permissionsGroup' => $groupCenter->getPermissionsGroup() + )); + + if (!$groupCenterManaged) { + $em->persist($groupCenter); + return $groupCenter; + } + + return $groupCenterManaged; + } + + /** + * Creates a form to edit a User entity. + * + * @param User $user The entity + * + * @return \Symfony\Component\Form\Form The form + */ + private function createEditForm(User $user) + { + $form = $this->createForm(UserType::class, $user, array( + 'action' => $this->generateUrl('admin_user_update', array('id' => $user->getId())), + 'method' => 'PUT', + )); + + $form->add('submit', SubmitType::class, array('label' => 'Update')); + + return $form; + } + + /** + * Edits an existing User entity. + * + */ + public function updateAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($id); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $editForm = $this->createEditForm($user); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + $em->flush(); + + return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $id))); + } + + return $this->render('ChillMainBundle:User:edit.html.twig', array( + 'entity' => $user, + 'edit_form' => $editForm->createView(), + 'add_groupcenter_form' => $this->createAddLinkGroupCenterForm($user)->createView(), + 'delete_groupcenter_form' => array_map( + function(\Symfony\Component\Form\Form $form) { + return $form->createView(); + + }, + iterator_to_array($this->getDeleteLinkGroupCenterByUser($user), true)) + )); + } + + /** + * Edits the user password + * + */ + public function updatePasswordAction(Request $request, $id) + { + $em = $this->getDoctrine()->getManager(); + + $user = $em->getRepository('ChillMainBundle:User')->find($id); + + if (!$user) { + throw $this->createNotFoundException('Unable to find User entity.'); + } + + $editForm = $this->createEditPasswordForm($user); + $editForm->handleRequest($request); + + if ($editForm->isValid()) { + $password = $editForm->get('new_password')->getData(); + + // logging for prod + $this->get('logger')->info('update password for an user', [ + 'by' => $this->getUser()->getUsername(), + 'user' => $user->getUsername() + ]); + + $user->setPassword($this->get('security.password_encoder') + ->encodePassword($user, $password)); + + $em->flush(); + + $this->addFlash('success', $this->get('translator')->trans('Password successfully updated!')); + + return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $id))); + } + + return $this->render('ChillMainBundle:User:edit_password.html.twig', array( + 'entity' => $user, + 'edit_form' => $editForm->createView(), + )); + } + + + /** + * Creates a form to delete a link to a GroupCenter + * + * @param mixed $permissionsGroup The entity id + * + * @return \Symfony\Component\Form\Form The form + */ + private function createDeleteLinkGroupCenterForm(User $user, GroupCenter $groupCenter) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl('admin_user_delete_group_center', + array('uid' => $user->getId(), 'gcid' => $groupCenter->getId()))) + ->setMethod('DELETE') + ->add('submit', SubmitType::class, array('label' => 'Delete')) + ->getForm() + ; + } + + /** + * create a form to add a link to a groupcenter + * + * @param User $user + * @return \Symfony\Component\Form\Form + */ + private function createAddLinkGroupCenterForm(User $user) + { + return $this->createFormBuilder() + ->setAction($this->generateUrl('admin_user_add_group_center', + array('uid' => $user->getId()))) + ->setMethod('POST') + ->add(self::FORM_GROUP_CENTER_COMPOSED, ComposedGroupCenterType::class) + ->add('submit', SubmitType::class, array('label' => 'Add a new groupCenter')) + ->getForm() + ; + } + + /** + * + * @param User $user + */ + private function getDeleteLinkGroupCenterByUser(User $user) + { + foreach ($user->getGroupCenters() as $groupCenter) { + yield $groupCenter->getId() => $this + ->createDeleteLinkGroupCenterForm($user, $groupCenter); + } + } +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadCenters.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadCenters.php new file mode 100644 index 000000000..d0bb7178f --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadCenters.php @@ -0,0 +1,68 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Chill\MainBundle\Entity\Center; + +/** + * + * + * @author Julien Fastré + */ +class LoadCenters extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 100; + } + + public static $centers = array( + array( + 'name' => 'Center A', + 'ref' => 'centerA' + ), + array( + 'name' => 'Center B', + 'ref' => 'centerB' + ) + ); + + public static $refs = array(); + + public function load(ObjectManager $manager) + { + foreach (static::$centers as $new) { + $centerA = new Center(); + $centerA->setName($new['name']); + + $manager->persist($centerA); + $this->addReference($new['ref'], $centerA); + static::$refs[] = $new['ref']; + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadCountries.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadCountries.php new file mode 100644 index 000000000..6f0967586 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadCountries.php @@ -0,0 +1,48 @@ + + */ +class LoadCountries extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface { + + /** + * + * @var ContainerInterface + */ + private $container; + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + + public function getOrder() { + return 20; + } + + public function load(ObjectManager $manager) { + + echo "loading countries... \n"; + + $languages = $this->container->getParameter('chill_main.available_languages'); + + foreach (LoadCountriesCommand::prepareCountryList($languages) as $country){ + $manager->persist($country); + } + + $manager->flush(); + } + + +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadGroupCenters.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadGroupCenters.php new file mode 100644 index 000000000..2629c0613 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadGroupCenters.php @@ -0,0 +1,63 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\DataFixtures\ORM\LoadCenters; +use Chill\MainBundle\DataFixtures\ORM\LoadPermissionsGroup; + +/** + * + * + * @author Julien Fastré + */ +class LoadGroupCenters extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 500; + } + + public static $refs = array(); + + public function load(ObjectManager $manager) + { + foreach (LoadCenters::$refs as $centerRef) { + foreach (LoadPermissionsGroup::$refs as $permissionGroupRef) { + $GroupCenter = new GroupCenter(); + $GroupCenter->setCenter($this->getReference($centerRef)); + $GroupCenter->setPermissionsGroup($this->getReference($permissionGroupRef)); + + $manager->persist($GroupCenter); + + $reference = $centerRef.'_'.$permissionGroupRef; + $this->addReference($reference, $GroupCenter); + static::$refs[] = $reference; + echo "Creating $reference... \n"; + } + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadLanguages.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadLanguages.php new file mode 100644 index 000000000..325f139a0 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadLanguages.php @@ -0,0 +1,80 @@ + + */ +class LoadLanguages extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface +{ + // The regional version of language are language with _ in the code + // This array contains regional code to not exclude + private $regionalVersionToInclude = ["ro_MD"]; + + // Array of ancien languages (to exclude) + private $ancientToExclude = ["ang", "egy", "fro", "goh", "grc", "la", "non", "peo", "pro", "sga", + "dum", "enm", "frm", "gmh", "mga", "akk", "phn", "zxx", "got", "und"]; + + /** + * + * @var ContainerInterface + */ + private $container; + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + + public function getOrder() { + return 10; + } + + public function load(ObjectManager $manager) { + + echo "loading languages... \n"; + + foreach (Intl::getLanguageBundle()->getLanguageNames() as $code => $language) { + if ( + !in_array($code, $this->regionalVersionToInclude) + && + !in_array($code, $this->ancientToExclude) + ) { + + $lang = (new Language()) + ->setId($code) + ->setName($this->prepareName($code)) + ; + $manager->persist($lang); + } + } + + $manager->flush(); + } + + /** + * prepare names for languages + * + * @param string $languageCode + * @return string[] languages name indexed by available language code + */ + private function prepareName($languageCode) { + foreach ($this->container->getParameter('chill_main.available_languages') as $lang) { + $names[$lang] = Intl::getLanguageBundle()->getLanguageName($languageCode); + } + + return $names; + } + + +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadPermissionsGroup.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadPermissionsGroup.php new file mode 100644 index 000000000..b2ed78934 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadPermissionsGroup.php @@ -0,0 +1,87 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Entity\PermissionsGroup; + +/** + * + * + * @author Julien Fastré + */ +class LoadPermissionsGroup extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 400; + } + + public static $permissionGroup = array( + array( + 'name' => 'social', + 'role_scopes' => array( + 'role_scope_CHILL_FOO_EDIT_social', + 'role_scope_CHILL_FOO_SEE_administrative', + "role_scope_CHILL_FOO_EDIT_all" + ) + ), + array( + 'name' => 'administrative', + 'role_scopes' => array( + "role_scope_CHILL_FOO_SEE_social", + "role_scope_CHILL_FOO_EDIT_administrative", + "role_scope_CHILL_FOO_EDIT_all" + ) + ), + array( + 'name' => 'direction', + 'role_scopes' => array( + "role_scope_CHILL_FOO_EDIT_all", + "role_scope_CHILL_FOO_SEE_DETAILS_social", + "role_scope_CHILL_FOO_SEE_DETAILS_administrative" + ) + ) + ); + + public static $refs = array(); + + public function load(ObjectManager $manager) + { + foreach (static::$permissionGroup as $new) { + $permissionGroup = new PermissionsGroup(); + $permissionGroup->setName($new['name']); + foreach ($new['role_scopes'] as $roleScopeRef) { + $permissionGroup->addRoleScope($this->getReference($roleScopeRef)); + } + + $manager->persist($permissionGroup); + $reference = 'permission_group_'.$new['name']; + echo "Creating $reference \n"; + $this->setReference($reference, $permissionGroup); + static::$refs[] = $reference; + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadPostalCodes.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadPostalCodes.php new file mode 100644 index 000000000..95019d394 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadPostalCodes.php @@ -0,0 +1,105 @@ +, + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Entity\PostalCode; + +/** + * Description of LoadPostalCodes + * + * @author Julien Fastré + * @author Champs Libres + */ +class LoadPostalCodes extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 50; + } + + public static $refs = array(); + + public function load(ObjectManager $manager) + { + $lines = str_getcsv(self::$codes, "\n"); + $belgium = $manager->getRepository('ChillMainBundle:Country') + ->findOneBy(array('countryCode' => 'BE')); + + foreach($lines as $line) { + $code = str_getcsv($line); + $c = new PostalCode(); + $c->setCountry($belgium) + ->setCode($code[0]) + ->setName(\ucwords(\strtolower($code[2]))) + ; + $manager->persist($c); + $ref = 'postal_code_'.$code[0]; + + if (! $this->hasReference($ref)) { + $this->addReference($ref, $c); + self::$refs[] = $ref; + } + } + + $manager->flush(); + } + + private static $codes = << + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Entity\RoleScope; +use Chill\MainBundle\DataFixtures\ORM\LoadScopes; + +/** + * + * + * @author Julien Fastré + */ +class LoadRoleScopes extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 300; + } + + public static $permissions = array( + 'CHILL_FOO_SEE' => array( + 'names' => array( + 'fr' => 'voir foo', + 'en' => 'see foo', + 'nl' => 'zie foo' + ) + ), + 'CHILL_FOO_SEE_DETAILS' => array( + 'names' => array( + 'fr' => 'voir foo avec détails', + 'en' => 'see foo with details', + 'nl' => 'zie foo in details' + ) + ), + 'CHILL_FOO_EDIT' => array( + 'names' => array( + 'fr' => 'modifier foo', + 'en' => 'edit foo', + 'nl' => 'editie foo' + ) + ) + ); + + public static $references = array(); + + public function load(ObjectManager $manager) + { + foreach (static::$permissions as $key => $permission) { + foreach(LoadScopes::$references as $scopeReference) { + $roleScope = new RoleScope(); + $roleScope->setRole($key) + ->setScope($this->getReference($scopeReference)) + ; + $reference = 'role_scope_'.$key.'_'.$this->getReference($scopeReference)->getName()['en']; + echo "Creating $reference \n"; + $this->addReference($reference, $roleScope); + $manager->persist($roleScope); + static::$references[] = $reference; + } + } + + $manager->flush(); + } + +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadScopes.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadScopes.php new file mode 100644 index 000000000..f43fb0919 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadScopes.php @@ -0,0 +1,79 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DataFixtures\ORM; + +use Doctrine\Common\DataFixtures\AbstractFixture; +use Doctrine\Common\DataFixtures\OrderedFixtureInterface; +use Doctrine\Common\Persistence\ObjectManager; + +/** + * Create scopes + * + * @author Julien Fastré + */ +class LoadScopes extends AbstractFixture implements OrderedFixtureInterface +{ + public function getOrder() + { + return 200; + } + + public $scopes = array( + array( + 'names' => array( + 'fr' => 'tous', + 'en' => 'all', + 'nl' => 'algemeen' + ), + ), + array( + 'names' => array( + 'fr' => 'social', + 'en' => 'social', + 'nl' => 'sociaal' + ) + ), + array( + 'names' => array( + 'fr' => 'administratif', + 'en' => 'administrative', + 'nl' => 'administratief' + ) + ) + ); + + public static $references = array(); + + public function load(ObjectManager $manager) + { + foreach ($this->scopes as $new) { + $scope = new \Chill\MainBundle\Entity\Scope(); + $scope->setName($new['names']); + + $manager->persist($scope); + $reference = 'scope_'.$new['names']['en']; + $this->addReference($reference, $scope); + static::$references[] = $reference; + } + + $manager->flush(); + } +} diff --git a/src/Bundle/ChillMain/DataFixtures/ORM/LoadUsers.php b/src/Bundle/ChillMain/DataFixtures/ORM/LoadUsers.php new file mode 100644 index 000000000..1ace9ee03 --- /dev/null +++ b/src/Bundle/ChillMain/DataFixtures/ORM/LoadUsers.php @@ -0,0 +1,98 @@ + + */ +class LoadUsers extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface +{ + /** + * + * @var ContainerInterface + */ + private $container; + + public function getOrder() + { + return 1000; + } + + public static $refs = array( + 'center a_social' => array( + 'groupCenterRefs' => ['centerA_permission_group_social'] + ), + 'center a_administrative' => array( + 'groupCenterRefs' => ['centerA_permission_group_administrative'] + ), + 'center a_direction' => array( + 'groupCenterRefs' => ['centerA_permission_group_direction'] + ), + 'center b_social' => array( + 'groupCenterRefs' => ['centerB_permission_group_social'] + ), + 'center b_administrative' => array( + 'groupCenterRefs' => ['centerB_permission_group_administrative'] + ), + 'center b_direction' => array( + 'groupCenterRefs' => ['centerB_permission_group_direction'] + ), + 'multi_center' => array( + 'groupCenterRefs' => ['centerA_permission_group_social', + 'centerB_permission_group_social'] + ) + + ); + + public function load(ObjectManager $manager) + { + foreach (self::$refs as $username => $params) { + + $user = new User(); + + $user + ->setUsername($username) + ->setPassword( + $this->container->get('security.encoder_factory') + ->getEncoder($user) + ->encodePassword('password', $user->getSalt()) + ) + ->setEmail(sprintf("%s@chill.social", \str_replace(' ', '', $username))) + ; + + foreach ($params['groupCenterRefs'] as $groupCenterRef) { + $user->addGroupCenter($this->getReference($groupCenterRef)); + } + + echo 'Creating user ' . $username ."... \n"; + $manager->persist($user); + $this->addReference($username, $user); + } + + $manager->flush(); + } + + public function setContainer(ContainerInterface $container = null) + { + if (NULL === $container) { + throw new \LogicException('$container should not be null'); + } + + $this->container = $container; + } + +} diff --git a/src/Bundle/ChillMain/DependencyInjection/ChillMainExtension.php b/src/Bundle/ChillMain/DependencyInjection/ChillMainExtension.php new file mode 100644 index 000000000..d33ef4eb9 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/ChillMainExtension.php @@ -0,0 +1,253 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; +use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; +use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; +use Chill\MainBundle\DependencyInjection\Configuration; +use Chill\MainBundle\Doctrine\DQL\GetJsonFieldByKey; +use Chill\MainBundle\Doctrine\DQL\Unaccent; +use Chill\MainBundle\Doctrine\DQL\JsonAggregate; +use Chill\MainBundle\Doctrine\DQL\JsonbExistsInArray; +use Chill\MainBundle\Doctrine\DQL\Similarity; +use Chill\MainBundle\Doctrine\DQL\OverlapsI; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; +use Chill\MainBundle\Doctrine\DQL\Replace; + +/** + * This class load config for chillMainExtension. + */ +class ChillMainExtension extends Extension implements PrependExtensionInterface, + Widget\HasWidgetFactoriesExtensionInterface +{ + /** + * widget factory + * + * @var WidgetFactoryInterface[] + */ + protected $widgetFactories = array(); + + public function addWidgetFactory(WidgetFactoryInterface $factory) + { + $this->widgetFactories[] = $factory; + } + + /** + * + * @return WidgetFactoryInterface[] + */ + public function getWidgetFactories() + { + return $this->widgetFactories; + } + + /** + * {@inheritDoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + // configuration for main bundle + $configuration = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($configuration, $configs); + + $container->setParameter('chill_main.installation_name', + $config['installation_name']); + + $container->setParameter('chill_main.available_languages', + $config['available_languages']); + + $container->setParameter('chill_main.routing.resources', + $config['routing']['resources']); + + $container->setParameter('chill_main.pagination.item_per_page', + $config['pagination']['item_per_page']); + + $container->setParameter('chill_main.notifications', + $config['notifications']); + + $container->setParameter('chill_main.redis', + $config['redis']); + + $container->setParameter('chill_main.phone_helper', + $config['phone_helper'] ?? []); + + // add the key 'widget' without the key 'enable' + $container->setParameter('chill_main.widgets', + isset($config['widgets']['homepage']) ? + array('homepage' => $config['widgets']['homepage']): + array() + ); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.yml'); + $loader->load('services/logger.yml'); + $loader->load('services/repositories.yml'); + $loader->load('services/pagination.yml'); + $loader->load('services/export.yml'); + $loader->load('services/form.yml'); + $loader->load('services/validator.yml'); + $loader->load('services/widget.yml'); + $loader->load('services/controller.yml'); + $loader->load('services/routing.yml'); + $loader->load('services/fixtures.yml'); + $loader->load('services/menu.yml'); + $loader->load('services/security.yml'); + $loader->load('services/notification.yml'); + $loader->load('services/redis.yml'); + $loader->load('services/command.yml'); + $loader->load('services/phonenumber.yml'); + $loader->load('services/cache.yml'); + $loader->load('services/templating.yml'); + + $this->configureCruds($container, $config['cruds'], $loader); + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new Configuration($this->widgetFactories, $container); + } + + + public function prepend(ContainerBuilder $container) + { + $bundles = $container->getParameter('kernel.bundles'); + //add ChillMain to assetic-enabled bundles + if (!isset($bundles['AsseticBundle'])) { + throw new MissingBundleException('AsseticBundle'); + } + + $asseticConfig = $container->getExtensionConfig('assetic'); + $asseticConfig['bundles'][] = 'ChillMainBundle'; + $container->prependExtensionConfig('assetic', + array('bundles' => array('ChillMainBundle'))); + + //add installation_name and date_format to globals + $chillMainConfig = $container->getExtensionConfig($this->getAlias()); + $config = $this->processConfiguration($this + ->getConfiguration($chillMainConfig, $container), $chillMainConfig); + $twigConfig = array( + 'globals' => array( + 'installation' => array( + 'name' => $config['installation_name']), + 'available_languages' => $config['available_languages'] + ), + 'form_themes' => array('ChillMainBundle:Form:fields.html.twig') + ); + $container->prependExtensionConfig('twig', $twigConfig); + + //add DQL function to ORM (default entity_manager) + $container->prependExtensionConfig('doctrine', array( + 'orm' => array( + 'dql' => array( + 'string_functions' => array( + 'unaccent' => Unaccent::class, + 'GET_JSON_FIELD_BY_KEY' => GetJsonFieldByKey::class, + 'AGGREGATE' => JsonAggregate::class, + 'REPLACE' => Replace::class, + ), + 'numeric_functions' => [ + 'JSONB_EXISTS_IN_ARRAY' => JsonbExistsInArray::class, + 'SIMILARITY' => Similarity::class, + 'OVERLAPSI' => OverlapsI::class + ] + ) + ) + )); + + //add dbal types (default entity_manager) + $container->prependExtensionConfig('doctrine', array( + 'dbal' => [ + 'types' => [ + 'dateinterval' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class + ] + ] + )); + + //add current route to chill main + $container->prependExtensionConfig('chill_main', array( + 'routing' => array( + 'resources' => array( + '@ChillMainBundle/Resources/config/routing.yml' + ) + + ) + )); + + //add a channel to log app events + $container->prependExtensionConfig('monolog', array( + 'channels' => array('chill') + )); + } + + /** + * + * @param ContainerBuilder $container + * @param array $config the config under 'cruds' key + * @return null + */ + protected function configureCruds(ContainerBuilder $container, $config, Loader\YamlFileLoader $loader) + { + if (count($config) === 0) { + return; + } + + $loader->load('services/crud.yml'); + + $container->setParameter('chill_main_crud_route_loader_config', $config); + + $definition = new Definition(); + $definition + ->setClass(\Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader::class) + ->addArgument('%chill_main_crud_route_loader_config%') + ; + + $container->setDefinition('chill_main_crud_route_loader', $definition); + + $alreadyExistingNames = []; + + foreach ($config as $crudEntry) { + $controller = $crudEntry['controller']; + $controllerServiceName = 'cscrud_'.$crudEntry['name'].'_controller'; + $name = $crudEntry['name']; + + // check for existing crud names + if (\in_array($name, $alreadyExistingNames)) { + throw new LogicException(sprintf("the name %s is defined twice in CRUD", $name)); + } + + if (!$container->has($controllerServiceName)) { + $controllerDefinition = new Definition($controller); + $controllerDefinition->addTag('controller.service_arguments'); + $controllerDefinition->setAutoconfigured(true); + $controllerDefinition->setClass($crudEntry['controller']); + $container->setDefinition($controllerServiceName, $controllerDefinition); + } + + $container->setParameter('chill_main_crud_config_'.$name, $crudEntry); + $container->getDefinition($controllerServiceName) + ->addMethodCall('setCrudConfig', ['%chill_main_crud_config_'.$name.'%']); + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php new file mode 100644 index 000000000..9197ddb83 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ACLFlagsCompilerPass.php @@ -0,0 +1,39 @@ + + */ +class ACLFlagsCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $permissionGroupType = $container->getDefinition(PermissionsGroupType::class); + + foreach($container->findTaggedServiceIds('chill_main.flags') as $id => $tags) { + $reference = new Reference($id); + + foreach ($tags as $tag) { + switch($tag['scope']) { + case PermissionsGroupType::FLAG_SCOPE: + + $permissionGroupType->addMethodCall('addFlagProvider', [ $reference ]); + break; + default: + throw new \LogicalException(sprintf( + "This tag 'scope' is not implemented: %s, on service with id %s", $tag['scope'], $id) + ); + } + } + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ExportsCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ExportsCompilerPass.php new file mode 100644 index 000000000..3b4b2069e --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/ExportsCompilerPass.php @@ -0,0 +1,210 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Compiles the services tagged with : + * + * - chill.export + * - chill.export_formatter + * - chill.export_aggregator + * - chill.export_filter + * - chill.export_elements_provider + * + * + * @author Julien Fastré + */ +class ExportsCompilerPass implements CompilerPassInterface +{ + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.export_manager')) { + throw new \LogicException('service chill.main.export_manager ' + . 'is not defined. It is required by ExportsCompilerPass'); + } + + $chillManagerDefinition = $container->getDefinition( + 'chill.main.export_manager' + ); + + $this->compileExports($chillManagerDefinition, $container); + $this->compileFilters($chillManagerDefinition, $container); + $this->compileAggregators($chillManagerDefinition, $container); + $this->compileFormatters($chillManagerDefinition, $container); + $this->compileExportElementsProvider($chillManagerDefinition, $container); + } + + private function compileExports(Definition $chillManagerDefinition, + ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes["alias"])) { + throw new \LogicException("the 'alias' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["alias"], $knownAliases)) { + throw new \LogicException("There is already a chill.export service with alias " + .$attributes["alias"].". Choose another alias."); + } + $knownAliases[] = $attributes["alias"]; + + $chillManagerDefinition->addMethodCall( + 'addExport', + array(new Reference($id), $attributes["alias"]) + ); + } + } + } + + private function compileFilters(Definition $chillManagerDefinition, + ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_filter' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes["alias"])) { + throw new \LogicException("the 'alias' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["alias"], $knownAliases)) { + throw new \LogicException("There is already a chill.export_filter service with alias " + .$attributes["alias"].". Choose another alias."); + } + $knownAliases[] = $attributes["alias"]; + + $chillManagerDefinition->addMethodCall( + 'addFilter', + array(new Reference($id), $attributes["alias"]) + ); + } + } + } + + private function compileAggregators(Definition $chillManagerDefinition, + ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_aggregator' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes["alias"])) { + throw new \LogicException("the 'alias' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["alias"], $knownAliases)) { + throw new \LogicException("There is already a chill.export_aggregator service with alias " + .$attributes["alias"].". Choose another alias."); + } + $knownAliases[] = $attributes["alias"]; + + $chillManagerDefinition->addMethodCall( + 'addAggregator', + array(new Reference($id), $attributes["alias"]) + ); + } + } + } + + private function compileFormatters(Definition $chillManagerDefinition, + ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_formatter' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes["alias"])) { + throw new \LogicException("the 'alias' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["alias"], $knownAliases)) { + throw new \LogicException("There is already a chill.export_formatter service with alias " + .$attributes["alias"].". Choose another alias."); + } + $knownAliases[] = $attributes["alias"]; + + $chillManagerDefinition->addMethodCall( + 'addFormatter', + array(new Reference($id), $attributes["alias"]) + ); + } + } + } + + private function compileExportElementsProvider(Definition $chillManagerDefinition, + ContainerBuilder $container) + { + $taggedServices = $container->findTaggedServiceIds( + 'chill.export_elements_provider' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + foreach ($tagAttributes as $attributes) { + if (!isset($attributes["prefix"])) { + throw new \LogicException("the 'prefix' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["prefix"], $knownAliases)) { + throw new \LogicException("There is already a chill.export_elements_provider service with prefix " + .$attributes["prefix"].". Choose another prefix."); + } + $knownAliases[] = $attributes["prefix"]; + + $chillManagerDefinition->addMethodCall( + 'addExportElementsProvider', + array(new Reference($id), $attributes["prefix"]) + ); + } + } + } + +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php new file mode 100644 index 000000000..3239e7045 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/GroupingCenterCompilerPass.php @@ -0,0 +1,46 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; +use Chill\MainBundle\Form\Type\Export\PickCenterType; + +/** + * + * + * + */ +class GroupingCenterCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (FALSE === $container->hasDefinition('chill.main.form.pick_centers_type')) { + throw new \LogicException("The service chill.main.form.pick_centers_type does " + . "not exists in container"); + } + + $pickCenterType = $container->getDefinition('chill.main.form.pick_centers_type'); + + foreach ($container->findTaggedServiceIds('chill.grouping_center') as $serviceId => $tagged) { + $pickCenterType->addMethodCall('addGroupingCenter', + [ new Reference($serviceId) ]); + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/MenuCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/MenuCompilerPass.php new file mode 100644 index 000000000..b6b66a755 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/MenuCompilerPass.php @@ -0,0 +1,49 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; +use Chill\MainBundle\Routing\MenuComposer; + +/** + * + * + * @author Julien Fastré + */ +class MenuCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.menu_composer')) { + throw new \LogicException(sprintf("The service %s does not exists in " + . "container.", MenuComposer::class)); + } + + $menuComposerDefinition = $container->getDefinition('chill.main.menu_composer'); + + foreach ($container->findTaggedServiceIds('chill.menu_builder') as $id => $tags) { + $class = $container->getDefinition($id)->getClass(); + foreach ($class::getMenuIds() as $menuId) { + $menuComposerDefinition + ->addMethodCall('addLocalMenuBuilder', [new Reference($id), $menuId]); + } + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php new file mode 100644 index 000000000..941aeeb7a --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/NotificationCounterCompilerPass.php @@ -0,0 +1,46 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Chill\MainBundle\Templating\UI\CountNotificationUser; +use Symfony\Component\DependencyInjection\Reference; + +/** + * + * + * @author Julien Fastré + */ +class NotificationCounterCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition(CountNotificationUser::class)) { + throw new \LogicException("The service ".CountNotificationUser::class." " + . "should be defined"); + } + + $notificationCounterDefinition = $container->getDefinition(CountNotificationUser::class); + + foreach ($container->findTaggedServiceIds('chill.count_notification.user') as $id => $tags) { + $notificationCounterDefinition + ->addMethodCall('addNotificationCounter', [new Reference($id)]); + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/CompilerPass/WidgetsCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/WidgetsCompilerPass.php new file mode 100644 index 000000000..b28b8fdbb --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/CompilerPass/WidgetsCompilerPass.php @@ -0,0 +1,34 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\CompilerPass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; + +/** + * Compile the service definition to register widgets. + * + */ +class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { + + public function process(ContainerBuilder $container) + { + $this->doProcess($container, 'chill_main', 'chill_main.widgets'); + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/ConfigConsistencyCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/ConfigConsistencyCompilerPass.php new file mode 100644 index 000000000..3331b29bb --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/ConfigConsistencyCompilerPass.php @@ -0,0 +1,61 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Description of ConfigConsistencyCompilerPass + * + * @author Julien Fastré + */ +class ConfigConsistencyCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $availableLanguages = $container + ->getParameter('chill_main.available_languages'); + $methodCallsTranslator = $container + ->findDefinition('translator.default') + ->getMethodCalls(); + + $fallbackLocales = array(); + foreach($methodCallsTranslator as $call) { + if ($call[0] === 'setFallbackLocales') { + $fallbackLocales = array_merge($fallbackLocales, + $call[1][0]); + } + } + + if (count($fallbackLocales) === 0) { + throw new \LogicException('the fallback locale are not defined. ' + . 'The framework config should not allow this.'); + } + + $diff = array_diff($fallbackLocales, $availableLanguages); + if (count($diff) > 0) { + throw new \RuntimeException(sprintf('The chill_main.available_languages' + . ' parameter does not contains fallback locales. The languages %s' + . ' are missing.', implode(', ', $diff))); + } + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/Configuration.php b/src/Bundle/ChillMain/DependencyInjection/Configuration.php new file mode 100644 index 000000000..04a1fb87a --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Configuration.php @@ -0,0 +1,178 @@ +setWidgetFactories($widgetFactories); + $this->containerBuilder = $containerBuilder; + } + + /** + * {@inheritDoc} + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('chill_main'); + + $rootNode + ->children() + ->scalarNode('installation_name') + ->cannotBeEmpty() + ->defaultValue('Chill') + ->end() // end of scalar 'installation_name' + ->arrayNode('available_languages') + ->defaultValue(array('fr')) + ->prototype('scalar')->end() + ->end() // end of array 'available_languages' + ->arrayNode('routing') + ->children() + ->arrayNode('resources') + ->prototype('scalar')->end() + ->end() // end of array 'resources' + ->end() // end of children + ->end() // end of array node 'routing' + ->arrayNode('pagination') + ->canBeDisabled() + ->children() + ->integerNode('item_per_page') + ->info('The number of item to show in the page result, by default') + ->min(1) + ->defaultValue(50) + ->end() // end of integer 'item_per_page' + ->end() // end of children + ->end() // end of pagination + ->arrayNode('notifications') + ->children() + ->scalarNode('from_email') + ->cannotBeEmpty() + ->end() + ->scalarNode('from_name') + ->cannotBeEmpty() + ->end() + ->enumNode('scheme') + ->cannotBeEmpty() + ->values(['http', 'https']) + ->defaultValue('https') + ->end() + ->scalarNode('host') + ->cannotBeEmpty() + ->end() + ->end() + ->end() // end of notifications + ->arrayNode('phone_helper') + ->canBeUnset() + ->children() + ->scalarNode('twilio_sid') + ->defaultNull() + ->end() + ->scalarNode('twilio_secret') + ->defaultNull() + ->end() + ->end() + ->end() + ->arrayNode('redis') + ->children() + ->scalarNode('host') + ->cannotBeEmpty() + ->end() + ->scalarNode('port') + ->defaultValue(6379) + ->end() + ->scalarNode('timeout') + ->defaultValue(1) + ->end() + ->end() + ->end() + ->arrayNode('widgets') + ->canBeEnabled() + ->canBeUnset() + ->children() + ->append($this->addWidgetsConfiguration('homepage', $this->containerBuilder)) + ->end() // end of widgets/children + ->end() // end of widgets + ->arrayNode('cruds') + ->defaultValue([]) + ->arrayPrototype() + ->children() + ->scalarNode('class')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('controller') + ->cannotBeEmpty() + ->defaultValue(\Chill\MainBundle\CRUD\Controller\CRUDController::class) + ->end() + ->scalarNode('name')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end() + ->scalarNode('base_role')->defaultNull()->end() + ->scalarNode('form_class')->defaultNull()->end() + ->arrayNode('actions') + ->defaultValue([ + 'edit' => [], + 'new' => [] + ]) + ->useAttributeAsKey('name') + ->arrayPrototype() + ->children() + ->scalarNode('controller_action') + ->defaultNull() + ->info('the method name to call in the route. Will be set to the action name if left empty.') + ->example("'action'") + ->end() + ->scalarNode('path') + ->defaultNull() + ->info('the path that will be **appended** after the base path. Do not forget to add ' + . 'arguments for the method. Will be set to the action name, including an `{id}` ' + . 'parameter if left empty.') + ->example('/{id}/my-action') + ->end() + ->arrayNode('requirements') + ->ignoreExtraKeys(false) + ->info('the requirements for the route. Will be set to `[ \'id\' => \'\d+\' ]` if left empty.') + ->end() + ->scalarNode('role') + ->defaultNull() + ->info('the role that will be required for this action. Override option `base_role`') + ->end() + ->scalarNode('template') + ->defaultNull() + ->info('the template to render the view') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + + ->end() + ->end() // end of root/children + ->end() // end of root + ; + + + return $treeBuilder; + } +} diff --git a/src/Bundle/ChillMain/DependencyInjection/MissingBundleException.php b/src/Bundle/ChillMain/DependencyInjection/MissingBundleException.php new file mode 100644 index 000000000..2b4f33b88 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/MissingBundleException.php @@ -0,0 +1,20 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; + +/** + * + * + * @author Julien Fastré + */ +class RoleProvidersCompilerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.role_provider')) { + throw new \LogicException('service chill.main.role_provider ' + . 'is not defined. It is required by RoleProviderCompilerPass'); + } + + $definition = $container->getDefinition( + 'chill.main.role_provider' + ); + + $taggedServices = $container->findTaggedServiceIds( + 'chill.role' + ); + + foreach ($taggedServices as $id => $tagAttributes) { + $definition->addMethodCall( + 'addProvider', + array(new Reference($id)) + ); + } + + } + +} diff --git a/src/Bundle/ChillMain/DependencyInjection/SearchableServicesCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/SearchableServicesCompilerPass.php new file mode 100644 index 000000000..12f27933e --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/SearchableServicesCompilerPass.php @@ -0,0 +1,73 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Reference; + +class SearchableServicesCompilerPass implements CompilerPassInterface +{ + + /* + * (non-PHPdoc) + * @see \Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface::process() + */ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.search_provider')) { + throw new \LogicException('service chill.main.search_provider ' + . 'is not defined.'); + } + + $definition = $container->getDefinition( + 'chill.main.search_provider' + ); + + $taggedServices = $container->findTaggedServiceIds( + 'chill.search' + ); + + $knownAliases = array(); + + foreach ($taggedServices as $id => $tagAttributes) { + + foreach ($tagAttributes as $attributes) { + + if (!isset($attributes["alias"])) { + throw new \LogicException("the 'name' attribute is missing in your ". + "service '$id' definition"); + } + + if (array_search($attributes["alias"], $knownAliases)) { + throw new \LogicException("There is already a chill.search service with alias " + .$attributes["alias"].". Choose another alias."); + } + $knownAliases[] = $attributes["alias"]; + + $definition->addMethodCall( + 'addSearchService', + array(new Reference($id), $attributes["alias"]) + ); + } + } + } +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/DependencyInjection/TimelineCompilerClass.php b/src/Bundle/ChillMain/DependencyInjection/TimelineCompilerClass.php new file mode 100644 index 000000000..92aaaa003 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/TimelineCompilerClass.php @@ -0,0 +1,65 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Add services taggued with `name: chill.timeline` to + * timeline_builder service definition + * + * @author Julien Fastré + */ +class TimelineCompilerClass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition('chill.main.timeline_builder')) { + throw new \LogicException('service chill.main.timeline_builder ' + . 'is not defined.'); + } + + $definition = $container->getDefinition( + 'chill.main.timeline_builder' + ); + + $taggedServices = $container->findTaggedServiceIds( + 'chill.timeline' + ); + + foreach ($taggedServices as $id => $tagAttributes) { + + foreach ($tagAttributes as $attributes) { + + if (!isset($attributes["context"])) { + throw new \LogicException("the 'context' attribute is missing in your ". + "service '$id' definition"); + } + + $definition->addMethodCall( + 'addProvider', + array($attributes["context"], $id) + ); + } + } + } + +} diff --git a/src/Bundle/ChillMain/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php b/src/Bundle/ChillMain/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php new file mode 100644 index 000000000..d26e09293 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Widget/AbstractWidgetsCompilerPass.php @@ -0,0 +1,388 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\Widget; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Doctrine\Common\Proxy\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Definition; +use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; +use Chill\MainBundle\DependencyInjection\Widget\HasWidgetFactoriesExtensionInterface; + +/** + * Compile the configurations and inject required service into container. + * + * The widgets are services tagged with : + * + * ``` + * { name: chill_widget, alias: my_alias, place: my_place } + * ``` + * + * Or, if the tag does not exist or if you need to add some config to your + * service depending on the config, you should use a `WidgetFactory` (see + * `WidgetFactoryInterface`. + * + * To reuse this compiler pass, simple execute the doProcess metho in your + * compiler. Example : + * + * ``` + * namespace Chill\MainBundle\DependencyInjection\CompilerPass; + * + * use Symfony\Component\DependencyInjection\ContainerBuilder; + * use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass; + * class WidgetsCompilerPass extends AbstractWidgetsCompilerPass { + * + * public function process(ContainerBuilder $container) + * { + * $this->doProcess($container, 'chill_main', 'chill_main.widgets'); + * } + * } + * ``` + * + * + */ +abstract class AbstractWidgetsCompilerPass implements CompilerPassInterface +{ + private $widgetServices = array(); + + /** + * + * @var WidgetFactoryInterface[] + */ + private $widgetFactories; + + /** + * The service which will manage the widgets + * + * @var string + */ + const WIDGET_MANAGER = 'chill.main.twig.widget'; + + /** + * the method wich register the widget into give service. + */ + const WIDGET_MANAGER_METHOD_REGISTER = 'addWidget'; + + /** + * the value of the `name` key in service definitions's tag + * + * @var string + */ + const WIDGET_SERVICE_TAG_NAME = 'chill_widget'; + + /** + * the key used to collect the alias in the service definition's tag. + * the alias must be + * injected into the configuration under 'alias' key. + * + * @var string + */ + const WIDGET_SERVICE_TAG_ALIAS = 'alias'; + + /** + * the key used to collect the authorized place in the service definition's tag + * + * @var string + */ + const WIDGET_SERVICE_TAG_PLACES = 'place'; + + /** + * the key to use to order widget for a given place + */ + const WIDGET_CONFIG_ORDER = 'order'; + + /** + * the key to use to identify widget for a given place + */ + const WIDGET_CONFIG_ALIAS = 'widget_alias'; + + + /** + * process the configuration and the container to add the widget available + * + * @param ContainerBuilder $container + * @param string $extension the extension of your bundle + * @param string $containerWidgetConfigParameterName the key under which we can use the widget configuration + * @throws \LogicException + * @throws \UnexpectedValueException if the given extension does not implement HasWidgetExtensionInterface + * @throws \InvalidConfigurationException if there are errors in the config + */ + public function doProcess(ContainerBuilder $container, $extension, + $containerWidgetConfigParameterName) + { + if (!$container->hasDefinition(self::WIDGET_MANAGER)) { + throw new \LogicException("the service ".self::WIDGET_MANAGER." should". + " be present. It is required by ".self::class); + } + + $managerDefinition = $container->getDefinition(self::WIDGET_MANAGER); + + // collect the widget factories + /* @var $extensionClass HasWidgetFactoriesExtensionInterface */ + $extensionClass = $container->getExtension($extension); + // throw an error if extension does not implement HasWidgetFactoriesExtensionInterface + if (!$extensionClass instanceof HasWidgetFactoriesExtensionInterface) { + throw new \UnexpectedValueException(sprintf("The extension for %s " + . "do not implements %s. You should implement %s on %s", + $extension, + HasWidgetFactoriesExtensionInterface::class, + HasWidgetFactoriesExtensionInterface::class, + get_class($extensionClass))); + } + + + $this->widgetFactories = $extensionClass->getWidgetFactories(); + + // collect the availabled tagged services + $this->collectTaggedServices($container); + + // collect the widgets and their config : + $widgetParameters = $container->getParameter($containerWidgetConfigParameterName); + + // and add them to the delegated_block + foreach($widgetParameters as $place => $widgets) { + + foreach ($widgets as $param) { + $alias = $param[self::WIDGET_CONFIG_ALIAS]; + // check that the service exists + if (!array_key_exists($alias, $this->widgetServices)) { + throw new InvalidConfigurationException(sprintf("The alias %s". + " is not defined.", $alias)); + } + + // check that the widget is allowed at this place + if (!$this->isPlaceAllowedForWidget($place, $alias, $container)) { + throw new \InvalidConfigurationException(sprintf( + "The widget with alias %s is not allowed at place %s", + $alias, + $place + )); + } + + // get the order, eventually corrected + $order = $this->cacheAndGetOrdering($place, $param[self::WIDGET_CONFIG_ORDER]); + + // register the widget with config to the service, using the method + // `addWidget` + if ($this->widgetServices[$alias] instanceof WidgetFactoryInterface) { + /* @var $factory WidgetFactoryInterface */ + $factory = $this->widgetServices[$alias]; + // get the config (under the key which equals to widget_alias + $config = isset($param[$factory->getWidgetAlias()]) ? + $param[$factory->getWidgetAlias()] : array(); + // register the service into the container + $serviceId =$this->registerServiceIntoContainer($container, + $factory, $place, $order, $config); + + $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, + array( + $place, + $order, + new Reference($serviceId), + $config + )); + } else { + $managerDefinition->addMethodCall(self::WIDGET_MANAGER_METHOD_REGISTER, + array( + $place, + $order, + new Reference($this->widgetServices[$alias]), + array() // the config is alway an empty array + )); + } + } + } + } + + /** + * register the service into container. + * + * @param ContainerBuilder $container + * @param WidgetFactoryInterface $factory + * @param string $place + * @param float $order + * @param array $config + * @return string the id of the new service + */ + protected function registerServiceIntoContainer( + ContainerBuilder $container, + WidgetFactoryInterface $factory, + $place, + $order, + array $config + ) { + $serviceId = $factory->getServiceId($container, $place, $order, $config); + $definition = $factory->createDefinition($container, $place, + $order, $config); + $container->setDefinition($serviceId, $definition); + + return $serviceId; + } + + /** + * cache of ordering by place. + * + * @internal used by function cacheAndGetOrdering + * @var array + */ + private $cacheOrdering = array(); + + /** + * check if the ordering has already be used for the given $place and, + * if yes, correct the ordering by incrementation of 1 until the ordering + * has not be used. + * + * recursive method. + * + * @param string $place + * @param float $ordering + * @return float + */ + private function cacheAndGetOrdering($place, $ordering) { + // create a key in the cache array if not exists + if (!array_key_exists($place, $this->cacheOrdering)) { + $this->cacheOrdering[$place] = array(); + } + + // check if the order exists + if (array_search($ordering, $this->cacheOrdering[$place])) { + // if the order exists, increment of 1 and try again + return $this->cacheAndGetOrdering($place, $ordering + 1); + } else { + // cache the ordering + $this->cacheOrdering[$place][] = $ordering; + + return $ordering; + } + } + + /** + * get the places where the service is allowed + * + * @param Definition $definition + * @return unknown + */ + private function isPlaceAllowedForWidget($place, $widgetAlias, ContainerBuilder $container) + { + if ($this->widgetServices[$widgetAlias] instanceof WidgetFactoryInterface) { + if (in_array($place, $this->widgetServices[$widgetAlias] + ->getAllowedPlaces())) { + return true; + } + + } else { + $definition = $container->findDefinition($this->widgetServices[$widgetAlias]); + + foreach($definition->getTag(self::WIDGET_SERVICE_TAG_NAME) as $attrs) { + $placeValue = $attrs[self::WIDGET_SERVICE_TAG_PLACES]; + + if ($placeValue === $place) { + return true; + } + } + } + + return false; + } + + /** + * This method collect all service tagged with `self::WIDGET_SERVICE_TAG`, and + * add also the widget defined by factories + * + * This method also check that the service is correctly tagged with `alias` and + * `places`, or the factory give a correct alias and more than one place. + * + * @param ContainerBuilder $container + * @throws InvalidConfigurationException + * @throws InvalidArgumentException + */ + protected function collectTaggedServices(ContainerBuilder $container) + { + // first, check the service tagged in service definition + foreach ($container->findTaggedServiceIds(self::WIDGET_SERVICE_TAG_NAME) as $id => $attrs) { + foreach ($attrs as $attr) { + + // check the alias is set + if (!isset($attr[self::WIDGET_SERVICE_TAG_ALIAS])) { + throw new InvalidConfigurationException("you should add an ".self::WIDGET_SERVICE_TAG_ALIAS. + " key on the service ".$id); + } + + // check the place is set + if (!isset($attr[self::WIDGET_SERVICE_TAG_PLACES])) { + throw new InvalidConfigurationException(sprintf( + "You should add a %s key on the service %s", + self::WIDGET_SERVICE_TAG_PLACES, + $id + )); + } + + // check the alias does not exists yet + if (array_key_exists($attr[self::WIDGET_SERVICE_TAG_ALIAS], $this->widgetServices)) { + throw new InvalidArgumentException("a service has already be defined with the ". + self::WIDGET_SERVICE_TAG_ALIAS." ".$attr[self::WIDGET_SERVICE_TAG_ALIAS]); + } + + // register the service as available + $this->widgetServices[$attr[self::WIDGET_SERVICE_TAG_ALIAS]] = $id; + } + } + + // add the services defined by factories + foreach($this->widgetFactories as $factory) { + /* @var $factory WidgetFactoryInterface */ + $alias = $factory->getWidgetAlias(); + + // check the alias is not empty + if (empty($alias)) { + throw new \LogicException(sprintf( + "the widget factory %s returns an empty alias", + get_class($factory))); + } + + // check the places are not empty + if (!is_array($factory->getAllowedPlaces())) { + throw new \UnexpectedValueException("the method 'getAllowedPlaces' " + . "should return a non-empty array. Unexpected value on ". + get_class($factory)); + } + + if (count($factory->getAllowedPlaces()) == 0) { + throw new \LengthException("The method 'getAllowedPlaces' should " + . "return a non-empty array, but returned 0 elements on ". + get_class($factory).'::getAllowedPlaces()'); + } + + // check the alias does not exists yet + if (array_key_exists($alias, $this->widgetServices)) { + throw new InvalidArgumentException("a service has already be defined with the ". + self::WIDGET_SERVICE_TAG_ALIAS." ".$alias); + } + + // register the factory as available + $this->widgetServices[$factory->getWidgetAlias()] = $factory; + + } + } + + +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/DependencyInjection/Widget/AddWidgetConfigurationTrait.php b/src/Bundle/ChillMain/DependencyInjection/Widget/AddWidgetConfigurationTrait.php new file mode 100644 index 000000000..81e91c53e --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Widget/AddWidgetConfigurationTrait.php @@ -0,0 +1,252 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\Widget; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Chill\MainBundle\DependencyInjection\Widget\AbstractWidgetsCompilerPass as WidgetsCompilerPass; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + + +/** + * This trait allow to add automatic configuration for widget inside your config. + * + * Usage + * ====== + * + * 1. Register widget factories + * ---------------------------- + * + * Add widget factories, using `setWidgetFactories` + * + * Example : + * + * ``` + * use Symfony\Component\DependencyInjection\ContainerBuilder; + * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait; + * + * class MyConfig + * { + * + * use addWidgetConfigurationTrait; + * + * public function __construct(array $widgetFactories = array(), ContainerBuilder $container) + * { + * $this->setWidgetFactories($widgetFactories); + * // will be used on next step + * $this->container = $container; + * } + * + * } + * ``` + * + * + * + * 2. add widget config to your config + * ----------------------------------- + * + * ``` + * use Symfony\Component\DependencyInjection\ContainerBuilder; + * use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait; + * use Symfony\Component\Config\Definition\Builder\TreeBuilder; + * + * class MyConfig + * { + * + * use addWidgetConfigurationTrait; + * + * private $container; + * + * public function __construct(array $widgetFactories = array(), ContainerBuilder $container) + * { + * $this->setWidgetFactories($widgetFactories); + * $this->container; + * } + * + * public function getConfigTreeBuilder() + * { + * $treeBuilder = new TreeBuilder(); + * $root = $treeBuilder->root('my_root'); + * + * $root->children() + * ->arrayNode('widgets') + * ->canBeDisabled() + * ->children() + * ->append($this->addWidgetsConfiguration('homepage', $this->container)) + * ->end() + * ->end() + * ; + * + * return $treeBuilder; + * } + * + * } + * ``` + * + * the above code will add to the config : + * + * ``` + * widgets: + * enabled: true + * + * # register widgets on place "homepage" + * homepage: + * order: ~ # Required + * widget_alias: ~ # One of "person_list"; "add_person", Required + * person_list: + * # options for the person_list + * ``` + * + * + */ +trait AddWidgetConfigurationTrait +{ + /** + * @param WidgetFactoryInterface[] + */ + private $widgetFactories; + + /** + * + * @param WidgetFactoryInterface[] $widgetFactories + */ + public function setWidgetFactories(array $widgetFactories) + { + $this->widgetFactories = $widgetFactories; + } + + /** + * @return WidgetFactoryInterface[] + */ + public function getWidgetFactories() + { + return $this->widgetFactories; + } + + /** + * add configuration nodes for the widget at the given place. + * + * @param type $place + * @param ContainerBuilder $containerBuilder + * @return type + */ + protected function addWidgetsConfiguration($place, ContainerBuilder $containerBuilder) + { + $treeBuilder = new TreeBuilder(); + $root = $treeBuilder->root($place) + ->canBeUnset() + ->info('register widgets on place "'.$place.'"'); + + // if no childen, return the root + if (count(iterator_to_array($this->filterWidgetByPlace($place))) === 0) { + return $root; + } + + $prototypeChildren = $root->prototype('array')->children(); + + $prototypeChildren + ->floatNode(WidgetsCompilerPass::WIDGET_CONFIG_ORDER) + ->isRequired() + ->info("the ordering of the widget. May be a number with decimal") + ->example("10.58") + ->end() + ->scalarNode(WidgetsCompilerPass::WIDGET_CONFIG_ALIAS) + // this node is scalar: when the configuration is build, the + // tagged services are not available. But when the config reference + // is build, the services are avaialble => we add the possible aliases + // in the info. + ->info("the widget alias (see your installed bundles config). " + . "Possible values are (maybe incomplete) : ". + \implode(", ", $this->getWidgetAliasesbyPlace($place, $containerBuilder))) + ->isRequired() + ->end() + ; + + // adding the possible config on each widget under the widget_alias + foreach ($this->filterWidgetByPlace($place) as $factory) { + $builder = new TreeBuilder(); + $widgetOptionsRoot = $builder->root($factory->getWidgetAlias()); + $widgetOptionsRoot->canBeUnset() + ->info(sprintf('the configuration for the widget "%s" (only required if this widget is set in widget_alias)', + $factory->getWidgetAlias())); + $factory->configureOptions($place, $widgetOptionsRoot->children()); + $prototypeChildren->append($widgetOptionsRoot); + } + + return $root; + } + + /** + * get all widget factories for the given place. + * + * @param string $place + * @return \Generator a generator containing a widget factory + */ + protected function filterWidgetByPlace($place) + { + foreach($this->widgetFactories as $factory) { + if (in_array($place, $factory->getAllowedPlaces())) { + yield $factory; + } + } + } + + /** + * get the all possible aliases for the given place. This method + * search within service tags and widget factories + * + * **Note** that services are not available when the config is build: the whole + * aliases will be checked in compiler pass, or when the command + * `config:dump-reference` is runned. + * + * @param type $place + * @param ContainerBuilder $containerBuilder + * @return type + * @throws InvalidConfigurationException if a service's tag does not have the "alias" key + */ + protected function getWidgetAliasesbyPlace($place, ContainerBuilder $containerBuilder) + { + $result = array(); + + foreach ($this->filterWidgetByPlace($place) as $factory) { + $result[] = $factory->getWidgetAlias(); + } + + // append the aliases added without factory + foreach ($containerBuilder + ->findTaggedServiceIds(WidgetsCompilerPass::WIDGET_SERVICE_TAG_NAME) + as $serviceId => $tags) { + foreach ($tags as $tag) { + // throw an error if no alias in definition + if (!array_key_exists(WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS, $tag)) { + throw new InvalidConfigurationException(sprintf( + "The service with id %s does not have any %d key", + $serviceId, + WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS + )); + } + // add the key to the possible results + $result[] = $tag[WidgetsCompilerPass::WIDGET_SERVICE_TAG_ALIAS]; + } + } + + return $result; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php b/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php new file mode 100644 index 000000000..7e5a12871 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/AbstractWidgetFactory.php @@ -0,0 +1,57 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\Widget\Factory; + +use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Allow to easily create WidgetFactory. + * + * Extending this factory, the widget will be created using the already defined + * service created "as other services" in your configuration (the most frequent + * way is using `services.yml` file. + * + * If you need to create different service based upon place, position or + * definition, you should implements directly + * `Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface` + * + * + */ +abstract class AbstractWidgetFactory implements WidgetFactoryInterface +{ + + /** + * + * {@inheritdoc} + * + * Will create the definition by returning the definition from the `services.yml` + * file (or `services.xml` or `what-you-want.yml`). + * + * @see \Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface::createDefinition() + */ + public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config) + { + return $containerBuilder->getDefinition($this + ->getServiceId($containerBuilder, $place, $order, $config) + ); + } + +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php b/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php new file mode 100644 index 000000000..6e7836b01 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Widget/Factory/WidgetFactoryInterface.php @@ -0,0 +1,103 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\Widget\Factory; + + +use Symfony\Component\Config\Definition\Builder\NodeBuilder; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Factory for creating configuration of widgets. + * + * When you need a widget with some configuration, you should implements this + * interface on a factory. The factory will add configuration to the bundle + * giving the places for your widget. + * + * Using this interface, **you do not need** to define the service in your + * container configuration (`services.yml` files). + * + * Once the class is created, you should inject the factory inside the container + * at compile time, in your `Bundle` class : + * + * + * ``` + * namespace Chill\PersonBundle; + * + * use Symfony\Component\HttpKernel\Bundle\Bundle; + * use Symfony\Component\DependencyInjection\ContainerBuilder; + * use Chill\PersonBundle\Widget\PersonListWidgetFactory; + * + * class ChillPersonBundle extends Bundle + * { + * public function build(ContainerBuilder $container) + * { + * parent::build($container); + * // register a widget factory into chill_main : + * $container->getExtension('chill_main') + * ->addWidgetFactory(new PersonListWidgetFactory()); + * } + * } + * ``` + * + * + * + */ +interface WidgetFactoryInterface +{ + /** + * configure options for the widget. Those options will be added in + * configuration in the bundle where the widget will be used. + * + * @param type $place + * @param NodeBuilder $node + */ + public function configureOptions($place, NodeBuilder $node); + + /** + * get the widget alias. This alias will be used in configuration (`config.yml`) + */ + public function getWidgetAlias(); + + /** + * Create a definition for the service which will render the widget. + * + * (Note: you can define the service by yourself, as other services, + * using the `AbstractWidgetFactory`) + * + * @param ContainerBuilder $containerBuilder + * @param type $place + * @param type $order + * @param array $config + */ + public function createDefinition(ContainerBuilder $containerBuilder, $place, $order, array $config); + + /** + * return the service id to build the widget. + * + * @param ContainerBuilder $containerBuilder + * @param string $place + * @param float $order + * @param array $config + * + * @return string the service definition + * + */ + public function getServiceId(ContainerBuilder $containerBuilder, $place, $order, array $config); +} diff --git a/src/Bundle/ChillMain/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php b/src/Bundle/ChillMain/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php new file mode 100644 index 000000000..b25c487b6 --- /dev/null +++ b/src/Bundle/ChillMain/DependencyInjection/Widget/HasWidgetFactoriesExtensionInterface.php @@ -0,0 +1,43 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\DependencyInjection\Widget; + +use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface; + +/** + * Register widget factories to an extension. + * + */ +interface HasWidgetFactoriesExtensionInterface { + + /** + * register a widget factory + * + * @param \Chill\MainBundle\DependencyInjection\Widget\WidgetFactoryInterface $factory + */ + public function addWidgetFactory(WidgetFactoryInterface $factory); + + /** + * get all the widget factories registered for this extension + * + * @return WidgetFactoryInterface[] + */ + public function getWidgetFactories(); +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/GetJsonFieldByKey.php b/src/Bundle/ChillMain/Doctrine/DQL/GetJsonFieldByKey.php new file mode 100644 index 000000000..addc6b9b7 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/GetJsonFieldByKey.php @@ -0,0 +1,53 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; + +/** + * + * + * @author Julien Fastré + */ +class GetJsonFieldByKey extends FunctionNode +{ + private $expr1; + private $expr2; + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->expr1 = $parser->StringPrimary(); + $parser->match(Lexer::T_COMMA); + $this->expr2 = $parser->StringPrimary(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(SqlWalker $sqlWalker) + { + return sprintf( + '(%s->%s)', + $this->expr1->dispatch($sqlWalker), + $this->expr2->dispatch($sqlWalker) + ); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/JsonAggregate.php b/src/Bundle/ChillMain/Doctrine/DQL/JsonAggregate.php new file mode 100644 index 000000000..54ef45493 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/JsonAggregate.php @@ -0,0 +1,49 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\SqlWalker; + +/** + * Return an aggregation of values in a json representation, as a string. + * + * Internally, this function use the postgresql `jsonb_agg` function. Using + * json allow to aggregate data from different types without have to cast them. + * + * @author Julien Fastré + */ +class JsonAggregate extends FunctionNode +{ + private $expr; + + public function getSql(SqlWalker $sqlWalker) + { + return sprintf('jsonb_agg(%s)', $this->expr->dispatch($sqlWalker)); + } + + public function parse(Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->expr = $parser->StringPrimary(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/JsonbExistsInArray.php b/src/Bundle/ChillMain/Doctrine/DQL/JsonbExistsInArray.php new file mode 100644 index 000000000..6c4eaa5c3 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/JsonbExistsInArray.php @@ -0,0 +1,41 @@ + + */ +class JsonbExistsInArray extends FunctionNode +{ + private $expr1; + private $expr2; + + public function getSql(SqlWalker $sqlWalker): string + { + return sprintf( + 'jsonb_exists(%s, %s)', + $this->expr1->dispatch($sqlWalker), + $sqlWalker->walkInputParameter($this->expr2) + ); + } + + public function parse(Parser $parser): void + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + $this->expr1 = $parser->StringPrimary(); + $parser->match(Lexer::T_COMMA); + $this->expr2 = $parser->InputParameter(); + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/OverlapsI.php b/src/Bundle/ChillMain/Doctrine/DQL/OverlapsI.php new file mode 100644 index 000000000..ae9df72f4 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/OverlapsI.php @@ -0,0 +1,110 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; + +/** + * DQL function for OVERLAPS function in postgresql + * + * If a value is null in period start, it will be replaced by -infinity. + * If a value is null in period end, it will be replaced by infinity + * + */ +class OverlapsI extends FunctionNode +{ + private $firstPeriodStart; + + private $firstPeriodEnd; + + private $secondPeriodStart; + + private $secondPeriodEnd; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return '(' + .$this->makeCase($sqlWalker, $this->firstPeriodStart, 'start').', ' + .$this->makeCase($sqlWalker, $this->firstPeriodEnd, 'end'). + ') OVERLAPS (' + .$this->makeCase($sqlWalker, $this->secondPeriodStart, 'start').', ' + .$this->makeCase($sqlWalker, $this->secondPeriodEnd, 'end').')' + ; + } + + protected function makeCase($sqlWalker, $part, $position) + { + //return $part->dispatch($sqlWalker); + + switch ($position) { + case 'start' : + $p = '-infinity'; + break; + case 'end': + $p = 'infinity'; + break; + } + + if ($part instanceof \Doctrine\ORM\Query\AST\PathExpression) { + return 'CASE WHEN ' + .' '.$part->dispatch($sqlWalker).' IS NOT NULL ' + . 'THEN '. + $part->dispatch($sqlWalker) + . ' ELSE '. + "'".$p."'::date " + . 'END'; + } else { + return 'CASE WHEN ' + .' '.$part->dispatch($sqlWalker).'::date IS NOT NULL ' + . 'THEN '. + $part->dispatch($sqlWalker) + . '::date ELSE '. + "'".$p."'::date " + . 'END'; + } + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstPeriodStart = $parser->StringPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->firstPeriodEnd = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + + $parser->match(Lexer::T_COMMA); + + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->secondPeriodStart = $parser->StringPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->secondPeriodEnd = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/Replace.php b/src/Bundle/ChillMain/Doctrine/DQL/Replace.php new file mode 100644 index 000000000..6d30ec7d9 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/Replace.php @@ -0,0 +1,64 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; + +/** + * + * + */ +class Replace extends FunctionNode +{ + protected $string; + + protected $from; + + protected $to; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker): string + { + return 'REPLACE('. + $this->string->dispatch($sqlWalker). + ', '. + $this->from->dispatch($sqlWalker). + ', '. + $this->to->dispatch($sqlWalker). + ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser): void + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->string = $parser->StringPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->from = $parser->StringPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->to = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/Similarity.php b/src/Bundle/ChillMain/Doctrine/DQL/Similarity.php new file mode 100644 index 000000000..988e55f5f --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/Similarity.php @@ -0,0 +1,54 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; + +/** + * + * + * + */ +class Similarity extends FunctionNode +{ + private $firstPart; + + private $secondPart; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'SIMILARITY('.$this->firstPart->dispatch($sqlWalker). + ', ' . $this->secondPart->dispatch($sqlWalker) .")"; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstPart = $parser->StringPrimary(); + + $parser->match(Lexer::T_COMMA); + + $this->secondPart = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } +} diff --git a/src/Bundle/ChillMain/Doctrine/DQL/Unaccent.php b/src/Bundle/ChillMain/Doctrine/DQL/Unaccent.php new file mode 100644 index 000000000..2ced15344 --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/DQL/Unaccent.php @@ -0,0 +1,53 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Doctrine\DQL; + +use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\Lexer; + +/** + * Unaccent string using postgresql extension unaccent : + * http://www.postgresql.org/docs/current/static/unaccent.html + * + * Usage : StringFunction UNACCENT(string) + * + * @author Julien Fastré + */ +class Unaccent extends FunctionNode +{ + private $string; + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'UNACCENT(' . $this->string->dispatch($sqlWalker) .")"; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->string = $parser->StringPrimary(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + +} diff --git a/src/Bundle/ChillMain/Doctrine/Type/NativeDateIntervalType.php b/src/Bundle/ChillMain/Doctrine/Type/NativeDateIntervalType.php new file mode 100644 index 000000000..38c704a2d --- /dev/null +++ b/src/Bundle/ChillMain/Doctrine/Type/NativeDateIntervalType.php @@ -0,0 +1,111 @@ + + */ +class NativeDateIntervalType extends DateIntervalType +{ + const FORMAT = '%rP%YY%MM%DDT%HH%IM%SS'; + + public function getName(): string + { + return \Doctrine\DBAL\Types\Type::DATEINTERVAL; + } + + public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + { + return 'INTERVAL'; + } + + /** + * {@inheritdoc} + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (null === $value) { + return null; + } + + if ($value instanceof \DateInterval) { + return $value->format(self::FORMAT); + } + + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['null', 'DateInterval']); + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null || $value instanceof \DateInterval) { + return $value; + } + + try { + $strings = explode(' ', $value); + + if (count($strings) === 0) { + return null; + } + $intervalSpec = 'P'; + \reset($strings); + + do { + $intervalSpec .= $this->convertEntry($strings); + } while (next($strings) !== FALSE); + + return new \DateInterval($intervalSpec); + } catch (\Exception $exception) { + throw $this->createConversionException($value, $exception); + } + } + + private function convertEntry(&$strings) + { + $current = \current($strings); + + if (is_numeric($current)) { + $next = \next($strings); + switch($next) { + case 'year': + case 'years': + $unit = 'Y'; + break; + case 'mon': + case 'mons': + $unit = 'M'; + break; + case 'day': + case 'days': + $unit = 'D'; + break; + default: + throw $this->createConversionException(implode('', $strings)); + } + + return $current.$unit; + + } elseif (\preg_match('/([0-9]{2}\:[0-9]{2}:[0-9]{2})/', $current) === 1) { + $tExploded = explode(':', $current); + $intervalSpec = 'T'; + $intervalSpec.= $tExploded[0].'H'; + $intervalSpec.= $tExploded[1].'M'; + $intervalSpec.= $tExploded[2].'S'; + + return $intervalSpec; + } + } + + protected function createConversionException($value, $exception = null) + { + return ConversionException::conversionFailedFormat($value, $this->getName(), 'xx year xx mons xx days 01:02:03', $exception); + } + +} diff --git a/src/Bundle/ChillMain/Entity/Address.php b/src/Bundle/ChillMain/Entity/Address.php new file mode 100644 index 000000000..63e7b4479 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/Address.php @@ -0,0 +1,269 @@ +validFrom = new \DateTime(); + } + + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Set streetAddress1 + * + * @param string $streetAddress1 + * + * @return Address + */ + public function setStreetAddress1($streetAddress1) + { + $this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1; + + return $this; + } + + /** + * Get streetAddress1 + * + * @return string + */ + public function getStreetAddress1() + { + return $this->streetAddress1; + } + + /** + * Set streetAddress2 + * + * @param string $streetAddress2 + * + * @return Address + */ + public function setStreetAddress2($streetAddress2) + { + $this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2; + + return $this; + } + + /** + * Get streetAddress2 + * + * @return string + */ + public function getStreetAddress2() + { + return $this->streetAddress2; + } + + /** + * Set postcode + * + * @param \Chill\MainBundle\Entity\PostalCode $postcode + * + * @return Address + */ + public function setPostcode(\Chill\MainBundle\Entity\PostalCode $postcode = null) + { + $this->postcode = $postcode; + + return $this; + } + + /** + * Get postcode + * + * @return \Chill\MainBundle\Entity\PostalCode + */ + public function getPostcode() + { + return $this->postcode; + } + + /** + * + * @return \DateTime + */ + public function getValidFrom() + { + return $this->validFrom; + } + + /** + * + * @param \DateTime $validFrom + * @return \Chill\MainBundle\Entity\Address + */ + public function setValidFrom(\DateTime $validFrom) + { + $this->validFrom = $validFrom; + return $this; + } + + /** + * get "isNoAddress" + * + * Indicate true if the address is a fake address (homeless, ...) + * + * @return bool + */ + public function getIsNoAddress(): bool + { + return $this->isNoAddress; + } + + public function isNoAddress(): bool + { + return $this->getIsNoAddress(); + } + + /** + * set Is No Address + * + * Indicate true if the address is a fake address (homeless, ...) + * + * @param bool $isNoAddress + * @return $this + */ + public function setIsNoAddress(bool $isNoAddress) + { + $this->isNoAddress = $isNoAddress; + return $this; + } + + /** + * Get customs informations in the address + * + * @return array + */ + public function getCustoms(): array + { + return $this->customs; + } + + /** + * Store custom informations in the address + * + * @param array $customs + * @return $this + */ + public function setCustoms(array $customs): self + { + $this->customs = $customs; + + return $this; + } + + /** + * Validate the address. + * + * Check that: + * + * * if the address is not home address: + * * the postal code is present + * * the valid from is not null + * * the address street 1 is greater than 2 + * + * @param ExecutionContextInterface $context + * @param array $payload + */ + public function validate(ExecutionContextInterface $context, $payload) + { + if (!$this->getValidFrom() instanceof \DateTime) { + $context + ->buildViolation("address.date-should-be-set") + ->atPath('validFrom') + ->addViolation(); + } + + if ($this->isNoAddress()) { + return; + } + + if (empty($this->getStreetAddress1())) { + $context + ->buildViolation("address.street1-should-be-set") + ->atPath('streetAddress1') + ->addViolation(); + } + + if (!$this->getPostcode() instanceof PostalCode) { + $context + ->buildViolation("address.postcode-should-be-set") + ->atPath('postCode') + ->addViolation(); + } + } + + + public static function createFromAddress(Address $original) : Address + { + return (new Address()) + ->setPostcode($original->getPostcode()) + ->setStreetAddress1($original->getStreetAddress1()) + ->setStreetAddress2($original->getStreetAddress2()) + ->setValidFrom($original->getValidFrom()) + ; + } + +} + diff --git a/src/Bundle/ChillMain/Entity/Center.php b/src/Bundle/ChillMain/Entity/Center.php new file mode 100644 index 000000000..8eb5331f0 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/Center.php @@ -0,0 +1,90 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +/** + * + * + * @author Julien Fastré + */ +class Center implements HasCenterInterface +{ + /** + * + * @var string + */ + private $name; + + /** + * + * @var int + */ + private $id; + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $groupCenters; + + public function __construct() + { + $this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + public function getId() + { + return $this->id; + } + + public function getGroupCenters() + { + return $this->groupCenters; + } + + public function addGroupCenter(GroupCenter $groupCenter) + { + $this->groupCenters->add($groupCenter); + return $this; + } + + public function __toString() + { + return $this->getName(); + } + + public function getCenter() + { + return $this; + } + +} diff --git a/src/Bundle/ChillMain/Entity/Country.php b/src/Bundle/ChillMain/Entity/Country.php new file mode 100644 index 000000000..016e6ad09 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/Country.php @@ -0,0 +1,85 @@ +id; + } + + /** + * Set name + * + * @param string $name + * @return Country + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + + + public function __toString() { + return $this->getName(); + } + + /** + * + * @return the string + */ + public function getCountryCode() + { + return $this->countryCode; + } + + /** + * + * @param string $countryCode + */ + public function setCountryCode($countryCode) + { + $this->countryCode = $countryCode; + return $this; + } + +} diff --git a/src/Bundle/ChillMain/Entity/GroupCenter.php b/src/Bundle/ChillMain/Entity/GroupCenter.php new file mode 100644 index 000000000..d34b86475 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/GroupCenter.php @@ -0,0 +1,119 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Entity\PermissionsGroup; +use Doctrine\Common\Collections\Collection; +use Doctrine\Common\Collections\ArrayCollection; + +/** + * + * + * @author Julien Fastré + */ +class GroupCenter +{ + /** + * + * @var int + */ + private $id; + + /** + * + * @var Center + */ + private $center; + + /** + * + * @var Collection + */ + private $users; + + /** + * + * @var PermissionsGroup + */ + private $permissionsGroup; + + public function __construct() + { + $this->permissionsGroup = new ArrayCollection(); + $this->users = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + /** + * + * @return Center + */ + public function getCenter() + { + return $this->center; + } + + /** + * + * @param Center $center + * @return \Chill\MainBundle\Entity\GroupCenter + */ + public function setCenter(Center $center) + { + $this->center = $center; + return $this; + } + + public function getUsers() + { + return $this->users; + } + + /** + * + * @return PermissionGroup + */ + public function getPermissionsGroup() + { + return $this->permissionsGroup; + } + + /** + * + * @param \Chill\MainBundle\Entity\PermissionsGroup $permissionGroup + * @return \Chill\MainBundle\Entity\GroupCenter + */ + public function setPermissionsGroup(PermissionsGroup $permissionsGroup) + { + $this->permissionsGroup = $permissionsGroup; + return $this; + } + + + + + +} diff --git a/src/Bundle/ChillMain/Entity/HasCenterInterface.php b/src/Bundle/ChillMain/Entity/HasCenterInterface.php new file mode 100644 index 000000000..8d6d541d8 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/HasCenterInterface.php @@ -0,0 +1,36 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +/** + * Interface for entities which may be linked to a center + * + * + * @author Julien Fastré + */ +interface HasCenterInterface +{ + /** + * the linked center + * + * @return Center + */ + public function getCenter(); +} diff --git a/src/Bundle/ChillMain/Entity/HasScopeInterface.php b/src/Bundle/ChillMain/Entity/HasScopeInterface.php new file mode 100644 index 000000000..344d58557 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/HasScopeInterface.php @@ -0,0 +1,35 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +/** + * Interface for entities which have a scop + * + * @author Julien Fastré + */ +interface HasScopeInterface +{ + /** + * Return the linked scope + * + * @return Scope + */ + public function getScope(); +} diff --git a/src/Bundle/ChillMain/Entity/Language.php b/src/Bundle/ChillMain/Entity/Language.php new file mode 100644 index 000000000..37f7963ec --- /dev/null +++ b/src/Bundle/ChillMain/Entity/Language.php @@ -0,0 +1,82 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +/** + * Language + */ +class Language +{ + /** + * @var string + */ + private $id; + + /** + * @var string array + */ + private $name; + + /** + * Get id + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Set id + * + * @param string $id + * @return Language + */ + public function setId($id) + { + $this->id = $id; + return $this; + } + + /** + * Set name + * + * @param string array $name + * @return Language + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get name + * + * @return string array + */ + public function getName() + { + return $this->name; + } +} diff --git a/src/Bundle/ChillMain/Entity/PermissionsGroup.php b/src/Bundle/ChillMain/Entity/PermissionsGroup.php new file mode 100644 index 000000000..706c96d3e --- /dev/null +++ b/src/Bundle/ChillMain/Entity/PermissionsGroup.php @@ -0,0 +1,154 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +use Chill\MainBundle\Entity\RoleScope; +use Symfony\Component\Validator\Context\ExecutionContextInterface; + + +/** + * + * + * @author Julien Fastré + */ +class PermissionsGroup +{ + /** + * + * @var int + */ + private $id; + + /** + * + * @var string + */ + private $name; + + /** + * + * @var string[] + */ + private $flags = []; + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $roleScopes; + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $groupCenters; + + public function __construct() + { + $this->roleScopes = new \Doctrine\Common\Collections\ArrayCollection(); + $this->groupCenters = new \Doctrine\Common\Collections\ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + /** + * + * @return string + */ + public function getName() + { + return $this->name; + } + + public function getRoleScopes() + { + return $this->roleScopes; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * + * @param RoleScope $roleScope + */ + public function addRoleScope(RoleScope $roleScope) + { + $this->roleScopes->add($roleScope); + } + + /** + * + * @param RoleScope $roleScope + * @throws \RuntimeException if the roleScope could not be removed. + */ + public function removeRoleScope(RoleScope $roleScope) + { + $result = $this->roleScopes->removeElement($roleScope); + if ($result === FALSE) { + throw new \RuntimeException(sprintf("The roleScope '%s' could not be removed, " + . "aborting.", spl_object_hash($roleScope))); + } + } + + public function getFlags() + { + return $this->flags; + } + + public function setFlags(array $flags) + { + $this->flags = $flags; + + return $this; + } + + + /** + * Test that a role scope is associated only once with the permission group + * + * @param ExecutionContextInterface $context + */ + public function isRoleScopePresentOnce(ExecutionContextInterface $context) + { + $roleScopesId = array_map(function(RoleScope $roleScope) { + return $roleScope->getId(); + }, + $this->getRoleScopes()->toArray()); + $countedIds = array_count_values($roleScopesId); + + foreach ($countedIds as $id => $nb) { + if ($nb > 1) { + $context->buildViolation("A permission is already present " + . "for the same role and scope") + ->addViolation(); + } + } + } + + +} diff --git a/src/Bundle/ChillMain/Entity/PostalCode.php b/src/Bundle/ChillMain/Entity/PostalCode.php new file mode 100644 index 000000000..41143a6e6 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/PostalCode.php @@ -0,0 +1,113 @@ +id; + } + + /** + * Set name + * + * @param string $name + * + * @return PostalCode + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Get name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set code + * + * @param string $code + * + * @return PostalCode + */ + public function setCode($code) + { + $this->code = $code; + + return $this; + } + + /** + * Get code + * + * @return string + */ + public function getCode() + { + return $this->code; + } + + /** + * Set country + * + * @param \Chill\MainBundle\Entity\Country $country + * + * @return PostalCode + */ + public function setCountry(\Chill\MainBundle\Entity\Country $country = null) + { + $this->country = $country; + + return $this; + } + + /** + * Get country + * + * @return \Chill\MainBundle\Entity\Country + */ + public function getCountry() + { + return $this->country; + } +} + diff --git a/src/Bundle/ChillMain/Entity/RoleScope.php b/src/Bundle/ChillMain/Entity/RoleScope.php new file mode 100644 index 000000000..fa39052fc --- /dev/null +++ b/src/Bundle/ChillMain/Entity/RoleScope.php @@ -0,0 +1,105 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +/** + * + * + * @author Julien Fastré + */ +class RoleScope +{ + /** + * + * @var int + */ + private $id; + + /** + * + * @var string + */ + private $role; + + /** + * + * @var Scope + */ + private $scope; + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $permissionsGroups; + + public function __construct() { + $this->new = true; + $this->permissionsGroups = new \Doctrine\Common\Collections\ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + /** + * + * @return string + */ + public function getRole() + { + return $this->role; + } + + /** + * + * @return Scope + */ + public function getScope() + { + return $this->scope; + } + + /** + * + * @param type $role + * @return \Chill\MainBundle\Entity\RoleScope + */ + public function setRole($role) + { + $this->role = $role; + + return $this; + } + + /** + * + * @param \Chill\MainBundle\Entity\Scope $scope + * @return \Chill\MainBundle\Entity\RoleScope + */ + public function setScope(Scope $scope = null) + { + $this->scope = $scope; + + return $this; + } +} diff --git a/src/Bundle/ChillMain/Entity/Scope.php b/src/Bundle/ChillMain/Entity/Scope.php new file mode 100644 index 000000000..4a55d3c26 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/Scope.php @@ -0,0 +1,93 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Entity; + +use Chill\MainBundle\Entity\RoleScope; + +/** + * + * + * @author Julien Fastré + */ +class Scope +{ + /** + * + * @var int + */ + private $id; + + /** + * translatable names + * + * @var array + */ + private $name = array(); + + /** + * + * @var \Doctrine\Common\Collections\Collection + */ + private $roleScopes; + + public function __construct() + { + $this->roleScopes = new \Doctrine\Common\Collections\ArrayCollection(); + } + + /** + * + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * + * @return array + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * + * @return \Doctrine\Common\Collections\Collection + */ + public function getRoleScopes() + { + return $this->roleScopes; + } + + public function addRoleScope(RoleScope $roleScope) + { + $this->roleScopes->add($roleScope); + } +} diff --git a/src/Bundle/ChillMain/Entity/User.php b/src/Bundle/ChillMain/Entity/User.php new file mode 100644 index 000000000..b483cb906 --- /dev/null +++ b/src/Bundle/ChillMain/Entity/User.php @@ -0,0 +1,288 @@ +groupCenters = new ArrayCollection(); + } + + + /** + * Get id + * + * @return integer + */ + public function getId() + { + return $this->id; + } + + /** + * Set username + * + * @param string $name + * @return Agent + */ + public function setUsername($name) + { + $this->username = $name; + + return $this; + } + + public function __toString() { + return $this->getUsername(); + } + + public function eraseCredentials() + { + + } + + /** + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + public function getRoles() + { + return array('ROLE_USER'); + } + + public function getSalt() + { + return $this->salt; + } + + public function getUsername() + { + return $this->username; + } + + public function getUsernameCanonical() + { + return $this->usernameCanonical; + } + + public function getEmail() + { + return $this->email; + } + + public function getEmailCanonical() + { + return $this->emailCanonical; + } + + public function setUsernameCanonical($usernameCanonical) + { + $this->usernameCanonical = $usernameCanonical; + + return $this; + } + + public function setEmail($email) + { + $this->email = $email; + + return $this; + } + + public function setEmailCanonical($emailCanonical) + { + $this->emailCanonical = $emailCanonical; + + return $this; + } + + + function setPassword($password) + { + $this->password = $password; + + return $this; + } + + function setSalt($salt) + { + $this->salt = $salt; + return $this; + } + + /** + * {@inheritdoc} + * + * @return boolean + */ + public function isAccountNonExpired() + { + return true; + } + + /** + * {@inheritdoc} + * + */ + public function isAccountNonLocked() + { + return $this->locked; + } + + /** + * {@inheritdoc} + * + * @return boolean + */ + public function isCredentialsNonExpired() + { + return true; + } + + /** + * {@inheritdoc} + * + * @return boolean + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * + * @param bool $enabled + */ + public function setEnabled($enabled) + { + $this->enabled = $enabled; + + return $this; + } + + /** + * + * @return GroupCenter[] + */ + public function getGroupCenters() + { + return $this->groupCenters; + } + + /** + * + * @param \Chill\MainBundle\Entity\GroupCenter $groupCenter + * @return \Chill\MainBundle\Entity\User + */ + public function addGroupCenter(GroupCenter $groupCenter) + { + $this->groupCenters->add($groupCenter); + return $this; + } + + /** + * + * @param \Chill\MainBundle\Entity\GroupCenter $groupCenter + * @throws \RuntimeException if the groupCenter is not in the collection + */ + public function removeGroupCenter(GroupCenter $groupCenter) + { + if ($this->groupCenters->removeElement($groupCenter) === FALSE) { + throw new \RuntimeException(sprintf("The groupCenter could not be removed, " + . "it seems not to be associated with the user. Aborting.")); + } + } + + /** + * This function check that groupCenter are present only once. The validator + * use this function to avoid a user to be associated to the same groupCenter + * more than once. + */ + public function isGroupCenterPresentOnce(ExecutionContextInterface $context) + { + $groupCentersIds = array(); + foreach ($this->getGroupCenters() as $groupCenter) { + if (in_array($groupCenter->getId(), $groupCentersIds)) { + $context->buildViolation("The user has already those permissions") + ->addViolation(); + + } else { + $groupCentersIds[] = $groupCenter->getId(); + } + } + } + +} diff --git a/src/Bundle/ChillMain/Export/AggregatorInterface.php b/src/Bundle/ChillMain/Export/AggregatorInterface.php new file mode 100644 index 000000000..db65600bd --- /dev/null +++ b/src/Bundle/ChillMain/Export/AggregatorInterface.php @@ -0,0 +1,90 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +/** + * Interface for Aggregators. + * + * Aggregators gather result of a query. Most of the time, it will add + * a GROUP BY clause. + * + * @author Julien Fastré + */ +interface AggregatorInterface extends ModifierInterface +{ + /** + * give the list of keys the current export added to the queryBuilder in + * self::initiateQuery + * + * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, + * this function will return `array('count_id')`. + * + * @param mixed[] $data the data from the export's form (added by self::buildForm) + */ + public function getQueryKeys($data); + + /** + * get a callable which will be able to transform the results into + * viewable and understable string. + * + * The callable will have only one argument: the `value` to translate. + * + * The callable should also be able to return a key `_header`, which + * will contains the header of the column. + * + * The string returned **must** be already translated if necessary, + * **with an exception** for the string returned for `_header`. + * + * Example : + * + * ``` + * protected $translator; + * + * public function getLabels($key, array $values, $data) + * { + * return function($value) { + * case $value + * { + * case '_header' : + * return 'my header not translated'; + * case true: + * return $this->translator->trans('true'); + * case false: + * return $this->translator->trans('false'); + * default: + * // this should not happens ! + * throw new \LogicException(); + * } + * } + * ``` + * + * **Note:** Why each string must be translated with an exception for + * the `_header` ? For performance reasons: most of the value will be number + * which do not need to be translated, or value already translated in + * database. But the header must be, in every case, translated. + * + * @param string $key The column key, as added in the query + * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') + * @param mixed $data The data from the export's form (as defined in `buildForm` + * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + */ + public function getLabels($key, array $values, $data); + +} diff --git a/src/Bundle/ChillMain/Export/DirectExportInterface.php b/src/Bundle/ChillMain/Export/DirectExportInterface.php new file mode 100644 index 000000000..ebbda3240 --- /dev/null +++ b/src/Bundle/ChillMain/Export/DirectExportInterface.php @@ -0,0 +1,15 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +use Symfony\Component\Form\FormBuilderInterface; + +/** + * The common methods between different object used to build export (i.e. : ExportInterface, + * FilterInterface, AggregatorInterface) + * + * @author Julien Fastré + */ +interface ExportElementInterface +{ + /** + * get a title, which will be used in UI (and translated) + * + * @return string + */ + public function getTitle(); + + + /** + * Add a form to collect data from the user. + * + * @param FormBuilderInterface $builder + */ + public function buildForm(FormBuilderInterface $builder); + +} diff --git a/src/Bundle/ChillMain/Export/ExportElementValidatedInterface.php b/src/Bundle/ChillMain/Export/ExportElementValidatedInterface.php new file mode 100644 index 000000000..cbc2f3881 --- /dev/null +++ b/src/Bundle/ChillMain/Export/ExportElementValidatedInterface.php @@ -0,0 +1,46 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Export; + +use Symfony\Component\Validator\Context\ExecutionContextInterface; + +/** + * Add a validation method to validate data collected by Export Element + * + * Implements this interface on other `ExportElementInterface` to validate + * the form submitted by users. + * + * **note**: When this interface is implemented on filters or aggregators, + * the form is validated **only** if the filter/aggregator is `enabled` by the + * user. + * + * @link http://symfony.com/doc/current/validation/custom_constraint.html#class-constraint-validator Example of building violations + * + * @author Julien Fastré + */ +interface ExportElementValidatedInterface +{ + /** + * validate the form's data and, if required, build a contraint + * violation on the data. + * + * @param mixed $data the data, as returned by the user + * @param ExecutionContextInterface $context + */ + public function validateForm($data, ExecutionContextInterface $context); +} diff --git a/src/Bundle/ChillMain/Export/ExportElementsProviderInterface.php b/src/Bundle/ChillMain/Export/ExportElementsProviderInterface.php new file mode 100644 index 000000000..077272c9b --- /dev/null +++ b/src/Bundle/ChillMain/Export/ExportElementsProviderInterface.php @@ -0,0 +1,35 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Export; + +/** + * Interface to provide export elements dynamically. + * + * The typical use case is providing exports or aggregators depending on + * dynamic data. Example: providing exports for reports, reports depending + * on data stored in database. + * + * @author Julien Fastré + */ +interface ExportElementsProviderInterface +{ + /** + * @return ExportElementInterface[] + */ + public function getExportElements(); +} diff --git a/src/Bundle/ChillMain/Export/ExportInterface.php b/src/Bundle/ChillMain/Export/ExportInterface.php new file mode 100644 index 000000000..c260649d1 --- /dev/null +++ b/src/Bundle/ChillMain/Export/ExportInterface.php @@ -0,0 +1,165 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +use Doctrine\ORM\QueryBuilder; + +/** + * Interface for Export. + * + * An export is a class which will initiate a query for an export. + * + * **Note** : the report implementing this class will be modified by + * both filters **and** aggregators. If the report does not support + * aggregation, use `ListInterface`. + * + * @example Chill\PersonBundle\Export\CountPerson an example of implementation + * @author Julien Fastré + */ +interface ExportInterface extends ExportElementInterface +{ + /** + * Return the Export's type. This will inform _on what_ export will apply. + * Most of the type, it will be a string which references an entity. + * + * Example of types : Chill\PersonBundle\Export\Declarations::PERSON_TYPE + * + * @return string + */ + public function getType(); + + /** + * A description, which will be used in the UI to explain what the export does. + * This description will be translated. + * + * @return string + */ + public function getDescription(); + + /** + * The initial query, which will be modified by ModifiersInterface + * (i.e. AggregatorInterface, FilterInterface). + * + * This query should take into account the `$acl` and restrict result only to + * what the user is allowed to see. (Do not show personal data the user + * is not allowed to see). + * + * The returned object should be an instance of QueryBuilder or NativeQuery. + * + * @param array $requiredModifiers + * @param array $acl an array where each row has a `center` key containing the Chill\MainBundle\Entity\Center, and `circles` keys containing the reachable circles. Example: `array( array('center' => $centerA, 'circles' => array($circleA, $circleB) ) )` + * @param array $data the data from the form, if any + * @return QueryBuilder|\Doctrine\ORM\NativeQuery the query to execute. + */ + public function initiateQuery(array $requiredModifiers, array $acl, array $data = array()); + + /** + * Inform which ModifiersInterface (i.e. AggregatorInterface, FilterInterface) + * are allowed. The modifiers should be an array of types the _modifier_ apply on + * (@see ModifiersInterface::applyOn()). + * + * @return string[] + */ + public function supportsModifiers(); + + /** + * Return the required Role to execute the Export. + * + * @return \Symfony\Component\Security\Core\Role\Role + * + */ + public function requiredRole(); + + /** + * Return which formatter type is allowed for this report. + * + * @return string[] + */ + public function getAllowedFormattersTypes(); + + /** + * give the list of keys the current export added to the queryBuilder in + * self::initiateQuery + * + * Example: if your query builder will contains `SELECT count(id) AS count_id ...`, + * this function will return `array('count_id')`. + * + * @param mixed[] $data the data from the export's form (added by self::buildForm) + */ + public function getQueryKeys($data); + + /** + * Return the results of the query builder. + * + * @param QueryBuilder|\Doctrine\ORM\NativeQuery $query + * @param mixed[] $data the data from the export's fomr (added by self::buildForm) + * @return mixed[] an array of results + */ + public function getResult($query, $data); + + + /** + * transform the results to viewable and understable string. + * + * The callable will have only one argument: the `value` to translate. + * + * The callable should also be able to return a key `_header`, which + * will contains the header of the column. + * + * The string returned **must** be already translated if necessary, + * **with an exception** for the string returned for `_header`. + * + * Example : + * + * ``` + * protected $translator; + * + * public function getLabels($key, array $values, $data) + * { + * return function($value) { + * case $value + * { + * case '_header' : + * return 'my header not translated'; + * case true: + * return $this->translator->trans('true'); + * case false: + * return $this->translator->trans('false'); + * default: + * // this should not happens ! + * throw new \LogicException(); + * } + * } + * ``` + * + * **Note:** Why each string must be translated with an exception for + * the `_header` ? For performance reasons: most of the value will be number + * which do not need to be translated, or value already translated in + * database. But the header must be, in every case, translated. + * + * + * @param string $key The column key, as added in the query + * @param mixed[] $values The values from the result. if there are duplicates, those might be given twice. Example: array('FR', 'BE', 'CZ', 'FR', 'BE', 'FR') + * @param mixed $data The data from the export's form (as defined in `buildForm` + * @return \Closure where the first argument is the value, and the function should return the label to show in the formatted file. Example : `function($countryCode) use ($countries) { return $countries[$countryCode]->getName(); }` + */ + public function getLabels($key, array $values, $data); + +} diff --git a/src/Bundle/ChillMain/Export/ExportManager.php b/src/Bundle/ChillMain/Export/ExportManager.php new file mode 100644 index 000000000..89e5a6599 --- /dev/null +++ b/src/Bundle/ChillMain/Export/ExportManager.php @@ -0,0 +1,754 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +use Chill\MainBundle\Export\FilterInterface; +use Chill\MainBundle\Export\AggregatorInterface; +use Chill\MainBundle\Export\ExportInterface; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\HttpFoundation\Response; +use Psr\Log\LoggerInterface; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Form\Type\Export\PickCenterType; +use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; +use Chill\MainBundle\Form\Type\Export\ExportType; +use Chill\MainBundle\Export\ListInterface; + +/** + * Collects all agregators, filters and export from + * the installed bundle, and performs the export logic. + * + * + * @author Julien Fastré + */ +class ExportManager +{ + /** + * The collected filters, injected by DI + * + * @var FilterInterface[] + */ + private $filters = array(); + + /** + * The collected aggregators, injected by DI + * + * @var AggregatorInterface[] + */ + private $aggregators = array(); + + /** + * Collected Exports, injected by DI + * + * @var ExportInterface[] + */ + private $exports = array(); + + /** + * Collected Formatters, injected by DI + * + * @var FormatterInterface[] + */ + private $formatters = array(); + + /** + * a logger + * + * @var LoggerInterface + */ + private $logger; + + /** + * + * @var EntityManagerInterface + */ + private $em; + + /** + * + * @var AuthorizationChecker + */ + private $authorizationChecker; + + /** + * + * @var AuthorizationHelper + */ + private $authorizationHelper; + + /** + * + * @var \Symfony\Component\Security\Core\User\UserInterface + */ + private $user; + + public function __construct( + LoggerInterface $logger, + EntityManagerInterface $em, + AuthorizationCheckerInterface $authorizationChecker, + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage) + { + $this->logger = $logger; + $this->em = $em; + $this->authorizationChecker = $authorizationChecker; + $this->authorizationHelper = $authorizationHelper; + $this->user = $tokenStorage->getToken()->getUser(); + } + + /** + * add a Filter + * + * @internal Normally used by the dependency injection + * + * @param FilterInterface $filter + * @param string $alias + */ + public function addFilter(FilterInterface $filter, $alias) + { + $this->filters[$alias] = $filter; + } + + /** + * add an aggregator + * + * @internal used by DI + * + * @param AggregatorInterface $aggregator + * @param string $alias + */ + public function addAggregator(AggregatorInterface $aggregator, $alias) + { + $this->aggregators[$alias] = $aggregator; + } + + /** + * add an export + * + * @internal used by DI + * + * @param ExportInterface|DirectExportInterface $export + * @param type $alias + */ + public function addExport($export, $alias) + { + if ($export instanceof ExportInterface || $export instanceof DirectExportInterface) { + $this->exports[$alias] = $export; + } else { + throw new \InvalidArgumentException(sprintf("The export with alias %s " + . "does not implements %s or %s.", $alias, ExportInterface::class, + DirectExportInterface::class)); + } + } + + /** + * add a formatter + * + * @internal used by DI + * + * @param FormatterInterface $formatter + * @param type $alias + */ + public function addFormatter(FormatterInterface $formatter, $alias) + { + $this->formatters[$alias] = $formatter; + } + + public function addExportElementsProvider(ExportElementsProviderInterface $provider, $prefix) + { + foreach ($provider->getExportElements() as $suffix => $element) { + $alias = $prefix.'_'.$suffix; + + if ($element instanceof ExportInterface) { + $this->addExport($element, $alias); + } elseif ($element instanceof FilterInterface) { + $this->addFilter($element, $alias); + } elseif ($element instanceof AggregatorInterface) { + $this->addAggregator($element, $alias); + } elseif ($element instanceof FormatterInterface) { + $this->addFormatter($element, $alias); + } else { + throw new \LogicException("This element ".\get_class($element)." " + . "is not an instance of export element"); + } + } + } + + /** + * + * @return string[] the existing type for known exports + */ + public function getExistingExportsTypes() + { + $existingTypes = array(); + + foreach($this->exports as $export) { + if (!in_array($export->getType(), $existingTypes)) { + array_push($existingTypes, $export->getType()); + } + } + + return $existingTypes; + } + + /** + * Return all exports. The exports's alias are the array's keys. + * + * @param boolean $whereUserIsGranted if true (default), restrict to user which are granted the right to execute the export + * @return ExportInterface[] an array where export's alias are keys + */ + public function getExports($whereUserIsGranted = true) + { + foreach ($this->exports as $alias => $export) { + if ($whereUserIsGranted) { + if ($this->isGrantedForElement($export, null, null)) { + yield $alias => $export; + } + } else { + yield $alias => $export; + } + } + } + + /** + * Get all exports grouped in an array. + * + * @param bool $whereUserIsGranted + * @return array where keys are the groups's name and value is an array of exports + */ + public function getExportsGrouped($whereUserIsGranted = true): array + { + $groups = [ '_' => [] ]; + foreach ($this->getExports($whereUserIsGranted) as $alias => $export) { + if ($export instanceof GroupedExportInterface) { + $groups[$export->getGroup()][$alias] = $export; + } else { + $groups['_'][$alias] = $export; + } + } + + return $groups; + } + + /** + * Return an export by his alias + * + * @param string $alias + * @return ExportInterface + * @throws \RuntimeException + */ + public function getExport($alias) + { + if (!array_key_exists($alias, $this->exports)) { + throw new \RuntimeException("The export with alias $alias is not known."); + } + + return $this->exports[$alias]; + } + + /** + * + * @param string $alias + * @return FilterInterface + * @throws \RuntimeException if the filter is not known + */ + public function getFilter($alias) + { + if (!array_key_exists($alias, $this->filters)) { + throw new \RuntimeException("The filter with alias $alias is not known."); + } + + return $this->filters[$alias]; + } + + /** + * get all filters + * + * @param \Generator $aliases + */ + public function getFilters(array $aliases) + { + foreach($aliases as $alias) { + yield $alias => $this->getFilter($alias); + } + } + + /** + * + * @param string $alias + * @return AggregatorInterface + * @throws \RuntimeException if the aggregator is not known + */ + public function getAggregator($alias) + { + if (!array_key_exists($alias, $this->aggregators)) { + throw new \RuntimeException("The aggregator with alias $alias is not known."); + } + + return $this->aggregators[$alias]; + } + + public function getAggregators(array $aliases) + { + foreach ($aliases as $alias) { + yield $alias => $this->getAggregator($alias); + } + } + + public function getFormatter($alias) + { + if (!array_key_exists($alias, $this->formatters)) { + throw new \RuntimeException("The formatter with alias $alias is not known."); + } + + return $this->formatters[$alias]; + } + + /** + * Get all formatters which supports one of the given types. + * + * + * @param array $types + * @return \Generator + */ + public function getFormattersByTypes(array $types) + { + foreach ($this->formatters as $alias => $formatter) { + if (in_array($formatter->getType(), $types)) { + yield $alias => $formatter; + } + } + } + + + /** + * Return a \Generator containing filter which support type. If `$centers` is + * not null, restrict the given filters to the center the user have access to. + * + * if $centers is null, the function will returns all filters where the user + * has access in every centers he can reach (if the user can use the filter F in + * center A, but not in center B, the filter F will not be returned) + * + * @param \Chill\MainBundle\Entity\Center[] $centers the centers where the user have access to + * @return FilterInterface[] a \Generator that contains filters. The key is the filter's alias + */ + public function &getFiltersApplyingOn(ExportInterface $export, array $centers = null) + { + foreach ($this->filters as $alias => $filter) { + if (in_array($filter->applyOn(), $export->supportsModifiers()) + && $this->isGrantedForElement($filter, $export, $centers)) { + yield $alias => $filter; + } + } + } + + /** + * Return true if the current user has access to the ExportElement for every + * center, false if the user hasn't access to element for at least one center. + * + * @param \Chill\MainBundle\Export\ExportElementInterface $element + * @param ExportInterface|DirectExportInterface $export + * @param array|null $centers, if null, the function take into account all the reachables centers for the current user and the role given by element::requiredRole + * @return boolean + */ + public function isGrantedForElement(ExportElementInterface $element, ExportElementInterface $export = NULL, array $centers = null) + { + if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) { + $role = $element->requiredRole(); + } elseif ($element instanceof ModifierInterface ) { + if (is_null($element->addRole())) { + if (is_null($export)) { + throw new \LogicException("The export should not be null: as the " + . "ModifierInstance element is not an export, we should " + . "be aware of the export to determine which role is required"); + } else { + $role = $export->requiredRole(); + } + } else { + $role = $element->addRole(); + } + } else { + throw new \LogicException("The element is not an ModifiersInterface or " + . "an ExportInterface."); + } + + if ($centers === null) { + $centers = $this->authorizationHelper->getReachableCenters($this->user, + $role); + } + + if (count($centers) === 0) { + return false; + } + + foreach($centers as $center) { + if ($this->authorizationChecker->isGranted($role->getRole(), $center) === false) { + //debugging + $this->logger->debug('user has no access to element', array( + 'method' => __METHOD__, + 'type' => get_class($element), + 'center' => $center->getName(), + 'role' => $role->getRole() + )); + + return false; + } + } + + return true; + } + + /** + * Return a \Generator containing aggregators supported by the given export + * + * @internal This class check the interface implemented by export, and, if ´ListInterface´ is used, return an empty array + * @return AggregatorInterface[] a \Generator that contains aggretagors. The key is the filter's alias + */ + public function &getAggregatorsApplyingOn(ExportInterface $export, array $centers = null) + { + if ($export instanceof ListInterface) { + + return; + } + + foreach ($this->aggregators as $alias => $aggregator) { + if (in_array($aggregator->applyOn(), $export->supportsModifiers()) && + $this->isGrantedForElement($aggregator, $export, $centers)) { + yield $alias => $aggregator; + } + } + } + + /** + * Generate a response which contains the requested data. + * + * @param string $exportAlias + * @param mixed[] $data + * @return Response + */ + public function generate($exportAlias, array $pickedCentersData, array $data, array $formatterData) + { + $export = $this->getExport($exportAlias); + //$qb = $this->em->createQueryBuilder(); + $centers = $this->getPickedCenters($pickedCentersData); + + if ($export instanceof DirectExportInterface) { + return $export->generate( + $this->buildCenterReachableScopes($centers, $export), + $data[ExportType::EXPORT_KEY] + ); + } + + $query = $export->initiateQuery( + $this->retrieveUsedModifiers($data), + $this->buildCenterReachableScopes($centers, $export), + $data[ExportType::EXPORT_KEY] + ); + + if ($query instanceof \Doctrine\ORM\NativeQuery) { + // throw an error if the export require other modifier, which is + // not allowed when the export return a `NativeQuery` + if (count($export->supportsModifiers()) > 0) { + throw new \LogicException("The export with alias `$exportAlias` return " + . "a `\Doctrine\ORM\NativeQuery` and supports modifiers, which is not " + . "allowed. Either the method `supportsModifiers` should return an empty " + . "array, or return a `Doctrine\ORM\QueryBuilder`"); + } + } elseif ($query instanceof QueryBuilder) { + //handle filters + $this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers); + + //handle aggregators + $this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers); + + $this->logger->debug('current query is '.$query->getDQL(), array( + 'class' => self::class, 'function' => __FUNCTION__ + )); + } else { + throw new \UnexpectedValueException("The method `intiateQuery` should return " + . "a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` " + . "object."); + } + + $result = $export->getResult($query, $data[ExportType::EXPORT_KEY]); + + if (!is_iterable($result)) { + throw new \UnexpectedValueException( + sprintf( + 'The result of the export should be an iterable, %s given', + gettype($result) + ) + ); + } + + /* @var $formatter FormatterInterface */ + $formatter = $this->getFormatter($this->getFormatterAlias($data)); + $filtersData = array(); + $aggregatorsData = array(); + + if ($query instanceof QueryBuilder) { + $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + + foreach($aggregators as $alias => $aggregator) { + $aggregatorsData[$alias] = $data[ExportType::AGGREGATOR_KEY][$alias]['form']; + } + } + + $filters = $this->retrieveUsedFilters($data[ExportType::FILTER_KEY]); + + foreach($filters as $alias => $filter) { + $filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form']; + } + + return $formatter->getResponse( + $result, + $formatterData, + $exportAlias, + $data[ExportType::EXPORT_KEY], + $filtersData, + $aggregatorsData); + } + + /** + * build the array required for defining centers and circles in the initiate + * queries of ExportElementsInterfaces + * + * @param \Chill\MainBundle\Entity\Center[] $centers + */ + private function buildCenterReachableScopes(array $centers, ExportElementInterface $element) { + $r = array(); + + foreach($centers as $center) { + $r[] = array( + 'center' => $center, + 'circles' => $this->authorizationHelper->getReachableScopes($this->user, + $element->requiredRole(), $center) + ); + } + + return $r; + } + + /** + * get the aggregators typse used in the form export data + * + * @param array $data the data from the export form + * @return string[] + */ + public function getUsedAggregatorsAliases(array $data) + { + $aggregators = $this->retrieveUsedAggregators($data[ExportType::AGGREGATOR_KEY]); + + return array_keys(iterator_to_array($aggregators)); + } + + /** + * get the formatter alias from the form export data + * + * @param array $data the data from the export form + * @string the formatter alias + */ + public function getFormatterAlias(array $data) + { + return $data[ExportType::PICK_FORMATTER_KEY]['alias']; + } + + /** + * Get the Center picked by the user for this export. The data are + * extracted from the PickCenterType data + * + * @param array $data the data from a PickCenterType + * @return \Chill\MainBundle\Entity\Center[] the picked center + */ + public function getPickedCenters(array $data) + { + return $data; + } + + /** + * parse the data to retrieve the used filters and aggregators + * + * @param mixed $data + * @return string[] + */ + private function retrieveUsedModifiers($data) + { + if ($data === null) { + return []; + } + + $usedTypes = array_merge( + $this->retrieveUsedFiltersType($data[ExportType::FILTER_KEY]), + $this->retrieveUsedAggregatorsType($data[ExportType::AGGREGATOR_KEY]) + ); + + $this->logger->debug('Required types are '.implode(', ', $usedTypes), + array('class' => self::class, 'function' => __FUNCTION__)); + + return array_unique($usedTypes); + } + + /** + * Retrieve the filter used in this export. + * + * @param mixed $data the data from the `filters` key of the ExportType + * @return array an array with types + */ + private function retrieveUsedFiltersType($data) + { + if ($data === null) { + return []; + } + + $usedTypes = array(); + foreach($data as $alias => $filterData) { + if ($filterData['enabled'] == true){ + $filter = $this->getFilter($alias); + if (!in_array($filter->applyOn(), $usedTypes)) { + array_push($usedTypes, $filter->applyOn()); + } + } + } + + return $usedTypes; + } + + /** + * + * @param mixed $data + * @return string[] + */ + private function retrieveUsedAggregatorsType($data) + { + if ($data === null) { + return []; + } + + $usedTypes = array(); + foreach($this->retrieveUsedAggregators($data) as $alias => $aggregator) { + if (!in_array($aggregator->applyOn(), $usedTypes)) { + array_push($usedTypes, $aggregator->applyOn()); + } + } + + return $usedTypes; + } + + /** + * + * @param mixed $data + * @return AggregatorInterface[] + */ + private function retrieveUsedAggregators($data) + { + if ($data === null) { + return []; + } + + foreach ($data as $alias => $aggregatorData) { + if ($aggregatorData['enabled'] === true){ + yield $alias => $this->getAggregator($alias); + } + } + } + + /** + * + * @param type $data the data from the filter key of the ExportType + */ + private function retrieveUsedFilters($data) + { + if ($data === null) { + return []; + } + + foreach ($data as $alias => $filterData) { + if ($filterData['enabled'] === true) { + yield $alias => $this->getFilter($alias); + } + } + } + + /** + * alter the query with selected filters. + * + * This function check the acl. + * + * @param ExportInterface $export + * @param QueryBuilder $qb + * @param mixed $data the data under the initial 'filters' data + * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers + * @throw UnauthorizedHttpException if the user is not authorized + */ + private function handleFilters( + ExportInterface $export, + QueryBuilder $qb, + $data, + array $centers) + { + $filters = $this->retrieveUsedFilters($data); + + foreach($filters as $alias => $filter) { + if ($this->isGrantedForElement($filter, $export, $centers) === false) { + throw new UnauthorizedHttpException("You are not authorized to " + . "use the filter ".$filter->getTitle()); + } + + $formData = $data[$alias]; + + $this->logger->debug('alter query by filter '.$alias, array( + 'class' => self::class, 'function' => __FUNCTION__ + )); + $filter->alterQuery($qb, $formData['form']); + + } + } + + /** + * Alter the query with selected aggregators + * + * Check for acl. If an user is not authorized to see an aggregator, throw an + * UnauthorizedException. + * + * @param ExportInterface $export + * @param QueryBuilder $qb + * @param type $data + * @param \Chill\MainBundle\Entity\Center[] $centers the picked centers + * @throw UnauthorizedHttpException if the user is not authorized + */ + private function handleAggregators( + ExportInterface $export, + QueryBuilder $qb, + $data, + array $center) + { + $aggregators = $this->retrieveUsedAggregators($data); + + foreach ($aggregators as $alias => $aggregator) { + $formData = $data[$alias]; + $aggregator->alterQuery($qb, $formData['form']); + } + } + +} diff --git a/src/Bundle/ChillMain/Export/FilterInterface.php b/src/Bundle/ChillMain/Export/FilterInterface.php new file mode 100644 index 000000000..b644ae8a7 --- /dev/null +++ b/src/Bundle/ChillMain/Export/FilterInterface.php @@ -0,0 +1,70 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + + +/** + * Interface for filters. + * + * Filter will filter result on the query initiated by Export. Most of the time, + * it will add a `WHERE` clause on this query. + * + * Filters should not add column in `SELECT` clause. + * + * + * @author Julien Fastré + */ +interface FilterInterface extends ModifierInterface +{ + + const STRING_FORMAT = 'string'; + + /** + * Describe the filtering action. + * + * The description will be inserted in report to remains to the user the + * filters applyied on this report. + * + * Should return a statement about the filtering action, like : + * "filtering by date: from xxxx-xx-xx to xxxx-xx-xx" or + * "filtering by nationality: only Germany, France" + * + * The format will be determined by the $format. Currently, only 'string' is + * supported, later some 'html' will be added. The filter should always + * implement the 'string' format and fallback to it if other format are used. + * + * If no i18n is necessery, or if the filter translate the string by himself, + * this function should return a string. If the filter does not do any translation, + * the return parameter should be an array, where + * + * - the first element is the string, + * - and the second an array of parameters (may be an empty array) + * - the 3rd the domain (optional) + * - the 4th the locale (optional) + * + * Example: `array('my string with %parameter%', ['%parameter%' => 'good news'], 'mydomain', 'mylocale')` + * + * @param array $data + * @param string $format the format + * @return string|array a string with the data or, if translatable, an array where first element is string, second elements is an array of arguments + */ + public function describeAction($data, $format = 'string'); + +} diff --git a/src/Bundle/ChillMain/Export/Formatter/CSVFormatter.php b/src/Bundle/ChillMain/Export/Formatter/CSVFormatter.php new file mode 100644 index 000000000..a270d8978 --- /dev/null +++ b/src/Bundle/ChillMain/Export/Formatter/CSVFormatter.php @@ -0,0 +1,465 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export\Formatter; + +use Chill\MainBundle\Export\ExportInterface; +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff + +/** + * + * + * @author Julien Fastré + * @deprecated this formatter is not used any more. + */ +class CSVFormatter implements FormatterInterface +{ + /** + * + * @var TranslatorInterface + */ + protected $translator; + + protected $result; + + protected $formatterData; + + protected $export; + + protected $aggregators; + + protected $exportData; + + protected $aggregatorsData; + + protected $filtersData; + + protected $labels; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + + public function __construct(TranslatorInterface $translator, + ExportManager $manager) + { + $this->translator = $translator; + $this->exportManager = $manager; + } + + public function getType() + { + return 'tabular'; + } + + public function getName() + { + return 'Comma separated values (CSV)'; + } + + /** + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param type $exportAlias + * @param array $aggregatorAliases + */ + public function buildForm(FormBuilderInterface $builder, $exportAlias, array $aggregatorAliases) + { + $aggregators = $this->exportManager->getAggregators($aggregatorAliases); + $nb = count($aggregatorAliases); + + foreach ($aggregators as $alias => $aggregator) { + $builderAggregator = $builder->create($alias, FormType::class, array( + 'label' => $aggregator->getTitle(), + 'block_name' => '_aggregator_placement_csv_formatter' + )); + $this->appendAggregatorForm($builderAggregator, $nb); + $builder->add($builderAggregator); + } + } + + /** + * append a form line by aggregator on the formatter form. + * + * This form allow to choose the aggregator position (row or column) and + * the ordering + * + * @param FormBuilderInterface $builder + * @param string $nbAggregators + */ + private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) + { + $builder->add('order', ChoiceType::class, array( + 'choices' => array_combine( + range(1, $nbAggregators), + range(1, $nbAggregators) + ), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false + )); + + $builder->add('position', ChoiceType::class, array( + 'choices' => array( + 'row' => 'r', + 'column' => 'c' + ), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false + )); + } + + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + $this->result = $result; + $this->orderingHeaders($formatterData); + $this->export = $this->exportManager->getExport($exportAlias); + $this->aggregators = iterator_to_array($this->exportManager + ->getAggregators(array_keys($aggregatorsData))); + $this->exportData = $exportData; + $this->aggregatorsData = $aggregatorsData; + $this->labels = $this->gatherLabels(); + $this->filtersData = $filtersData; + + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); + //$response->headers->set('Content-Disposition','attachment; filename="export.csv"'); + + $response->setContent($this->generateContent()); + + return $response; + } + + /** + * ordering aggregators, preserving key association. + * + * This function do not mind about position. + * + * If two aggregators have the same order, the second given will be placed + * after. This is not significant for the first ordering. + * + * @param type $formatterData + * @return type + */ + protected function orderingHeaders($formatterData) + { + $this->formatterData = $formatterData; + uasort($this->formatterData, function($a, $b) { + + return ($a['order'] <= $b['order'] ? -1 : 1); + }); + } + + protected function generateContent() + { + $rowKeysNb = count($this->getRowHeaders()); + $columnKeysNb = count($this->getColumnHeaders()); + $resultsKeysNb = count($this->export->getQueryKeys($this->exportData)); + $results = $this->getOrderedResults(); + /* @var $columnHeaders string[] the column headers associations */ + $columnHeaders = array(); + /* @var $data string[] the data of the csv file */ + $contentData = array(); + $content = array(); + + function findColumnPosition(&$columnHeaders, $columnToFind) { + $i = 0; + foreach($columnHeaders as $set) { + if ($set === $columnToFind) { + return $i; + } + $i++; + } + + //we didn't find it, adding the column + $columnHeaders[] = $columnToFind; + + return $i++; + } + + // create a file pointer connected to the output stream + $output = fopen('php://output', 'w'); + + //title + fputcsv($output, array($this->translator->trans($this->export->getTitle()))); + //blank line + fputcsv($output, array("")); + + // add filtering description + foreach($this->gatherFiltersDescriptions() as $desc) { + fputcsv($output, array($desc)); + } + // blank line + fputcsv($output, array("")); + + // iterate on result to : 1. populate row headers, 2. populate column headers, 3. add result + foreach ($results as $row) { + $rowHeaders = array_slice($row, 0, $rowKeysNb); + + //first line : we create line and adding them row headers + if (!isset($line)) { + $line = array_slice($row, 0, $rowKeysNb); + } + + // do we have to create a new line ? if the rows are equals, continue on the line, else create a next line + if (array_slice($line, 0, $rowKeysNb) !== $rowHeaders) { + $contentData[] = $line; + $line = array_slice($row, 0, $rowKeysNb); + } + + // add the column headers + /* @var $columns string[] the column for this row */ + $columns = array_slice($row, $rowKeysNb, $columnKeysNb); + $columnPosition = findColumnPosition($columnHeaders, $columns); + + //fill with blank at the position given by the columnPosition + nbRowHeaders + for ($i=0; $i < $columnPosition; $i++) { + if (!isset($line[$rowKeysNb + $i])) { + $line[$rowKeysNb + $i] = ""; + } + } + + $resultData = array_slice($row, $resultsKeysNb*-1); + foreach($resultData as $data) { + $line[] = $data; + } + + } + + // we add the last line + $contentData[] = $line; + + //column title headers + for ($i=0; $i < $columnKeysNb; $i++) { + $line = array_fill(0, $rowKeysNb, ''); + + foreach($columnHeaders as $set) { + $line[] = $set[$i]; + } + + $content[] = $line; + } + + + //row title headers + $headerLine = array(); + foreach($this->getRowHeaders() as $headerKey) { + $headerLine[] = array_key_exists('_header', $this->labels[$headerKey]) ? + $this->labels[$headerKey]['_header'] : ''; + } + foreach($this->export->getQueryKeys($this->exportData) as $key) { + $headerLine[] = array_key_exists('_header', $this->labels[$key]) ? + $this->labels[$key]['_header'] : ''; + } + fputcsv($output, $headerLine); + unset($headerLine); //free memory + + //generate CSV + foreach($content as $line) { + fputcsv($output, $line); + } + foreach($contentData as $line) { + fputcsv($output, $line); + } + + $text = stream_get_contents($output); + fclose($output); + + return $text; + } + + + private function getOrderedResults() + { + $r = array(); + $results = $this->result; + $labels = $this->labels; + $rowKeys = $this->getRowHeaders(); + $columnKeys = $this->getColumnHeaders(); + $resultsKeys = $this->export->getQueryKeys($this->exportData); + $headers = array_merge($rowKeys, $columnKeys); + + foreach ($results as $row) { + $line = array(); + foreach ($headers as $key) { + + $line[] = call_user_func($labels[$key], $row[$key]); + } + + //append result + foreach ($resultsKeys as $key) { + $line[] = call_user_func($labels[$key], $row[$key]); + } + + $r[] = $line; + } + + array_multisort($r); + + return $r; + } + + + protected function getRowHeaders() + { + return $this->getPositionnalHeaders('r'); + } + + protected function getColumnHeaders() + { + return $this->getPositionnalHeaders('c'); + } + + /** + * + * @param string $position may be 'c' (column) or 'r' (row) + * @return string[] + * @throws \RuntimeException + */ + private function getPositionnalHeaders($position) + { + $headers = array(); + foreach($this->formatterData as $alias => $data) { + if (!array_key_exists($alias, $this->aggregatorsData)) { + throw new \RuntimeException("the formatter wants to use the " + . "aggregator with alias $alias, but the export do not " + . "contains data about it"); + } + + $aggregator = $this->aggregators[$alias]; + + if ($data['position'] === $position) { + $headers = array_merge($headers, $aggregator->getQueryKeys($this->aggregatorsData[$alias])); + } + } + + return $headers; + } + + /** + * + * @param mixed $result + * @param \Chill\MainBundle\Export\AggregatorInterface[] $aggregators + */ + protected function gatherLabels() + { + return array_merge( + $this->gatherLabelsFromAggregators(), + $this->gatherLabelsFromExport() + ); + } + + protected function gatherLabelsFromAggregators() + { + $labels = array(); + /* @var $aggretator \Chill\MainBundle\Export\AggregatorInterface */ + foreach ($this->aggregators as $alias => $aggregator) { + $keys = $aggregator->getQueryKeys($this->aggregatorsData[$alias]); + + // gather data in an array + foreach($keys as $key) { + $values = array_map(function($row) use ($key, $alias) { + if (!array_key_exists($key, $row)) { + throw new \LogicException("the key '".$key."' is declared by " + . "the aggregator with alias '".$alias."' but is not " + . "present in results"); + } + + return $row[$key]; + }, $this->result); + $labels[$key] = $aggregator->getLabels($key, array_unique($values), + $this->aggregatorsData[$alias]); + } + } + + return $labels; + } + + protected function gatherLabelsFromExport() + { + $labels = array(); + $export = $this->export; + $keys = $this->export->getQueryKeys($this->exportData); + + foreach($keys as $key) { + $values = array_map(function($row) use ($key, $export) { + if (!array_key_exists($key, $row)) { + throw new \LogicException("the key '".$key."' is declared by " + . "the export with title '".$export->getTitle()."' but is not " + . "present in results"); + } + + return $row[$key]; + }, $this->result); + $labels[$key] = $this->export->getLabels($key, array_unique($values), + $this->exportData); + } + + return $labels; + } + + public function gatherFiltersDescriptions() + { + $descriptions = array(); + + foreach($this->filtersData as $key => $filterData) { + + $statement = $this->exportManager + ->getFilter($key) + ->describeAction($filterData); + + if ($statement === null) { + continue; + } + + if (is_array($statement)) { + $descriptions[] = $this->translator->trans( + $statement[0], + $statement[1], + isset($statement[2]) ? $statement[2] : null, + isset($statement[3]) ? $statement[3] : null); + } else { + $descriptions[] = $statement; + } + } + + return $descriptions; + } + +} diff --git a/src/Bundle/ChillMain/Export/Formatter/CSVListFormatter.php b/src/Bundle/ChillMain/Export/Formatter/CSVListFormatter.php new file mode 100644 index 000000000..a6981760a --- /dev/null +++ b/src/Bundle/ChillMain/Export/Formatter/CSVListFormatter.php @@ -0,0 +1,238 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export\Formatter; + +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff + +/** + * Create a CSV List for the export + * + * @author Champs-Libres + */ +class CSVListFormatter implements FormatterInterface +{ + + /** + * This variable cache the labels internally + * + * @var string[] + */ + protected $labelsCache = null; + + protected $result = null; + + protected $exportAlias = null; + + protected $exportData = null; + + protected $formatterData = null; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + + + public function __construct(TranslatorInterface $translator, ExportManager $manager) + { + $this->translator = $translator; + $this->exportManager = $manager; + } + + public function getType() + { + return FormatterInterface::TYPE_LIST; + } + + public function getName() + { + return 'CSV vertical list'; + } + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param type $exportAlias + * @param array $aggregatorAliases + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ){ + $builder->add('numerotation', ChoiceType::class, array( + 'choices' => array( + 'yes' => true, + 'no' => false + ), + 'expanded' => true, + 'multiple' => false, + 'label' => "Add a number on first column", + 'choices_as_values' => true, + 'data' => true + )); + } + + /** + * Generate a response from the data collected on differents ExportElementInterface + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @return \Symfony\Component\HttpFoundation\Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + $this->result = $result; + $this->exportAlias = $exportAlias; + $this->exportData = $exportData; + $this->formatterData = $formatterData; + + $output = fopen('php://output', 'w'); + + $this->prepareHeaders($output); + + $i = 1; + foreach ($result as $row) { + $line = array(); + + if ($this->formatterData['numerotation'] === true) { + $line[] = $i; + } + + foreach ($row as $key => $value) { + $line[] = $this->getLabel($key, $value); + } + + fputcsv($output, $line); + + $i++; + } + + $csvContent = stream_get_contents($output); + fclose($output); + + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); + //$response->headers->set('Content-Disposition','attachment; filename="export.csv"'); + + $response->setContent($csvContent); + + return $response; + } + + /** + * add the headers to the csv file + * + * @param resource $output + */ + protected function prepareHeaders($output) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : array(); + $header_line = array(); + + if ($this->formatterData['numerotation'] === true) { + $header_line[] = $this->translator->trans('Number'); + } + + foreach ($first_row as $key => $value) { + $header_line[] = $this->translator->trans( + $this->getLabel($key, '_header')); + } + + if (count($header_line) > 0) { + fputcsv($output, $header_line); + } + } + + /** + * Give the label corresponding to the given key and value. + * + * @param string $key + * @param string $value + * @return string + * @throws \LogicException if the label is not found + */ + protected function getLabel($key, $value) + { + + if ($this->labelsCache === null) { + $this->prepareCacheLabels(); + } + + if (!\array_key_exists($key, $this->labelsCache)){ + throw new \OutOfBoundsException(sprintf("The key \"%s\" " + . "is not present in the list of keys handled by " + . "this query. Check your `getKeys` and `getLabels` " + . "methods. Available keys are %s.", $key, + \implode(", ", \array_keys($this->labelsCache)))); + } + + return $this->labelsCache[$key]($value); + } + + /** + * Prepare the label cache which will be used by getLabel. This function + * should be called only once in the generation lifecycle. + */ + protected function prepareCacheLabels() + { + $export = $this->exportManager->getExport($this->exportAlias); + $keys = $export->getQueryKeys($this->exportData); + + foreach($keys as $key) { + // get an array with all values for this key if possible + $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + // store the label in the labelsCache property + $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + } + } + + +} diff --git a/src/Bundle/ChillMain/Export/Formatter/CSVPivotedListFormatter.php b/src/Bundle/ChillMain/Export/Formatter/CSVPivotedListFormatter.php new file mode 100644 index 000000000..c6a3f0971 --- /dev/null +++ b/src/Bundle/ChillMain/Export/Formatter/CSVPivotedListFormatter.php @@ -0,0 +1,229 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export\Formatter; + +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +/** + * Create a CSV List for the export where the header are printed on the + * first column, and the result goes from left to right. + * + * @author Champs-Libres + */ +class CSVPivotedListFormatter implements FormatterInterface +{ + + /** + * This variable cache the labels internally + * + * @var string[] + */ + protected $labelsCache = null; + + protected $result = null; + + protected $exportAlias = null; + + protected $exportData = null; + + protected $formatterData = null; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + + + public function __construct(TranslatorInterface $translator, ExportManager $manager) + { + $this->translator = $translator; + $this->exportManager = $manager; + } + + public function getType() + { + return FormatterInterface::TYPE_LIST; + } + + public function getName() + { + return 'CSV horizontal list'; + } + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param type $exportAlias + * @param array $aggregatorAliases + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ){ + $builder->add('numerotation', ChoiceType::class, array( + 'choices' => array( + 'yes' => true, + 'no' => false + ), + 'expanded' => true, + 'multiple' => false, + 'label' => "Add a number on first column", + 'choices_as_values' => true, + 'data' => true + )); + } + + /** + * Generate a response from the data collected on differents ExportElementInterface + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @return \Symfony\Component\HttpFoundation\Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + $this->result = $result; + $this->exportAlias = $exportAlias; + $this->exportData = $exportData; + $this->formatterData = $formatterData; + + $output = fopen('php://output', 'w'); + + $i = 1; + $lines = array(); + $this->prepareHeaders($lines); + + foreach ($result as $row) { + $j = 0; + + if ($this->formatterData['numerotation'] === true) { + $lines[$j][] = $i; + $j++; + } + + foreach ($row as $key => $value) { + $lines[$j][] = $this->getLabel($key, $value); + $j++; + } + $i++; + } + + //adding the lines to the csv output + foreach($lines as $line) { + fputcsv($output, $line); + } + + $csvContent = stream_get_contents($output); + fclose($output); + + $response = new Response(); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); + $response->headers->set('Content-Disposition','attachment; filename="export.csv"'); + + $response->setContent($csvContent); + + return $response; + } + + /** + * add the headers to lines array + * + * @param array $lines the lines where the header will be added + */ + protected function prepareHeaders(array &$lines) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : array(); + $header_line = array(); + + if ($this->formatterData['numerotation'] === true) { + $lines[] = array($this->translator->trans('Number')); + } + + foreach ($first_row as $key => $value) { + $lines[] = array($this->getLabel($key, '_header')); + } + } + + /** + * Give the label corresponding to the given key and value. + * + * @param string $key + * @param string $value + * @return string + * @throws \LogicException if the label is not found + */ + protected function getLabel($key, $value) + { + + if ($this->labelsCache === null) { + $this->prepareCacheLabels(); + } + + return $this->labelsCache[$key]($value); + } + + /** + * Prepare the label cache which will be used by getLabel. This function + * should be called only once in the generation lifecycle. + */ + protected function prepareCacheLabels() + { + $export = $this->exportManager->getExport($this->exportAlias); + $keys = $export->getQueryKeys($this->exportData); + + foreach($keys as $key) { + // get an array with all values for this key if possible + $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + // store the label in the labelsCache property + $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + } + } + + +} diff --git a/src/Bundle/ChillMain/Export/Formatter/SpreadSheetFormatter.php b/src/Bundle/ChillMain/Export/Formatter/SpreadSheetFormatter.php new file mode 100644 index 000000000..7d5eb77aa --- /dev/null +++ b/src/Bundle/ChillMain/Export/Formatter/SpreadSheetFormatter.php @@ -0,0 +1,585 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Export\Formatter; + +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +/** + * + * + * @author Julien Fastré + */ +class SpreadSheetFormatter implements FormatterInterface +{ + /** + * + * @var TranslatorInterface + */ + protected $translator; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * The result, as returned by the export + * + * replaced when `getResponse` is called. + * + * @var type + */ + protected $result; + + /** + * + * replaced when `getResponse` is called. + * + * @var type + */ + protected $formatterData; + + /** + * The export + * + * replaced when `getResponse` is called. + * + * @var \Chill\MainBundle\Export\ExportInterface + */ + protected $export; + + /** + * + * replaced when `getResponse` is called. + * + * @var type + */ + //protected $aggregators; + + /** + * array containing value of export form + * + * replaced when `getResponse` is called. + * + * @var array + */ + protected $exportData; + + /** + * an array where keys are the aggregators aliases and + * values are the data + * + * replaced when `getResponse` is called. + * + * @var type + */ + protected $aggregatorsData; + + /** + * + * replaced when `getResponse` is called. + * + * @var array + */ + protected $filtersData; + + /** + * + * replaced when `getResponse` is called. + * + * @var array + */ + //protected $labels; + + /** + * temporary file to store spreadsheet + * + * @var string + */ + protected $tempfile; + + /** + * cache for displayable result. + * + * This cache is reset when `getResponse` is called. + * + * The array's keys are the keys in the raw result, and + * values are the callable which will transform the raw result to + * displayable result. + * + * @var array + */ + private $cacheDisplayableResult; + + /** + * Whethe `cacheDisplayableResult` is initialized or not. + * + * @var boolean + */ + private $cacheDisplayableResultIsInitialized = false; + + public function __construct(TranslatorInterface $translatorInterface, ExportManager $exportManager) + { + $this->translator = $translatorInterface; + $this->exportManager = $exportManager; + } + + + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ) { + // choosing between formats + $builder->add('format', ChoiceType::class, array( + 'choices' => array( + 'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods', + 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx', + 'Comma separated values (.csv)' => 'csv' + ), + 'choices_as_values' => true, + 'placeholder' => 'Choose the format' + )); + + // ordering aggregators + $aggregators = $this->exportManager->getAggregators($aggregatorAliases); + $nb = count($aggregatorAliases); + + foreach ($aggregators as $alias => $aggregator) { + $builderAggregator = $builder->create($alias, FormType::class, array( + 'label' => $aggregator->getTitle(), + 'block_name' => '_aggregator_placement_spreadsheet_formatter' + )); + $this->appendAggregatorForm($builderAggregator, $nb); + $builder->add($builderAggregator); + } + } + + /** + * append a form line by aggregator on the formatter form. + * + * This form allow to choose the aggregator position (row or column) and + * the ordering + * + * @param FormBuilderInterface $builder + * @param string $nbAggregators + */ + private function appendAggregatorForm(FormBuilderInterface $builder, $nbAggregators) + { + $builder->add('order', ChoiceType::class, array( + 'choices' => array_combine( + range(1, $nbAggregators), + range(1, $nbAggregators) + ), + 'choices_as_values' => true, + 'multiple' => false, + 'expanded' => false + )); + + } + + public function getName() + { + return 'SpreadSheet (xlsx, ods)'; + } + + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + // store all data when the process is initiated + $this->result = $result; + $this->formatterData = $formatterData; + $this->export = $this->exportManager->getExport($exportAlias); + $this->exportData = $exportData; + $this->filtersData = $filtersData; + $this->aggregatorsData = $aggregatorsData; + + // reset cache + $this->cacheDisplayableResult = array(); + $this->cacheDisplayableResultIsInitialized = false; + + $response = new Response(); + $response->headers->set('Content-Type', + $this->getContentType($this->formatterData['format'])); + + $this->tempfile = \tempnam(\sys_get_temp_dir(), ''); + $this->generatecontent(); + + $f = \fopen($this->tempfile, 'r'); + $response->setContent(\stream_get_contents($f)); + fclose($f); + + // remove the temp file from disk + \unlink($this->tempfile); + + return $response; + } + + /** + * Generate the content and write it to php://temp + */ + protected function generateContent() + { + list($spreadsheet, $worksheet) = $this->createSpreadsheet(); + + $this->addTitleToWorkSheet($worksheet); + $line = $this->addFiltersDescription($worksheet); + + // at this point, we are going to sort retsults for an easier manipulation + list($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys) = + $this->sortResult(); + + $line = $this->addHeaders($worksheet, $globalKeys, $line); + + $line = $this->addContentTable($worksheet, $sortedResult, $line); + + switch ($this->formatterData['format']) + { + case 'ods': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Ods'); + break; + case 'xlsx': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Xlsx'); + break; + case 'csv': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Csv'); + break; + default: + // this should not happen + // throw an exception to ensure that the error is catched + throw new \LogicException(); + } + + $writer->save($this->tempfile); + } + + /** + * Create a spreadsheet and a working worksheet + * + * @return array where 1st member is spreadsheet, 2nd is worksheet + */ + protected function createSpreadsheet() + { + $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + + // setting the worksheet title and code name + $worksheet + ->setTitle($this->getTitle()) + ->setCodeName('result'); + + return [$spreadsheet, $worksheet]; + } + + /** + * Add the title to the worksheet and merge the cell containing + * the title + * + * @param Worksheet $worksheet + */ + protected function addTitleToWorkSheet(Worksheet &$worksheet) + { + $worksheet->setCellValue('A1', $this->getTitle()); + $worksheet->mergeCells('A1:G1'); + } + + /** + * Add filter description since line 3. + * + * return the line number after the last description + * + * @param Worksheet $worksheet + * @return int the line number after the last description + */ + protected function addFiltersDescription(Worksheet &$worksheet) + { + $line = 3; + + foreach ($this->filtersData as $alias => $data) { + $filter = $this->exportManager->getFilter($alias); + $description = $filter->describeAction($data, 'string'); + + if (is_array($description)) { + $description = $this->translator + ->trans( + $description[0], + isset($description[1]) ? $description[1] : [] + ); + } + + $worksheet->setCellValue('A'.$line, $description); + $line ++; + } + + return $line; + } + + /** + * sort the results, and return an array where : + * - 0 => sorted results + * - 1 => export keys + * - 2 => aggregator keys + * - 3 => global keys (aggregator keys and export keys) + * + * + * Example, assuming that the result contains two aggregator keys : + * + * array in result : + * + * ``` + * array( + * array( //row 1 + * 'export_result' => 1, + * 'aggregator_1' => 2, + * 'aggregator_2' => 3 + * ), + * array( // row 2 + * 'export_result' => 4, + * 'aggregator_1' => 5, + * 'aggregator_2' => 6 + * ) + * ) + * ``` + * + * the sorted result will be : + * + * ``` + * array( + * array( 2, 3, 1 ), + * array( 5, 6, 4 ) + * ) + * ``` + * + */ + protected function sortResult() + { + // get the keys for each row + $exportKeys = $this->export->getQueryKeys($this->exportData); + $aggregatorKeys = $this->getAggregatorKeysSorted(); + + $globalKeys = \array_merge($aggregatorKeys, $exportKeys); + + $sortedResult = \array_map(function ($row) use ($globalKeys) { + $newRow = array(); + + foreach ($globalKeys as $key) { + $newRow[] = $this->getDisplayableResult($key, $row[$key]); + } + + return $newRow; + }, $this->result); + + \array_multisort($sortedResult); + + return array($sortedResult, $exportKeys, $aggregatorKeys, $globalKeys); + } + + /** + * get an array of aggregator keys. The keys are sorted as asked + * by user in the form. + * + * @return string[] an array containing the keys of aggregators + */ + protected function getAggregatorKeysSorted() + { + // empty array for aggregators keys + $keys = array(); + // this association between key and aggregator alias will be used + // during sorting + $aggregatorKeyAssociation = array(); + + foreach ($this->aggregatorsData as $alias => $data) { + $aggregator = $this->exportManager->getAggregator($alias); + $aggregatorsKeys = $aggregator->getQueryKeys($data); + // append the keys from aggregator to the $keys existing array + $keys = \array_merge($keys, $aggregatorsKeys); + // append the key with the alias, which will be use later for sorting + foreach ($aggregatorsKeys as $key) { + $aggregatorKeyAssociation[$key] = $alias; + } + } + + // sort the result using the form + usort($keys, function ($a, $b) use ($aggregatorKeyAssociation) { + $A = $this->formatterData[$aggregatorKeyAssociation[$a]]['order']; + $B = $this->formatterData[$aggregatorKeyAssociation[$b]]['order']; + + if ($A === $B) { + return 0; + } elseif ($A > $B) { + return 1; + } else { + return -1; + } + + }); + + return $keys; + } + + /** + * add headers to worksheet + * + * return the line number where the next content (i.e. result) should + * be appended. + * + * @param Worksheet $worksheet + * @param array $aggregatorKeys + * @param array $exportKeys + * @param int $line + * @return int + */ + protected function addHeaders( + Worksheet &$worksheet, + array $globalKeys, + $line + ) { + // get the displayable form of headers + $displayables = array(); + foreach ($globalKeys as $key) { + $displayables[] = $this->translator->trans( + $this->getDisplayableResult($key, '_header') + ); + } + + // add headers on worksheet + $worksheet->fromArray( + $displayables, + NULL, + 'A'.$line); + + return $line + 1; + } + + protected function addContentTable(Worksheet $worksheet, + $sortedResults, + $line + ) { + $worksheet->fromArray( + $sortedResults, + NULL, + 'A'.$line); + + return $line + count($sortedResults) + 1; + } + + protected function getTitle() + { + return $this->translator->trans($this->export->getTitle()); + } + + /** + * Get the displayable result. + * + * @param string $key + * @param string $value + * @return string + */ + protected function getDisplayableResult($key, $value) + { + if ($this->cacheDisplayableResultIsInitialized === false) { + $this->initializeCache($key); + } + + return call_user_func($this->cacheDisplayableResult[$key], $value); + } + + protected function initializeCache($key) + { + /* + * this function follows the following steps : + * + * 1. associate all keys used in result with their export element + * (export or aggregator) and data; + * 2. associate all keys used in result with all the possible values : + * this array will be necessary to call `getLabels` + * 3. store the `callable` in an associative array, in cache + */ + // 1. create an associative array with key and export / aggregator + $keysExportElementAssociation = array(); + // keys for export + foreach ($this->export->getQueryKeys($this->exportData) as $key) { + $keysExportElementAssociation[$key] = [$this->export, + $this->exportData]; + } + // keys for aggregator + foreach ($this->aggregatorsData as $alias => $data) { + $aggregator = $this->exportManager->getAggregator($alias); + + foreach ($aggregator->getQueryKeys($data) as $key) { + $keysExportElementAssociation[$key] = [$aggregator, $data]; + } + } + + // 2. collect all the keys before iteration + $keys = array_keys($keysExportElementAssociation); + + $allValues = array(); + // store all the values in an array + foreach ($this->result as $row) { + foreach ($keys as $key) { + $allValues[$key][] = $row[$key]; + } + } + + // 3. iterate on `keysExportElementAssociation` to store the callable + // in cache + foreach ($keysExportElementAssociation as $key => list($element, $data)) { + $this->cacheDisplayableResult[$key] = + $element->getLabels($key, \array_unique($allValues[$key]), $data); + } + + // the cache is initialized ! + $this->cacheDisplayableResultIsInitialized = true; + } + + protected function getContentType($format) + { + switch ($format) + { + case 'csv': + return 'text/csv'; + case 'ods': + return 'application/vnd.oasis.opendocument.spreadsheet'; + case 'xlsx': + return 'application/vnd.openxmlformats-officedocument.' + . 'spreadsheetml.sheet'; + } + } + + public function getType() + { + return 'tabular'; + } +} diff --git a/src/Bundle/ChillMain/Export/Formatter/SpreadsheetListFormatter.php b/src/Bundle/ChillMain/Export/Formatter/SpreadsheetListFormatter.php new file mode 100644 index 000000000..93ca1c08b --- /dev/null +++ b/src/Bundle/ChillMain/Export/Formatter/SpreadsheetListFormatter.php @@ -0,0 +1,287 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export\Formatter; + +use Symfony\Component\HttpFoundation\Response; +use Chill\MainBundle\Export\FormatterInterface; +use Symfony\Component\Translation\TranslatorInterface; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use PhpOffice\PhpSpreadsheet\Spreadsheet; +use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; +use PhpOffice\PhpSpreadsheet\Shared\Date; +use PhpOffice\PhpSpreadsheet\Style\NumberFormat; + +// command to get the report with curl : curl --user "center a_social:password" "http://localhost:8000/fr/exports/generate/count_person?export[filters][person_gender_filter][enabled]=&export[filters][person_nationality_filter][enabled]=&export[filters][person_nationality_filter][form][nationalities]=&export[aggregators][person_nationality_aggregator][order]=1&export[aggregators][person_nationality_aggregator][form][group_by_level]=country&export[submit]=&export[_token]=RHpjHl389GrK-bd6iY5NsEqrD5UKOTHH40QKE9J1edU" --globoff + +/** + * Create a CSV List for the export + * + * @author Champs-Libres + */ +class SpreadsheetListFormatter implements FormatterInterface +{ + /** + * This variable cache the labels internally + * + * @var string[] + */ + protected $labelsCache = null; + + protected $result = null; + + protected $exportAlias = null; + + protected $exportData = null; + + protected $formatterData = null; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + + + public function __construct(TranslatorInterface $translator, ExportManager $manager) + { + $this->translator = $translator; + $this->exportManager = $manager; + } + + public function getType() + { + return FormatterInterface::TYPE_LIST; + } + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param type $exportAlias + * @param array $aggregatorAliases + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ){ + $builder + ->add('format', ChoiceType::class, array( + 'choices' => array( + 'OpenDocument Format (.ods) (LibreOffice, ...)' => 'ods', + 'Microsoft Excel 2007-2013 XML (.xlsx) (Microsoft Excel, LibreOffice)' => 'xlsx' + ), + 'choices_as_values' => true, + 'placeholder' => 'Choose the format' + )) + ->add('numerotation', ChoiceType::class, array( + 'choices' => array( + 'yes' => true, + 'no' => false + ), + 'expanded' => true, + 'multiple' => false, + 'label' => "Add a number on first column", + 'choices_as_values' => true, + 'data' => true + )); + } + + public function getName() + { + return 'Spreadsheet list formatter (.xlsx, .ods)'; + } + + /** + * Generate a response from the data collected on differents ExportElementInterface + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @return \Symfony\Component\HttpFoundation\Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ) { + $this->result = $result; + $this->exportAlias = $exportAlias; + $this->exportData = $exportData; + $this->formatterData = $formatterData; + + $spreadsheet = new Spreadsheet(); + $worksheet = $spreadsheet->getActiveSheet(); + + $this->prepareHeaders($worksheet); + + $i = 1; + foreach ($result as $row) { + $line = array(); + + if ($this->formatterData['numerotation'] === true) { + $worksheet->setCellValue('A'.($i+1), (string) $i); + } + + $a = $this->formatterData['numerotation'] ? 'B' : 'A'; + foreach ($row as $key => $value) { + $row = $a.($i+1); + if ($value instanceof \DateTimeInterface) { + $worksheet->setCellValue($row, Date::PHPToExcel($value)); + $worksheet->getStyle($row) + ->getNumberFormat() + ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY); + } else { + $worksheet->setCellValue($row, $this->getLabel($key, $value)); + } + $a ++; + } + + $i++; + } + + switch ($this->formatterData['format']) + { + case 'ods': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Ods'); + $contentType = "application/vnd.oasis.opendocument.spreadsheet"; + break; + case 'xlsx': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Xlsx'); + $contentType = 'application/vnd.openxmlformats-officedocument.' + . 'spreadsheetml.sheet'; + break; + case 'csv': + $writer = \PhpOffice\PhpSpreadsheet\IOFactory + ::createWriter($spreadsheet, 'Csv'); + $contentType = 'text/csv'; + break; + default: + // this should not happen + // throw an exception to ensure that the error is catched + throw new \OutOfBoundsException("The format ".$this->formatterData['format']. + " is not supported"); + } + + $response = new Response(); + $response->headers->set('content-type', $contentType); + + $tempfile = \tempnam(\sys_get_temp_dir(), ''); + $writer->save($tempfile); + + $f = \fopen($tempfile, 'r'); + $response->setContent(\stream_get_contents($f)); + fclose($f); + + // remove the temp file from disk + \unlink($tempfile); + + return $response; + } + + /** + * add the headers to the csv file + * + * @param Worksheet $worksheet + */ + protected function prepareHeaders(Worksheet $worksheet) + { + $keys = $this->exportManager->getExport($this->exportAlias)->getQueryKeys($this->exportData); + // we want to keep the order of the first row. So we will iterate on the first row of the results + $first_row = count($this->result) > 0 ? $this->result[0] : array(); + $header_line = array(); + + if ($this->formatterData['numerotation'] === true) { + $header_line[] = $this->translator->trans('Number'); + } + + foreach ($first_row as $key => $value) { + $header_line[] = $this->translator->trans( + $this->getLabel($key, '_header')); + } + + if (count($header_line) > 0) { + $worksheet->fromArray($header_line, NULL, 'A1'); + } + } + + /** + * Give the label corresponding to the given key and value. + * + * @param string $key + * @param string $value + * @return string + * @throws \LogicException if the label is not found + */ + protected function getLabel($key, $value) + { + + if ($this->labelsCache === null) { + $this->prepareCacheLabels(); + } + + if (!\array_key_exists($key, $this->labelsCache)){ + throw new \OutOfBoundsException(sprintf("The key \"%s\" " + . "is not present in the list of keys handled by " + . "this query. Check your `getKeys` and `getLabels` " + . "methods. Available keys are %s.", $key, + \implode(", ", \array_keys($this->labelsCache)))); + } + + return $this->labelsCache[$key]($value); + } + + /** + * Prepare the label cache which will be used by getLabel. This function + * should be called only once in the generation lifecycle. + */ + protected function prepareCacheLabels() + { + $export = $this->exportManager->getExport($this->exportAlias); + $keys = $export->getQueryKeys($this->exportData); + + foreach($keys as $key) { + // get an array with all values for this key if possible + $values = \array_map(function ($v) use ($key) { return $v[$key]; }, $this->result); + // store the label in the labelsCache property + $this->labelsCache[$key] = $export->getLabels($key, $values, $this->exportData); + } + } + + +} diff --git a/src/Bundle/ChillMain/Export/FormatterInterface.php b/src/Bundle/ChillMain/Export/FormatterInterface.php new file mode 100644 index 000000000..c507a594f --- /dev/null +++ b/src/Bundle/ChillMain/Export/FormatterInterface.php @@ -0,0 +1,71 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +use Symfony\Component\Form\FormBuilderInterface; + +/** + * + * @author Julien Fastré + */ +interface FormatterInterface +{ + const TYPE_TABULAR = 'tabular'; + const TYPE_LIST = 'list'; + + public function getType(); + + public function getName(); + + /** + * build a form, which will be used to collect data required for the execution + * of this formatter. + * + * @uses appendAggregatorForm + * @param FormBuilderInterface $builder + * @param String $exportAlias Alias of the export which is being executed. An export gets the data and implements the \Chill\MainBundle\Export\ExportInterface + * @param Array(String) $aggregatorAliases Array of the aliases of the aggregators. An aggregator do the "group by" on the data. $aggregatorAliases + */ + public function buildForm( + FormBuilderInterface $builder, + $exportAlias, + array $aggregatorAliases + ); + + /** + * Generate a response from the data collected on differents ExportElementInterface + * + * @param mixed[] $result The result, as given by the ExportInterface + * @param mixed[] $formatterData collected from the current form + * @param string $exportAlias the id of the current export + * @param array $filtersData an array containing the filters data. The key are the filters id, and the value are the data + * @param array $aggregatorsData an array containing the aggregators data. The key are the filters id, and the value are the data + * @return \Symfony\Component\HttpFoundation\Response The response to be shown + */ + public function getResponse( + $result, + $formatterData, + $exportAlias, + array $exportData, + array $filtersData, + array $aggregatorsData + ); + +} diff --git a/src/Bundle/ChillMain/Export/GroupedExportInterface.php b/src/Bundle/ChillMain/Export/GroupedExportInterface.php new file mode 100644 index 000000000..9330fd32b --- /dev/null +++ b/src/Bundle/ChillMain/Export/GroupedExportInterface.php @@ -0,0 +1,16 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Export; + +use Doctrine\ORM\QueryBuilder; + +/** + * Modifiers modify the export's query. + * + * Known subclasses : AggregatorInterface and FilterInterface + * + * @author Julien Fastré + */ +interface ModifierInterface extends ExportElementInterface +{ + /** + * The role required for executing this Modifier + * + * If null, will used the ExportInterface::requiredRole role from + * the current executing export. + * + * @return NULL|\Symfony\Component\Security\Core\Role\Role A role required to execute this ModifiersInterface + */ + public function addRole(); + + /** + * On which type of Export this ModifiersInterface may apply. + * + * @return string the type on which the Modifiers apply + */ + public function applyOn(); + + /** + * Alter the query initiated by the export, to add the required statements + * (`GROUP BY`, `SELECT`, `WHERE`) + * + * @param QueryBuilder $qb the QueryBuilder initiated by the Export (and eventually modified by other Modifiers) + * @param mixed[] $data the data from the Form (builded by buildForm) + */ + public function alterQuery(QueryBuilder $qb, $data); +} diff --git a/src/Bundle/ChillMain/Form/AdvancedSearchType.php b/src/Bundle/ChillMain/Form/AdvancedSearchType.php new file mode 100644 index 000000000..a70a34b31 --- /dev/null +++ b/src/Bundle/ChillMain/Form/AdvancedSearchType.php @@ -0,0 +1,59 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Chill\MainBundle\Search\SearchProvider; + +/** + * + * + * @author Julien Fastré + */ +class AdvancedSearchType extends AbstractType +{ + /** + * + * @var SearchProvider + */ + protected $searchProvider; + + public function __construct(SearchProvider $searchProvider) + { + $this->searchProvider = $searchProvider; + } + + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $this->searchProvider + ->getHasAdvancedFormByName($options['search_service']) + ->createSearchForm($builder) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('search_service') + ->setAllowedTypes('search_service', [ 'string' ]) + ; + } +} diff --git a/src/Bundle/ChillMain/Form/CenterType.php b/src/Bundle/ChillMain/Form/CenterType.php new file mode 100644 index 000000000..9ab037304 --- /dev/null +++ b/src/Bundle/ChillMain/Form/CenterType.php @@ -0,0 +1,41 @@ +add('name', TextType::class) + ; + } + + /** + * @param OptionsResolverInterface $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Chill\MainBundle\Entity\Center' + )); + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return 'chill_mainbundle_center'; + } + +} diff --git a/src/Bundle/ChillMain/Form/ChoiceLoader/PostalCodeChoiceLoader.php b/src/Bundle/ChillMain/Form/ChoiceLoader/PostalCodeChoiceLoader.php new file mode 100644 index 000000000..027c05e4a --- /dev/null +++ b/src/Bundle/ChillMain/Form/ChoiceLoader/PostalCodeChoiceLoader.php @@ -0,0 +1,89 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\ChoiceLoader; + +use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Chill\MainBundle\Repository\PostalCodeRepository; +use Symfony\Component\Form\ChoiceList\LazyChoiceList; +use Chill\MainBundle\Entity\PostalCode; + +/** + * + * + * @author Julien Fastré + */ +class PostalCodeChoiceLoader implements ChoiceLoaderInterface +{ + /** + * + * @var PostalCodeRepository + */ + protected $postalCodeRepository; + + protected $lazyLoadedPostalCodes = []; + + public function __construct(PostalCodeRepository $postalCodeRepository) + { + $this->postalCodeRepository = $postalCodeRepository; + } + + public function loadChoiceList($value = null): ChoiceListInterface + { + $list = new \Symfony\Component\Form\ChoiceList\ArrayChoiceList( + $this->lazyLoadedPostalCodes, + function(PostalCode $pc = null) use ($value) { + return \call_user_func($value, $pc); + }); + + return $list; + } + + public function loadChoicesForValues(array $values, $value = null) + { + $choices = []; + + foreach($values as $value) { + if (empty($value)) { + $choices[] = null; + } else { + $choices[] = $this->postalCodeRepository->find($value); + } + } + + return $choices; + } + + public function loadValuesForChoices(array $choices, $value = null) + { + $values = []; + + foreach ($choices as $choice) { + if (NULL === $choice) { + $values[] = null; + continue; + } + + $id = \call_user_func($value, $choice); + $values[] = $id; + $this->lazyLoadedPostalCodes[$id] = $choice; + } + + return $values; + } +} diff --git a/src/Bundle/ChillMain/Form/DataMapper/AddressDataMapper.php b/src/Bundle/ChillMain/Form/DataMapper/AddressDataMapper.php new file mode 100644 index 000000000..36383f7e3 --- /dev/null +++ b/src/Bundle/ChillMain/Form/DataMapper/AddressDataMapper.php @@ -0,0 +1,121 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\DataMapper; + +use Symfony\Component\Form\DataMapperInterface; +use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Entity\PostalCode; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * Add a data mapper to Address. + * + * If the address is incomplete, the data mapper returns null + */ +class AddressDataMapper implements DataMapperInterface +{ + /** + * + * @param Address $address + * @param \Iterator $forms + */ + public function mapDataToForms($address, $forms) + { + if (NULL === $address) { + return; + } + + if (!$address instanceof Address) { + throw new UnexpectedTypeException($address, Address::class); + } + + foreach ($forms as $key => $form) { + /** @var FormInterface $form */ + switch ($key) { + case 'streetAddress1': + $form->setData($address->getStreetAddress1()); + break; + case 'streetAddress2': + $form->setData($address->getStreetAddress2()); + break; + case 'postCode': + $form->setData($address->getPostcode()); + break; + case 'validFrom': + $form->setData($address->getValidFrom()); + break; + case 'isNoAddress': + $form->setData($address->isNoAddress()); + break; + default: + break; + } + } + } + + /** + * + * @param \Iterator $forms + * @param Address $address + */ + public function mapFormsToData($forms, &$address) + { + if (!$address instanceof Address) { + $address = new Address(); + } + + $isNoAddress = false; + foreach ($forms as $key => $form) { + if ($key === 'isNoAddress') { + $isNoAddress = $form->get('isNoAddress')->getData(); + } + } + + foreach ($forms as $key => $form) { + /** @var FormInterface $form */ + switch($key) { + case 'postCode': + if (!$form->getData() instanceof PostalCode && !$isNoAddress) { + $address = null; + return; + } + $address->setPostcode($form->getData()); + break; + case 'streetAddress1': + if (empty($form->getData()) && !$isNoAddress) { + $address = null; + return; + } + $address->setStreetAddress1($form->getData()); + break; + case 'streetAddress2': + $address->setStreetAddress2($form->getData()); + break; + case 'validFrom': + $address->setValidFrom($form->getData()); + break; + case 'isNoAddress': + $address->setIsNoAddress($form->getData()); + break; + default: + break; + } + } + } +} diff --git a/src/Bundle/ChillMain/Form/PermissionsGroupType.php b/src/Bundle/ChillMain/Form/PermissionsGroupType.php new file mode 100644 index 000000000..d9b4ab730 --- /dev/null +++ b/src/Bundle/ChillMain/Form/PermissionsGroupType.php @@ -0,0 +1,82 @@ +add('name', TextType::class) + ; + + $flags = $this->getFlags(); + + if (count($flags) > 0) { + $builder + ->add('flags', ChoiceType::class, [ + 'choices' => \array_combine($flags, $flags), + 'multiple' => true, + 'expanded' => true, + 'required' => false + ]); + } + } + + /** + * + * @return array + */ + protected function getFlags(): array + { + $flags = []; + + foreach ($this->flagProviders as $flagProvider) { + $flags = \array_merge($flags, $flagProvider->getPermissionsGroupFlags()); + } + + return $flags; + } + + public function addFlagProvider(PermissionsGroupFlagProvider $provider) + { + $this->flagProviders[] = $provider; + } + + /** + * @param OptionsResolverInterface $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Chill\MainBundle\Entity\PermissionsGroup' + )); + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return 'chill_mainbundle_permissionsgroup'; + } +} diff --git a/src/Bundle/ChillMain/Form/ScopeType.php b/src/Bundle/ChillMain/Form/ScopeType.php new file mode 100644 index 000000000..12eb9fec4 --- /dev/null +++ b/src/Bundle/ChillMain/Form/ScopeType.php @@ -0,0 +1,40 @@ +add('name', TranslatableStringFormType::class) + ; + } + + /** + * @param OptionsResolverInterface $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Chill\MainBundle\Entity\Scope' + )); + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return 'chill_mainbundle_scope'; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/AddressType.php b/src/Bundle/ChillMain/Form/Type/AddressType.php new file mode 100644 index 000000000..96012054e --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/AddressType.php @@ -0,0 +1,117 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Chill\MainBundle\Entity\Address; +use Chill\MainBundle\Form\Type\PostalCodeType; +use Chill\MainBundle\Form\DataMapper\AddressDataMapper; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +/** + * A type to create/update Address entity + * + * Options: + * + * - `has_valid_from` (boolean): show if an entry "has valid from" must be + * shown. + * - `null_if_empty` (boolean): replace the address type by null if the street + * or the postCode is empty. This is useful when the address is not required and + * embedded in another form. + * - `customize_data` (callable): allow to add custom field to the form, which + * will be store in the `customs` property + */ +class AddressType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('streetAddress1', TextType::class, array( + 'required' => !$options['has_no_address'] // true if has no address is false + )) + ->add('streetAddress2', TextType::class, array( + 'required' => false + )) + ->add('postCode', PostalCodeType::class, array( + 'label' => 'Postal code', + 'placeholder' => 'Choose a postal code', + 'required' => !$options['has_no_address'] // true if has no address is false + )) + ; + + if ($options['has_valid_from']) { + $builder + ->add('validFrom', DateType::class, array( + 'required' => true, + 'widget' => 'single_text', + 'format' => 'dd-MM-yyyy' + ) + ); + } + + if ($options['has_no_address']) { + $builder + ->add('isNoAddress', ChoiceType::class, [ + 'required' => true, + 'choices' => [ + 'address.consider homeless' => true, + 'address.real address' => false + ], + 'label' => 'address.address_homeless' + ]); + } + + if ($options['customize_data'] !== NULL && is_callable($options['customize_data'])) { + $customsBuilder = $builder->create('customs', NULL, [ + 'compound' => true, + 'label' => false + ]); + \call_user_func($options['customize_data'], $customsBuilder); + $builder->add($customsBuilder); + } + + if ($options['null_if_empty'] === TRUE) { + $builder->setDataMapper(new AddressDataMapper()); + } + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('data_class', Address::class) + ->setDefined('has_valid_from') + ->setAllowedTypes('has_valid_from', 'bool') + ->setDefault('has_valid_from', true) + ->setDefined('has_no_address') + ->setDefault('has_no_address', false) + ->setAllowedTypes('has_no_address', 'bool') + ->setDefined('null_if_empty') + ->setDefault('null_if_empty', false) + ->setAllowedTypes('null_if_empty', 'bool') + ->setDefined('customize_data') + ->setAllowedTypes('customize_data', 'callable') + ; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/AppendScopeChoiceTypeTrait.php b/src/Bundle/ChillMain/Form/Type/AppendScopeChoiceTypeTrait.php new file mode 100644 index 000000000..2cddce3cd --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/AppendScopeChoiceTypeTrait.php @@ -0,0 +1,153 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\FormEvent; +use Chill\MainBundle\Entity\User; +use Chill\MainBundle\Entity\Center; +use Symfony\Component\Security\Core\Role\Role; +use Chill\MainBundle\Form\Type\DataTransformer\ScopeTransformer; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +/** + * Trait to add an input with reachable scope for a given center and role. + * + * Example usage : + * + * ``` + * class AbcType extends Symfony\Component\Form\AbstractType + * { + * use AppendScopeChoiceTypeTrait; + * protected $authorizationHelper; + * protected $translatableStringHelper; + * protected $user; + * protected $om; + * + * public function __construct(AuthorizationHelper $helper, + * TokenStorageInterface $tokenStorage, + * TranslatableStringHelper $translatableStringHelper, + * ObjectManager $om) + * { + * $this->authorizationHelper = $helper; + * $this->user = $tokenStorage->getToken()->getUser(); + * $this->translatableStringHelper = $translatableStringHelper; + * $this->om = $om; + * } + * + * public function buildForm(FormBuilder $builder, array $options) + * { + * // ... add your form there + * + * // append the scope using FormEvents: PRE_SET_DATA + * $this->appendScopeChoices($builder, $options['role'], + * $options['center'], $this->user, $this->authorizationHelper, + * $this->translatableStringHelper, $this->om); + * } + * + * public function configureOptions(OptionsResolver $resolver) + * { + * // ... add your options + * + * // add an option 'role' and 'center' to your form (optional) + * $this->appendScopeChoicesOptions($resolver); + * } + * + * } + * ``` + * + * @author Julien Fastré + * @author Champs Libres + */ +trait AppendScopeChoiceTypeTrait +{ + /** + * Append a scope choice field, with the scopes reachable by given + * user for the given role and center. + * + * The field is added on event FormEvents::PRE_SET_DATA + * + * @param FormBuilderInterface $builder + * @param Role $role + * @param Center $center + * @param User $user + * @param AuthorizationHelper $authorizationHelper + * @param TranslatableStringHelper $translatableStringHelper + * @param string $name + */ + protected function appendScopeChoices(FormBuilderInterface $builder, + Role $role, Center $center, User $user, + AuthorizationHelper $authorizationHelper, + TranslatableStringHelper $translatableStringHelper, + ObjectManager $om, $name = 'scope') + { + $reachableScopes = $authorizationHelper + ->getReachableScopes($user, $role, $center); + + $choices = array(); + foreach($reachableScopes as $scope) { + $choices[$scope->getId()] = $translatableStringHelper + ->localize($scope->getName()); + } + + $dataTransformer = new ScopeTransformer($om); + + $builder->addEventListener(FormEvents::PRE_SET_DATA, + function (FormEvent $event) use ($choices, $name, $dataTransformer, $builder) { + $form = $event->getForm(); + $form->add( + $builder + ->create($name, ChoiceType::class, array( + 'choices' => array_combine(array_values($choices),array_keys($choices)), + 'choices_as_values' => true, + 'auto_initialize' => false + ) + ) + ->addModelTransformer($dataTransformer) + ->getForm() + ); + }); + } + + /** + * Append a `role` and `center` option to the form. + * + * The allowed types are : + * - Chill\MainBundle\Entity\Center for center + * - Symfony\Component\Security\Core\Role\Role for role + * + * @param OptionsResolver $resolver + */ + public function appendScopeChoicesOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired(array('center', 'role')) + ->setAllowedTypes('center', 'Chill\MainBundle\Entity\Center') + ->setAllowedTypes('role', 'Symfony\Component\Security\Core\Role\Role') + ; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/CenterType.php b/src/Bundle/ChillMain/Form/Type/CenterType.php new file mode 100644 index 000000000..41db8dc98 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/CenterType.php @@ -0,0 +1,136 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer; +use Symfony\Component\Form\Extension\Core\Type\HiddenType; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; + +/** + * + * + * @author Julien Fastré + */ +class CenterType extends AbstractType +{ + /** + * The user linked with this type. + * + * @var \Chill\MainBundle\Entity\User + */ + protected $user; + + /** + * associative array where keys are center.id and + * value are center objects + * + * @var Center[] + */ + protected $reachableCenters = array(); + + /** + * + * @var CenterTransformer + */ + protected $transformer; + + public function __construct(TokenStorage $tokenStorage, + CenterTransformer $transformer) + { + $this->user = $tokenStorage->getToken()->getUser(); + $this->transformer = $transformer; + $this->prepareReachableCenterByUser(); + } + + /** + * return a 'hidden' field if only one center is available. + * + * Return a 'choice' field if more than one center is available. + * + * @return string + * @throws \RuntimeException if the user is not associated with any center + */ + public function getParent() + { + $nbReachableCenters = count($this->reachableCenters); + + if ($nbReachableCenters === 0) { + throw new \RuntimeException("The user is not associated with " + . "any center. Associate user with a center"); + } elseif ($nbReachableCenters === 1) { + return HiddenType::class; + } else { + return EntityType::class; + } + } + + /** + * configure default options, i.e. add choices if user can reach multiple + * centers. + * + * @param OptionsResolver $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + if (count($this->reachableCenters) > 1) { + $resolver->setDefault('class', Center::class); + $resolver->setDefault('choices', $this->reachableCenters); + } + } + + /** + * add a data transformer if user can reach only one center + * + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + if ($this->getParent() === HiddenType::class) { + $builder->addModelTransformer($this->transformer); + } + } + + /** + * populate reachableCenters as an associative array where + * keys are center.id and value are center entities. + * + */ + private function prepareReachableCenterByUser() + { + $groupCenters = $this->user->getGroupCenters(); + + foreach ($groupCenters as $groupCenter) { + + $center = $groupCenter->getCenter(); + + if (!array_key_exists($center->getId(), + $this->reachableCenters)) { + $this->reachableCenters[$center->getId()] = $center; + } + } + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/ChillCollectionType.php b/src/Bundle/ChillMain/Form/Type/ChillCollectionType.php new file mode 100644 index 000000000..1068522c9 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ChillCollectionType.php @@ -0,0 +1,61 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormView; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormInterface; + +/** + * Available options : + * + * - `button_add_label` + * - `button_remove_label` + * - `identifier`: an identifier to identify the kind of collecton. Useful if some + * javascript should be launched associated to `add_entry`, `remove_entry` events. + * + * + * @author Julien Fastré + */ +class ChillCollectionType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefaults([ + 'button_add_label' => 'Add an entry', + 'button_remove_label' => 'Remove entry', + 'identifier' => '' + ]); + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['button_add_label'] = $options['button_add_label']; + $view->vars['button_remove_label'] = $options['button_remove_label']; + $view->vars['allow_delete'] = (int) $options['allow_delete']; + $view->vars['allow_add'] = (int) $options['allow_add']; + $view->vars['identifier'] = $options['identifier']; + } + + public function getParent() + { + return \Symfony\Component\Form\Extension\Core\Type\CollectionType::class; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/ChillDateTimeType.php b/src/Bundle/ChillMain/Form/Type/ChillDateTimeType.php new file mode 100644 index 000000000..07adc0cb8 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ChillDateTimeType.php @@ -0,0 +1,50 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateTimeType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Display the date in a date picker. + * + * Extends the symfony `Symfony\Component\Form\Extension\Core\Type\DateType` + * to automatically create a date picker. + * + * @author Mathieu Jaumotte + */ +class ChillDateTimeType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('date_widget', 'single_text') + ->setDefault('date_format', 'dd-MM-yyyy') + ->setDefault('time_widget', 'choice') + ->setDefault('minutes', range(0, 59, 5)) + ->setDefault('hours', range(8, 22)) + ->setDefault('html5', true) + ; + } + + public function getParent() + { + return DateTimeType::class; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/ChillDateType.php b/src/Bundle/ChillMain/Form/Type/ChillDateType.php new file mode 100644 index 000000000..2f5a6945c --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ChillDateType.php @@ -0,0 +1,47 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\DateType; +use Symfony\Component\OptionsResolver\OptionsResolver; + +/** + * Display the date in a date picker. + * + * Extends the symfony `Symfony\Component\Form\Extension\Core\Type\DateType` + * to automatically create a date picker. + * + * @author Julien Fastré + */ +class ChillDateType extends AbstractType +{ + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefault('widget', 'single_text') + ->setDefault('attr', [ 'class' => 'datepicker' ]) + ->setDefault('format', 'dd-MM-yyyy') + ; + } + + public function getParent() + { + return DateType::class; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/ComposedGroupCenterType.php b/src/Bundle/ChillMain/Form/Type/ComposedGroupCenterType.php new file mode 100644 index 000000000..0fe628401 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ComposedGroupCenterType.php @@ -0,0 +1,64 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; + +use Chill\MainBundle\Entity\PermissionsGroup; +use Chill\MainBundle\Entity\Center; + +/** + * + * + * @author Julien Fastré + */ +class ComposedGroupCenterType extends AbstractType +{ + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('permissionsgroup', EntityType::class, array( + 'class' => 'Chill\MainBundle\Entity\PermissionsGroup', + 'choice_label' => function(PermissionsGroup $group) { + return $group->getName(); + } + ))->add('center', EntityType::class, array( + 'class' => 'Chill\MainBundle\Entity\Center', + 'choice_label' => function(Center $center) { + return $center->getName(); + } + )) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', 'Chill\MainBundle\Entity\GroupCenter'); + } + + public function getBlockPrefix() + { + return 'composed_groupcenter'; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/ComposedRoleScopeType.php b/src/Bundle/ChillMain/Form/Type/ComposedRoleScopeType.php new file mode 100644 index 000000000..e3554a126 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ComposedRoleScopeType.php @@ -0,0 +1,123 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Entity\Scope; +use Chill\MainBundle\Security\RoleProvider; + + +/** + * Form to Edit/create a role scope. If the role scope does not + * exists in the database, he is generated. + * + * @author Julien Fastré + * @author Champs Libres + */ +class ComposedRoleScopeType extends AbstractType +{ + /** + * + * @var string[] + */ + private $roles = array(); + + /** + * + * @var string[] + */ + private $rolesWithoutScope = array(); + + /** + * + * @var TranslatableStringHelper + */ + private $translatableStringHelper; + + /** + * + * @var RoleProvider + */ + private $roleProvider; + + public function __construct( + TranslatableStringHelper $translatableStringHelper, + RoleProvider $roleProvider + ) { + $this->roles = $roleProvider->getRoles(); + $this->rolesWithoutScope = $roleProvider->getRolesWithoutScopes(); + $this->translatableStringHelper = $translatableStringHelper; + $this->roleProvider = $roleProvider; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + // store values used in internal function + $translatableStringHelper = $this->translatableStringHelper; + $rolesWithoutScopes = $this->rolesWithoutScope; + + //build roles + $values = array(); + foreach ($this->roles as $role) { + $values[$role] = $role; + } + + $builder + ->add('role', ChoiceType::class, array( + 'choices' => array_combine(array_values($values),array_keys($values)), + 'choices_as_values' => true, + 'placeholder' => 'Choose amongst roles', + 'choice_attr' => function($role) use ($rolesWithoutScopes) { + if (in_array($role, $rolesWithoutScopes)) { + return array('data-has-scope' => '0'); + } else { + return array('data-has-scope' => '1'); + } + }, + 'group_by' => function($role, $key, $index) { + return $this->roleProvider->getRoleTitle($role); + } + )) + ->add('scope', EntityType::class, array( + 'class' => 'ChillMainBundle:Scope', + 'choice_label' => function(Scope $scope) use ($translatableStringHelper) { + return $translatableStringHelper->localize($scope->getName()); + }, + 'required' => false, + 'data' => null + )); + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('data_class', 'Chill\MainBundle\Entity\RoleScope'); + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/DataTransformer/CenterTransformer.php b/src/Bundle/ChillMain/Form/Type/DataTransformer/CenterTransformer.php new file mode 100644 index 000000000..a1930cf85 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DataTransformer/CenterTransformer.php @@ -0,0 +1,70 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\Form\Exception\TransformationFailedException; + +/** + * Transform a center object to his id, and vice-versa + * + * @author Julien Fastré + */ +class CenterTransformer implements DataTransformerInterface +{ + /** + * + * @var ObjectManager + */ + private $om; + + public function __construct(ObjectManager $om) + { + $this->om = $om; + } + + public function reverseTransform($id) + { + if ($id === NULL) { + return NULL; + } + + $center = $this->om->getRepository('ChillMainBundle:Center') + ->find($id); + + if ($center === NULL) { + throw new TransformationFailedException(sprintf( + 'No center found with id %d', $id)); + } + + return $center; + } + + public function transform($center) + { + if ($center === NULL) { + return ''; + } + + return $center->getId(); + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/DataTransformer/DateIntervalTransformer.php b/src/Bundle/ChillMain/Form/Type/DataTransformer/DateIntervalTransformer.php new file mode 100644 index 000000000..29a81447c --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DataTransformer/DateIntervalTransformer.php @@ -0,0 +1,92 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Symfony\Component\Form\Exception\UnexpectedTypeException; + +/** + * + * + * @author Julien Fastré + */ +class DateIntervalTransformer implements DataTransformerInterface +{ + /** + * + * @param \DateInterval $value + * @throws UnexpectedTypeException + */ + public function transform($value) + { + if (empty($value)) { + return null; + } + + if (!$value instanceof \DateInterval) { + throw new UnexpectedTypeException($value, \DateInterval::class); + } + + if ($value->d > 0) { + // we check for weeks (weeks are converted to 7 days) + if ($value->d % 7 === 0) { + return [ + 'n' => $value->d / 7, + 'unit' => 'W' + ]; + } else { + return [ + 'n' => $value->d, + 'unit' => 'D' + ]; + } + } elseif ($value->m > 0) { + return [ + 'n' => $value->m, + 'unit' => 'M' + ]; + } elseif ($value->y > 0) { + return [ + 'n' => $value->y, + 'unit' => 'Y' + ]; + } + + throw new TransformationFailedException('the date interval does not ' + . 'contains any days, months or years'); + } + + public function reverseTransform($value) + { + if (empty($value) or empty($value['n'])) { + return null; + } + + $string = 'P'.$value['n'].$value['unit']; + + try { + return new \DateInterval($string); + } catch (\Exception $e) { + throw new TransformationFailedException("Could not transform value " + . "into DateInterval", 1542, $e); + } + + + } +} diff --git a/src/Bundle/ChillMain/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php b/src/Bundle/ChillMain/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php new file mode 100644 index 000000000..be1e0a20b --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DataTransformer/MultipleObjectsToIdTransformer.php @@ -0,0 +1,85 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Collections\ArrayCollection; + +class MultipleObjectsToIdTransformer implements DataTransformerInterface +{ + /** + * @var ObjectManager + */ + private $em; + + /** + * @var string + */ + private $class; + + /** + * @param ObjectManager $em + */ + public function __construct(ObjectManager $em, $class) + { + $this->em = $em; + $this->class = $class; + } + + /** + * Transforms an object (use) to a string (id). + * + * @param array $array + * @return ArrayCollection + */ + public function transform($array) + { + $ret = array(); + + foreach ($array as $el) { + $ret[] = ($el->getId()); + } + + return $ret; + } + + /** + * Transforms a string (id) to an object (item). + * + * @param string $id + * @return ArrayCollection + */ + public function reverseTransform($array) + { + $ret = new ArrayCollection(); + + foreach ($array as $el) { + $ret->add( + $this->em + ->getRepository($this->class) + ->find($el) + ); + } + return $ret; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/Form/Type/DataTransformer/ObjectToIdTransformer.php b/src/Bundle/ChillMain/Form/Type/DataTransformer/ObjectToIdTransformer.php new file mode 100644 index 000000000..7193c93a6 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DataTransformer/ObjectToIdTransformer.php @@ -0,0 +1,87 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Doctrine\Common\Persistence\ObjectManager; + +class ObjectToIdTransformer implements DataTransformerInterface +{ + /** + * @var ObjectManager + */ + private $om; + + /** + * @var string + */ + private $class; + + /** + * @param ObjectManager $om + */ + public function __construct(ObjectManager $om, $class) + { + $this->om = $om; + $this->class = $class; + } + + /** + * Transforms an object to a string (id) + * + * @param Object|null $Object + * @return string + */ + public function transform($object) + { + if (!$object) { + return ""; + } + + return $object->getId(); + } + + /** + * Transforms a string (id) to an object + * + * @param string $id + * @return Object|null + * @throws TransformationFailedException if object is not found. + */ + public function reverseTransform($id) + { + if (!$id) { + return null; + } + + $object = $this->om + ->getRepository($this->class) + ->find($id) + ; + + if (! $object) { + throw new TransformationFailedException(); + } + + return $object; + } +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/Form/Type/DataTransformer/ScopeTransformer.php b/src/Bundle/ChillMain/Form/Type/DataTransformer/ScopeTransformer.php new file mode 100644 index 000000000..ca53b85b0 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DataTransformer/ScopeTransformer.php @@ -0,0 +1,77 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\DataTransformer; + +use Symfony\Component\Form\DataTransformerInterface; +use Doctrine\Common\Persistence\ObjectManager; +use Symfony\Component\Form\Exception\TransformationFailedException; +use Chill\MainBundle\Templating\TranslatableStringHelper; + +/** + * + * + * @author Julien Fastré + */ +class ScopeTransformer implements DataTransformerInterface +{ + /** + * + * @var ObjectManager + */ + protected $om; + + /** + * + * @var TranslatableStringHelper + */ + protected $helper; + + public function __construct(ObjectManager $om) + { + $this->om = $om; + } + + public function transform($scope) + { + if ($scope === NULL) { + return NULL; + } + + return $scope->getId(); + } + + public function reverseTransform($id) + { + if ($id == NULL) { + return NULL; + } + + $scope = $this->om->getRepository('ChillMainBundle:Scope') + ->find($id); + + if ($scope === NULL) { + throw new TransformationFailedException(sprintf("The scope with id " + . "'%d' were not found", $id)); + } + + return $scope; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/DateIntervalType.php b/src/Bundle/ChillMain/Form/Type/DateIntervalType.php new file mode 100644 index 000000000..c0fb741e8 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/DateIntervalType.php @@ -0,0 +1,109 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Core\Type\IntegerType; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Chill\MainBundle\Form\Type\DataTransformer\DateIntervalTransformer; +use Symfony\Component\Validator\Constraints\GreaterThan; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException; + + +/** + * Show a dateInterval type + * + * Options: + * + * - `unit_choices`: an array of available units choices. + * + * The oiriginal `unit_choices` are : + * ``` + * [ + * 'Days' => 'D', + * 'Weeks' => 'W', + * 'Months' => 'M', + * 'Years' => 'Y' + * ] + * ``` + * + * You can remove one or more entries: + * + * ``` + * $builder + * ->add('duration', DateIntervalType::class, array( + * 'unit_choices' => [ + * 'Years' => 'Y', + * 'Months' => 'M', + * ] + * )); + * ``` + * + */ +class DateIntervalType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('n', IntegerType::class, [ + 'scale' => 0, + 'constraints' => [ + new GreaterThan([ + 'value' => 0 + ]) + ] + ]) + ->add('unit', ChoiceType::class, [ + 'choices' => $options['unit_choices'], + 'choices_as_values' => true + ]) + ; + + $builder->addModelTransformer(new DateIntervalTransformer()); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setDefined('unit_choices') + ->setDefault('unit_choices', [ + 'Days' => 'D', + 'Weeks' => 'W', + 'Months' => 'M', + 'Years' => 'Y' + ]) + ->setAllowedValues('unit_choices', function($values) { + if (FALSE === is_array($values)) { + throw new InvalidOptionsException("The value `unit_choice` should be an array"); + } + + $diff = \array_diff(\array_values($values), ['D', 'W', 'M', 'Y']); + if (count($diff) == 0) { + return true; + } else { + throw new InvalidOptionsException(sprintf("The values of the " + . "units should be 'D', 'W', 'M', 'Y', those are invalid: %s", + \implode(', ', $diff))); + } + }) + ; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/AggregatorType.php b/src/Bundle/ChillMain/Form/Type/Export/AggregatorType.php new file mode 100644 index 000000000..dd2125546 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/AggregatorType.php @@ -0,0 +1,74 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Symfony\Component\Form\Extension\Core\Type\FormType; + +use Chill\MainBundle\Export\ExportManager; + +/** + * + * + * @author Julien Fastré + */ +class AggregatorType extends AbstractType +{ + + public function __construct() + { + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $exportManager = $options['export_manager']; + $aggregator = $exportManager->getAggregator($options['aggregator_alias']); + + $builder + ->add('enabled', CheckboxType::class, array( + 'value' => true, + 'required' => false, + 'data' => false + )); + + $filterFormBuilder = $builder->create('form', FormType::class, array( + 'compound' => true, + 'required' => false, + 'error_bubbling' => false + )); + $aggregator->buildForm($filterFormBuilder); + + $builder->add($filterFormBuilder); + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired('aggregator_alias') + ->setRequired('export_manager') + ->setDefault('compound', true) + ->setDefault('error_bubbling', false) + ; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/ExportType.php b/src/Bundle/ChillMain/Form/Type/Export/ExportType.php new file mode 100644 index 000000000..24dd63dd3 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/ExportType.php @@ -0,0 +1,147 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Form\Type\Export\FilterType; +use Chill\MainBundle\Form\Type\Export\AggregatorType; +use Symfony\Component\Form\Extension\Core\Type\FormType; +use Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint; +use Chill\MainBundle\Export\ExportElementWithValidationInterface; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; + +/** + * + * + * @author Julien Fastré + */ +class ExportType extends AbstractType +{ + /** + * + * @var ExportManager + */ + protected $exportManager; + + const FILTER_KEY = 'filters'; + const AGGREGATOR_KEY = 'aggregators'; + const PICK_FORMATTER_KEY = 'pick_formatter'; + const EXPORT_KEY = 'export'; + + public function __construct(ExportManager $exportManager) + { + $this->exportManager = $exportManager; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $export = $this->exportManager->getExport($options['export_alias']); + + $exportOptions = array( + 'compound' => true, + 'constraints' => array( + + ) + ); + + // add a contraint if required by export + $exportBuilder = $builder->create(self::EXPORT_KEY/*, FormType::class, $exportOptions*/); + + $export->buildForm($exportBuilder); + + $builder->add($exportBuilder, null, $exportOptions); + + if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { + //add filters + $filters = $this->exportManager->getFiltersApplyingOn($export, $options['picked_centers']); + $filterBuilder = $builder->create(self::FILTER_KEY, FormType::class, array('compound' => true)); + + foreach($filters as $alias => $filter) { + $filterBuilder->add($alias, FilterType::class, array( + 'filter_alias' => $alias, + 'export_manager' => $this->exportManager, + 'label' => $filter->getTitle(), + 'constraints' => array( + new ExportElementConstraint(['element' => $filter]) + ) + )); + } + + $builder->add($filterBuilder); + + //add aggregators + $aggregators = $this->exportManager + ->getAggregatorsApplyingOn($export, $options['picked_centers']); + $aggregatorBuilder = $builder->create(self::AGGREGATOR_KEY, FormType::class, + array('compound' => true)); + + foreach($aggregators as $alias => $aggregator) { + $aggregatorBuilder->add($alias, AggregatorType::class, array( + 'aggregator_alias' => $alias, + 'export_manager' => $this->exportManager, + 'label' => $aggregator->getTitle(), + 'constraints' => array( + new ExportElementConstraint(['element' => $aggregator]) + ) + )); + } + + $builder->add($aggregatorBuilder); + } + + // add export form + $exportBuilder = $builder->create(self::EXPORT_KEY, FormType::class, array('compound' => true)); + $this->exportManager->getExport($options['export_alias']) + ->buildForm($exportBuilder); + $builder->add($exportBuilder); + + if ($export instanceof \Chill\MainBundle\Export\ExportInterface) { + $builder->add(self::PICK_FORMATTER_KEY, PickFormatterType::class, array( + 'export_alias' => $options['export_alias'] + )); + } + + } + + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(array('export_alias', 'picked_centers')) + ->setAllowedTypes('export_alias', array('string')) + ->setDefault('compound', true) + ->setDefault('constraints', array( + //new \Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraint() + )) + ; + + } + + public function getParent() + { + return FormType::class; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/FilterType.php b/src/Bundle/ChillMain/Form/Type/Export/FilterType.php new file mode 100644 index 000000000..21f1bcd48 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/FilterType.php @@ -0,0 +1,78 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; +use Chill\MainBundle\Export\ExportElementWithValidationInterface; +use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Validator\Context\ExecutionContextInterface; +use Symfony\Component\Form\Extension\Core\Type\FormType; + +/** + * + * + * @author Julien Fastré + */ +class FilterType extends AbstractType +{ + const ENABLED_FIELD = 'enabled'; + + public function __construct() + { + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + + $exportManager = $options['export_manager']; + $filter = $exportManager->getFilter($options['filter_alias']); + + $builder + ->add(self::ENABLED_FIELD, CheckboxType::class, array( + 'value' => true, + 'data' => false, + 'required' => false + )); + + $filterFormBuilder = $builder->create('form', FormType::class, array( + 'compound' => true, + 'error_bubbling' => false, + 'required' => false, + )); + $filter->buildForm($filterFormBuilder); + + $builder->add($filterFormBuilder); + + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired('filter_alias') + ->setRequired('export_manager') + ->setDefault('compound', true) + ->setDefault('error_bubbling', false) + ; + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/FormatterType.php b/src/Bundle/ChillMain/Form/Type/Export/FormatterType.php new file mode 100644 index 000000000..023822d12 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/FormatterType.php @@ -0,0 +1,59 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Export\ExportManager; + +/** + * + * + * @author Julien Fastré + */ +class FormatterType extends AbstractType +{ + /** + * + * @var ExportManager + */ + protected $exportManager; + + public function __construct(ExportManager $manager) + { + $this->exportManager = $manager; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(array('formatter_alias', 'export_alias', + 'aggregator_aliases')); + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $formatter = $this->exportManager->getFormatter($options['formatter_alias']); + + $formatter->buildForm($builder, $options['export_alias'], + $options['aggregator_aliases']); + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/PickCenterType.php b/src/Bundle/ChillMain/Form/Type/Export/PickCenterType.php new file mode 100644 index 000000000..7f7143a4e --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/PickCenterType.php @@ -0,0 +1,182 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Export\ExportManager; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Doctrine\ORM\EntityRepository; +use Chill\MainBundle\Entity\Center; +use Chill\MainBundle\Center\GroupingCenterInterface; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\CallbackTransformer; +use Doctrine\Common\Collections\Collection; + +/** + * Pick centers amongst available centers for the user + * + */ +class PickCenterType extends AbstractType +{ + /** + * + * @var \Symfony\Component\Security\Core\User\UserInterface + */ + protected $user; + + /** + * + * @var ExportManager + */ + protected $exportManager; + + /** + * + * @var GroupingCenterInterface[] + */ + protected $groupingCenters = []; + + const CENTERS_IDENTIFIERS = 'c'; + + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + public function __construct(TokenStorageInterface $tokenStorage, + ExportManager $exportManager, AuthorizationHelper $authorizationHelper) + { + $this->exportManager = $exportManager; + $this->user = $tokenStorage->getToken()->getUser(); + $this->authorizationHelper = $authorizationHelper; + } + + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired('export_alias') + ; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $export = $this->exportManager->getExport($options['export_alias']); + $centers = $this->authorizationHelper->getReachableCenters($this->user, + $export->requiredRole()); + + $builder->add(self::CENTERS_IDENTIFIERS, EntityType::class, array( + 'class' => 'ChillMainBundle:Center', + 'query_builder' => function(EntityRepository $er) use ($centers) { + $qb = $er->createQueryBuilder('c'); + $ids = array_map(function(Center $el) { return $el->getId(); }, + $centers); + return $qb->where($qb->expr()->in('c.id', $ids)); + }, + 'multiple' => true, + 'expanded' => true, + 'choice_label' => function(Center $c) { return $c->getName(); }, + 'data' => count($this->groupingCenters) > 0 ? null : $centers + )); + + if (count($this->groupingCenters) > 0) { + $groupingBuilder = $builder->create('g', null, [ + 'compound' => true + ]); + + foreach ($this->groupingCenters as $key => $gc) { + $choices = $this->buildChoices($centers, $gc); + + if (count($choices) > 0) { + $groupingBuilder->add($key, ChoiceType::class, [ + 'choices' => $choices, + 'multiple' => true, + 'expanded' => true, + 'label' => $gc->getName(), + 'required' => false + ]); + } + } + + if ($groupingBuilder->count() > 0) { + $builder->add($groupingBuilder); + } + } + + $builder->addModelTransformer(new CallbackTransformer( + function($data) use ($centers) { return $this->transform($data, $centers); }, + function($data) use ($centers) { return $this->reverseTransform($data, $centers); } + )); + } + + public function addGroupingCenter(GroupingCenterInterface $grouping) + { + $this->groupingCenters[md5($grouping->getName())] = $grouping; + } + + protected function buildChoices($reachablesCenters, GroupingCenterInterface $gc) + { + $result = []; + + foreach ($gc->getGroups() as $group) { + foreach ($gc->getCentersForGroup($group) as $center) { + if (\in_array($center, $reachablesCenters)) { + $result[$group] = $group; + } + } + } + + return $result; + } + + protected function transform($data, $centers) + { + return $data; + } + + protected function reverseTransform($data, $centers) + { + $picked = $data[self::CENTERS_IDENTIFIERS] + instanceof \Doctrine\Common\Collections\Collection ? + $data[self::CENTERS_IDENTIFIERS]->toArray() + : + $data[self::CENTERS_IDENTIFIERS]; + + if (\array_key_exists('g', $data)) { + foreach($data['g'] as $gcid => $group) { + $picked = + \array_merge( + \array_intersect( + $this->groupingCenters[$gcid] ->getCentersForGroup($group), + $centers + ), + $picked + ) + ; + } + } + + return \array_unique($picked); + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Export/PickFormatterType.php b/src/Bundle/ChillMain/Form/Type/Export/PickFormatterType.php new file mode 100644 index 000000000..aee1507f8 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Export/PickFormatterType.php @@ -0,0 +1,71 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type\Export; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +use Chill\MainBundle\Export\ExportManager; + +/** + * Choose a formatter amongst the available formatters + * + * + * @author Julien Fastré + */ +class PickFormatterType extends AbstractType +{ + protected $exportManager; + + public function __construct(ExportManager $exportManager) + { + $this->exportManager = $exportManager; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $export = $this->exportManager->getExport($options['export_alias']); + $allowedFormatters = $this->exportManager + ->getFormattersByTypes($export->getAllowedFormattersTypes()); + + //build choices + $choices = array(); + foreach($allowedFormatters as $alias => $formatter) { + $choices[$formatter->getName()] = $alias; + } + + $builder->add('alias', ChoiceType::class, array( + 'choices' => $choices, + 'choices_as_values' => true, + 'multiple' => false, + 'placeholder' => 'Choose a format' + )); + + //$builder->get('type')->addModelTransformer($transformer); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(array('export_alias')); + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/PostalCodeType.php b/src/Bundle/ChillMain/Form/Type/PostalCodeType.php new file mode 100644 index 000000000..1c1242a5a --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/PostalCodeType.php @@ -0,0 +1,110 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Entity\PostalCode; +use Symfony\Component\Form\FormInterface; +use Symfony\Component\Form\FormView; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * A form to pick between PostalCode + * + * @author Julien Fastré + * @author Champs Libres + */ +class PostalCodeType extends AbstractType +{ + /** + * + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + + /** + * + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * + * @var PostalCodeChoiceLoader + */ + protected $choiceLoader; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + public function __construct( + TranslatableStringHelper $helper, + UrlGeneratorInterface $urlGenerator, + PostalCodeChoiceLoader $choiceLoader, + TranslatorInterface $translator + ) { + $this->translatableStringHelper = $helper; + $this->urlGenerator = $urlGenerator; + $this->choiceLoader = $choiceLoader; + $this->translator = $translator; + } + + + public function getParent() + { + return EntityType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + // create a local copy for usage in Closure + $helper = $this->translatableStringHelper; + $resolver + ->setDefault('class', PostalCode::class) + ->setDefault('choice_label', function(PostalCode $code) use ($helper) { + return $code->getCode().' '.$code->getName().' ['. + $helper->localize($code->getCountry()->getName()).']'; + }) + ->setDefault('choice_loader', $this->choiceLoader) + ->setDefault('placeholder', 'Select a postal code') + ; + } + + public function buildView(FormView $view, FormInterface $form, array $options) + { + $view->vars['attr']['data-postal-code'] = 'data-postal-code'; + $view->vars['attr']['data-select-interactive-loading'] = true; + $view->vars['attr']['data-search-url'] = $this->urlGenerator + ->generate('chill_main_postal_code_search'); + $view->vars['attr']['data-placeholder'] = $this->translator->trans($options['placeholder']); + $view->vars['attr']['data-no-results-label'] = $this->translator->trans('select2.no_results'); + $view->vars['attr']['data-error-load-label'] = $this->translator->trans('select2.error_loading'); + $view->vars['attr']['data-searching-label'] = $this->translator->trans('select2.searching'); + } + +} diff --git a/src/Bundle/ChillMain/Form/Type/ScopePickerType.php b/src/Bundle/ChillMain/Form/Type/ScopePickerType.php new file mode 100644 index 000000000..dbbc28255 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/ScopePickerType.php @@ -0,0 +1,143 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Doctrine\ORM\EntityRepository; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Chill\MainBundle\Entity\Scope; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Chill\MainBundle\Templating\TranslatableStringHelper; +use Chill\MainBundle\Entity\Center; +use Symfony\Component\Security\Core\Role\Role; + +/** + * Allow to pick amongst available scope for the current + * user. + * + * options : + * + * - `center`: the center of the entity + * - `role` : the role of the user + * + * @author Julien Fastré + */ +class ScopePickerType extends AbstractType +{ + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var TokenStorageInterface + */ + protected $tokenStorage; + + /** + * + * @var EntityRepository + */ + protected $scopeRepository; + + /** + * + * @var TranslatableStringHelper + */ + protected $translatableStringHelper; + + public function __construct( + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage, + EntityRepository $scopeRepository, + TranslatableStringHelper $translatableStringHelper + ) { + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + $this->scopeRepository = $scopeRepository; + $this->translatableStringHelper = $translatableStringHelper; + } + + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + // create `center` option + ->setRequired('center') + ->setAllowedTypes('center', [Center::class ]) + // create ``role` option + ->setRequired('role') + ->setAllowedTypes('role', ['string', Role::class ]) + ; + + $resolver + ->setDefault('class', Scope::class) + ->setDefault('placeholder', 'Choose the circle') + ->setDefault('choice_label', function(Scope $c) { + return $this->translatableStringHelper->localize($c->getName()); + }) + ->setNormalizer('query_builder', function(Options $options) { + return $this->buildAccessibleScopeQuery($options['center'], $options['role']); + }) + ; + } + + public function getParent() + { + return EntityType::class; + } + + /** + * + * @return \Doctrine\ORM\QueryBuilder + */ + protected function buildAccessibleScopeQuery(Center $center, Role $role) + { + $roles = $this->authorizationHelper->getParentRoles($role); + $roles[] = $role; + + $qb = $this->scopeRepository->createQueryBuilder('s'); + $qb + // jointure to center + ->join('s.roleScopes', 'rs') + ->join('rs.permissionsGroups', 'pg') + ->join('pg.groupCenters', 'gc') + // add center constraint + ->where($qb->expr()->eq('IDENTITY(gc.center)', ':center')) + ->setParameter('center', $center->getId()) + // role constraints + ->andWhere($qb->expr()->in('rs.role', ':roles')) + ->setParameter('roles', \array_map( + function(Role $role) { + return $role->getRole(); + }, + $roles + )) + // user contraint + ->andWhere(':user MEMBER OF gc.users') + ->setParameter('user', $this->tokenStorage->getToken()->getUser()) + ; + + return $qb; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Select2ChoiceType.php b/src/Bundle/ChillMain/Form/Type/Select2ChoiceType.php new file mode 100644 index 000000000..f71638bb3 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Select2ChoiceType.php @@ -0,0 +1,52 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Form\Extension\Core\Type\ChoiceType; + +/** + * Extends choice to allow adding select2 library on widget + * + * @author Julien Fastré + */ +class Select2ChoiceType extends AbstractType +{ + public function getBlockPrefix() + { + return 'select2_choice'; + } + + public function getParent() + { + return ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults( + array( + 'attr' => array('class' => 'select2 '), + 'choices_as_values' => true + )); + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Select2CountryType.php b/src/Bundle/ChillMain/Form/Type/Select2CountryType.php new file mode 100644 index 000000000..48a921b58 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Select2CountryType.php @@ -0,0 +1,88 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Form\Type\DataTransformer\ObjectToIdTransformer; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Form\Type\Select2ChoiceType; + +/** + * Extends choice to allow adding select2 library on widget + * + * @author Julien Fastré + * @author Marc Ducobu + */ +class Select2CountryType extends AbstractType +{ + /** + * @var RequestStack + */ + private $requestStack; + + /** + * @var ObjectManager + */ + private $em; + + public function __construct(RequestStack $requestStack,ObjectManager $em) + { + $this->requestStack = $requestStack; + $this->em = $em; + } + + public function getBlockPrefix() + { + return 'select2_chill_country'; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $transformer = new ObjectToIdTransformer($this->em,'Chill\MainBundle\Entity\Country'); + $builder->addModelTransformer($transformer); + } + + public function getParent() + { + return Select2ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $locale = $this->requestStack->getCurrentRequest()->getLocale(); + $countries = $this->em->getRepository('Chill\MainBundle\Entity\Country')->findAll(); + $choices = array(); + + foreach ($countries as $c) { + $choices[$c->getId()] = $c->getName()[$locale]; + } + + asort($choices, SORT_STRING | SORT_FLAG_CASE); + + $resolver->setDefaults(array( + 'class' => 'Chill\MainBundle\Entity\Country', + 'choices' => array_combine(array_values($choices),array_keys($choices)) + )); + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Select2EntityType.php b/src/Bundle/ChillMain/Form/Type/Select2EntityType.php new file mode 100644 index 000000000..e72bf0d8e --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Select2EntityType.php @@ -0,0 +1,50 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; + +/** + * Extends choice to allow adding select2 library on widget + * + * @author Julien Fastré + */ +class Select2EntityType extends AbstractType +{ + public function getBlockPrefix() + { + return 'select2_entity'; + } + + public function getParent() + { + return EntityType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->replaceDefaults( + array('attr' => array('class' => 'select2 ')) + ); + } +} diff --git a/src/Bundle/ChillMain/Form/Type/Select2LanguageType.php b/src/Bundle/ChillMain/Form/Type/Select2LanguageType.php new file mode 100644 index 000000000..42a0f74cd --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/Select2LanguageType.php @@ -0,0 +1,85 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Form\FormBuilderInterface; +use Chill\MainBundle\Form\Type\DataTransformer\MultipleObjectsToIdTransformer; +use Doctrine\Common\Persistence\ObjectManager; +use Chill\MainBundle\Form\Type\Select2ChoiceType; + +/** + * Extends choice to allow adding select2 library on widget for languages (multiple) + */ +class Select2LanguageType extends AbstractType +{ + /** + * @var RequestStack + */ + private $requestStack; + + /** + * @var ObjectManager + */ + private $em; + + public function __construct(RequestStack $requestStack,ObjectManager $em) + { + $this->requestStack = $requestStack; + $this->em = $em; + } + + public function getBlockPrefix() + { + return 'select2_chill_language'; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $transformer = new MultipleObjectsToIdTransformer($this->em,'Chill\MainBundle\Entity\Language'); + $builder->addModelTransformer($transformer); + } + + public function getParent() + { + return Select2ChoiceType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $locale = $this->requestStack->getCurrentRequest()->getLocale(); + $languages = $this->em->getRepository('Chill\MainBundle\Entity\Language')->findAll(); + $choices = array(); + + foreach ($languages as $l) { + $choices[$l->getId()] = $l->getName()[$locale]; + } + + asort($choices, SORT_STRING | SORT_FLAG_CASE); + + $resolver->setDefaults(array( + 'class' => 'Chill\MainBundle\Entity\Language', + 'choices' => array_combine(array_values($choices),array_keys($choices)) + )); + } +} diff --git a/src/Bundle/ChillMain/Form/Type/TranslatableStringFormType.php b/src/Bundle/ChillMain/Form/Type/TranslatableStringFormType.php new file mode 100644 index 000000000..18a0a6e9f --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/TranslatableStringFormType.php @@ -0,0 +1,36 @@ +availableLanguages = $availableLanguages; + $this->frameworkTranslatorFallback = $translator->getFallbackLocales(); + } + + public function buildForm(FormBuilderInterface $builder, array $options) { + foreach ($this->availableLanguages as $lang) { + $builder->add($lang, TextType::class, + array('required' => (in_array($lang, + $this->frameworkTranslatorFallback)))); + } + } + + public function getBlockPrefix() + { + return 'translatable_string'; + } +} diff --git a/src/Bundle/ChillMain/Form/Type/UserPickerType.php b/src/Bundle/ChillMain/Form/Type/UserPickerType.php new file mode 100644 index 000000000..a179f4740 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Type/UserPickerType.php @@ -0,0 +1,112 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Form\Type; + +use Symfony\Component\Form\AbstractType; +use Chill\MainBundle\Security\Authorization\AuthorizationHelper; +use Doctrine\ORM\EntityRepository; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Bridge\Doctrine\Form\Type\EntityType; +use Chill\MainBundle\Entity\User; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Security\Core\Role\Role; + + +/** + * Pick a user available for the given role and center. + * + * Options : + * + * - `role` : the role the user can reach + * - `center`: the center a user can reach + * + * @author Julien Fastré + */ +class UserPickerType extends AbstractType +{ + /** + * + * @var AuthorizationHelper + */ + protected $authorizationHelper; + + /** + * + * @var TokenStorageInterface + */ + protected $tokenStorage; + + /** + * + * @var \Chill\MainBundle\Repository\UserRepository + */ + protected $userRepository; + + public function __construct( + AuthorizationHelper $authorizationHelper, + TokenStorageInterface $tokenStorage, + EntityRepository $userRepository + ) { + $this->authorizationHelper = $authorizationHelper; + $this->tokenStorage = $tokenStorage; + $this->userRepository = $userRepository; + } + + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + // create `center` option + ->setRequired('center') + ->setAllowedTypes('center', [\Chill\MainBundle\Entity\Center::class ]) + // create ``role` option + ->setRequired('role') + ->setAllowedTypes('role', ['string', \Symfony\Component\Security\Core\Role\Role::class ]) + ; + + $resolver + ->setDefault('having_permissions_group_flag', null) + ->setAllowedTypes('having_permissions_group_flag', ['string', 'null']) + ->setDefault('class', User::class) + ->setDefault('placeholder', 'Choose an user') + ->setDefault('choice_label', function(User $u) { + return $u->getUsername(); + }) + ->setNormalizer('choices', function(Options $options) { + + $users = $this->authorizationHelper + ->findUsersReaching($options['role'], $options['center']); + + if (NULL !== $options['having_permissions_group_flag']) { + return $this->userRepository + ->findUsersHavingFlags($options['having_permissions_group_flag'], $users) + ; + } + + return $users; + }) + ; + } + + public function getParent() + { + return EntityType::class; + } +} diff --git a/src/Bundle/ChillMain/Form/UserPasswordType.php b/src/Bundle/ChillMain/Form/UserPasswordType.php new file mode 100644 index 000000000..4486b5643 --- /dev/null +++ b/src/Bundle/ChillMain/Form/UserPasswordType.php @@ -0,0 +1,111 @@ +passwordEncoder = $passwordEncoder; + $this->chillLogger = $chillLogger; + } + + + /** + * @param FormBuilderInterface $builder + * @param array $options + */ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder + ->add('new_password', RepeatedType::class, array( + 'type' => PasswordType::class, + 'required' => false, + 'options' => array(), + 'first_options' => array( + 'label' => 'Password' + ), + 'second_options' => array( + 'label' => 'Repeat the password' + ), + 'invalid_message' => "The password fields must match", + 'constraints' => array( + new Length(array( + 'min' => 9, + 'minMessage' => 'The password must be greater than {{ limit }} characters' + )), + new NotBlank(), + new Regex(array( + 'pattern' => "/((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\/{}~=µ\(\)£]).{6,})/", + 'message' => "The password must contains one letter, one " + . "capitalized letter, one number and one special character " + . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed." + )) + ) + )) + ->add('actual_password', PasswordType::class, [ + 'label' => 'Your actual password', + 'mapped' => false, + 'constraints' => [ + new Callback([ + 'callback' => function($password, ExecutionContextInterface $context, $payload) use ($options) { + if (TRUE === $this->passwordEncoder->isPasswordValid($options['user'], $password)) { + return; + } + + // password problem :-) + $this->chillLogger + ->notice("incorrect password when trying to change password", [ + 'username' => $options['user']->getUsername() + ]); + $context->addViolation('Incorrect password'); + } + ]) + ] + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver + ->setRequired('user') + ->setAllowedTypes('user', \Chill\MainBundle\Entity\User::class) + ; + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return 'chill_mainbundle_user_password'; + } +} diff --git a/src/Bundle/ChillMain/Form/UserType.php b/src/Bundle/ChillMain/Form/UserType.php new file mode 100644 index 000000000..0b7c587d7 --- /dev/null +++ b/src/Bundle/ChillMain/Form/UserType.php @@ -0,0 +1,94 @@ +add('username') + ->add('email') + ; + if ($options['is_creation']) { + $builder->add('plainPassword', RepeatedType::class, array( + 'mapped' => false, + 'type' => PasswordType::class, + 'required' => false, + 'options' => array(), + 'first_options' => array( + 'label' => 'Password' + ), + 'second_options' => array( + 'label' => 'Repeat the password' + ), + 'invalid_message' => "The password fields must match", + 'constraints' => array( + new Length(array( + 'min' => 9, + 'minMessage' => 'The password must be greater than {{ limit }} characters' + )), + new NotBlank(), + new Regex(array( + 'pattern' => "/((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%!,;:+\"'-\/{}~=µ\(\)£]).{6,})/", + 'message' => "The password must contains one letter, one " + . "capitalized letter, one number and one special character " + . "as *[@#$%!,;:+\"'-/{}~=µ()£]). Other characters are allowed." + )) + ) + )); + + } else { + $builder->add($builder + ->create('enabled', ChoiceType::class, array( + 'choices' => array( + 'Disabled, the user is not allowed to login' => 0, + 'Enabled, the user is active' => 1 + ), + 'expanded' => false, + 'multiple' => false, + 'choices_as_values' => true // Can be removed when upgraded to Sf3. + )) + ); + } + } + + /** + * @param OptionsResolverInterface $resolver + */ + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults(array( + 'data_class' => 'Chill\MainBundle\Entity\User' + )); + + $resolver + ->setDefaults(array('is_creation' => false)) + ->addAllowedValues('is_creation', array(true, false)) + ; + } + + /** + * @return string + */ + public function getBlockPrefix() + { + return 'chill_mainbundle_user'; + } +} diff --git a/src/Bundle/ChillMain/Form/Utils/PermissionsGroupFlagProvider.php b/src/Bundle/ChillMain/Form/Utils/PermissionsGroupFlagProvider.php new file mode 100644 index 000000000..39ddb62d5 --- /dev/null +++ b/src/Bundle/ChillMain/Form/Utils/PermissionsGroupFlagProvider.php @@ -0,0 +1,18 @@ + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/src/Bundle/ChillMain/Notification/Mailer.php b/src/Bundle/ChillMain/Notification/Mailer.php new file mode 100644 index 000000000..2b784b3a1 --- /dev/null +++ b/src/Bundle/ChillMain/Notification/Mailer.php @@ -0,0 +1,168 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Notification; + +use Chill\MainBundle\Entity\User; +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Classe d'aide pour l'envoi de notification. + * + * Héberge toutes les méthodes pour ré-écrire les URL en fonction de la langue + * de l'utilisateur. + * + */ +class Mailer +{ + /** + * @var LoggerInterface + */ + protected $logger; + + /** + * + * @var \Twig\Environment + */ + protected $twig; + + /** + * + * @var \Swift_Mailer + */ + protected $mailer; + + /** + * + * @var \Swift_Mailer + */ + protected $forcedMailer; + + /** + * + * @var RouterInterface + */ + protected $router; + + /** + * + * @var TranslatorInterface + */ + protected $translator; + + /** + * + * @var array + */ + protected $routeParameters; + + public function __construct( + LoggerInterface $logger, + \Twig\Environment $twig, + \Swift_Mailer $mailer, + // due to bug https://github.com/symfony/swiftmailer-bundle/issues/127 + \Swift_Transport $mailerTransporter, + RouterInterface $router, + TranslatorInterface $translator, + $routeParameters + ) { + $this->logger = $logger; + $this->twig = $twig; + $this->mailer = $mailer; + $this->router = $router; + $this->translator = $translator; + $this->routeParameters = $routeParameters; + $this->forcedMailer = new \Swift_Mailer($mailerTransporter); + } + + + /** + * Envoie une notification à un utilisateur. + * + * @param \User $to + * @param array $subject Subject of the message [ 0 => $message (required), 1 => $parameters (optional), 3 => $domain (optional) ] + * @param array $bodies The bodies. An array where keys are the contentType and values the bodies + * @param \callable $callback a callback to customize the message (add attachment, etc.) + */ + public function sendNotification( + $recipient, + array $subject, + array $bodies, + callable $callback = null, + $force = false + ) + { + $fromEmail = $this->routeParameters['from_email']; + $fromName = $this->routeParameters['from_name']; + $to = $recipient instanceof User ? $recipient->getEmail() : $recipient; + + $subjectI18n = $this->translator->trans( + $subject[0], + $subject[1] ?? [], + $subject[2] ?? null + ); + + $message = (new \Swift_Message($subjectI18n)) + ->setFrom($fromEmail, $fromName) + ->setTo($to) + ; + + foreach ($bodies as $contentType => $content) { + $message->setBody($content, $contentType); + } + + if ($callback !== null) { + \call_user_func($callback, $message); + } + + $this->logger->info("[notification] Sending notification", [ + 'to' => $message->getTo(), + 'subject' => $message->getSubject() + ]); + + $this->sendMessage($message, $force); + } + + public function sendMessage(\Swift_Message $message, $force) + { + if ($force) { + $this->forcedMailer->send($message); + } else { + $this->mailer->send($message); + } + } + + public function renderContentToUser(User $to, $template, array $parameters = array()) + { + $context = $this->router->getContext(); + $previousHost = $context->getHost(); + $previousScheme = $context->getScheme(); + + $context->setHost($this->routeParameters['host']); + $context->setScheme($this->routeParameters['scheme']); + + $content = $this->twig->render($template, $parameters); + + // reset the host + $context->setHost($previousHost); + $context->setScheme($previousScheme); + + return $content; + } +} diff --git a/src/Bundle/ChillMain/Pagination/ChillPaginationTwig.php b/src/Bundle/ChillMain/Pagination/ChillPaginationTwig.php new file mode 100644 index 000000000..ef36685b4 --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/ChillPaginationTwig.php @@ -0,0 +1,76 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +/** + * add twig function to render pagination + * + * @author Julien Fastré + * @author Champs Libres + */ +class ChillPaginationTwig extends \Twig_Extension +{ + const LONG_TEMPLATE = 'ChillMainBundle:Pagination:long.html.twig'; + const SHORT_TEMPLATE = 'ChillMainBundle:Pagination:short.html.twig'; + + public function getName() + { + return 'chill_pagination'; + } + + public function getFunctions() + { + return array( + new \Twig_SimpleFunction( + 'chill_pagination', + array($this, 'paginationRender'), + array( + 'needs_environment' => true, + 'is_safe' => ['html'] + ) + ) + ); + } + + public function paginationRender( + \Twig_Environment $env, + PaginatorInterface $paginator, + $template = 'ChillMainBundle:Pagination:long.html.twig' + ) { + switch ($template) { + case 'long': + $t = self::LONG_TEMPLATE; + break; + case 'short': + $t = self::SHORT_TEMPLATE; + break; + default: + $t = $template; + } + + return $env->render($t, array( + 'paginator' => $paginator, + 'current' => $paginator->getCurrentPage()->getNumber() + )); + } +} diff --git a/src/Bundle/ChillMain/Pagination/Page.php b/src/Bundle/ChillMain/Pagination/Page.php new file mode 100644 index 000000000..e6b3943ed --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/Page.php @@ -0,0 +1,113 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** + * a page is an element of a pagination + * + * @author Julien Fastré + * @author Champs Libres + */ +class Page implements PageInterface +{ + /** + * + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * the number of the current page + * + * @var int + */ + protected $number; + + /** + * The route for the current page + * + * @var string + */ + protected $route; + + /** + * Parameters for the route to the current page + * + * @var array + */ + protected $routeParameters; + + /** + * the number of item per page + * + * @var int + */ + protected $itemPerPage; + + /** + * The number of items in the whole iteration + * + * @var int + */ + protected $totalItems; + + + public function __construct( + $number, + $itemPerPage, + UrlGeneratorInterface $urlGenerator, + $route, + array $routeParameters, + $totalItems + ) { + $this->urlGenerator = $urlGenerator; + $this->number = $number; + $this->itemPerPage = $itemPerPage; + $this->route = $route; + $this->routeParameters = $routeParameters; + $this->totalItems = $totalItems; + } + + public function generateUrl() + { + return $this->urlGenerator->generate($this->route, $this->routeParameters); + } + + public function getFirstItemNumber() + { + return ($this->number - 1) * $this->itemPerPage; + } + + public function getLastItemNumber() + { + $last = $this->number * $this->itemPerPage - 1; + + return $last < $this->totalItems ? $last : $this->totalItems; + } + + public function getNumber() + { + return $this->number; + } + +} diff --git a/src/Bundle/ChillMain/Pagination/PageGenerator.php b/src/Bundle/ChillMain/Pagination/PageGenerator.php new file mode 100644 index 000000000..26add87fa --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/PageGenerator.php @@ -0,0 +1,73 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +/** + * PageGenerator associated with a Paginator + * + * @author Julien Fastré + * @author Champs Libres + */ +class PageGenerator implements \Iterator +{ + /** + * + * @var Paginator + */ + protected $paginator; + + /** + * + * @var int + */ + protected $current = 1; + + public function __construct(Paginator $paginator) + { + $this->paginator = $paginator;; + } + + public function current() + { + return $this->paginator->getPage($current); + } + + public function key() + { + return $this->current; + } + + public function next() + { + $this->current++; + } + + public function rewind() + { + $this->current = 1; + } + + public function valid() + { + return $this->current > 0 + && $this->current <= $this->paginator->countPages(); + } +} diff --git a/src/Bundle/ChillMain/Pagination/PageInterface.php b/src/Bundle/ChillMain/Pagination/PageInterface.php new file mode 100644 index 000000000..aa87c289c --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/PageInterface.php @@ -0,0 +1,43 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +/** + * Represents a page included in a pagination + * + * @author Julien Fastré + */ +interface PageInterface +{ + public function generateUrl(); + + /** + * get the page number. + * + * The first page number is 1. + */ + public function getNumber(); + + public function getFirstItemNumber(); + + public function getLastItemNumber(); + +} diff --git a/src/Bundle/ChillMain/Pagination/Paginator.php b/src/Bundle/ChillMain/Pagination/Paginator.php new file mode 100644 index 000000000..82577d078 --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/Paginator.php @@ -0,0 +1,247 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + + +/** + * Standard paginator class. + * + * Represent a set of paginated pages; + * + * @author Julien Fastré + * @author Champs Libres + */ +class Paginator implements PaginatorInterface +{ + /** + * The number of total items + * + * @var int + */ + protected $totalItems; + + /** + * the number of items on a single page + * + * @var int + */ + protected $itemPerPage; + /** + * The number of the current page + * + * @var int + */ + protected $currentPageNumber; + + /** + * the route of the pages + * + * @var string + */ + protected $route; + + /** + * the parameters of the route + * + * @var string[] + */ + protected $routeParameters; + + /** + * the generator for url + * + * @var UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * the key in the GET parameter to indicate the page number in + * generated routes + * + * @var string + */ + protected $pageKey; + + /** + * the key in the GET parameter to indicate the number of item per page + * + * @var string + */ + protected $itemPerPageKey; + + + public function __construct( + $totalItems, + $itemPerPage, + $currentPageNumber, + $route, + array $routeParameters, + UrlGeneratorInterface $urlGenerator, + $pageKey, + $itemPerPageKey + ) { + $this->totalItems = $totalItems; + $this->itemPerPage = $itemPerPage; + $this->currentPageNumber = $currentPageNumber; + $this->route = $route; + $this->routeParameters = $routeParameters; + $this->urlGenerator = $urlGenerator; + $this->pageKey = $pageKey; + $this->itemPerPageKey = $itemPerPageKey; + } + + public function count() + { + return $this->countPages(); + } + + /** + * + * @return \Chill\MainBundle\Pagination\Page + */ + public function getCurrentPage() + { + return $this->getPage($this->currentPageNumber); + } + + public function getCurrentPageFirstItemNumber() + { + return $this->getCurrentPage()->getFirstItemNumber(); + } + + public function isCurrentPage(PageInterface $page) + { + return $page->getNumber() === $this->currentPageNumber; + } + + public function getItemsPerPage() + { + return $this->itemPerPage; + } + + public function setItemsPerPage($itemPerPage) + { + $this->itemPerPage = $itemPerPage; + } + + public function getTotalItems() + { + return $this->totalItems; + } + + public function countPages() + { + $nb = floor($this->totalItems / $this->itemPerPage); + + if ($this->totalItems % $this->itemPerPage > 0) { + $nb++; + } + + return $nb == 0 ? 1 : (int) $nb; + } + + /** + * + * @return \Chill\MainBundle\Pagination\Page + * @throws \RuntimeException if the next page does not exists + */ + public function getNextPage() + { + if (!$this->hasNextPage()) { + throw new \RuntimeException("this page has no next page"); + } + + return $this->getPage($this->currentPageNumber + 1); + } + + /** + * + * @return \Chill\MainBundle\Pagination\Page + * @throws \RuntimeException if the next page does not exists + */ + public function getPreviousPage() + { + if (!$this->hasPreviousPage()) { + throw new \RuntimeException("this page has no previous page"); + } + + return $this->getPage($this->currentPageNumber - 1); + } + + /** + * + * @return bool + */ + public function hasNextPage() + { + return $this->hasPage($this->currentPageNumber + 1); + } + + /** + * + * @return bool + */ + public function hasPreviousPage() + { + return $this->hasPage($this->currentPageNumber - 1); + } + + public function hasPage($number) + { + return $number > 0 and + $number <= $this->countPages(); + } + + + /** + * + * @param type $number + * @return \Chill\MainBundle\Pagination\Page + */ + public function getPage($number) { + if (!$this->hasPage($number)) { + throw new \RuntimeException("The page with number $number does not " + . "exists"); + } + + return new Page( + $number, + $this->itemPerPage, + $this->urlGenerator, + $this->route, + array_merge($this->routeParameters, array( + $this->pageKey => $number, + $this->itemPerPageKey => $this->itemPerPage + )), + $this->totalItems + ); + } + + public function getPagesGenerator() + { + for ($i = 1; $i <= $this->countPages(); $i++) { + yield $this->getPage($i); + } + } + +} diff --git a/src/Bundle/ChillMain/Pagination/PaginatorFactory.php b/src/Bundle/ChillMain/Pagination/PaginatorFactory.php new file mode 100644 index 000000000..0e8338da3 --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/PaginatorFactory.php @@ -0,0 +1,146 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Create paginator instances + * + * @author Julien Fastré + * @author Champs Libres + */ +class PaginatorFactory +{ + /** + * the default item per page. This may be overriden by + * the request or inside the paginator. + * + * @var int + */ + private $itemPerPage; + + /** + * the router and generator for url + * + * @var RouterInterface + */ + private $router; + + /** + * the request stack + * + * @var RequestStack + */ + private $requestStack; + + const DEFAULT_CURRENT_PAGE_KEY = 'page'; + const DEFAULT_ITEM_PER_NUMBER_KEY = 'item_per_page'; + const DEFAULT_PAGE_NUMBER = 1; + + + public function __construct( + RequestStack $requestStack, + RouterInterface $router, + $itemPerPage = 50 + ) { + $this->itemPerPage = $itemPerPage; + $this->requestStack = $requestStack; + $this->router = $router; + } + + /** + * create a paginator instance + * + * The default route and route parameters are the current ones. If set, + * thos route are overriden. + * + * @param int $totalItems + * @param string|null $route the specific route to use in pages + * @param array|null $routeParameters the specific route parameters to use in pages + * @return PaginatorInterface + */ + public function create( + $totalItems, + $route = null, + array $routeParameters = null + ) { + + return new Paginator( + $totalItems, + $this->getCurrentItemsPerPage(), + $this->getCurrentPageNumber(), + $route === null ? $this->getCurrentRoute() : $route, + $routeParameters === null ? $this->getCurrentRouteParameters() : + $routeParameters, + $this->router, + self::DEFAULT_CURRENT_PAGE_KEY, + self::DEFAULT_ITEM_PER_NUMBER_KEY); + } + + /** + * + * @return int + */ + public function getCurrentPageNumber() + { + return $this->requestStack + ->getCurrentRequest() + ->query + ->getInt(self::DEFAULT_CURRENT_PAGE_KEY, self::DEFAULT_PAGE_NUMBER); + } + + public function getCurrentItemsPerPage() + { + return $this->requestStack + ->getCurrentRequest() + ->query + ->getInt(self::DEFAULT_ITEM_PER_NUMBER_KEY, $this->itemPerPage); + } + + public function getCurrentPageFirstItemNumber() + { + return ($this->getCurrentPageNumber() - 1) * + $this->getCurrentItemsPerPage(); + } + + protected function getCurrentRoute() + { + $request = $this->requestStack->getCurrentRequest(); + + return $request->get('_route'); + } + + protected function getCurrentRouteParameters() + { + return array_merge( + $this->router->getContext()->getParameters(), + // get the route parameters + $this->requestStack + ->getCurrentRequest() + ->attributes->get('_route_params'), + // get the query parameters + $this->requestStack + ->getCurrentRequest()->query->all() + ); + } +} diff --git a/src/Bundle/ChillMain/Pagination/PaginatorInterface.php b/src/Bundle/ChillMain/Pagination/PaginatorInterface.php new file mode 100644 index 000000000..b9ee9c8a6 --- /dev/null +++ b/src/Bundle/ChillMain/Pagination/PaginatorInterface.php @@ -0,0 +1,136 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +namespace Chill\MainBundle\Pagination; + +/** + * Represent a set of numbered pages + * + * Allow to calculate and render pagination for a set of pages. + * + * The items are elements that `could` be shown. The item are divided and shown + * into pages. Each page is numbered and count a given number of item per page. + * + * The first page number is 1, although the first result number is 0. + * + * @author Julien Fastré + */ +interface PaginatorInterface extends \Countable +{ + /** + * get the number of results for this paginator + * + * @return int + */ + public function getTotalItems(); + + /** + * get the first result for the current page + * + * @return int + */ + public function getCurrentPageFirstItemNumber(); + + /* + * get the number of items per page + */ + public function getItemsPerPage(); + + /* + * set the number of items per page + */ + public function setItemsPerPage($itemsPerPage); + + /** + * get the number of pages for this pagination. + * + * @return int + */ + public function countPages(); + + /** + * get the current page + * + * @return PageInterface + */ + public function getCurrentPage(); + + /** + * check if the given page is the current page + * + * @param \Chill\MainBundle\Pagination\PageInterface $page + * @return bool + */ + public function isCurrentPage(PageInterface $page); + + /** + * check if the page with the given number exists + * + * @param int $number + */ + public function hasPage($number); + + /** + * get page by his number + * + * @param int $number + * @throws \RuntimeException if the pagination has no page with specified number + */ + public function getPage($number); + + /** + * get the next page + * + * @return PageInterface + * @throws \RuntimeException if the pagination has not next page + */ + public function getNextPage(); + + /** + * get the previous page + * + * @return PageInterface + * @throws \RuntimeException if the pagination has not previous page + */ + public function getPreviousPage(); + + /** + * check if the current page has a next page + * + * @return bool + */ + public function hasNextPage(); + + /** + * check if the current page has a page before + * + * @return bool + */ + public function hasPreviousPage(); + + /** + * get a generator to generate pages + * + * @return \Generator which return PageInterface elements + */ + public function getPagesGenerator(); + + +} diff --git a/src/Bundle/ChillMain/Phonenumber/PhonenumberHelper.php b/src/Bundle/ChillMain/Phonenumber/PhonenumberHelper.php new file mode 100644 index 000000000..8b3df2894 --- /dev/null +++ b/src/Bundle/ChillMain/Phonenumber/PhonenumberHelper.php @@ -0,0 +1,265 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Phonenumber; + +use GuzzleHttp\Client; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Exception\ServerException; +use GuzzleHttp\Exception\ConnectException; +use Psr\Log\LoggerInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * Helper to some task linked to phonenumber. + * + * Currently, only Twilio is supported (https://www.twilio.com/lookup). A method + * allow to check if the helper is configured for validation. This should be used + * before doing some validation. + * + * + */ +class PhonenumberHelper +{ + /** + * + * @var Client + */ + protected $twilioClient; + + /** + * + * @var LoggerInterface + */ + protected $logger; + + /** + * + * @var CacheItemPoolInterface + */ + protected $cachePool; + + const LOOKUP_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; + const FORMAT_URI = 'https://lookups.twilio.com/v1/PhoneNumbers/%s'; + + + public function __construct( + CacheItemPoolInterface $cachePool, + $config, + LoggerInterface $logger + ) { + $this->logger = $logger; + $this->cachePool = $cachePool; + + if (\array_key_exists('twilio_sid', $config) + && !empty($config['twilio_sid']) + && \array_key_exists('twilio_secret', $config) + && !empty($config['twilio_secret'])) { + + $this->twilioClient = new Client([ + 'auth' => [ $config['twilio_sid'], $config['twilio_secret'] ] + ]); + } + + } + + /** + * Return true if the validation is configured and available. + * + * @return bool + */ + public function isPhonenumberValidationConfigured() : bool + { + return NULL !== $this->twilioClient; + } + + /** + * REturn true if the phoennumber is a mobile phone. Return always false + * if the validation is not configured. + * + * @param string $phonenumber + * @return bool + */ + public function isValidPhonenumberMobile($phonenumber) : bool + { + $validation = $this->performTwilioLookup($phonenumber); + + if (NULL === $validation) { + return false; + } + + return $validation === 'mobile'; + } + + /** + * Return true if the phonenumber is a landline or voip phone. Return always false + * if the validation is not configured. + * + * @param string $phonenumber + * @return bool + */ + public function isValidPhonenumberLandOrVoip($phonenumber) : bool + { + $validation = $this->performTwilioLookup($phonenumber); + + if (NULL === $validation) { + return false; + } + + return \in_array($validation, [ 'landline', 'voip' ]); + } + + /** + * Return true if the phonenumber is a landline or voip phone. Return always false + * if the validation is not configured. + * + * @param string $phonenumber + * @return bool + */ + public function isValidPhonenumberAny($phonenumber) : bool + { + $validation = $this->performTwilioLookup($phonenumber); + + if (NULL === $validation) { + return false; + } + + return \in_array($validation, [ 'landline', 'voip', 'mobile' ]); + } + + public function format($phonenumber) + { + return $this->performTwilioFormat($phonenumber); + } + + protected function performTwilioFormat($phonenumber) + { + if (FALSE === $this->isPhonenumberValidationConfigured()) { + return $phonenumber; + } + + // filter only number + $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); + + $item = $this->cachePool->getItem('pnum_format_nat_'.$filtered); + + if ($item->isHit()) { + return $item->get(); + } + + try { + $response = $this->twilioClient->get(sprintf(self::FORMAT_URI, '+'.$filtered), [ + 'http_errors' => true, + ]); + + } catch (ClientException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to client error", [ + "message" => $e->getResponseBodySummary($e->getResponse()), + "status_code" => $e->getResponse()->getStatusCode(), + "phonenumber" => $phonenumber + ]); + + return $phonenumber; + } catch (ServerException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to server error", [ + "message" => $e->getResponseBodySummary($e->getResponse()), + "status_code" => $e->getResponse()->getStatusCode(), + "phonenumber" => $phonenumber + ]); + + return null; + } catch (ConnectException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to connect error", [ + "message" => $e->getMessage(), + "phonenumber" => $phonenumber + ]); + + return null; + } + + $format = \json_decode($response->getBody())->national_format; + + $item + ->set($format) + // expires after 3d + ->expiresAfter(3600 * 24 * 3) + ; + + $this->cachePool->save($item); + + return $format; + } + + protected function performTwilioLookup($phonenumber) + { + if (FALSE === $this->isPhonenumberValidationConfigured()) { + return null; + } + + // filter only number + $filtered = \preg_replace("/[^0-9]/", "", $phonenumber); + + $item = $this->cachePool->getItem('pnum_'.$filtered); + + if ($item->isHit()) { + return $item->get(); + } + + try { + $response = $this->twilioClient->get(sprintf(self::LOOKUP_URI, '+'.$filtered), [ + 'http_errors' => true, + 'query' => [ + 'Type' => 'carrier' + ] + ]); + } catch (ClientException $e) { + return 'invalid'; + } catch (ServerException $e) { + $this->logger->error("[phonenumber helper] Could not perform validation " + . "due to server error", [ + "message" => $e->getResponseBodySummary($e->getResponse()), + "status_code" => $e->getResponse()->getStatusCode(), + "phonenumber" => $phonenumber + ]); + + return null; + } catch (ConnectException $e) { + $this->logger->error("[phonenumber helper] Could not format number " + . "due to connect error", [ + "message" => $e->getMessage(), + "phonenumber" => $phonenumber + ]); + + return null; + } + + $validation = \json_decode($response->getBody())->carrier->type; + + $item + ->set($validation) + // expires after 12h + ->expiresAfter(3600 * 12) + ; + + $this->cachePool->save($item); + + return $validation; + } +} diff --git a/src/Bundle/ChillMain/Phonenumber/Templating.php b/src/Bundle/ChillMain/Phonenumber/Templating.php new file mode 100644 index 000000000..45bfceae0 --- /dev/null +++ b/src/Bundle/ChillMain/Phonenumber/Templating.php @@ -0,0 +1,52 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Phonenumber; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; +use Chill\MainBundle\Phonenumber\PhonenumberHelper; + +/** + * + * + */ +class Templating extends AbstractExtension +{ + /** + * + * @var PhonenumberHelper + */ + protected $phonenumberHelper; + + public function __construct(PhonenumberHelper $phonenumberHelper) + { + $this->phonenumberHelper = $phonenumberHelper; + } + + public function getFilters() + { + return [ + new TwigFilter('chill_format_phonenumber', [$this, 'formatPhonenumber']) + ]; + } + + public function formatPhonenumber($phonenumber) + { + return $this->phonenumberHelper->format($phonenumber) ?? $phonenumber; + } +} diff --git a/src/Bundle/ChillMain/README.md b/src/Bundle/ChillMain/README.md new file mode 100644 index 000000000..043642931 --- /dev/null +++ b/src/Bundle/ChillMain/README.md @@ -0,0 +1,9 @@ +ChillMain +========= + +An app for social-profit organisations + +Documentation & installation +============================ + +Read documentation here : http://chill.readthedocs.org diff --git a/src/Bundle/ChillMain/Redis/ChillRedis.php b/src/Bundle/ChillMain/Redis/ChillRedis.php new file mode 100644 index 000000000..a196d2b60 --- /dev/null +++ b/src/Bundle/ChillMain/Redis/ChillRedis.php @@ -0,0 +1,26 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Redis; + +/** + * Redis client configured by chill main + * + */ +class ChillRedis extends \Redis +{ +} diff --git a/src/Bundle/ChillMain/Redis/RedisConnectionFactory.php b/src/Bundle/ChillMain/Redis/RedisConnectionFactory.php new file mode 100644 index 000000000..51f2d2c6d --- /dev/null +++ b/src/Bundle/ChillMain/Redis/RedisConnectionFactory.php @@ -0,0 +1,78 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Redis; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * + * + * @author Julien Fastré + */ +class RedisConnectionFactory implements EventSubscriberInterface +{ + protected $host; + + protected $port; + + protected $timeout; + + /** + * + * @var Redis + */ + protected $redis; + + public function __construct($parameters) + { + $this->host = $parameters['host']; + $this->port = $parameters['port']; + $this->timeout = $parameters['timeout']; + } + + + public static function getSubscribedEvents(): array + { + return [ + 'kernel.finish_request' => [ + [ 'onKernelFinishRequest' ] + ] + ]; + } + + public function create() + { + $redis = $this->redis = new ChillRedis(); + + $result = $redis->connect($this->host, $this->port, $this->timeout); + + if (FALSE === $result) { + throw new \RuntimeException("Could not connect to redis instance"); + } + + return $redis; + } + + public function onKernelFinishRequest() + { + if ($this->redis !== null) { + $this->redis->close(); + } + } + +} diff --git a/src/Bundle/ChillMain/Repository/CenterRepository.php b/src/Bundle/ChillMain/Repository/CenterRepository.php new file mode 100644 index 000000000..05a439bfd --- /dev/null +++ b/src/Bundle/ChillMain/Repository/CenterRepository.php @@ -0,0 +1,27 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Repository; + +/** + * + * + */ +class CenterRepository extends \Doctrine\ORM\EntityRepository +{ + +} diff --git a/src/Bundle/ChillMain/Repository/PostalCodeRepository.php b/src/Bundle/ChillMain/Repository/PostalCodeRepository.php new file mode 100644 index 000000000..5b8ab8423 --- /dev/null +++ b/src/Bundle/ChillMain/Repository/PostalCodeRepository.php @@ -0,0 +1,28 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Repository; + +/** + * + * + * @author Julien Fastré + */ +class PostalCodeRepository extends \Doctrine\ORM\EntityRepository +{ + +} diff --git a/src/Bundle/ChillMain/Repository/UserRepository.php b/src/Bundle/ChillMain/Repository/UserRepository.php new file mode 100644 index 000000000..51bcda6eb --- /dev/null +++ b/src/Bundle/ChillMain/Repository/UserRepository.php @@ -0,0 +1,121 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +namespace Chill\MainBundle\Repository; + +use Chill\MainBundle\Entity\GroupCenter; +use Chill\MainBundle\Entity\User; + +/** + * + * + */ +class UserRepository extends \Doctrine\ORM\EntityRepository +{ + public function countByUsernameOrEmail($pattern) + { + $qb = $this->queryByUsernameOrEmail($pattern); + + $qb->select('COUNT(u)'); + + return (int) $qb->getQuery()->getSingleScalarResult(); + } + + public function findByUsernameOrEmail($pattern) + { + $qb = $this->queryByUsernameOrEmail($pattern); + + return $qb->getQuery()->getResult(); + } + + public function findOneByUsernameOrEmail($pattern) + { + $qb = $this->queryByUsernameOrEmail($pattern); + + return $qb->getQuery()->getSingleResult(); + } + + /** + * Get the users having a specific flags + * + * If provided, only the users amongst "filtered users" are searched. This + * allows to make a first search amongst users based on role and center + * and, then filter those users having some flags. + * + * @param \Chill\MainBundle\Entity\User[] $amongstUsers + */ + public function findUsersHavingFlags($flag, $amongstUsers = []) + { + $gcs = $this->_em->createQuery("SELECT DISTINCT gc " + . "FROM ".GroupCenter::class." gc " + . "JOIN gc.permissionsGroup pg " + . "WHERE " + . "JSONB_EXISTS_IN_ARRAY(pg.flags, :flag) = :true ") + ->setParameters([ + 'true' => true, + 'flag' => $flag + ]) + ->getResult(); + + if (count($gcs) === 0) { + return []; + } + + $qb = $this->_em->createQueryBuilder(); + $qb + ->select('DISTINCT u') + ->from(User::class, 'u') + ->where("u.enabled = 'TRUE'") + ; + + $orx = $qb->expr()->orX(); + foreach($gcs as $i => $gc) { + $orx->add(':gc_'.$i.' MEMBER OF u.groupCenters'); + $qb->setParameter('gc_'.$i, $gc); + } + + $qb->andWhere($orx); + + if (count($amongstUsers) > 0) { + $qb + ->andWhere($qb->expr()->in('u', ':amongstUsers')) + ->setParameter('amongstUsers', $amongstUsers) + ; + } + + return $qb->getQuery()->getResult(); + } + + protected function queryByUsernameOrEmail($pattern) + { + $qb = $this->createQueryBuilder('u'); + + $searchByPattern = $qb->expr()->orX(); + + $searchByPattern + ->add($qb->expr()->eq('u.usernameCanonical', 'LOWER(UNACCENT(:pattern))')) + ->add($qb->expr()->eq('u.emailCanonical', 'LOWER(UNACCENT(:pattern))')) + ; + + $qb + ->where($searchByPattern) + ->setParameter('pattern', $pattern) + ; + + return $qb; + } +} diff --git a/src/Bundle/ChillMain/Resources/Gruntfile.js b/src/Bundle/ChillMain/Resources/Gruntfile.js new file mode 100644 index 000000000..ff2a6b438 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/Gruntfile.js @@ -0,0 +1,143 @@ +module.exports = function(grunt) { + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + chill: { + folders: { + pub: './public', + fonts: '<%= chill.folders.pub %>/fonts', + bower: './bower_components/', + css: '<%= chill.folders.pub %>/css/', + js: '<%= chill.folders.pub %>/js/', + sass: '<%= chill.folders.pub %>/sass/', + } + }, + bower: { + install: { + options: { + targetDir: '<%= chill.folders.bower %>', + install: true, + copy: false, + //cleanBowerDir: true, + verbose: true + } + } + }, + copy: { + scratch: { + files: [ + { + cwd: '<%= chill.folders.bower %>Scratch-CSS-Design/stylesheets/sass', + src: ['**', '!_custom.scss', '!custom/**/*'], + dest: '<%= chill.folders.sass %>', + expand: true, + }, + { + cwd: '<%= chill.folders.bower %>Scratch-CSS-Design/fonts/', + src: '**', + dest: '<%= chill.folders.fonts %>', + expand: true, + } + ] + }, + select2: { + files: [ + { + cwd: '<%= chill.folders.bower %>select2', + src: ['*.js'], + dest: '<%= chill.folders.js %>select2', + expand: true, + }, + { + cwd: '<%= chill.folders.bower %>select2', + src: ['*.css', 'select2.png'], + dest: '<%= chill.folders.css %>select2', + expand: true, + } + ] + }, + pikaday: { + files: [ + { + cwd: '<%= chill.folders.bower %>pikaday/css', + src: ['pikaday.css'], + dest: '<%= chill.folders.css %>', + expand: true, + }, + { + cwd: '<%= chill.folders.bower %>pikaday', + src: ['pikaday.js', 'plugins/pikaday.jquery.js'], + dest: '<%= chill.folders.js %>pikaday', + expand: true, + } + ] + }, + moment: { + files: [ + { + cwd: '<%= chill.folders.bower %>moment', + src: ['moment.js'], + dest: '<%= chill.folders.js %>', + expand: true, + } + ] + }, + chill_standard: { /* copy all files in chill standard (done by app/console assets:install) */ + files: [ + { + cwd: './public', + src: '**', + dest: '../../../../web/bundles/chillmain/', + expand: true, + } + ] + }, + jquery: { + src: '<%= chill.folders.bower %>jquery/dist/jquery.js', + dest: '<%= chill.folders.js %>/jquery.js' + } + }, + sass: { + dist: { + options: { + debugInfo: false, + }, + files: [{ + expand: true, + cwd: '<%= chill.folders.sass.src %>', + src: ['*.scss'], + dest: '<%= chill.folders.css %>', + ext: '.css' + }] + } + }, + watch: { + css: { + files: [ '<%= chill.folders.sass %>/*.scss', '<%= chill.folders.sass %>/**/*.scss' ], + tasks: ['generatecss'], + /* + options: { + spawn: false, + interrupt: true, + } + */ + } + }, + clean: { + /*css: ['<%= chill.folders.css %>*', '!<%= chill.folders.css %>sass/_custom.scss'], */ + js: ['<%= chill.folders.js %>/select2*', '<%= chill.folders.js %>/pikaday*', '<%= chill.folders.js %>/moment*', '<%= chill.folders.js %>/jquery*'], + chill_standard: ['../../../../web/bundles/chillmain/'], + bowerDir: ['<%= chill.folders.bower %>'] + } + }); + + grunt.loadNpmTasks('grunt-bower-task'); + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-sass'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-contrib-clean'); + + grunt.registerTask('generatecss', [/*'clean:css',*/'copy:scratch', 'sass']); + grunt.registerTask('dependencies', ['bower', 'copy']); + grunt.registerTask('default', ['dependencies', 'generatecss']); +}; \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/README.md b/src/Bundle/ChillMain/Resources/README.md new file mode 100644 index 000000000..ab650727a --- /dev/null +++ b/src/Bundle/ChillMain/Resources/README.md @@ -0,0 +1,19 @@ +ScratchCSS +========== + +Installation +------------ + +```bash +npm install +grunt +``` + +Developpement : autoupdate +-------------------------- + +```bash +grunt watch +``` + +This command will copy all the css file into `../../../../web/bundles/chillmain/` \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/bower.json b/src/Bundle/ChillMain/Resources/bower.json new file mode 100644 index 000000000..c179fdf8d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/bower.json @@ -0,0 +1,21 @@ +{ + "name": "Chill-JS-CSS", + "version": "0.0.0", + "homepage": "https://github.com/Chill-project/Main", + "authors": [ + "Champs-Libres " + ], + "description": "JS-CSS files for Chill, a software for social workers", + "private": true, + "devDependencies": { + "Scratch-CSS-Design": "https://github.com/Champs-Libres/ScratchCSS.git", + "pikaday": "https://github.com/dbushell/Pikaday.git#~1.2.0", + "moment": "~2.8.3", + "select2": "~3.5.2", + "jquery": "~2.1.1" + }, + "license": "MIT", + "dependencies": { + "font-awesome": "~4.7.0" + } +} diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/Address.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/Address.orm.yml new file mode 100644 index 000000000..58a80d671 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/Address.orm.yml @@ -0,0 +1,28 @@ +Chill\MainBundle\Entity\Address: + type: entity + table: chill_main_address + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + streetAddress1: + type: string + length: 255 + streetAddress2: + type: string + length: 255 + validFrom: + type: date + isNoAddress: + type: boolean + default: false + customs: + type: json + default: [] + manyToOne: + postcode: + targetEntity: Chill\MainBundle\Entity\PostalCode + lifecycleCallbacks: { } diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/Center.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/Center.orm.yml new file mode 100644 index 000000000..c09a18bf7 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/Center.orm.yml @@ -0,0 +1,18 @@ +Chill\MainBundle\Entity\Center: + type: entity + table: centers + repositoryClass: Chill\MainBundle\Repository\CenterRepository + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + oneToMany: + groupCenters: + targetEntity: Chill\MainBundle\Entity\GroupCenter + mappedBy: groupCenters \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/Country.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/Country.orm.yml new file mode 100644 index 000000000..6956d5545 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/Country.orm.yml @@ -0,0 +1,18 @@ +Chill\MainBundle\Entity\Country: + type: entity + table: null + cache: + usage: READ_ONLY + region: country_cache_region + fields: + id: + type: integer + id: true + generator: + strategy: AUTO + name: + type: json_array + countryCode: + type: string + length: 3 + lifecycleCallbacks: { } diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/GroupCenter.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/GroupCenter.orm.yml new file mode 100644 index 000000000..1fc2cc75d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/GroupCenter.orm.yml @@ -0,0 +1,26 @@ +Chill\MainBundle\Entity\GroupCenter: + type: entity + table: group_centers + cache: + usage: NONSTRICT_READ_WRITE + region: acl_cache_region + id: + id: + type: integer + id: true + generator: + strategy: AUTO + manyToOne: + center: + targetEntity: Chill\MainBundle\Entity\Center + inversedBy: groupCenters + cache: + usage: NONSTRICT_READ_WRITE + permissionsGroup: + targetEntity: Chill\MainBundle\Entity\PermissionsGroup + cache: + usage: NONSTRICT_READ_WRITE + manyToMany: + users: + targetEntity: Chill\MainBundle\Entity\User + mappedBy: groupCenters \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/Language.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/Language.orm.yml new file mode 100644 index 000000000..616bb9564 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/Language.orm.yml @@ -0,0 +1,13 @@ +Chill\MainBundle\Entity\Language: + type: entity + table: null + cache: + usage: READ_ONLY + region: language_cache_region + id: + id: + type: string + fields: + name: + type: json_array + lifecycleCallbacks: { } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/PermissionsGroup.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/PermissionsGroup.orm.yml new file mode 100644 index 000000000..a8b5daa12 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/PermissionsGroup.orm.yml @@ -0,0 +1,32 @@ +Chill\MainBundle\Entity\PermissionsGroup: + type: entity + table: permission_groups + cache: + usage: NONSTRICT_READ_WRITE + region: acl_cache_region + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + flags: + type: json + options: + default: '[]' + manyToMany: + roleScopes: + targetEntity: Chill\MainBundle\Entity\RoleScope + inversedBy: permissionsGroups + cache: + usage: NONSTRICT_READ_WRITE + cascade: [ persist ] + oneToMany: + groupCenters: + targetEntity: Chill\MainBundle\Entity\GroupCenter + mappedBy: permissionsGroup + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/PostalCode.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/PostalCode.orm.yml new file mode 100644 index 000000000..59d74ddf3 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/PostalCode.orm.yml @@ -0,0 +1,24 @@ +Chill\MainBundle\Entity\PostalCode: + type: entity + table: chill_main_postal_code + repositoryClass: Chill\MainBundle\Repository\PostalCodeRepository + indexes: + - { name: search_name_code, columns: [ "code", "label" ] } + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + name: + type: string + length: 255 + column: label + code: + type: string + length: 100 + manyToOne: + country: + targetEntity: Chill\MainBundle\Entity\Country + lifecycleCallbacks: { } diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/RoleScope.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/RoleScope.orm.yml new file mode 100644 index 000000000..f9a20f38e --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/RoleScope.orm.yml @@ -0,0 +1,28 @@ +Chill\MainBundle\Entity\RoleScope: + type: entity + table: role_scopes + cache: + usage: NONSTRICT_READ_WRITE + region: acl_cache_region + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + role: + type: string + length: 255 + manyToOne: + scope: + targetEntity: Chill\MainBundle\Entity\Scope + inversedBy: roleScopes + nullable: true + cache: + usage: NONSTRICT_READ_WRITE + manyToMany: + permissionsGroups: + targetEntity: Chill\MainBundle\Entity\PermissionsGroup + mappedBy: roleScopes + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/Scope.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/Scope.orm.yml new file mode 100644 index 000000000..df2fdd663 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/Scope.orm.yml @@ -0,0 +1,21 @@ +Chill\MainBundle\Entity\Scope: + type: entity + table: scopes + cache: + usage: NONSTRICT_READ_WRITE + region: acl_cache_region + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + name: + type: json_array + oneToMany: + roleScopes: + targetEntity: Chill\MainBundle\Entity\RoleScope + mappedBy: scope + cache: + usage: NONSTRICT_READ_WRITE \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/doctrine/User.orm.yml b/src/Bundle/ChillMain/Resources/config/doctrine/User.orm.yml new file mode 100644 index 000000000..ad1398d3d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/doctrine/User.orm.yml @@ -0,0 +1,53 @@ +Chill\MainBundle\Entity\User: + type: entity + table: users + repositoryClass: Chill\MainBundle\Repository\UserRepository + cache: + usage: NONSTRICT_READ_WRITE + region: acl_cache_region + id: + id: + type: integer + id: true + generator: + strategy: AUTO + fields: + username: + type: string + length: 80 + usernameCanonical: + name: username_canonical + type: string + length: 80 + unique: true + email: + type: string + length: 150 + nullable: true + emailCanonical: + name: email_canonical + type: string + length: 150 + nullable: true + unique: true + password: + type: string + length: 255 + salt: + type: string + length: 255 + nullable: true + enabled: + type: boolean + default: true + locked: + type: boolean + default: false + manyToMany: + groupCenters: + targetEntity: Chill\MainBundle\Entity\GroupCenter + inversedBy: users + cache: + usage: NONSTRICT_READ_WRITE + + diff --git a/src/Bundle/ChillMain/Resources/config/routing.yml b/src/Bundle/ChillMain/Resources/config/routing.yml new file mode 100644 index 000000000..983e5237d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing.yml @@ -0,0 +1,88 @@ +chill_main_admin_permissionsgroup: + resource: "@ChillMainBundle/Resources/config/routing/permissionsgroup.yml" + prefix: "{_locale}/admin/permissionsgroup" + +chill_main_admin_user: + resource: "@ChillMainBundle/Resources/config/routing/user.yml" + prefix: "{_locale}/admin/user" + +chill_main_admin_scope: + resource: "@ChillMainBundle/Resources/config/routing/scope.yml" + prefix: "{_locale}/admin/scope" + +chill_main_admin: + resource: "@ChillMainBundle/Resources/config/routing/center.yml" + prefix: "{_locale}/admin/center" + +chill_main_exports: + resource: "@ChillMainBundle/Resources/config/routing/exports.yml" + prefix: "{_locale}/exports" + +chill_postal_code: + resource: "@ChillMainBundle/Resources/config/routing/postal-code.yml" + prefix: "{_locale}/postal-code" + +chill_password: + resource: "@ChillMainBundle/Resources/config/routing/password.yml" + prefix: "{_locale}/password" + +chill_password_recover: + resource: "@ChillMainBundle/Resources/config/routing/password_recover.yml" + prefix: "public/{_locale}/password" + +root: + path: / + defaults: + _controller: FrameworkBundle:Redirect:urlRedirect + path: /homepage + permanent: false + +chill_main_homepage_without_locale: + path: /homepage + defaults: { _controller: ChillMainBundle:Default:indexWithoutLocale } + +chill_main_homepage: + path: /{_locale}/homepage + defaults: { _controller: ChillMainBundle:Default:index } + + +chill_main_admin_central: + path: /{_locale}/admin + defaults: { _controller: ChillMainBundle:Admin:index } + options: + menus: + admin_permissions: + order: 0 + label: Main admin menu + +chill_main_admin_permissions: + path: /{_locale}/admin/permissions + defaults: {_controller: ChillMainBundle:Admin:indexPermissions } + +chill_main_search: + path: /{_locale}/search.{_format} + defaults: { _controller: ChillMainBundle:Search:search, _format: 'html' } + requirements: + _format: html|json + +chill_main_advanced_search: + path: /{_locale}/search/advanced/{name} + defaults: { _controller: ChillMainBundle:Search:advancedSearch } + +chill_main_advanced_search_list: + path: /{_locale}/search/advanced + defaults: { _controller: ChillMainBundle:Search:advancedSearchList } + +login: + path: /login + defaults: { _controller: ChillMainBundle:Login:login } + +login_check: + path: /login_check + +logout: + path: /logout + +chill_main_test: + path: /{_locale}/main/test + defaults: { _controller: ChillMainBundle:Default:test } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/routing/center.yml b/src/Bundle/ChillMain/Resources/config/routing/center.yml new file mode 100644 index 000000000..557da3995 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/center.yml @@ -0,0 +1,35 @@ +admin_center: + path: / + defaults: { _controller: "ChillMainBundle:Center:index" } + options: + menus: + admin_permissions: + order: 100 + label: Center list + +admin_center_show: + path: /{id}/show + defaults: { _controller: "ChillMainBundle:Center:show" } + +admin_center_new: + path: /new + defaults: { _controller: "ChillMainBundle:Center:new" } + options: + menus: + admin_permissions: + order: 101 + label: New center + +admin_center_create: + path: /create + defaults: { _controller: "ChillMainBundle:Center:create" } + methods: POST + +admin_center_edit: + path: /{id}/edit + defaults: { _controller: "ChillMainBundle:Center:edit" } + +admin_center_update: + path: /{id}/update + defaults: { _controller: "ChillMainBundle:Center:update" } + methods: [POST, PUT] diff --git a/src/Bundle/ChillMain/Resources/config/routing/exports.yml b/src/Bundle/ChillMain/Resources/config/routing/exports.yml new file mode 100644 index 000000000..966902952 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/exports.yml @@ -0,0 +1,17 @@ +chill_main_export_index: + path: / + defaults: { _controller: ChillMainBundle:Export:index } + +chill_main_export_new: + path: /new/{alias} + defaults: { _controller: ChillMainBundle:Export:new } + +chill_main_export_generate: + path: /generate/{alias} + defaults: { _controller: ChillMainBundle:Export:generate } + methods: [GET] + +chill_main_export_download: + path: /download/{alias} + defaults: { _controller: ChillMainBundle:Export:downloadResult } + methods: [ GET ] \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/routing/password.yml b/src/Bundle/ChillMain/Resources/config/routing/password.yml new file mode 100644 index 000000000..ae87e1ee2 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/password.yml @@ -0,0 +1,4 @@ +change_my_password: + path: /edit + defaults: { _controller: ChillMainBundle:Password:userPassword } + diff --git a/src/Bundle/ChillMain/Resources/config/routing/password_recover.yml b/src/Bundle/ChillMain/Resources/config/routing/password_recover.yml new file mode 100644 index 000000000..1f5bee846 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/password_recover.yml @@ -0,0 +1,15 @@ +password_recover: + path: /recover + defaults: { _controller: ChillMainBundle:Password:recover } + +password_request_recover: + path: /request-recover + defaults: { _controller: ChillMainBundle:Password:requestRecover } + +password_request_recover_confirm: + path: /request-confirm + defaults: { _controller: ChillMainBundle:Password:requestRecoverConfirm } + +password_request_recover_changed: + path: /request-changed + defaults: { _controller: ChillMainBundle:Password:changeConfirmed } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/routing/permissionsgroup.yml b/src/Bundle/ChillMain/Resources/config/routing/permissionsgroup.yml new file mode 100644 index 000000000..ac4c18c55 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/permissionsgroup.yml @@ -0,0 +1,45 @@ +admin_permissionsgroup: + path: / + defaults: { _controller: "ChillMainBundle:PermissionsGroup:index" } + options: + menus: + admin_permissions: + order: 300 + label: Permissions group list + +admin_permissionsgroup_show: + path: /{id}/show + defaults: { _controller: "ChillMainBundle:PermissionsGroup:show" } + +admin_permissionsgroup_new: + path: /new + defaults: { _controller: "ChillMainBundle:PermissionsGroup:new" } + options: + menus: + admin_permissions: + order: 301 + label: New permission group + +admin_permissionsgroup_create: + path: /create + defaults: { _controller: "ChillMainBundle:PermissionsGroup:create" } + methods: POST + +admin_permissionsgroup_edit: + path: /{id}/edit + defaults: { _controller: "ChillMainBundle:PermissionsGroup:edit" } + +admin_permissionsgroup_update: + path: /{id}/update + defaults: { _controller: "ChillMainBundle:PermissionsGroup:update" } + methods: [POST, PUT] + +admin_permissionsgroup_delete_role_scope: + path: /{pgid}/delete_link_role_scope/{rsid} + defaults: { _controller: "ChillMainBundle:PermissionsGroup:deleteLinkRoleScope" } + methods: [DELETE] + +admin_permissionsgroup_add_role_scope: + path: /{id}/add_link_role_scope + defaults: { _controller: "ChillMainBundle:PermissionsGroup:addLinkRoleScope" } + methods: [PUT] diff --git a/src/Bundle/ChillMain/Resources/config/routing/postal-code.yml b/src/Bundle/ChillMain/Resources/config/routing/postal-code.yml new file mode 100644 index 000000000..d8892c098 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/postal-code.yml @@ -0,0 +1,4 @@ +chill_main_postal_code_search: + path: /search + defaults: { _controller: ChillMainBundle:PostalCode:search } + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/routing/scope.yml b/src/Bundle/ChillMain/Resources/config/routing/scope.yml new file mode 100644 index 000000000..dd0d0ec32 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/scope.yml @@ -0,0 +1,35 @@ +admin_scope: + path: / + defaults: { _controller: "ChillMainBundle:Scope:index" } + options: + menus: + admin_permissions: + order: 200 + label: List circles + +admin_scope_show: + path: /{id}/show + defaults: { _controller: "ChillMainBundle:Scope:show" } + +admin_scope_new: + path: /new + defaults: { _controller: "ChillMainBundle:Scope:new" } + options: + menus: + admin_permissions: + order: 201 + label: New circle + +admin_scope_create: + path: /create + defaults: { _controller: "ChillMainBundle:Scope:create" } + methods: POST + +admin_scope_edit: + path: /{id}/edit + defaults: { _controller: "ChillMainBundle:Scope:edit" } + +admin_scope_update: + path: /{id}/update + defaults: { _controller: "ChillMainBundle:Scope:update" } + methods: [POST, PUT] diff --git a/src/Bundle/ChillMain/Resources/config/routing/user.yml b/src/Bundle/ChillMain/Resources/config/routing/user.yml new file mode 100644 index 000000000..73050fae3 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/routing/user.yml @@ -0,0 +1,54 @@ +admin_user: + path: / + defaults: { _controller: "ChillMainBundle:User:index" } + options: + menus: + admin_permissions: + order: 400 + label: List users + +admin_user_show: + path: /{id}/show + defaults: { _controller: "ChillMainBundle:User:show" } + +admin_user_new: + path: /new + defaults: { _controller: "ChillMainBundle:User:new" } + options: + menus: + admin_permissions: + order: 401 + label: Add a new user + +admin_user_create: + path: /create + defaults: { _controller: "ChillMainBundle:User:create" } + methods: POST + +admin_user_edit: + path: /{id}/edit + defaults: { _controller: "ChillMainBundle:User:edit" } + +admin_user_edit_password: + path: /{id}/edit_password + defaults: { _controller: "ChillMainBundle:User:editPassword" } + +admin_user_update: + path: /{id}/update + defaults: { _controller: "ChillMainBundle:User:update" } + methods: [POST, PUT] + +admin_user_update_password: + path: /{id}/update_password + defaults: { _controller: "ChillMainBundle:User:updatePassword" } + methods: [POST, PUT] + +admin_user_delete_group_center: + path: /{uid}/delete_link_groupcenter/{gcid} + defaults: { _controller: "ChillMainBundle:User:deleteLinkGroupCenter" } + methods: [DELETE] + +admin_user_add_group_center: + path: /{uid}/add_link_groupcenter + defaults: { _controller: "ChillMainBundle:User:addLinkGroupCenter" } + methods: [POST] diff --git a/src/Bundle/ChillMain/Resources/config/services.yml b/src/Bundle/ChillMain/Resources/config/services.yml new file mode 100644 index 000000000..010deb3ca --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services.yml @@ -0,0 +1,62 @@ +parameters: +# cl_chill_main.example.class: Chill\MainBundle\Example + +services: + chill.main.helper.translatable_string: + class: Chill\MainBundle\Templating\TranslatableStringHelper + arguments: + - "@request_stack" + - "@translator.default" + Chill\MainBundle\Templating\TranslatableStringHelper: '@chill.main.helper.translatable_string' + + chill.main.twig.translatable_string: + class: Chill\MainBundle\Templating\TranslatableStringTwig + calls: + - [ setContainer, ["@service_container"]] + tags: + - { name: twig.extension } + + chill.main.twig.widget: + class: Chill\MainBundle\Templating\Widget\WidgetRenderingTwig + arguments: + - "@event_dispatcher" + tags: + - { name: twig.extension } + + chill.main.twig.csv_cell: + class: Chill\MainBundle\Templating\CSVCellTwig + tags: + - { name: twig.extension } + + chill.main.form.data_transformer.center_transformer: + class: Chill\MainBundle\Form\Type\DataTransformer\CenterTransformer + arguments: + - "@doctrine.orm.entity_manager" + + chill.main.search_provider: + class: Chill\MainBundle\Search\SearchProvider + + chill.main.timeline_builder: + class: Chill\MainBundle\Timeline\TimelineBuilder + arguments: + - "@doctrine.orm.entity_manager" + calls: + - [ setContainer, ["@service_container"]] + + chill.main.validator.role_scope_scope_presence: + class: Chill\MainBundle\Validation\Validator\RoleScopeScopePresence + arguments: + - "@chill.main.role_provider" + - "@logger" + - "@translator" + tags: + - { name: validator.constraint_validator, alias: 'role_scope_scope_presence' } + + chill.main.export_manager: + class: Chill\MainBundle\Export\ExportManager + arguments: + - "@logger" + - "@doctrine.orm.entity_manager" + - "@security.authorization_checker" + - "@chill.main.security.authorization.helper" + - "@security.token_storage" diff --git a/src/Bundle/ChillMain/Resources/config/services/cache.yml b/src/Bundle/ChillMain/Resources/config/services/cache.yml new file mode 100644 index 000000000..de362ea30 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/cache.yml @@ -0,0 +1,7 @@ +services: + chill_main.tag_aware_cache: + class: Symfony\Component\Cache\Adapter\TagAwareAdapter + arguments: + - '@cache.user_data' + - '@cache.tags' + Symfony\Component\Cache\Adapter\TagAwareAdapter: '@chill_main.tag_aware_cache' diff --git a/src/Bundle/ChillMain/Resources/config/services/command.yml b/src/Bundle/ChillMain/Resources/config/services/command.yml new file mode 100644 index 000000000..d06ce2cc4 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/command.yml @@ -0,0 +1,15 @@ +services: + Chill\MainBundle\Command\ChillImportUsersCommand: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $logger: '@Psr\Log\LoggerInterface' + $passwordEncoder: '@Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface' + $validator: '@Symfony\Component\Validator\Validator\ValidatorInterface' + + tags: + - { name: console.command } + + Chill\MainBundle\Command\ChillUserSendRenewPasswordCodeCommand: + autowire: true + tags: + - { name: console.command } diff --git a/src/Bundle/ChillMain/Resources/config/services/controller.yml b/src/Bundle/ChillMain/Resources/config/services/controller.yml new file mode 100644 index 000000000..562753837 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/controller.yml @@ -0,0 +1,12 @@ +services: + + Chill\MainBundle\Controller\: + autowire: true + resource: '../../../Controller' + tags: ['controller.service_arguments'] + + Chill\MainBundle\Controller\PasswordController: + autowire: true + arguments: + $chillLogger: '@monolog.logger.chill' + tags: ['controller.service_arguments'] diff --git a/src/Bundle/ChillMain/Resources/config/services/crud.yml b/src/Bundle/ChillMain/Resources/config/services/crud.yml new file mode 100644 index 000000000..478122b73 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/crud.yml @@ -0,0 +1,16 @@ +services: +# Chill\MainBundle\CRUD\Routing\CRUDRoutesLoader: +# +# tags: +# - routing.loader + + Chill\MainBundle\CRUD\Resolver\Resolver: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + $crudConfig: '%chill_main_crud_route_loader_config%' + + Chill\MainBundle\CRUD\Templating\TwigCRUDResolver: + arguments: + $resolver: '@Chill\MainBundle\CRUD\Resolver\Resolver' + tags: + - { name: twig.extension } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/export.yml b/src/Bundle/ChillMain/Resources/config/services/export.yml new file mode 100644 index 000000000..bd6d730da --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/export.yml @@ -0,0 +1,47 @@ +services: + chill.main.export_element_validator: + class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator + tags: + - { name: validator.constraint_validator } + +# deprecated in favor of spreadsheet_formatter +# chill.main.export.csv_formatter: +# class: Chill\MainBundle\Export\Formatter\CSVFormatter +# arguments: +# - "@translator" +# - "@chill.main.export_manager" +# tags: +# - { name: chill.export_formatter, alias: 'csv' } + + chill.main.export.spreadsheet_formatter: + class: Chill\MainBundle\Export\Formatter\SpreadSheetFormatter + arguments: + - "@translator" + - "@chill.main.export_manager" + tags: + - { name: chill.export_formatter, alias: 'spreadsheet' } + + chill.main.export.list_formatter: + class: Chill\MainBundle\Export\Formatter\CSVListFormatter + arguments: + - "@translator" + - "@chill.main.export_manager" + tags: + - { name: chill.export_formatter, alias: 'csvlist' } + + chill.main.export.list_spreadsheet_formatter: + class: Chill\MainBundle\Export\Formatter\SpreadsheetListFormatter + arguments: + - "@translator" + - "@chill.main.export_manager" + tags: + - { name: chill.export_formatter, alias: 'spreadlist' } + + chill.main.export.pivoted_list_formatter: + class: Chill\MainBundle\Export\Formatter\CSVPivotedListFormatter + arguments: + - "@translator" + - "@chill.main.export_manager" + tags: + - { name: chill.export_formatter, alias: 'csv_pivoted_list' } + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/fixtures.yml b/src/Bundle/ChillMain/Resources/config/services/fixtures.yml new file mode 100644 index 000000000..b3ef847e4 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/fixtures.yml @@ -0,0 +1,4 @@ +services: + Chill\MainBundle\DataFixtures\ORM\: + resource: ../../../DataFixtures/ORM + tags: [ 'doctrine.fixture.orm' ] diff --git a/src/Bundle/ChillMain/Resources/config/services/form.yml b/src/Bundle/ChillMain/Resources/config/services/form.yml new file mode 100644 index 000000000..d9f8a5700 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/form.yml @@ -0,0 +1,139 @@ +services: + chill.main.form.type.translatable.string: + class: Chill\MainBundle\Form\Type\TranslatableStringFormType + arguments: + - "%chill_main.available_languages%" + #- "%framework.translator.fallback%" + - "@translator.default" + tags: + - { name: form.type, alias: translatable_string } + + chill.main.form.type.select2choice: + class: Chill\MainBundle\Form\Type\Select2ChoiceType + tags: + - {name: form.type, alias: select2_choice} + + chill.main.form.type.select2entity: + class: Chill\MainBundle\Form\Type\Select2EntityType + tags: + - {name: form.type, alias: select2_entity } + + chill.main.form.type.select2country: + class: Chill\MainBundle\Form\Type\Select2CountryType + arguments: + - "@request_stack" + - "@doctrine.orm.entity_manager" + tags: + - { name: form.type, alias: select2_chill_country } + + chill.main.form.type.select2language: + class: Chill\MainBundle\Form\Type\Select2LanguageType + arguments: + - "@request_stack" + - "@doctrine.orm.entity_manager" + tags: + - { name: form.type, alias: select2_chill_language } + + chill.main.form.type.center: + class: Chill\MainBundle\Form\Type\CenterType + arguments: + - "@security.token_storage" + - "@chill.main.form.data_transformer.center_transformer" + tags: + - { name: form.type, alias: center } + + chill.main.form.type.composed_role_scope: + class: Chill\MainBundle\Form\Type\ComposedRoleScopeType + arguments: + - "@chill.main.helper.translatable_string" + - "@chill.main.role_provider" + tags: + - { name: form.type, alias: composed_role_scope } + + + chill.main.form.type.postal_code_type: + class: Chill\MainBundle\Form\Type\PostalCodeType + arguments: + - "@chill.main.helper.translatable_string" + - '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' + - '@chill.main.form.choice_loader.postal_code' + - '@Symfony\Component\Translation\TranslatorInterface' + tags: + - { name: form.type } + + chill.main.form.choice_loader.postal_code: + class: Chill\MainBundle\Form\ChoiceLoader\PostalCodeChoiceLoader + arguments: + - '@Chill\MainBundle\Repository\PostalCodeRepository' + + chill.main.form.type.export: + class: Chill\MainBundle\Form\Type\Export\ExportType + arguments: + - "@chill.main.export_manager" + tags: + - { name: form.type } + + chill.main.form.pick_formatter_type: + class: Chill\MainBundle\Form\Type\Export\PickFormatterType + arguments: + - "@chill.main.export_manager" + tags: + - { name: form.type } + + chill.main.form.pick_centers_type: + class: Chill\MainBundle\Form\Type\Export\PickCenterType + arguments: + - "@security.token_storage" + - "@chill.main.export_manager" + - "@chill.main.security.authorization.helper" + tags: + - { name: form.type } + + chill.main.form.formatter_type: + class: Chill\MainBundle\Form\Type\Export\FormatterType + arguments: + - "@chill.main.export_manager" + tags: + - { name: form.type } + + chill.main.form.date_type: + class: Chill\MainBundle\Form\Type\ChillDateType + tags: + - { name: form.type } + + chill.main.form.pick_user_type: + class: Chill\MainBundle\Form\Type\UserPickerType + arguments: + - "@chill.main.security.authorization.helper" + - "@security.token_storage" + - "@chill.main.user_repository" + tags: + - { name: form.type } + + chill.main.form.pick_scope_type: + class: Chill\MainBundle\Form\Type\ScopePickerType + arguments: + - "@chill.main.security.authorization.helper" + - "@security.token_storage" + - "@chill.main.scope_repository" + - "@chill.main.helper.translatable_string" + tags: + - { name: form.type } + + chill.main.form.advanced_search_type: + class: Chill\MainBundle\Form\AdvancedSearchType + arguments: + - "@chill.main.search_provider" + tags: + - { name: form.type } + + Chill\MainBundle\Form\UserPasswordType: + arguments: + $chillLogger: '@monolog.logger.chill' + $passwordEncoder: '@Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface' + tags: + - { name: form.type } + + Chill\MainBundle\Form\PermissionsGroupType: + tags: + - { name: form.type } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/logger.yml b/src/Bundle/ChillMain/Resources/config/services/logger.yml new file mode 100644 index 000000000..2da5a4a4c --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/logger.yml @@ -0,0 +1,4 @@ +services: + chill.main.logger: + # a logger to log events from the app (deletion, remove, etc.) + alias: monolog.logger.chill diff --git a/src/Bundle/ChillMain/Resources/config/services/menu.yml b/src/Bundle/ChillMain/Resources/config/services/menu.yml new file mode 100644 index 000000000..ac331bd5a --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/menu.yml @@ -0,0 +1,18 @@ +services: + Chill\MainBundle\Routing\MenuBuilder\UserMenuBuilder: + arguments: + $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface' + tags: + - { name: 'chill.menu_builder' } + + Chill\MainBundle\Routing\MenuBuilder\SectionMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: 'chill.menu_builder' } + + Chill\MainBundle\Routing\MenuBuilder\AdminSectionMenuBuilder: + arguments: + $authorizationChecker: '@Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface' + tags: + - { name: 'chill.menu_builder' } diff --git a/src/Bundle/ChillMain/Resources/config/services/notification.yml b/src/Bundle/ChillMain/Resources/config/services/notification.yml new file mode 100644 index 000000000..54d63365c --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/notification.yml @@ -0,0 +1,10 @@ +services: + Chill\MainBundle\Notification\Mailer: + arguments: + - "@logger" + - "@twig" + - "@mailer" + - "@swiftmailer.transport" + - "@router" + - "@translator" + - "%chill_main.notifications%" diff --git a/src/Bundle/ChillMain/Resources/config/services/pagination.yml b/src/Bundle/ChillMain/Resources/config/services/pagination.yml new file mode 100644 index 000000000..ca9b50e4f --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/pagination.yml @@ -0,0 +1,13 @@ +services: + chill_main.paginator_factory: + class: Chill\MainBundle\Pagination\PaginatorFactory + arguments: + - "@request_stack" + - "@router" + - "%chill_main.pagination.item_per_page%" + Chill\MainBundle\Pagination\PaginatorFactory: '@chill_main.paginator_factory' + + chill_main.paginator.twig_extensions: + class: Chill\MainBundle\Pagination\ChillPaginationTwig + tags: + - { name: twig.extension } diff --git a/src/Bundle/ChillMain/Resources/config/services/phonenumber.yml b/src/Bundle/ChillMain/Resources/config/services/phonenumber.yml new file mode 100644 index 000000000..f297c03e0 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/phonenumber.yml @@ -0,0 +1,19 @@ +services: + Chill\MainBundle\Phonenumber\PhonenumberHelper: + arguments: + $logger: '@Psr\Log\LoggerInterface' + $config: '%chill_main.phone_helper%' + $cachePool: '@cache.user_data' + + Chill\MainBundle\Phonenumber\Templating: + arguments: + $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' + tags: + - { name: twig.extension } + + Chill\MainBundle\Validation\Validator\ValidPhonenumber: + arguments: + $logger: '@Psr\Log\LoggerInterface' + $phonenumberHelper: '@Chill\MainBundle\Phonenumber\PhonenumberHelper' + tags: + - { name: validator.constraint_validator } diff --git a/src/Bundle/ChillMain/Resources/config/services/redis.yml b/src/Bundle/ChillMain/Resources/config/services/redis.yml new file mode 100644 index 000000000..b76555161 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/redis.yml @@ -0,0 +1,10 @@ +services: + Chill\MainBundle\Redis\RedisConnectionFactory: + arguments: + $parameters: "%chill_main.redis%" + tags: + - { name: kernel.event_subcriber } + + Chill\MainBundle\Redis\ChillRedis: + factory: 'Chill\MainBundle\Redis\RedisConnectionFactory:create' + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/repositories.yml b/src/Bundle/ChillMain/Resources/config/services/repositories.yml new file mode 100644 index 000000000..897fd4313 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/repositories.yml @@ -0,0 +1,35 @@ +services: + chill.main.countries_repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - "Chill\\MainBundle\\Entity\\Country" + + chill.main.user_repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - "Chill\\MainBundle\\Entity\\User" + + chill.main.scope_repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - "Chill\\MainBundle\\Entity\\Scope" + + chill.main.postalcode_repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - "Chill\\MainBundle\\Entity\\PostalCode" + + Chill\MainBundle\Repository\PostalCodeRepository: '@chill.main.postalcode_repository' + + chill.main.center_repository: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine.orm.entity_manager", getRepository] + arguments: + - "Chill\\MainBundle\\Entity\\Center" + + Chill\MainBundle\Repository\CenterRepository: '@chill.main.center_repository' + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/routing.yml b/src/Bundle/ChillMain/Resources/config/services/routing.yml new file mode 100644 index 000000000..c935f1a4d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/routing.yml @@ -0,0 +1,24 @@ +services: + chill.main.menu_composer: + class: Chill\MainBundle\Routing\MenuComposer + arguments: + - '@Symfony\Component\Routing\RouterInterface' + - '@Knp\Menu\FactoryInterface' + - '@Symfony\Component\Translation\TranslatorInterface' + Chill\MainBundle\Routing\MenuComposer: '@chill.main.menu_composer' + + chill.main.routes_loader: + class: Chill\MainBundle\Routing\Loader\ChillRoutesLoader + arguments: + - "%chill_main.routing.resources%" + tags: + - { name: routing.loader } + + chill.main.twig.chill_menu: + class: Chill\MainBundle\Routing\MenuTwig + arguments: + - "@chill.main.menu_composer" + calls: + - [setContainer, ["@service_container"]] + tags: + - { name: twig.extension } diff --git a/src/Bundle/ChillMain/Resources/config/services/security.yml b/src/Bundle/ChillMain/Resources/config/services/security.yml new file mode 100644 index 000000000..e341eb196 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/security.yml @@ -0,0 +1,52 @@ +services: + chill.main.security.authorization.helper: + class: Chill\MainBundle\Security\Authorization\AuthorizationHelper + arguments: + $roleHierarchy: "@security.role_hierarchy" + $hierarchy: "%security.role_hierarchy.roles%" + $em: '@Doctrine\ORM\EntityManagerInterface' + Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper' + + chill.main.role_provider: + class: Chill\MainBundle\Security\RoleProvider + + chill.main.user_provider: + class: Chill\MainBundle\Security\UserProvider\UserProvider + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + + Chill\MainBundle\Security\Authorization\ChillExportVoter: + arguments: + $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' + tags: + - { name: security.voter } + + Chill\MainBundle\Security\PasswordRecover\TokenManager: + arguments: + $secret: '%kernel.secret%' + $logger: '@Psr\Log\LoggerInterface' + + Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper: + arguments: + $tokenManager: '@Chill\MainBundle\Security\PasswordRecover\TokenManager' + $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' + $mailer: '@Chill\MainBundle\Notification\Mailer' + $routeParameters: "%chill_main.notifications%" + + Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEventSubscriber: + arguments: + $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' + tags: + - { name: kernel.event_subscriber } + + Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker: + arguments: + $chillRedis: '@Chill\MainBundle\Redis\ChillRedis' + $logger: '@Psr\Log\LoggerInterface' + + Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter: + arguments: + $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' + $requestStack: '@Symfony\Component\HttpFoundation\RequestStack' + tags: + - { name: security.voter } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/templating.yml b/src/Bundle/ChillMain/Resources/config/services/templating.yml new file mode 100644 index 000000000..5b927b7ac --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/templating.yml @@ -0,0 +1,38 @@ +services: + twig_intl: + class: Twig_Extensions_Extension_Intl + tags: + - { name: twig.extension } + + twig_date: + class: Twig_Extensions_Extension_Date + arguments: + - "@translator" + tags: + - { name: twig.extension } + + twig_text: + class: Twig_Extensions_Extension_Text + tags: + - { name: twig.extension } + + Chill\MainBundle\Templating\ChillTwigHelper: + tags: + - { name: twig.extension } + + Chill\MainBundle\Templating\ChillTwigRoutingHelper: + arguments: + $requestStack: '@Symfony\Component\HttpFoundation\RequestStack' + $originalExtension: '@twig.extension.routing' + tags: + - { name: twig.extension } + + Chill\MainBundle\Templating\Entity\ChillEntityRenderExtension: + tags: + - { name: twig.extension } + + Chill\MainBundle\Templating\Entity\AddressRendering: + arguments: + $twig: '@templating.engine.twig' + tags: + - { name: 'chill.render_entity' } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/config/services/validator.yml b/src/Bundle/ChillMain/Resources/config/services/validator.yml new file mode 100644 index 000000000..c15b2181e --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/validator.yml @@ -0,0 +1,13 @@ +services: + chill_main.validator_user_circle_consistency: + class: Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistencyValidator + arguments: + - "@chill.main.security.authorization.helper" + tags: + - { name: "validator.constraint_validator" } + + Chill\MainBundle\Validation\Validator\UserUniqueEmailAndUsername: + arguments: + $em: '@Doctrine\ORM\EntityManagerInterface' + tags: + - { name: "validator.constraint_validator" } diff --git a/src/Bundle/ChillMain/Resources/config/services/widget.yml b/src/Bundle/ChillMain/Resources/config/services/widget.yml new file mode 100644 index 000000000..53f29da5b --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/services/widget.yml @@ -0,0 +1,2 @@ +services: + Chill\MainBundle\Templating\UI\CountNotificationUser: ~ diff --git a/src/Bundle/ChillMain/Resources/config/validation.yml b/src/Bundle/ChillMain/Resources/config/validation.yml new file mode 100644 index 000000000..dd5f8d985 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/config/validation.yml @@ -0,0 +1,53 @@ +Chill\MainBundle\Entity\PermissionsGroup: + properties: + name: + - NotBlank: ~ + - Length: + max: 50 + roleScopes: + - Valid: ~ + constraints: + - Callback: + callback: isRoleScopePresentOnce + +Chill\MainBundle\Entity\User: + properties: + username: + - Length: + max: 70 + min: 3 + email: + - Email: ~ + constraints: + - Callback: + callback: isGroupCenterPresentOnce + - \Chill\MainBundle\Validation\Constraint\UserUniqueEmailAndUsernameConstraint: ~ + +Chill\MainBundle\Entity\RoleScope: + constraints: + - \Chill\MainBundle\Validation\Constraint\RoleScopeScopePresenceConstraint: ~ + +Chill\MainBundle\Entity\Center: + properties: + name: + - NotBlank: ~ + - Length: + max: 50 + min: 2 + +Chill\MainBundle\Entity\Address: + constraints: + - Callback: validate + +Chill\MainBundle\Entity\PostalCode: + properties: + name: + - Length: + max: 250 + min: 2 + code: + - Length: + min: 2 + max: 100 + country: + - NotNull: ~ diff --git a/src/Bundle/ChillMain/Resources/doc/index.rst b/src/Bundle/ChillMain/Resources/doc/index.rst new file mode 100644 index 000000000..e69de29bb diff --git a/src/Bundle/ChillMain/Resources/fixtures/postal_code_with_error.csv b/src/Bundle/ChillMain/Resources/fixtures/postal_code_with_error.csv new file mode 100644 index 000000000..30837ba53 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/fixtures/postal_code_with_error.csv @@ -0,0 +1 @@ +1000,BRUXELLES - (BRUXELLES),DDS,BRUXELLES,BRUXELLES,Bruxelles diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20141128194409.php b/src/Bundle/ChillMain/Resources/migrations/Version20141128194409.php new file mode 100644 index 000000000..fe44997f7 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20141128194409.php @@ -0,0 +1,56 @@ +addSql("CREATE TABLE Country (id INT NOT NULL, name JSON NOT NULL, countryCode VARCHAR(3) NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE TABLE centers (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE TABLE Language (id VARCHAR(255) NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE TABLE group_centers (id INT NOT NULL, center_id INT DEFAULT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE INDEX IDX_A14D8F3D5932F377 ON group_centers (center_id);"); + $this->addSql("CREATE TABLE groupcenter_permissionsgroup (groupcenter_id INT NOT NULL, permissionsgroup_id INT NOT NULL, PRIMARY KEY(groupcenter_id, permissionsgroup_id));"); + $this->addSql("CREATE INDEX IDX_55DFEC607EC2FA68 ON groupcenter_permissionsgroup (groupcenter_id);"); + $this->addSql("CREATE INDEX IDX_55DFEC606FA97D46 ON groupcenter_permissionsgroup (permissionsgroup_id);"); + $this->addSql("CREATE TABLE role_scopes (id INT NOT NULL, scope_id INT DEFAULT NULL, role VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE INDEX IDX_AFF20281682B5931 ON role_scopes (scope_id);"); + $this->addSql("CREATE TABLE permission_groups (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE TABLE permissionsgroup_rolescope (permissionsgroup_id INT NOT NULL, rolescope_id INT NOT NULL, PRIMARY KEY(permissionsgroup_id, rolescope_id));"); + $this->addSql("CREATE INDEX IDX_B22441DC6FA97D46 ON permissionsgroup_rolescope (permissionsgroup_id);"); + $this->addSql("CREATE INDEX IDX_B22441DCA0AE1DB7 ON permissionsgroup_rolescope (rolescope_id);"); + $this->addSql("CREATE TABLE users (id INT NOT NULL, username VARCHAR(80) NOT NULL, password VARCHAR(255) NOT NULL, salt VARCHAR(255) DEFAULT NULL, enabled BOOLEAN NOT NULL, locked BOOLEAN NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE TABLE user_groupcenter (user_id INT NOT NULL, groupcenter_id INT NOT NULL, PRIMARY KEY(user_id, groupcenter_id));"); + $this->addSql("CREATE INDEX IDX_33FFE54AA76ED395 ON user_groupcenter (user_id);"); + $this->addSql("CREATE INDEX IDX_33FFE54A7EC2FA68 ON user_groupcenter (groupcenter_id);"); + $this->addSql("CREATE TABLE scopes (id INT NOT NULL, name JSON NOT NULL, PRIMARY KEY(id));"); + $this->addSql("CREATE SEQUENCE Country_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE group_centers_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE role_scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE permission_groups_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("CREATE SEQUENCE scopes_id_seq INCREMENT BY 1 MINVALUE 1 START 1;"); + $this->addSql("ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D5932F377 FOREIGN KEY (center_id) REFERENCES centers (id) NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC607EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT FK_55DFEC606FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE role_scopes ADD CONSTRAINT FK_AFF20281682B5931 FOREIGN KEY (scope_id) REFERENCES scopes (id) NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DC6FA97D46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE permissionsgroup_rolescope ADD CONSTRAINT FK_B22441DCA0AE1DB7 FOREIGN KEY (rolescope_id) REFERENCES role_scopes (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54AA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + $this->addSql("ALTER TABLE user_groupcenter ADD CONSTRAINT FK_33FFE54A7EC2FA68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE;"); + } + + public function down(Schema $schema) + { + // this down() migration is auto-generated, please modify it to your needs + + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20150821105642.php b/src/Bundle/ChillMain/Resources/migrations/Version20150821105642.php new file mode 100644 index 000000000..211532c93 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20150821105642.php @@ -0,0 +1,133 @@ + GroupCenter + * to + * ManyToOne : a GroupCenter can have only one PermissionGroup + * + * @link https://redmine.champs-libres.coop/issues/578 The issue describing the move + */ +class Version20150821105642 extends AbstractMigration implements + \Symfony\Component\DependencyInjection\ContainerAwareInterface +{ + /** + * + * @var ContainerInterface + */ + private $container; + + + /** + * @param Schema $schema + */ + public function up(Schema $schema) + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE group_centers ADD permissionsGroup_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE group_centers ADD CONSTRAINT FK_A14D8F3D447BBB3B FOREIGN KEY (permissionsGroup_id) REFERENCES permission_groups (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('CREATE INDEX IDX_A14D8F3D447BBB3B ON group_centers (permissionsGroup_id)'); + + } + + public function postUp(Schema $schema) + { + //transform data from groupcenter_permissionsgroup table + $em = $this->container->get('doctrine.orm.entity_manager'); + + //get all existing associations + $rsm = new ResultSetMapping(); + $rsm->addScalarResult('groupcenter_id', 'groupcenter_id'); + $rsm->addScalarResult('permissionsgroup_id', 'permissionsgroup_id'); + + $groupPermissionsAssociations = $em->createNativeQuery( + "SELECT groupcenter_id, permissionsgroup_id " + . "FROM groupcenter_permissionsgroup", + $rsm + ) + ->getScalarResult(); + + //update + foreach ($groupPermissionsAssociations as $groupPermissionAssociation) { + //get the corresponding groupCenter + $rsmGroupCenter = new ResultSetMapping(); + $rsmGroupCenter->addScalarResult('id', 'id'); + $rsmGroupCenter->addScalarResult('permissionsGroup_id', 'permissionsGroup_id'); + $rsmGroupCenter->addScalarResult('center_id', 'center_id'); + + $groupCenters = $em->createNativeQuery("SELECT id, permissionsGroup_id, center_id " + . "FROM group_centers " + . "WHERE id = :groupcenter_id AND permissionsGroup_id IS NULL", + $rsmGroupCenter) + ->setParameter('groupcenter_id', $groupPermissionAssociation['groupcenter_id']) + ->getResult(); + + if (count($groupCenters) === 1) { + // we have to update this group with the current association + $em->getConnection()->executeUpdate("UPDATE group_centers " + . "SET permissionsGroup_id = ? " + . "WHERE id = ?", array( + $groupPermissionAssociation['permissionsgroup_id'], + $groupPermissionAssociation['groupcenter_id']) + ); + } elseif (count($groupCenters) === 0) { + // the association was multiple. We have to create a new group_center + $rsmNewId = new ResultSetMapping(); + $rsmNewId->addScalarResult('new_id', 'new_id'); + $newId = $em->createNativeQuery("select nextval('group_centers_id_seq') as new_id", + $rsmNewId) + ->getSingleScalarResult(); + + $em->getConnection()->insert("group_centers", array( + 'id' => $newId, + 'center_id' => $group_center['center_id'], + 'permissionsGroup_id' => $groupPermissionAssociation['permissionsgroup_id'] + )); + + // we have to link existing users to new created groupcenter + $em->getConnection()->executeQuery('INSERT INTO user_groupcenter ' + . '(user_id, groupcenter_id) SELECT user_id, '.$newId.' ' + . 'FROM user_groupcenter WHERE groupcenter_id = ' + .$groupPermissionAssociation['groupcenter_id']); + } else { + throw new \RuntimeException("Error in the data : we should not have two groupCenter " + . "with the same id !"); + } + } + + + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE group_centers DROP CONSTRAINT FK_A14D8F3D447BBB3B'); + $this->addSql('DROP INDEX IDX_A14D8F3D447BBB3B'); + $this->addSql('ALTER TABLE group_centers DROP permissionGroup_id'); + + } + + public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null) + { + if ($container === NULL) { + throw new \RuntimeException('Container is not provided. This migration ' + . 'need container to set a default center'); + } + + $this->container = $container; + } + +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20150821122935.php b/src/Bundle/ChillMain/Resources/migrations/Version20150821122935.php new file mode 100644 index 000000000..38a4f1b5f --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20150821122935.php @@ -0,0 +1,39 @@ +addSql('DROP TABLE groupcenter_permissionsgroup'); + $this->addSql('ALTER TABLE group_centers ALTER permissionsGroup_id SET NOT NULL'); + + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->addSql('ALTER TABLE group_centers ALTER permissionsGroup_id SET DEFAULT NULL'); + $this->addSql('CREATE TABLE groupcenter_permissionsgroup (groupcenter_id INT NOT NULL, permissionsgroup_id INT NOT NULL, PRIMARY KEY(groupcenter_id, permissionsgroup_id))'); + $this->addSql('CREATE INDEX idx_55dfec607ec2fa68 ON groupcenter_permissionsgroup (groupcenter_id)'); + $this->addSql('CREATE INDEX idx_55dfec606fa97d46 ON groupcenter_permissionsgroup (permissionsgroup_id)'); + + $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT fk_55dfec607ec2fa68 FOREIGN KEY (groupcenter_id) REFERENCES group_centers (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE groupcenter_permissionsgroup ADD CONSTRAINT fk_55dfec606fa97d46 FOREIGN KEY (permissionsgroup_id) REFERENCES permission_groups (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20160310122322.php b/src/Bundle/ChillMain/Resources/migrations/Version20160310122322.php new file mode 100644 index 000000000..566dc7f79 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20160310122322.php @@ -0,0 +1,66 @@ +abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('CREATE SEQUENCE chill_main_address_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE chill_main_postal_code_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE chill_main_address (' + . 'id INT NOT NULL, ' + . 'postcode_id INT DEFAULT NULL, ' + . 'streetAddress1 VARCHAR(255) NOT NULL, ' + . 'streetAddress2 VARCHAR(255) NOT NULL, ' + . 'validFrom DATE NOT NULL, ' + . 'PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_165051F6EECBFDF1 ON chill_main_address ' + . '(postcode_id)'); + $this->addSql('CREATE TABLE chill_main_postal_code (' + . 'id INT NOT NULL, ' + . 'country_id INT DEFAULT NULL, ' + . 'label VARCHAR(255) NOT NULL, ' + . 'code VARCHAR(100) NOT NULL, ' + . 'PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_6CA145FAF92F3E70 ON chill_main_postal_code ' + . '(country_id)'); + $this->addSql('ALTER TABLE chill_main_address ADD CONSTRAINT ' + . 'FK_165051F6EECBFDF1 ' + . 'FOREIGN KEY (postcode_id) ' + . 'REFERENCES chill_main_postal_code (id) ' + . 'NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE chill_main_postal_code ADD CONSTRAINT ' + . 'FK_6CA145FAF92F3E70 ' + . 'FOREIGN KEY (country_id) ' + . 'REFERENCES Country (id) ' + . 'NOT DEFERRABLE INITIALLY IMMEDIATE'); + + } + + /** + * @param Schema $schema + */ + public function down(Schema $schema) + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() != 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_main_address ' + . 'DROP CONSTRAINT FK_165051F6EECBFDF1'); + $this->addSql('DROP SEQUENCE chill_main_address_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE chill_main_postal_code_id_seq CASCADE'); + $this->addSql('DROP TABLE chill_main_address'); + $this->addSql('DROP TABLE chill_main_postal_code'); + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20180703191509.php b/src/Bundle/ChillMain/Resources/migrations/Version20180703191509.php new file mode 100644 index 000000000..82117de9d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20180703191509.php @@ -0,0 +1,33 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + try { + $this->addSql('CREATE EXTENSION IF NOT EXISTS pg_trgm'); + $this->addSql('CREATE INDEX search_name_code ON chill_main_postal_code USING GIN (LOWER(code) gin_trgm_ops, LOWER(label) gin_trgm_ops)'); + } catch (\Exception $e) { + $this->skipIf(true, "Could not create extension pg_trgm"); + } + + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX search_name_code'); + + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20180709181423.php b/src/Bundle/ChillMain/Resources/migrations/Version20180709181423.php new file mode 100644 index 000000000..93ca04e12 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20180709181423.php @@ -0,0 +1,87 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE users ADD usernameCanonical VARCHAR(80) DEFAULT NULL'); + $this->addSql('UPDATE users SET usernameCanonical=LOWER(UNACCENT(username))'); + $this->addSql('ALTER TABLE users ALTER usernameCanonical DROP NOT NULL'); + $this->addSql('ALTER TABLE users ALTER usernameCanonical SET DEFAULT NULL'); + $this->addSql('ALTER TABLE users ADD email VARCHAR(150) DEFAULT NULL'); + $this->addSql('ALTER TABLE users ADD emailCanonical VARCHAR(150) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9F5A5DC32 ON users (usernameCanonical)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_1483A5E9885281E ON users (emailCanonical)'); + + $this->addSql(<<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_user_on_update() RETURNS TRIGGER AS + $BODY$ + BEGIN + IF NEW.username <> OLD.username OR NEW.email <> OLD.email OR OLD.emailcanonical IS NULL OR OLD.usernamecanonical IS NULL THEN + UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; + END IF; + + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL +SQL + ); + + $this->addSql(<<addSql(<<<'SQL' + CREATE OR REPLACE FUNCTION canonicalize_user_on_insert() RETURNS TRIGGER AS + $BODY$ + BEGIN + UPDATE users SET usernamecanonical=LOWER(UNACCENT(NEW.username)), emailcanonical=LOWER(UNACCENT(NEW.email)) WHERE id=NEW.id; + + RETURN NEW; + END; + $BODY$ LANGUAGE PLPGSQL; +SQL + ); + + $this->addSql(<<abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('DROP INDEX UNIQ_1483A5E9F5A5DC32'); + $this->addSql('DROP INDEX UNIQ_1483A5E9885281E'); + $this->addSql('ALTER TABLE users DROP usernameCanonical'); + $this->addSql('ALTER TABLE users DROP email'); + $this->addSql('ALTER TABLE users DROP emailCanonical'); + $this->addSql('DROP TRIGGER canonicalize_user_on_insert ON users'); + $this->addSql('DROP FUNCTION canonicalize_user_on_insert()'); + $this->addSql('DROP TRIGGER canonicalize_user_on_update ON users'); + $this->addSql('DROP FUNCTION canonicalize_user_on_update()'); + + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20180905101426.php b/src/Bundle/ChillMain/Resources/migrations/Version20180905101426.php new file mode 100644 index 000000000..2f7d1afc2 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20180905101426.php @@ -0,0 +1,25 @@ +addSql('ALTER TABLE permission_groups ADD flags JSONB DEFAULT \'[]\' NOT NULL'); + $this->addSql('ALTER TABLE group_centers ALTER permissionsgroup_id DROP NOT NULL'); + + } + + public function down(Schema $schema) : void + { + $this->addSql('ALTER TABLE permission_groups DROP COLUMN flags'); + $this->addSql('ALTER TABLE group_centers ALTER permissionsgroup_id SET DEFAULT NULL'); + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20180911093642.php b/src/Bundle/ChillMain/Resources/migrations/Version20180911093642.php new file mode 100644 index 000000000..09031278d --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20180911093642.php @@ -0,0 +1,43 @@ +addSql("DROP TRIGGER canonicalize_user_on_update ON users"); + + $this->addSql(<<addSql("DROP TRIGGER canonicalize_user_on_update ON users"); + + $this->addSql(<<addSql('ALTER TABLE chill_main_address ADD isNoAddress BOOLEAN NOT NULL DEFAULT FALSE'); + } + + public function down(Schema $schema) : void + { + $this->addSql('ALTER TABLE chill_main_address DROP isNoAddress'); + } +} diff --git a/src/Bundle/ChillMain/Resources/migrations/Version20210308111926.php b/src/Bundle/ChillMain/Resources/migrations/Version20210308111926.php new file mode 100644 index 000000000..368a3dec8 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/migrations/Version20210308111926.php @@ -0,0 +1,32 @@ +abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_main_address ADD customs JSONB DEFAULT \'[]\''); + + } + + public function down(Schema $schema) : void + { + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); + + $this->addSql('ALTER TABLE chill_main_address DROP customs'); + } + + public function getDescription(): string + { + return "Add custom data in addresses"; + } +} diff --git a/src/Bundle/ChillMain/Resources/package.json b/src/Bundle/ChillMain/Resources/package.json new file mode 100644 index 000000000..b25e525a6 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/package.json @@ -0,0 +1,17 @@ +{ + "name": "chill-js-css", + "version": "0.0.0", + "description": "JS-CSS files for Chill, a software for social workers", + "directories": {}, + "author": "Champs-Libres ", + "devDependencies": { + "grunt": "^1.0.3", + "grunt-bower-task": "^0.4.0", + "grunt-contrib-clean": "^0.6.0", + "grunt-contrib-copy": "^0.7.0", + "grunt-contrib-sass": "^0.8.1", + "grunt-contrib-watch": "^0.6.1", + "jit-grunt": "^0.9.0" + }, + "dependencies": {} +} diff --git a/src/Bundle/ChillMain/Resources/public/css/chillmain.css b/src/Bundle/ChillMain/Resources/public/css/chillmain.css new file mode 100644 index 000000000..85c247173 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/chillmain.css @@ -0,0 +1,25 @@ +div#usefulbar { background-color: #fbba3a; z-index: 1000; padding-right: 15px; } +div#usefulbar form { margin: 0; } +div#usefulbar i.menu { font-size: 2em; } +div#usefulbar ul { display: flex; justify-content: flex-end; margin: 0; padding-top: 5px; padding-right: 10px; } +div#usefulbar li { color: white; margin-left: 10px; } +div#usefulbar li a { color: white; text-shadow: 0px 0px 1px #555; } +div#usefulbar li i.icon-user-add:before { vertical-align: -5px; } +div#usefulbar li#search_element { text-align: right; } +div#usefulbar li#search_element div#search_form { margin: 0; padding: 0; } +div#usefulbar li#search_element div#search_form div { margin: 0; } +div#usefulbar li#search_element div#search_form .field { margin: 0; } +div#usefulbar li#search_element div#search_form button { color: white; border: none; bottom: -2px; height: 35px; } + +div#flashMessages { margin-top: 20px; } +div#flashMessages .flash-notice { margin-top: 10px; margin-bottom: 10px; } + +.personName { font-variant: small-caps; text-transform: capitalize; } + +.personName { text-transform: capitalize; } + +input.belgian_national_number_inversed_date { width: 7em; margin-right: 1em; } + +input.belgian_national_number_daily_counter { width: 4em; margin-right: 1em; } + +input.belgian_national_number_control_digit { width: 3em; } \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/css/pikaday.css b/src/Bundle/ChillMain/Resources/public/css/pikaday.css new file mode 100644 index 000000000..495802417 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/pikaday.css @@ -0,0 +1,173 @@ +@charset "UTF-8"; + +/*! + * Pikaday + * Copyright © 2014 David Bushell | BSD & MIT license | http://dbushell.com/ + */ + +.pika-single { + z-index: 9999; + display: block; + position: relative; + width: 240px; + padding: 8px; + color: #333; + background: #fff; + border: 1px solid #ccc; + border-bottom-color: #bbb; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.pika-single.is-hidden { + display: none; +} + +.pika-single.is-bound { + position: absolute; + box-shadow: 0 5px 15px -5px rgba(0,0,0,.5); +} + +.pika-title { + position: relative; + text-align: center; +} + +.pika-label { + display: inline-block; + *display: inline; + position: relative; + z-index: 9999; + overflow: hidden; + margin: 0; + padding: 5px 3px; + font-size: 14px; + line-height: 20px; + font-weight: bold; + background-color: #fff; +} +.pika-title select { + cursor: pointer; + position: absolute; + z-index: 9998; + margin: 0; + left: 0; + top: 5px; + filter: alpha(opacity=0); + opacity: 0; +} + +.pika-prev, +.pika-next { + display: block; + cursor: pointer; + position: relative; + outline: none; + border: 0; + padding: 0; + width: 20px; + height: 30px; + /* hide text using text-indent trick, using width value (it's enough) */ + text-indent: 20px; + white-space: nowrap; + overflow: hidden; + background-color: transparent; + background-position: center center; + background-repeat: no-repeat; + background-size: 75% 75%; + opacity: .5; + *position: absolute; + *top: 0; +} + +.pika-prev:hover, +.pika-next:hover { + opacity: 1; +} + +.pika-prev, +.is-rtl .pika-next { + float: left; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAUklEQVR42u3VMQoAIBADQf8Pgj+OD9hG2CtONJB2ymQkKe0HbwAP0xucDiQWARITIDEBEnMgMQ8S8+AqBIl6kKgHiXqQqAeJepBo/z38J/U0uAHlaBkBl9I4GwAAAABJRU5ErkJggg=='); + *left: 0; +} + +.pika-next, +.is-rtl .pika-prev { + float: right; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAeCAYAAAAsEj5rAAAAU0lEQVR42u3VOwoAMAgE0dwfAnNjU26bYkBCFGwfiL9VVWoO+BJ4Gf3gtsEKKoFBNTCoCAYVwaAiGNQGMUHMkjGbgjk2mIONuXo0nC8XnCf1JXgArVIZAQh5TKYAAAAASUVORK5CYII='); + *right: 0; +} + +.pika-prev.is-disabled, +.pika-next.is-disabled { + cursor: default; + opacity: .2; +} + +.pika-select { + display: inline-block; + *display: inline; +} + +.pika-table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + border: 0; +} + +.pika-table th, +.pika-table td { + width: 14.285714285714286%; + padding: 0; +} + +.pika-table th { + color: #999; + font-size: 12px; + line-height: 25px; + font-weight: bold; + text-align: center; +} + +.pika-button { + cursor: pointer; + display: block; + outline: none; + border: 0; + margin: 0; + width: 100%; + padding: 5px; + color: #666; + font-size: 12px; + line-height: 15px; + text-align: right; + background: #f5f5f5; +} + +.is-today .pika-button { + color: #33aaff; + font-weight: bold; +} + +.is-selected .pika-button { + color: #fff; + font-weight: bold; + background: #33aaff; + box-shadow: inset 0 1px 3px #178fe5; + border-radius: 3px; +} + +.is-disabled .pika-button { + pointer-events: none; + cursor: default; + color: #999; + opacity: .3; +} + +.pika-button:hover { + color: #fff !important; + background: #ff8000 !important; + box-shadow: none !important; + border-radius: 3px !important; +} diff --git a/src/Bundle/ChillMain/Resources/public/css/scratch.css b/src/Bundle/ChillMain/Resources/public/css/scratch.css new file mode 100644 index 000000000..31d1f0ab1 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/scratch.css @@ -0,0 +1,4688 @@ +@charset "UTF-8"; +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ +html { + font-family: sans-serif; + /* 1 */ + -ms-text-size-adjust: 100%; + /* 2 */ + -webkit-text-size-adjust: 100%; + /* 2 */ } + +/** + * Remove default margin. + */ +body { + margin: 0; } + +/* HTML5 display definitions + ========================================================================== */ +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; } + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ +audio, +canvas, +progress, +video { + display: inline-block; + /* 1 */ + vertical-align: baseline; + /* 2 */ } + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ +audio:not([controls]) { + display: none; + height: 0; } + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ +[hidden], +template { + display: none; } + +/* Links + ========================================================================== */ +/** + * Remove the gray background color from active links in IE 10. + */ +a { + background-color: transparent; } + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ +a:active, +a:hover { + outline: 0; } + +/* Text-level semantics + ========================================================================== */ +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ +abbr[title] { + border-bottom: 1px dotted; } + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ +b, +strong { + font-weight: bold; } + +/** + * Address styling not present in Safari and Chrome. + */ +dfn { + font-style: italic; } + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ +h1 { + font-size: 2em; + margin: 0.67em 0; } + +/** + * Address styling not present in IE 8/9. + */ +mark { + background: #ff0; + color: #000; } + +/** + * Address inconsistent and variable font size in all browsers. + */ +small { + font-size: 80%; } + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +/* Embedded content + ========================================================================== */ +/** + * Remove border when inside `a` element in IE 8/9/10. + */ +img { + border: 0; } + +/** + * Correct overflow not hidden in IE 9/10/11. + */ +svg:not(:root) { + overflow: hidden; } + +/* Grouping content + ========================================================================== */ +/** + * Address margin not present in IE 8/9 and Safari. + */ +figure { + margin: 1em 40px; } + +/** + * Address differences between Firefox and other browsers. + */ +hr { + box-sizing: content-box; + height: 0; } + +/** + * Contain overflow in all browsers. + */ +pre { + overflow: auto; } + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; } + +/* Forms + ========================================================================== */ +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ +button, +input, +optgroup, +select, +textarea { + color: inherit; + /* 1 */ + font: inherit; + /* 2 */ + margin: 0; + /* 3 */ } + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ +button { + overflow: visible; } + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ +button, +select { + text-transform: none; } + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + /* 2 */ + cursor: pointer; + /* 3 */ } + +/** + * Re-set default cursor for disabled elements. + */ +button[disabled], +html input[disabled] { + cursor: default; } + +/** + * Remove inner padding and border in Firefox 4+. + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; } + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ +input { + line-height: normal; } + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; } + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ +input[type="search"] { + -webkit-appearance: textfield; + /* 1 */ + box-sizing: content-box; + /* 2 */ } + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +/** + * Define consistent border, margin, and padding. + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ +legend { + border: 0; + /* 1 */ + padding: 0; + /* 2 */ } + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ +textarea { + overflow: auto; } + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ +optgroup { + font-weight: bold; } + +/* Tables + ========================================================================== */ +/** + * Remove most spacing between table cells. + */ +table { + border-collapse: collapse; + border-spacing: 0; } + +td, +th { + padding: 0; } + +/*! + * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'FontAwesome'; + src: url("../fonts/fontawesome-webfont.eot?v=4.5.0"); + src: url("../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0") format("embedded-opentype"), url("../fonts/fontawesome-webfont.woff2?v=4.5.0") format("woff2"), url("../fonts/fontawesome-webfont.woff?v=4.5.0") format("woff"), url("../fonts/fontawesome-webfont.ttf?v=4.5.0") format("truetype"), url("../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular") format("svg"); + font-weight: normal; + font-style: normal; } +.fa { + 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; } + +/* makes the font 33% larger relative to the icon container */ +.fa-lg { + font-size: 1.33333em; + line-height: 0.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.28571em; + text-align: center; } + +.fa-ul { + padding-left: 0; + margin-left: 2.14286em; + list-style-type: none; } + .fa-ul > li { + position: relative; } + +.fa-li { + position: absolute; + left: -2.14286em; + width: 2.14286em; + top: 0.14286em; + text-align: center; } + .fa-li.fa-lg { + left: -1.85714em; } + +.fa-border { + padding: .2em .25em .15em; + border: solid 0.08em #eee; + border-radius: .1em; } + +.fa-pull-left { + float: left; } + +.fa-pull-right { + float: right; } + +.fa.fa-pull-left { + margin-right: .3em; } +.fa.fa-pull-right { + margin-left: .3em; } + +/* Deprecated as of 4.4.0 */ +.pull-right { + float: right; } + +.pull-left { + float: left; } + +.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(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); + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); } + +.fa-flip-vertical { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); } + +:root .fa-rotate-90, +:root .fa-rotate-180, +:root .fa-rotate-270, +:root .fa-flip-horizontal, +:root .fa-flip-vertical { + 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; } + +/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.fa-glass:before { + content: ""; } + +.fa-music:before { + content: ""; } + +.fa-search:before { + content: ""; } + +.fa-envelope-o:before { + content: ""; } + +.fa-heart:before { + content: ""; } + +.fa-star:before { + content: ""; } + +.fa-star-o:before { + content: ""; } + +.fa-user:before { + content: ""; } + +.fa-film:before { + content: ""; } + +.fa-th-large:before { + content: ""; } + +.fa-th:before { + content: ""; } + +.fa-th-list:before { + content: ""; } + +.fa-check:before { + content: ""; } + +.fa-remove:before, +.fa-close:before, +.fa-times:before { + content: ""; } + +.fa-search-plus:before { + content: ""; } + +.fa-search-minus:before { + content: ""; } + +.fa-power-off:before { + content: ""; } + +.fa-signal:before { + content: ""; } + +.fa-gear:before, +.fa-cog:before { + content: ""; } + +.fa-trash-o:before { + content: ""; } + +.fa-home:before { + content: ""; } + +.fa-file-o:before { + content: ""; } + +.fa-clock-o:before { + content: ""; } + +.fa-road:before { + content: ""; } + +.fa-download:before { + content: ""; } + +.fa-arrow-circle-o-down:before { + content: ""; } + +.fa-arrow-circle-o-up:before { + content: ""; } + +.fa-inbox:before { + content: ""; } + +.fa-play-circle-o:before { + content: ""; } + +.fa-rotate-right:before, +.fa-repeat:before { + content: ""; } + +.fa-refresh:before { + content: ""; } + +.fa-list-alt:before { + content: ""; } + +.fa-lock:before { + content: ""; } + +.fa-flag:before { + content: ""; } + +.fa-headphones:before { + content: ""; } + +.fa-volume-off:before { + content: ""; } + +.fa-volume-down:before { + content: ""; } + +.fa-volume-up:before { + content: ""; } + +.fa-qrcode:before { + content: ""; } + +.fa-barcode:before { + content: ""; } + +.fa-tag:before { + content: ""; } + +.fa-tags:before { + content: ""; } + +.fa-book:before { + content: ""; } + +.fa-bookmark:before { + content: ""; } + +.fa-print:before { + content: ""; } + +.fa-camera:before { + content: ""; } + +.fa-font:before { + content: ""; } + +.fa-bold:before { + content: ""; } + +.fa-italic:before { + content: ""; } + +.fa-text-height:before { + content: ""; } + +.fa-text-width:before { + content: ""; } + +.fa-align-left:before { + content: ""; } + +.fa-align-center:before { + content: ""; } + +.fa-align-right:before { + content: ""; } + +.fa-align-justify:before { + content: ""; } + +.fa-list:before { + content: ""; } + +.fa-dedent:before, +.fa-outdent:before { + content: ""; } + +.fa-indent:before { + content: ""; } + +.fa-video-camera:before { + content: ""; } + +.fa-photo:before, +.fa-image:before, +.fa-picture-o:before { + content: ""; } + +.fa-pencil:before { + content: ""; } + +.fa-map-marker:before { + content: ""; } + +.fa-adjust:before { + content: ""; } + +.fa-tint:before { + content: ""; } + +.fa-edit:before, +.fa-pencil-square-o:before { + content: ""; } + +.fa-share-square-o:before { + content: ""; } + +.fa-check-square-o:before { + content: ""; } + +.fa-arrows:before { + content: ""; } + +.fa-step-backward:before { + content: ""; } + +.fa-fast-backward:before { + content: ""; } + +.fa-backward:before { + content: ""; } + +.fa-play:before { + content: ""; } + +.fa-pause:before { + content: ""; } + +.fa-stop:before { + content: ""; } + +.fa-forward:before { + content: ""; } + +.fa-fast-forward:before { + content: ""; } + +.fa-step-forward:before { + content: ""; } + +.fa-eject:before { + content: ""; } + +.fa-chevron-left:before { + content: ""; } + +.fa-chevron-right:before { + content: ""; } + +.fa-plus-circle:before { + content: ""; } + +.fa-minus-circle:before { + content: ""; } + +.fa-times-circle:before { + content: ""; } + +.fa-check-circle:before { + content: ""; } + +.fa-question-circle:before { + content: ""; } + +.fa-info-circle:before { + content: ""; } + +.fa-crosshairs:before { + content: ""; } + +.fa-times-circle-o:before { + content: ""; } + +.fa-check-circle-o:before { + content: ""; } + +.fa-ban:before { + content: ""; } + +.fa-arrow-left:before { + content: ""; } + +.fa-arrow-right:before { + content: ""; } + +.fa-arrow-up:before { + content: ""; } + +.fa-arrow-down:before { + content: ""; } + +.fa-mail-forward:before, +.fa-share:before { + content: ""; } + +.fa-expand:before { + content: ""; } + +.fa-compress:before { + content: ""; } + +.fa-plus:before { + content: ""; } + +.fa-minus:before { + content: ""; } + +.fa-asterisk:before { + content: ""; } + +.fa-exclamation-circle:before { + content: ""; } + +.fa-gift:before { + content: ""; } + +.fa-leaf:before { + content: ""; } + +.fa-fire:before { + content: ""; } + +.fa-eye:before { + content: ""; } + +.fa-eye-slash:before { + content: ""; } + +.fa-warning:before, +.fa-exclamation-triangle:before { + content: ""; } + +.fa-plane:before { + content: ""; } + +.fa-calendar:before { + content: ""; } + +.fa-random:before { + content: ""; } + +.fa-comment:before { + content: ""; } + +.fa-magnet:before { + content: ""; } + +.fa-chevron-up:before { + content: ""; } + +.fa-chevron-down:before { + content: ""; } + +.fa-retweet:before { + content: ""; } + +.fa-shopping-cart:before { + content: ""; } + +.fa-folder:before { + content: ""; } + +.fa-folder-open:before { + content: ""; } + +.fa-arrows-v:before { + content: ""; } + +.fa-arrows-h:before { + content: ""; } + +.fa-bar-chart-o:before, +.fa-bar-chart:before { + content: ""; } + +.fa-twitter-square:before { + content: ""; } + +.fa-facebook-square:before { + content: ""; } + +.fa-camera-retro:before { + content: ""; } + +.fa-key:before { + content: ""; } + +.fa-gears:before, +.fa-cogs:before { + content: ""; } + +.fa-comments:before { + content: ""; } + +.fa-thumbs-o-up:before { + content: ""; } + +.fa-thumbs-o-down:before { + content: ""; } + +.fa-star-half:before { + content: ""; } + +.fa-heart-o:before { + content: ""; } + +.fa-sign-out:before { + content: ""; } + +.fa-linkedin-square:before { + content: ""; } + +.fa-thumb-tack:before { + content: ""; } + +.fa-external-link:before { + content: ""; } + +.fa-sign-in:before { + content: ""; } + +.fa-trophy:before { + content: ""; } + +.fa-github-square:before { + content: ""; } + +.fa-upload:before { + content: ""; } + +.fa-lemon-o:before { + content: ""; } + +.fa-phone:before { + content: ""; } + +.fa-square-o:before { + content: ""; } + +.fa-bookmark-o:before { + content: ""; } + +.fa-phone-square:before { + content: ""; } + +.fa-twitter:before { + content: ""; } + +.fa-facebook-f:before, +.fa-facebook:before { + content: ""; } + +.fa-github:before { + content: ""; } + +.fa-unlock:before { + content: ""; } + +.fa-credit-card:before { + content: ""; } + +.fa-feed:before, +.fa-rss:before { + content: ""; } + +.fa-hdd-o:before { + content: ""; } + +.fa-bullhorn:before { + content: ""; } + +.fa-bell:before { + content: ""; } + +.fa-certificate:before { + content: ""; } + +.fa-hand-o-right:before { + content: ""; } + +.fa-hand-o-left:before { + content: ""; } + +.fa-hand-o-up:before { + content: ""; } + +.fa-hand-o-down:before { + content: ""; } + +.fa-arrow-circle-left:before { + content: ""; } + +.fa-arrow-circle-right:before { + content: ""; } + +.fa-arrow-circle-up:before { + content: ""; } + +.fa-arrow-circle-down:before { + content: ""; } + +.fa-globe:before { + content: ""; } + +.fa-wrench:before { + content: ""; } + +.fa-tasks:before { + content: ""; } + +.fa-filter:before { + content: ""; } + +.fa-briefcase:before { + content: ""; } + +.fa-arrows-alt:before { + content: ""; } + +.fa-group:before, +.fa-users:before { + content: ""; } + +.fa-chain:before, +.fa-link:before { + content: ""; } + +.fa-cloud:before { + content: ""; } + +.fa-flask:before { + content: ""; } + +.fa-cut:before, +.fa-scissors:before { + content: ""; } + +.fa-copy:before, +.fa-files-o:before { + content: ""; } + +.fa-paperclip:before { + content: ""; } + +.fa-save:before, +.fa-floppy-o:before { + content: ""; } + +.fa-square:before { + content: ""; } + +.fa-navicon:before, +.fa-reorder:before, +.fa-bars:before { + content: ""; } + +.fa-list-ul:before { + content: ""; } + +.fa-list-ol:before { + content: ""; } + +.fa-strikethrough:before { + content: ""; } + +.fa-underline:before { + content: ""; } + +.fa-table:before { + content: ""; } + +.fa-magic:before { + content: ""; } + +.fa-truck:before { + content: ""; } + +.fa-pinterest:before { + content: ""; } + +.fa-pinterest-square:before { + content: ""; } + +.fa-google-plus-square:before { + content: ""; } + +.fa-google-plus:before { + content: ""; } + +.fa-money:before { + content: ""; } + +.fa-caret-down:before { + content: ""; } + +.fa-caret-up:before { + content: ""; } + +.fa-caret-left:before { + content: ""; } + +.fa-caret-right:before { + content: ""; } + +.fa-columns:before { + content: ""; } + +.fa-unsorted:before, +.fa-sort:before { + content: ""; } + +.fa-sort-down:before, +.fa-sort-desc:before { + content: ""; } + +.fa-sort-up:before, +.fa-sort-asc:before { + content: ""; } + +.fa-envelope:before { + content: ""; } + +.fa-linkedin:before { + content: ""; } + +.fa-rotate-left:before, +.fa-undo:before { + content: ""; } + +.fa-legal:before, +.fa-gavel:before { + content: ""; } + +.fa-dashboard:before, +.fa-tachometer:before { + content: ""; } + +.fa-comment-o:before { + content: ""; } + +.fa-comments-o:before { + content: ""; } + +.fa-flash:before, +.fa-bolt:before { + content: ""; } + +.fa-sitemap:before { + content: ""; } + +.fa-umbrella:before { + content: ""; } + +.fa-paste:before, +.fa-clipboard:before { + content: ""; } + +.fa-lightbulb-o:before { + content: ""; } + +.fa-exchange:before { + content: ""; } + +.fa-cloud-download:before { + content: ""; } + +.fa-cloud-upload:before { + content: ""; } + +.fa-user-md:before { + content: ""; } + +.fa-stethoscope:before { + content: ""; } + +.fa-suitcase:before { + content: ""; } + +.fa-bell-o:before { + content: ""; } + +.fa-coffee:before { + content: ""; } + +.fa-cutlery:before { + content: ""; } + +.fa-file-text-o:before { + content: ""; } + +.fa-building-o:before { + content: ""; } + +.fa-hospital-o:before { + content: ""; } + +.fa-ambulance:before { + content: ""; } + +.fa-medkit:before { + content: ""; } + +.fa-fighter-jet:before { + content: ""; } + +.fa-beer:before { + content: ""; } + +.fa-h-square:before { + content: ""; } + +.fa-plus-square:before { + content: ""; } + +.fa-angle-double-left:before { + content: ""; } + +.fa-angle-double-right:before { + content: ""; } + +.fa-angle-double-up:before { + content: ""; } + +.fa-angle-double-down:before { + content: ""; } + +.fa-angle-left:before { + content: ""; } + +.fa-angle-right:before { + content: ""; } + +.fa-angle-up:before { + content: ""; } + +.fa-angle-down:before { + content: ""; } + +.fa-desktop:before { + content: ""; } + +.fa-laptop:before { + content: ""; } + +.fa-tablet:before { + content: ""; } + +.fa-mobile-phone:before, +.fa-mobile:before { + content: ""; } + +.fa-circle-o:before { + content: ""; } + +.fa-quote-left:before { + content: ""; } + +.fa-quote-right:before { + content: ""; } + +.fa-spinner:before { + content: ""; } + +.fa-circle:before { + content: ""; } + +.fa-mail-reply:before, +.fa-reply:before { + content: ""; } + +.fa-github-alt:before { + content: ""; } + +.fa-folder-o:before { + content: ""; } + +.fa-folder-open-o:before { + content: ""; } + +.fa-smile-o:before { + content: ""; } + +.fa-frown-o:before { + content: ""; } + +.fa-meh-o:before { + content: ""; } + +.fa-gamepad:before { + content: ""; } + +.fa-keyboard-o:before { + content: ""; } + +.fa-flag-o:before { + content: ""; } + +.fa-flag-checkered:before { + content: ""; } + +.fa-terminal:before { + content: ""; } + +.fa-code:before { + content: ""; } + +.fa-mail-reply-all:before, +.fa-reply-all:before { + content: ""; } + +.fa-star-half-empty:before, +.fa-star-half-full:before, +.fa-star-half-o:before { + content: ""; } + +.fa-location-arrow:before { + content: ""; } + +.fa-crop:before { + content: ""; } + +.fa-code-fork:before { + content: ""; } + +.fa-unlink:before, +.fa-chain-broken:before { + content: ""; } + +.fa-question:before { + content: ""; } + +.fa-info:before { + content: ""; } + +.fa-exclamation:before { + content: ""; } + +.fa-superscript:before { + content: ""; } + +.fa-subscript:before { + content: ""; } + +.fa-eraser:before { + content: ""; } + +.fa-puzzle-piece:before { + content: ""; } + +.fa-microphone:before { + content: ""; } + +.fa-microphone-slash:before { + content: ""; } + +.fa-shield:before { + content: ""; } + +.fa-calendar-o:before { + content: ""; } + +.fa-fire-extinguisher:before { + content: ""; } + +.fa-rocket:before { + content: ""; } + +.fa-maxcdn:before { + content: ""; } + +.fa-chevron-circle-left:before { + content: ""; } + +.fa-chevron-circle-right:before { + content: ""; } + +.fa-chevron-circle-up:before { + content: ""; } + +.fa-chevron-circle-down:before { + content: ""; } + +.fa-html5:before { + content: ""; } + +.fa-css3:before { + content: ""; } + +.fa-anchor:before { + content: ""; } + +.fa-unlock-alt:before { + content: ""; } + +.fa-bullseye:before { + content: ""; } + +.fa-ellipsis-h:before { + content: ""; } + +.fa-ellipsis-v:before { + content: ""; } + +.fa-rss-square:before { + content: ""; } + +.fa-play-circle:before { + content: ""; } + +.fa-ticket:before { + content: ""; } + +.fa-minus-square:before { + content: ""; } + +.fa-minus-square-o:before { + content: ""; } + +.fa-level-up:before { + content: ""; } + +.fa-level-down:before { + content: ""; } + +.fa-check-square:before { + content: ""; } + +.fa-pencil-square:before { + content: ""; } + +.fa-external-link-square:before { + content: ""; } + +.fa-share-square:before { + content: ""; } + +.fa-compass:before { + content: ""; } + +.fa-toggle-down:before, +.fa-caret-square-o-down:before { + content: ""; } + +.fa-toggle-up:before, +.fa-caret-square-o-up:before { + content: ""; } + +.fa-toggle-right:before, +.fa-caret-square-o-right:before { + content: ""; } + +.fa-euro:before, +.fa-eur:before { + content: ""; } + +.fa-gbp:before { + content: ""; } + +.fa-dollar:before, +.fa-usd:before { + content: ""; } + +.fa-rupee:before, +.fa-inr:before { + content: ""; } + +.fa-cny:before, +.fa-rmb:before, +.fa-yen:before, +.fa-jpy:before { + content: ""; } + +.fa-ruble:before, +.fa-rouble:before, +.fa-rub:before { + content: ""; } + +.fa-won:before, +.fa-krw:before { + content: ""; } + +.fa-bitcoin:before, +.fa-btc:before { + content: ""; } + +.fa-file:before { + content: ""; } + +.fa-file-text:before { + content: ""; } + +.fa-sort-alpha-asc:before { + content: ""; } + +.fa-sort-alpha-desc:before { + content: ""; } + +.fa-sort-amount-asc:before { + content: ""; } + +.fa-sort-amount-desc:before { + content: ""; } + +.fa-sort-numeric-asc:before { + content: ""; } + +.fa-sort-numeric-desc:before { + content: ""; } + +.fa-thumbs-up:before { + content: ""; } + +.fa-thumbs-down:before { + content: ""; } + +.fa-youtube-square:before { + content: ""; } + +.fa-youtube:before { + content: ""; } + +.fa-xing:before { + content: ""; } + +.fa-xing-square:before { + content: ""; } + +.fa-youtube-play:before { + content: ""; } + +.fa-dropbox:before { + content: ""; } + +.fa-stack-overflow:before { + content: ""; } + +.fa-instagram:before { + content: ""; } + +.fa-flickr:before { + content: ""; } + +.fa-adn:before { + content: ""; } + +.fa-bitbucket:before { + content: ""; } + +.fa-bitbucket-square:before { + content: ""; } + +.fa-tumblr:before { + content: ""; } + +.fa-tumblr-square:before { + content: ""; } + +.fa-long-arrow-down:before { + content: ""; } + +.fa-long-arrow-up:before { + content: ""; } + +.fa-long-arrow-left:before { + content: ""; } + +.fa-long-arrow-right:before { + content: ""; } + +.fa-apple:before { + content: ""; } + +.fa-windows:before { + content: ""; } + +.fa-android:before { + content: ""; } + +.fa-linux:before { + content: ""; } + +.fa-dribbble:before { + content: ""; } + +.fa-skype:before { + content: ""; } + +.fa-foursquare:before { + content: ""; } + +.fa-trello:before { + content: ""; } + +.fa-female:before { + content: ""; } + +.fa-male:before { + content: ""; } + +.fa-gittip:before, +.fa-gratipay:before { + content: ""; } + +.fa-sun-o:before { + content: ""; } + +.fa-moon-o:before { + content: ""; } + +.fa-archive:before { + content: ""; } + +.fa-bug:before { + content: ""; } + +.fa-vk:before { + content: ""; } + +.fa-weibo:before { + content: ""; } + +.fa-renren:before { + content: ""; } + +.fa-pagelines:before { + content: ""; } + +.fa-stack-exchange:before { + content: ""; } + +.fa-arrow-circle-o-right:before { + content: ""; } + +.fa-arrow-circle-o-left:before { + content: ""; } + +.fa-toggle-left:before, +.fa-caret-square-o-left:before { + content: ""; } + +.fa-dot-circle-o:before { + content: ""; } + +.fa-wheelchair:before { + content: ""; } + +.fa-vimeo-square:before { + content: ""; } + +.fa-turkish-lira:before, +.fa-try:before { + content: ""; } + +.fa-plus-square-o:before { + content: ""; } + +.fa-space-shuttle:before { + content: ""; } + +.fa-slack:before { + content: ""; } + +.fa-envelope-square:before { + content: ""; } + +.fa-wordpress:before { + content: ""; } + +.fa-openid:before { + content: ""; } + +.fa-institution:before, +.fa-bank:before, +.fa-university:before { + content: ""; } + +.fa-mortar-board:before, +.fa-graduation-cap:before { + content: ""; } + +.fa-yahoo:before { + content: ""; } + +.fa-google:before { + content: ""; } + +.fa-reddit:before { + content: ""; } + +.fa-reddit-square:before { + content: ""; } + +.fa-stumbleupon-circle:before { + content: ""; } + +.fa-stumbleupon:before { + content: ""; } + +.fa-delicious:before { + content: ""; } + +.fa-digg:before { + content: ""; } + +.fa-pied-piper:before { + content: ""; } + +.fa-pied-piper-alt:before { + content: ""; } + +.fa-drupal:before { + content: ""; } + +.fa-joomla:before { + content: ""; } + +.fa-language:before { + content: ""; } + +.fa-fax:before { + content: ""; } + +.fa-building:before { + content: ""; } + +.fa-child:before { + content: ""; } + +.fa-paw:before { + content: ""; } + +.fa-spoon:before { + content: ""; } + +.fa-cube:before { + content: ""; } + +.fa-cubes:before { + content: ""; } + +.fa-behance:before { + content: ""; } + +.fa-behance-square:before { + content: ""; } + +.fa-steam:before { + content: ""; } + +.fa-steam-square:before { + content: ""; } + +.fa-recycle:before { + content: ""; } + +.fa-automobile:before, +.fa-car:before { + content: ""; } + +.fa-cab:before, +.fa-taxi:before { + content: ""; } + +.fa-tree:before { + content: ""; } + +.fa-spotify:before { + content: ""; } + +.fa-deviantart:before { + content: ""; } + +.fa-soundcloud:before { + content: ""; } + +.fa-database:before { + content: ""; } + +.fa-file-pdf-o:before { + content: ""; } + +.fa-file-word-o:before { + content: ""; } + +.fa-file-excel-o:before { + content: ""; } + +.fa-file-powerpoint-o:before { + content: ""; } + +.fa-file-photo-o:before, +.fa-file-picture-o:before, +.fa-file-image-o:before { + content: ""; } + +.fa-file-zip-o:before, +.fa-file-archive-o:before { + content: ""; } + +.fa-file-sound-o:before, +.fa-file-audio-o:before { + content: ""; } + +.fa-file-movie-o:before, +.fa-file-video-o:before { + content: ""; } + +.fa-file-code-o:before { + content: ""; } + +.fa-vine:before { + content: ""; } + +.fa-codepen:before { + content: ""; } + +.fa-jsfiddle:before { + content: ""; } + +.fa-life-bouy:before, +.fa-life-buoy:before, +.fa-life-saver:before, +.fa-support:before, +.fa-life-ring:before { + content: ""; } + +.fa-circle-o-notch:before { + content: ""; } + +.fa-ra:before, +.fa-rebel:before { + content: ""; } + +.fa-ge:before, +.fa-empire:before { + content: ""; } + +.fa-git-square:before { + content: ""; } + +.fa-git:before { + content: ""; } + +.fa-y-combinator-square:before, +.fa-yc-square:before, +.fa-hacker-news:before { + content: ""; } + +.fa-tencent-weibo:before { + content: ""; } + +.fa-qq:before { + content: ""; } + +.fa-wechat:before, +.fa-weixin:before { + content: ""; } + +.fa-send:before, +.fa-paper-plane:before { + content: ""; } + +.fa-send-o:before, +.fa-paper-plane-o:before { + content: ""; } + +.fa-history:before { + content: ""; } + +.fa-circle-thin:before { + content: ""; } + +.fa-header:before { + content: ""; } + +.fa-paragraph:before { + content: ""; } + +.fa-sliders:before { + content: ""; } + +.fa-share-alt:before { + content: ""; } + +.fa-share-alt-square:before { + content: ""; } + +.fa-bomb:before { + content: ""; } + +.fa-soccer-ball-o:before, +.fa-futbol-o:before { + content: ""; } + +.fa-tty:before { + content: ""; } + +.fa-binoculars:before { + content: ""; } + +.fa-plug:before { + content: ""; } + +.fa-slideshare:before { + content: ""; } + +.fa-twitch:before { + content: ""; } + +.fa-yelp:before { + content: ""; } + +.fa-newspaper-o:before { + content: ""; } + +.fa-wifi:before { + content: ""; } + +.fa-calculator:before { + content: ""; } + +.fa-paypal:before { + content: ""; } + +.fa-google-wallet:before { + content: ""; } + +.fa-cc-visa:before { + content: ""; } + +.fa-cc-mastercard:before { + content: ""; } + +.fa-cc-discover:before { + content: ""; } + +.fa-cc-amex:before { + content: ""; } + +.fa-cc-paypal:before { + content: ""; } + +.fa-cc-stripe:before { + content: ""; } + +.fa-bell-slash:before { + content: ""; } + +.fa-bell-slash-o:before { + content: ""; } + +.fa-trash:before { + content: ""; } + +.fa-copyright:before { + content: ""; } + +.fa-at:before { + content: ""; } + +.fa-eyedropper:before { + content: ""; } + +.fa-paint-brush:before { + content: ""; } + +.fa-birthday-cake:before { + content: ""; } + +.fa-area-chart:before { + content: ""; } + +.fa-pie-chart:before { + content: ""; } + +.fa-line-chart:before { + content: ""; } + +.fa-lastfm:before { + content: ""; } + +.fa-lastfm-square:before { + content: ""; } + +.fa-toggle-off:before { + content: ""; } + +.fa-toggle-on:before { + content: ""; } + +.fa-bicycle:before { + content: ""; } + +.fa-bus:before { + content: ""; } + +.fa-ioxhost:before { + content: ""; } + +.fa-angellist:before { + content: ""; } + +.fa-cc:before { + content: ""; } + +.fa-shekel:before, +.fa-sheqel:before, +.fa-ils:before { + content: ""; } + +.fa-meanpath:before { + content: ""; } + +.fa-buysellads:before { + content: ""; } + +.fa-connectdevelop:before { + content: ""; } + +.fa-dashcube:before { + content: ""; } + +.fa-forumbee:before { + content: ""; } + +.fa-leanpub:before { + content: ""; } + +.fa-sellsy:before { + content: ""; } + +.fa-shirtsinbulk:before { + content: ""; } + +.fa-simplybuilt:before { + content: ""; } + +.fa-skyatlas:before { + content: ""; } + +.fa-cart-plus:before { + content: ""; } + +.fa-cart-arrow-down:before { + content: ""; } + +.fa-diamond:before { + content: ""; } + +.fa-ship:before { + content: ""; } + +.fa-user-secret:before { + content: ""; } + +.fa-motorcycle:before { + content: ""; } + +.fa-street-view:before { + content: ""; } + +.fa-heartbeat:before { + content: ""; } + +.fa-venus:before { + content: ""; } + +.fa-mars:before { + content: ""; } + +.fa-mercury:before { + content: ""; } + +.fa-intersex:before, +.fa-transgender:before { + content: ""; } + +.fa-transgender-alt:before { + content: ""; } + +.fa-venus-double:before { + content: ""; } + +.fa-mars-double:before { + content: ""; } + +.fa-venus-mars:before { + content: ""; } + +.fa-mars-stroke:before { + content: ""; } + +.fa-mars-stroke-v:before { + content: ""; } + +.fa-mars-stroke-h:before { + content: ""; } + +.fa-neuter:before { + content: ""; } + +.fa-genderless:before { + content: ""; } + +.fa-facebook-official:before { + content: ""; } + +.fa-pinterest-p:before { + content: ""; } + +.fa-whatsapp:before { + content: ""; } + +.fa-server:before { + content: ""; } + +.fa-user-plus:before { + content: ""; } + +.fa-user-times:before { + content: ""; } + +.fa-hotel:before, +.fa-bed:before { + content: ""; } + +.fa-viacoin:before { + content: ""; } + +.fa-train:before { + content: ""; } + +.fa-subway:before { + content: ""; } + +.fa-medium:before { + content: ""; } + +.fa-yc:before, +.fa-y-combinator:before { + content: ""; } + +.fa-optin-monster:before { + content: ""; } + +.fa-opencart:before { + content: ""; } + +.fa-expeditedssl:before { + content: ""; } + +.fa-battery-4:before, +.fa-battery-full:before { + content: ""; } + +.fa-battery-3:before, +.fa-battery-three-quarters:before { + content: ""; } + +.fa-battery-2:before, +.fa-battery-half:before { + content: ""; } + +.fa-battery-1:before, +.fa-battery-quarter:before { + content: ""; } + +.fa-battery-0:before, +.fa-battery-empty:before { + content: ""; } + +.fa-mouse-pointer:before { + content: ""; } + +.fa-i-cursor:before { + content: ""; } + +.fa-object-group:before { + content: ""; } + +.fa-object-ungroup:before { + content: ""; } + +.fa-sticky-note:before { + content: ""; } + +.fa-sticky-note-o:before { + content: ""; } + +.fa-cc-jcb:before { + content: ""; } + +.fa-cc-diners-club:before { + content: ""; } + +.fa-clone:before { + content: ""; } + +.fa-balance-scale:before { + content: ""; } + +.fa-hourglass-o:before { + content: ""; } + +.fa-hourglass-1:before, +.fa-hourglass-start:before { + content: ""; } + +.fa-hourglass-2:before, +.fa-hourglass-half:before { + content: ""; } + +.fa-hourglass-3:before, +.fa-hourglass-end:before { + content: ""; } + +.fa-hourglass:before { + content: ""; } + +.fa-hand-grab-o:before, +.fa-hand-rock-o:before { + content: ""; } + +.fa-hand-stop-o:before, +.fa-hand-paper-o:before { + content: ""; } + +.fa-hand-scissors-o:before { + content: ""; } + +.fa-hand-lizard-o:before { + content: ""; } + +.fa-hand-spock-o:before { + content: ""; } + +.fa-hand-pointer-o:before { + content: ""; } + +.fa-hand-peace-o:before { + content: ""; } + +.fa-trademark:before { + content: ""; } + +.fa-registered:before { + content: ""; } + +.fa-creative-commons:before { + content: ""; } + +.fa-gg:before { + content: ""; } + +.fa-gg-circle:before { + content: ""; } + +.fa-tripadvisor:before { + content: ""; } + +.fa-odnoklassniki:before { + content: ""; } + +.fa-odnoklassniki-square:before { + content: ""; } + +.fa-get-pocket:before { + content: ""; } + +.fa-wikipedia-w:before { + content: ""; } + +.fa-safari:before { + content: ""; } + +.fa-chrome:before { + content: ""; } + +.fa-firefox:before { + content: ""; } + +.fa-opera:before { + content: ""; } + +.fa-internet-explorer:before { + content: ""; } + +.fa-tv:before, +.fa-television:before { + content: ""; } + +.fa-contao:before { + content: ""; } + +.fa-500px:before { + content: ""; } + +.fa-amazon:before { + content: ""; } + +.fa-calendar-plus-o:before { + content: ""; } + +.fa-calendar-minus-o:before { + content: ""; } + +.fa-calendar-times-o:before { + content: ""; } + +.fa-calendar-check-o:before { + content: ""; } + +.fa-industry:before { + content: ""; } + +.fa-map-pin:before { + content: ""; } + +.fa-map-signs:before { + content: ""; } + +.fa-map-o:before { + content: ""; } + +.fa-map:before { + content: ""; } + +.fa-commenting:before { + content: ""; } + +.fa-commenting-o:before { + content: ""; } + +.fa-houzz:before { + content: ""; } + +.fa-vimeo:before { + content: ""; } + +.fa-black-tie:before { + content: ""; } + +.fa-fonticons:before { + content: ""; } + +.fa-reddit-alien:before { + content: ""; } + +.fa-edge:before { + content: ""; } + +.fa-credit-card-alt:before { + content: ""; } + +.fa-codiepie:before { + content: ""; } + +.fa-modx:before { + content: ""; } + +.fa-fort-awesome:before { + content: ""; } + +.fa-usb:before { + content: ""; } + +.fa-product-hunt:before { + content: ""; } + +.fa-mixcloud:before { + content: ""; } + +.fa-scribd:before { + content: ""; } + +.fa-pause-circle:before { + content: ""; } + +.fa-pause-circle-o:before { + content: ""; } + +.fa-stop-circle:before { + content: ""; } + +.fa-stop-circle-o:before { + content: ""; } + +.fa-shopping-bag:before { + content: ""; } + +.fa-shopping-basket:before { + content: ""; } + +.fa-hashtag:before { + content: ""; } + +.fa-bluetooth:before { + content: ""; } + +.fa-bluetooth-b:before { + content: ""; } + +.fa-percent:before { + content: ""; } + +.chill-blue { + color: #334d5c; } + +.chill-green { + color: #43b29d; } + +.chill-green-dark { + color: #328474; } + +.chill-yellow { + color: #eec84a; } + +.chill-orange { + color: #e2793d; } + +.chill-red { + color: #df4949; } + +.chill-gray { + color: #ececec; } + +.chill-beige { + color: #cabb9f; } + +.chill-pink { + color: #dd506d; } + +.chill-dark-gray { + color: #333333; } + +.chill-light-gray { + color: #b2b2b2; } + +/* +// Flash Colors +$alert-color: $light-yellow; +$error-color: $light-red; +$notice-color: lighten($base-accent-color, 40); +$success-color: $light-green; +*/ +/* etrange pour les forms */ +* { + box-sizing: inherit; } + +html { + box-sizing: border-box; } + +.container:after { + content: ""; + display: table; + clear: both; + border-spacing: 0; } + +.parent:before, .parent-mobile:before, .parent-tablet:before, .parent:after, .parent-mobile:after, .parent-tablet:after { + content: ""; + display: table; + border-spacing: 0; } +.parent:after, .parent-mobile:after, .parent-tablet:after { + clear: both; } +.parent, .parent-mobile, .parent-tablet { + zoom: 1; } + +.push-0, .push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-mobile-0, .push-mobile-1, .push-mobile-2, .push-mobile-3, .push-mobile-4, .push-mobile-5, .push-mobile-6, .push-mobile-7, .push-mobile-8, .push-mobile-9, .push-mobile-10, .push-mobile-11, .push-mobile-12, .push-tablet-0, .push-tablet-1, .push-tablet-2, .push-tablet-3, .push-tablet-4, .push-tablet-5, .push-tablet-6, .push-tablet-7, .push-tablet-8, .push-tablet-9, .push-tablet-10, .push-tablet-11, .push-tablet-12, .pull-0, .pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-mobile-0, .pull-mobile-1, .pull-mobile-2, .pull-mobile-3, .pull-mobile-4, .pull-mobile-5, .pull-mobile-6, .pull-mobile-7, .pull-mobile-8, .pull-mobile-9, .pull-mobile-10, .pull-mobile-11, .pull-mobile-12, .pull-tablet-0, .pull-tablet-1, .pull-tablet-2, .pull-tablet-3, .pull-tablet-4, .pull-tablet-5, .pull-tablet-6, .pull-tablet-7, .pull-tablet-8, .pull-tablet-9, .pull-tablet-10, .pull-tablet-11, .pull-tablet-12 { + position: relative; } + +.container { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.parent, .parent-mobile, .parent-tablet { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +.grid-0, .grid-1, .grid-2, .grid-3, .grid-4, .grid-5, .grid-6, .grid-7, .grid-8, .grid-9, .grid-10, .grid-11, .grid-12, .grid-mobile-0, .grid-mobile-1, .grid-mobile-2, .grid-mobile-3, .grid-mobile-4, .grid-mobile-5, .grid-mobile-6, .grid-mobile-7, .grid-mobile-8, .grid-mobile-9, .grid-mobile-10, .grid-mobile-11, .grid-mobile-12, .grid-tablet-0, .grid-tablet-1, .grid-tablet-2, .grid-tablet-3, .grid-tablet-4, .grid-tablet-5, .grid-tablet-6, .grid-tablet-7, .grid-tablet-8, .grid-tablet-9, .grid-tablet-10, .grid-tablet-11, .grid-tablet-12, .footer .footer-links, .footer ul { + display: inline-block; + min-height: 1px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } + +/** + * Str replace + * + * @param {string} $string String that you want to replace + * @param {string} $substr String that is to be replaced by `$newsubstr` + * @param {string} $newsubstr String that replaces `$substr` + * @param {number*} $all Flag for replaceing all (1+) or not (0) + * @return {string} + */ +/** + * Map set + * + * @param Map $map The map to use + * @param String $key The key to update + * @param Mixed $value The new value + * @return Map The new map + */ +/** + * Get the column width in percent for the global or a specific context + * + * @param int $columns The number of columns to calculate + * @param int $context : $gridle-columns-count The context to use + * @return percentage The width in percent + */ +/** + * Get a state map + * + * @param string $name The name of the state to get + * @return map A state map object + */ +/** + * Check if a state exist : + * + * @param string $name The name of the state to check + * @return Boolean true is exist + */ +/** + * Get the media queries variables : + * + * @param int $index The media query indes + * @param String $var The media query variable name + * @return String|int The variable value + */ +/** + * Get a variable + * + * @param String $varName The variable name + * @param String $stateMap-or-stateName The state name or a map state value + * @return Mixed The finded value + */ +/** + * Set a variable in a state + * @param Mixed $stateName-or-stateIndex The state name of state index + * @param String $var Variable name to assign + * @param Mixed $newValue The new value to assign + * @return List The states list (full) + */ +/** + * Generate a column + * + * @param String $name The column name (often count) + * @param int $columns The column count that the column will take + * @param int $context The context on witch the with will be calculed + * @param Boolean $generateClasses Set if the column has to be generated in css + */ +/** + * Generate classname + * + * @param List $parrern The pattern to use to generate classname + * @param String $state The state + * @param int $count The column count + */ +/** + * Get the media query for a particular state, or with, etc... + * + * @param Mixed $state-or-min-width The state name of the min with + * @param Mixed $max-width The max width if first param is a min width + * @return String The media query string without the @media + */ +/** + * Get states count + * + * @return int The number of states defined + */ +/** + * Setup + */ +/** + * Register a clear each class + */ +/** + * Register a special class + */ +/** + * Register default states + */ +/** + * Register default mobile first states : + */ +/** + * Set the debug device (not used for now) + * + * @param String $state The state to update + * @para m String $device The device to use (iphone, etc...) + */ +/** + * Parent clear each + */ +/** + * Vertical align : + */ +/** + * Show inline + * + * @param String $state The state name + */ +/** + * Generate a custom class for all the states + * + * @param list $pattern The name pattern of the class + * @param list $statesNames The states names to generate + */ +@media screen and (max-width: 400px) { + @-ms-viewport { + width: device-width; } } +#gridle-settings { + content: '{ "version" : "1.3.40", "states" : { "retina":{ "query" : "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi), (min-resolution: 2dppx)", "classes" : false, "name" : "retina", "_gridle" : true }, "tv":{ "query" : "only tv", "classes" : false, "name" : "tv", "_gridle" : true }, "print":{ "query" : "only print", "classes" : false, "name" : "print", "_gridle" : true }, "portrait":{ "query" : "only screen and (orientation: portrait)", "classes" : false, "name" : "portrait", "_gridle" : true }, "landscape":{ "query" : "only screen and (orientation: landscape)", "classes" : false, "name" : "landscape", "_gridle" : true }, "default":{ "name" : "default", "min-width" : null, "max-width" : null, "query" : null, "classes" : true, "context" : "12", "gutter-width" : "20px", "direction" : "ltr", "name-multiplicator" : "1", "debug" : false, "debug-show-class-names" : true, "ie7-support" : false, "html-states-classes" : false, "generate-push-classes" : true, "generate-pull-classes" : true, "generate-prefix-classes" : true, "generate-suffix-classes" : true, "generate-helpers-classes" : true, "_gridle" : true }, "mobile":{ "name" : "mobile", "min-width" : null, "max-width" : "400px", "query" : "screen and (max-width: 400px)", "classes" : true, "context" : "12", "gutter-width" : "20px", "direction" : "ltr", "name-multiplicator" : "1", "debug" : false, "debug-show-class-names" : true, "ie7-support" : false, "html-states-classes" : false, "generate-push-classes" : true, "generate-pull-classes" : true, "generate-prefix-classes" : true, "generate-suffix-classes" : true, "generate-helpers-classes" : true, "_gridle" : true }, "tablet":{ "name" : "tablet", "min-width" : "401px", "max-width" : "767px", "query" : "screen and (min-width: 401px) and (max-width: 767px)", "classes" : true, "context" : "12", "gutter-width" : "20px", "direction" : "ltr", "name-multiplicator" : "1", "debug" : false, "debug-show-class-names" : true, "ie7-support" : false, "html-states-classes" : false, "generate-push-classes" : true, "generate-pull-classes" : true, "generate-prefix-classes" : true, "generate-suffix-classes" : true, "generate-helpers-classes" : true, "_gridle" : true }} }'; } + +.grid-0, .grid-1, .grid-2, .grid-3, .grid-4, .grid-5, .grid-6, .grid-7, .grid-8, .grid-9, .grid-10, .grid-11, .grid-12, .grid-mobile-0, .grid-mobile-1, .grid-mobile-2, .grid-mobile-3, .grid-mobile-4, .grid-mobile-5, .grid-mobile-6, .grid-mobile-7, .grid-mobile-8, .grid-mobile-9, .grid-mobile-10, .grid-mobile-11, .grid-mobile-12, .grid-tablet-0, .grid-tablet-1, .grid-tablet-2, .grid-tablet-3, .grid-tablet-4, .grid-tablet-5, .grid-tablet-6, .grid-tablet-7, .grid-tablet-8, .grid-tablet-9, .grid-tablet-10, .grid-tablet-11, .grid-tablet-12 { + float: left; + direction: ltr; + padding-left: 10px; + padding-right: 10px; } + +.grid-0 { + width: 0%; } + +.push-0 { + left: 0%; } + +.pull-0 { + right: 0%; } + +.prefix-0 { + margin-left: 0%; } + +.suffix-0 { + margin-right: 0%; } + +.grid-1 { + width: 8.33333%; } + +.push-1 { + left: 8.33333%; } + +.pull-1 { + right: 8.33333%; } + +.prefix-1 { + margin-left: 8.33333%; } + +.suffix-1 { + margin-right: 8.33333%; } + +.grid-2 { + width: 16.66667%; } + +.push-2 { + left: 16.66667%; } + +.pull-2 { + right: 16.66667%; } + +.prefix-2 { + margin-left: 16.66667%; } + +.suffix-2 { + margin-right: 16.66667%; } + +.grid-3 { + width: 25%; } + +.push-3 { + left: 25%; } + +.pull-3 { + right: 25%; } + +.prefix-3 { + margin-left: 25%; } + +.suffix-3 { + margin-right: 25%; } + +.grid-4 { + width: 33.33333%; } + +.push-4 { + left: 33.33333%; } + +.pull-4 { + right: 33.33333%; } + +.prefix-4 { + margin-left: 33.33333%; } + +.suffix-4 { + margin-right: 33.33333%; } + +.grid-5 { + width: 41.66667%; } + +.push-5 { + left: 41.66667%; } + +.pull-5 { + right: 41.66667%; } + +.prefix-5 { + margin-left: 41.66667%; } + +.suffix-5 { + margin-right: 41.66667%; } + +.grid-6 { + width: 50%; } + +.push-6 { + left: 50%; } + +.pull-6 { + right: 50%; } + +.prefix-6 { + margin-left: 50%; } + +.suffix-6 { + margin-right: 50%; } + +.grid-7 { + width: 58.33333%; } + +.push-7 { + left: 58.33333%; } + +.pull-7 { + right: 58.33333%; } + +.prefix-7 { + margin-left: 58.33333%; } + +.suffix-7 { + margin-right: 58.33333%; } + +.grid-8 { + width: 66.66667%; } + +.push-8 { + left: 66.66667%; } + +.pull-8 { + right: 66.66667%; } + +.prefix-8 { + margin-left: 66.66667%; } + +.suffix-8 { + margin-right: 66.66667%; } + +.grid-9 { + width: 75%; } + +.push-9 { + left: 75%; } + +.pull-9 { + right: 75%; } + +.prefix-9 { + margin-left: 75%; } + +.suffix-9 { + margin-right: 75%; } + +.grid-10 { + width: 83.33333%; } + +.push-10 { + left: 83.33333%; } + +.pull-10 { + right: 83.33333%; } + +.prefix-10 { + margin-left: 83.33333%; } + +.suffix-10 { + margin-right: 83.33333%; } + +.grid-11 { + width: 91.66667%; } + +.push-11 { + left: 91.66667%; } + +.pull-11 { + right: 91.66667%; } + +.prefix-11 { + margin-left: 91.66667%; } + +.suffix-11 { + margin-right: 91.66667%; } + +.grid-12 { + width: 100%; } + +.push-12 { + left: 100%; } + +.pull-12 { + right: 100%; } + +.prefix-12 { + margin-left: 100%; } + +.suffix-12 { + margin-right: 100%; } + +.float-left { + float: left; } + +.float-right { + float: right; } + +.clear { + clear: both; } + +.clear-left { + clear: left; } + +.clear-right { + clear: right; } + +.no-gutter, +.no-margin { + padding-left: 0; + padding-right: 0; } + +.no-gutter-left, +.no-margin-left { + padding-left: 0; } + +.no-gutter-right, +.no-margin-right { + padding-right: 0; } + +.no-gutter-top, +.no-margin-top { + padding-top: 0; } + +.no-gutter-bottom, +.no-margin-bottom { + padding-bottom: 0; } + +.gutter, +.margins { + padding-left: 10px; + padding-right: 10px; } + +.gutter-left, +.margin-left { + padding-left: 10px; } + +.gutter-right, +.margin-right { + padding-right: 10px; } + +.gutter-top, +.margin-top { + padding-top: 10px; } + +.gutter-bottom, +.margin-bottom { + padding-bottom: 10px; } + +.auto-height { + height: inherit; } + +.centered { + display: block !important; + float: none !important; + margin-left: auto !important; + margin-right: auto !important; + clear: both !important; } + +.parent { + padding-left: 0; + padding-right: 0; } + +.vertical-align-middle { + font-size: 0; + clear: both; } + .vertical-align-middle > * { + display: inline-block; + float: none !important; + vertical-align: middle; + font-size: 1rem; } + +.vertical-align-top { + font-size: 0; + clear: both; } + .vertical-align-top > * { + display: inline-block; + float: none !important; + vertical-align: top; + font-size: 1rem; } + +.vertical-align-bottom { + font-size: 0; + clear: both; } + .vertical-align-bottom > * { + display: inline-block; + float: none !important; + vertical-align: bottom; + font-size: 1rem; } + +/** + * Visible, hide, etc... + */ +.hide { + display: none; } + +.not-visible { + visibility: hidden; } + +.show { + display: block; } + +.show-inline { + display: inline-block; } + +.visible { + visibility: visible; } + +/** + * Clear each class : + */ +@media screen and (max-width: 400px) { + .grid-mobile-0 { + width: 0%; } + + .push-mobile-0 { + left: 0%; } + + .pull-mobile-0 { + right: 0%; } + + .prefix-mobile-0 { + margin-left: 0%; } + + .suffix-mobile-0 { + margin-right: 0%; } + + .grid-mobile-1 { + width: 8.33333%; } + + .push-mobile-1 { + left: 8.33333%; } + + .pull-mobile-1 { + right: 8.33333%; } + + .prefix-mobile-1 { + margin-left: 8.33333%; } + + .suffix-mobile-1 { + margin-right: 8.33333%; } + + .grid-mobile-2 { + width: 16.66667%; } + + .push-mobile-2 { + left: 16.66667%; } + + .pull-mobile-2 { + right: 16.66667%; } + + .prefix-mobile-2 { + margin-left: 16.66667%; } + + .suffix-mobile-2 { + margin-right: 16.66667%; } + + .grid-mobile-3 { + width: 25%; } + + .push-mobile-3 { + left: 25%; } + + .pull-mobile-3 { + right: 25%; } + + .prefix-mobile-3 { + margin-left: 25%; } + + .suffix-mobile-3 { + margin-right: 25%; } + + .grid-mobile-4 { + width: 33.33333%; } + + .push-mobile-4 { + left: 33.33333%; } + + .pull-mobile-4 { + right: 33.33333%; } + + .prefix-mobile-4 { + margin-left: 33.33333%; } + + .suffix-mobile-4 { + margin-right: 33.33333%; } + + .grid-mobile-5 { + width: 41.66667%; } + + .push-mobile-5 { + left: 41.66667%; } + + .pull-mobile-5 { + right: 41.66667%; } + + .prefix-mobile-5 { + margin-left: 41.66667%; } + + .suffix-mobile-5 { + margin-right: 41.66667%; } + + .grid-mobile-6 { + width: 50%; } + + .push-mobile-6 { + left: 50%; } + + .pull-mobile-6 { + right: 50%; } + + .prefix-mobile-6 { + margin-left: 50%; } + + .suffix-mobile-6 { + margin-right: 50%; } + + .grid-mobile-7 { + width: 58.33333%; } + + .push-mobile-7 { + left: 58.33333%; } + + .pull-mobile-7 { + right: 58.33333%; } + + .prefix-mobile-7 { + margin-left: 58.33333%; } + + .suffix-mobile-7 { + margin-right: 58.33333%; } + + .grid-mobile-8 { + width: 66.66667%; } + + .push-mobile-8 { + left: 66.66667%; } + + .pull-mobile-8 { + right: 66.66667%; } + + .prefix-mobile-8 { + margin-left: 66.66667%; } + + .suffix-mobile-8 { + margin-right: 66.66667%; } + + .grid-mobile-9 { + width: 75%; } + + .push-mobile-9 { + left: 75%; } + + .pull-mobile-9 { + right: 75%; } + + .prefix-mobile-9 { + margin-left: 75%; } + + .suffix-mobile-9 { + margin-right: 75%; } + + .grid-mobile-10 { + width: 83.33333%; } + + .push-mobile-10 { + left: 83.33333%; } + + .pull-mobile-10 { + right: 83.33333%; } + + .prefix-mobile-10 { + margin-left: 83.33333%; } + + .suffix-mobile-10 { + margin-right: 83.33333%; } + + .grid-mobile-11 { + width: 91.66667%; } + + .push-mobile-11 { + left: 91.66667%; } + + .pull-mobile-11 { + right: 91.66667%; } + + .prefix-mobile-11 { + margin-left: 91.66667%; } + + .suffix-mobile-11 { + margin-right: 91.66667%; } + + .grid-mobile-12 { + width: 100%; } + + .push-mobile-12 { + left: 100%; } + + .pull-mobile-12 { + right: 100%; } + + .prefix-mobile-12 { + margin-left: 100%; } + + .suffix-mobile-12 { + margin-right: 100%; } + + .float-mobile-left { + float: left; } + + .float-mobile-right { + float: right; } + + .clear-mobile { + clear: both; } + + .clear-mobile-left { + clear: left; } + + .clear-mobile-right { + clear: right; } + + .no-gutter-mobile, + .no-margin-mobile { + padding-left: 0; + padding-right: 0; } + + .no-gutter-mobile-left, + .no-margin-mobile-left { + padding-left: 0; } + + .no-gutter-mobile-right, + .no-margin-mobile-right { + padding-right: 0; } + + .no-gutter-mobile-top, + .no-margin-mobile-top { + padding-top: 0; } + + .no-gutter-mobile-bottom, + .no-margin-mobile-bottom { + padding-bottom: 0; } + + .gutter-mobile, + .margins-mobile { + padding-left: 10px; + padding-right: 10px; } + + .gutter-mobile-left, + .margin-mobile-left { + padding-left: 10px; } + + .gutter-mobile-right, + .margin-mobile-right { + padding-right: 10px; } + + .gutter-mobile-top, + .margin-mobile-top { + padding-top: 10px; } + + .gutter-mobile-bottom, + .margin-mobile-bottom { + padding-bottom: 10px; } + + .auto-height-mobile { + height: inherit; } + + .centered-mobile { + display: block !important; + float: none !important; + margin-left: auto !important; + margin-right: auto !important; + clear: both !important; } + + .parent-mobile { + padding-left: 0; + padding-right: 0; } + + .vertical-align-mobile-middle { + font-size: 0; + clear: both; } + .vertical-align-mobile-middle > * { + display: inline-block; + float: none !important; + vertical-align: middle; + font-size: 1rem; } + + .vertical-align-mobile-top { + font-size: 0; + clear: both; } + .vertical-align-mobile-top > * { + display: inline-block; + float: none !important; + vertical-align: top; + font-size: 1rem; } + + .vertical-align-mobile-bottom { + font-size: 0; + clear: both; } + .vertical-align-mobile-bottom > * { + display: inline-block; + float: none !important; + vertical-align: bottom; + font-size: 1rem; } + + /** + * Visible, hide, etc... + */ + .hide-mobile { + display: none; } + + .not-visible-mobile { + visibility: hidden; } + + .show-mobile { + display: block; } + + .show-inline-mobile { + display: inline-block; } + + .visible-mobile { + visibility: visible; } + + /** + * Clear each class : + */ } +@media screen and (min-width: 401px) and (max-width: 767px) { + .grid-tablet-0 { + width: 0%; } + + .push-tablet-0 { + left: 0%; } + + .pull-tablet-0 { + right: 0%; } + + .prefix-tablet-0 { + margin-left: 0%; } + + .suffix-tablet-0 { + margin-right: 0%; } + + .grid-tablet-1 { + width: 8.33333%; } + + .push-tablet-1 { + left: 8.33333%; } + + .pull-tablet-1 { + right: 8.33333%; } + + .prefix-tablet-1 { + margin-left: 8.33333%; } + + .suffix-tablet-1 { + margin-right: 8.33333%; } + + .grid-tablet-2 { + width: 16.66667%; } + + .push-tablet-2 { + left: 16.66667%; } + + .pull-tablet-2 { + right: 16.66667%; } + + .prefix-tablet-2 { + margin-left: 16.66667%; } + + .suffix-tablet-2 { + margin-right: 16.66667%; } + + .grid-tablet-3 { + width: 25%; } + + .push-tablet-3 { + left: 25%; } + + .pull-tablet-3 { + right: 25%; } + + .prefix-tablet-3 { + margin-left: 25%; } + + .suffix-tablet-3 { + margin-right: 25%; } + + .grid-tablet-4 { + width: 33.33333%; } + + .push-tablet-4 { + left: 33.33333%; } + + .pull-tablet-4 { + right: 33.33333%; } + + .prefix-tablet-4 { + margin-left: 33.33333%; } + + .suffix-tablet-4 { + margin-right: 33.33333%; } + + .grid-tablet-5 { + width: 41.66667%; } + + .push-tablet-5 { + left: 41.66667%; } + + .pull-tablet-5 { + right: 41.66667%; } + + .prefix-tablet-5 { + margin-left: 41.66667%; } + + .suffix-tablet-5 { + margin-right: 41.66667%; } + + .grid-tablet-6 { + width: 50%; } + + .push-tablet-6 { + left: 50%; } + + .pull-tablet-6 { + right: 50%; } + + .prefix-tablet-6 { + margin-left: 50%; } + + .suffix-tablet-6 { + margin-right: 50%; } + + .grid-tablet-7 { + width: 58.33333%; } + + .push-tablet-7 { + left: 58.33333%; } + + .pull-tablet-7 { + right: 58.33333%; } + + .prefix-tablet-7 { + margin-left: 58.33333%; } + + .suffix-tablet-7 { + margin-right: 58.33333%; } + + .grid-tablet-8 { + width: 66.66667%; } + + .push-tablet-8 { + left: 66.66667%; } + + .pull-tablet-8 { + right: 66.66667%; } + + .prefix-tablet-8 { + margin-left: 66.66667%; } + + .suffix-tablet-8 { + margin-right: 66.66667%; } + + .grid-tablet-9 { + width: 75%; } + + .push-tablet-9 { + left: 75%; } + + .pull-tablet-9 { + right: 75%; } + + .prefix-tablet-9 { + margin-left: 75%; } + + .suffix-tablet-9 { + margin-right: 75%; } + + .grid-tablet-10 { + width: 83.33333%; } + + .push-tablet-10 { + left: 83.33333%; } + + .pull-tablet-10 { + right: 83.33333%; } + + .prefix-tablet-10 { + margin-left: 83.33333%; } + + .suffix-tablet-10 { + margin-right: 83.33333%; } + + .grid-tablet-11 { + width: 91.66667%; } + + .push-tablet-11 { + left: 91.66667%; } + + .pull-tablet-11 { + right: 91.66667%; } + + .prefix-tablet-11 { + margin-left: 91.66667%; } + + .suffix-tablet-11 { + margin-right: 91.66667%; } + + .grid-tablet-12 { + width: 100%; } + + .push-tablet-12 { + left: 100%; } + + .pull-tablet-12 { + right: 100%; } + + .prefix-tablet-12 { + margin-left: 100%; } + + .suffix-tablet-12 { + margin-right: 100%; } + + .float-tablet-left { + float: left; } + + .float-tablet-right { + float: right; } + + .clear-tablet { + clear: both; } + + .clear-tablet-left { + clear: left; } + + .clear-tablet-right { + clear: right; } + + .no-gutter-tablet, + .no-margin-tablet { + padding-left: 0; + padding-right: 0; } + + .no-gutter-tablet-left, + .no-margin-tablet-left { + padding-left: 0; } + + .no-gutter-tablet-right, + .no-margin-tablet-right { + padding-right: 0; } + + .no-gutter-tablet-top, + .no-margin-tablet-top { + padding-top: 0; } + + .no-gutter-tablet-bottom, + .no-margin-tablet-bottom { + padding-bottom: 0; } + + .gutter-tablet, + .margins-tablet { + padding-left: 10px; + padding-right: 10px; } + + .gutter-tablet-left, + .margin-tablet-left { + padding-left: 10px; } + + .gutter-tablet-right, + .margin-tablet-right { + padding-right: 10px; } + + .gutter-tablet-top, + .margin-tablet-top { + padding-top: 10px; } + + .gutter-tablet-bottom, + .margin-tablet-bottom { + padding-bottom: 10px; } + + .auto-height-tablet { + height: inherit; } + + .centered-tablet { + display: block !important; + float: none !important; + margin-left: auto !important; + margin-right: auto !important; + clear: both !important; } + + .parent-tablet { + padding-left: 0; + padding-right: 0; } + + .vertical-align-tablet-middle { + font-size: 0; + clear: both; } + .vertical-align-tablet-middle > * { + display: inline-block; + float: none !important; + vertical-align: middle; + font-size: 1rem; } + + .vertical-align-tablet-top { + font-size: 0; + clear: both; } + .vertical-align-tablet-top > * { + display: inline-block; + float: none !important; + vertical-align: top; + font-size: 1rem; } + + .vertical-align-tablet-bottom { + font-size: 0; + clear: both; } + .vertical-align-tablet-bottom > * { + display: inline-block; + float: none !important; + vertical-align: bottom; + font-size: 1rem; } + + /** + * Visible, hide, etc... + */ + .hide-tablet { + display: none; } + + .not-visible-tablet { + visibility: hidden; } + + .show-tablet { + display: block; } + + .show-inline-tablet { + display: inline-block; } + + .visible-tablet { + visibility: visible; } + + /** + * Clear each class : + */ } +ul.unstyled { + list-style: none; + padding-left: 0em; } + ul.unstyled ul { + list-style: disc outside; } + +a { + color: #334d5c; + text-decoration: none; } + a:hover { + color: #18242b; } + +.text-right { + text-align: right; } + +.navigation { + background-color: #333333; + border-bottom: none; + height: 60px; + z-index: 999; } + .navigation .logo-container { + height: 60px; } + .navigation .logo-container:before { + /* create a full-height inline block pseudo=element */ + content: ' '; + display: inline-block; + vertical-align: middle; + /* vertical alignment of the inline element */ + height: 100%; } + .navigation .logo-container img.logo { + max-width: 89%; + max-height: 50px; + display: inline-block; + vertical-align: middle; } + .navigation .nav { + z-index: 9999999; + height: 60px; } + .navigation ul.navigation-menu { + -webkit-transform-style: preserve-3d; + clear: both; + display: none; + margin: 0 auto; + overflow: visible; + padding: 0; + width: 100%; + z-index: 9999; + display: inline-block; + margin: 0; + padding: 0; } + .navigation ul li.nav-link { + background: #333333; + display: block; + line-height: 60px; + overflow: hidden; + padding-right: .8em; + text-align: right; + width: 100%; + z-index: 9999; + background: transparent; + display: inline; + line-height: 60px; + text-decoration: none; + width: auto; } + .navigation ul li.nav-link a { + color: white; + display: inline-block; + font-weight: 400; + padding-right: 1em; } + .navigation ul li.nav-link a:hover { + color: white; } + .navigation .active-nav-item a { + border-bottom: 1px solid rgba(255, 255, 255, 0.5); + padding-bottom: 3px; } + .navigation li.more.nav-link { + padding-right: 0; + padding-right: 1em; } + .navigation li.more.nav-link > ul > li:first-child a { + padding-top: 0; } + .navigation li.more.nav-link a { + margin-right: 1em; } + .navigation li.more.nav-link > a { + padding-right: 0.6em; } + .navigation li.more.nav-link > a:after { + position: absolute; + top: auto; + right: -0.4em; + bottom: auto; + left: auto; + content: "▾"; + color: rgba(255, 255, 255, 0.7); } + .navigation li.more { + overflow: visible; + padding-right: 0; + padding-right: .8em; + position: relative; } + .navigation li.more a { + padding-right: .8em; } + .navigation li.more > a { + padding-right: 1.6em; + position: relative; + margin-right: 1em; } + .navigation li.more > a:after { + content: '›'; + font-size: 1.2em; + position: absolute; + right: 0.5em; } + .navigation li.more:hover > .submenu { + display: block; } + .navigation ul.submenu { + display: none; + padding-left: 0; + left: -1em; + position: absolute; + top: 1.5em; } + .navigation ul.submenu .submenu { + left: 11.8em; + top: 0; } + .navigation ul.submenu li { + display: block; + padding-right: 0; + line-height: 46.15385px; } + .navigation ul.submenu li:first-child > a { + border-top-left-radius: 0; + border-top-right-radius: 0; } + .navigation ul.submenu li:last-child > a { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + padding-bottom: 0; } + .navigation ul.submenu li a { + background-color: #2b2b2b; + display: inline-block; + text-align: right; + width: 100%; + background-color: #333333; + padding-left: 1em; + text-align: left; } + .navigation .navigation-search { + padding: 17px 0 0; + position: relative; } + .navigation .navigation-search input[type=search] { + padding: .6em .55em; + padding-right: 3.5em; + width: calc(100% - 4.5em); + font-size: .9em; + margin: 0; } + .navigation .navigation-search button { + padding: 7px 12px; + position: absolute; + top: .99em; + right: 1em; } + .navigation ul li.nav-link2 { + position: relative; + padding-right: 2em; + text-align: left; + line-height: 60px; + z-index: 9999; + float: left; + list-style: none; } + .navigation ul li.nav-link2 div.li-content { + display: inline-block; + line-height: normal; + vertical-align: middle; } + .navigation ul li.nav-link2 div.li-content a.more:after { + content: "▾"; + color: rgba(255, 255, 255, 0.7); + padding-left: 0.4em; + font-size: 1.2em; } + .navigation ul li.nav-link2 ul { + display: none; + position: absolute; + line-height: normal; + list-style: none; + background-color: #333; + padding-right: 1.5em; + padding-left: 1.5em; + width: 100%; } + .navigation ul li.nav-link2 ul li { + padding-top: 0.7em; + padding-bottom: 0.7em; } + .navigation ul li.nav-link2 ul li:first-child { + padding-top: 0; } + .navigation ul li.nav-link2 ul li:last-child { + padding-bottom: 0; } + .navigation ul li.nav-link2:hover ul { + display: block; } + .navigation ul li.nav-link2 a { + width: 100%; + color: white; + font-weight: 400; } + .navigation ul li.nav-link2 a:hover { + color: white; } + +.navigation { + background-color: #334d5c; } + .navigation a.more:after { + color: #333333; } + .navigation li.nav-link2 a { + margin-bottom: 2px; } + .navigation li.nav-link2.lang-selection { + color: #b2b2b2; + font-size: 0.7em; } + .navigation li.nav-link2.lang-selection a.more:after { + color: #b2b2b2; } + .navigation li.nav-link2 ul { + top: 58px; } + .navigation li.nav-link2 ul a { + padding-left: 0; } + .navigation div.nav, .navigation div.navigation-search { + float: right; } + .navigation div.nav input[type=search], .navigation div.navigation-search input[type=search] { + padding: 0.2em; + float: left; + border: none; } + .navigation div.nav button, .navigation div.navigation-search button { + color: #b2b2b2; + background-color: #334d5c; + padding: 0 0 0 7px; + top: inherit; + font-size: 1.2em; + position: unset; + float: left; } + +html, body { + height: 100%; } + +.content { + min-height: 85%; } + +.footer { + background: #333333; + padding-top: 10px; + padding-bottom: 10px; + width: 100%; } + .footer .footer-logo { + text-align: center; + margin-bottom: 2em; } + .footer .footer-logo img { + height: 3em; } + .footer .footer-links { + clear: both; + margin-bottom: 1.5em; + float: left; + direction: ltr; + padding-left: 10px; + padding-right: 10px; + width: 66.66667%; + display: block !important; + float: none !important; + margin-left: auto !important; + margin-right: auto !important; + clear: both !important; } + .footer ul { + margin-bottom: 3em; + float: left; + direction: ltr; + padding-left: 10px; + padding-right: 10px; + width: 33.33333%; + list-style-type: none; } + .footer li { + text-align: center; } + .footer li a { + color: rgba(255, 255, 255, 0.4); } + .footer li a:hover { + color: white; } + .footer li h3 { + color: white; + font-size: 1em; + font-weight: 800; + margin-bottom: .4em; } + .footer hr { + clear: both; + border: 1px solid rgba(255, 255, 255, 0.7); + margin: 0 auto 1.5em; + width: 12em; } + .footer p { + clear: both; + color: white; + font-size: .9em; + line-height: 1.5em; + margin: auto; + max-width: 35em; + text-align: center; } + +.success { + background: rgba(67, 178, 157, 0.2); + color: #43b29d; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +.error { + background: rgba(223, 73, 73, 0.2); + color: #df4949; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +.alert { + background: rgba(226, 121, 61, 0.2); + color: #e2793d; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +.notice { + background: rgba(51, 77, 92, 0.2); + color: #334d5c; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +fieldset { + border: none; + margin: 0 0 0.75em 0; + padding: 1.5em; } + +input, label, select { + display: block; + /* font-family: $form-font-family; + font-size: $form-font-size; */ } + +label { + padding-top: 0.5em; + padding-bottom: 0.5em; + font-weight: bold; + margin-bottom: 0.375em; } + label.required:after { + content: "*"; + color: #df4949; + font-weight: 900; } + label abbr { + display: none; } + +.inline-choice { + white-space: nowrap; } + .inline-choice label { + white-space: normal; + display: inline; + line-height: 2em; + margin-right: 1em; + font-weight: normal; } + +textarea, +input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], 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"], input:not([type]), textarea, +select[multiple=multiple] { + background-color: white; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border: 1px solid #000; + box-shadow: none; + /*font-family: $form-font-family; + font-size: $form-font-size; */ + margin-bottom: 0.375em; + padding: 0.5em 0.5em; + width: 100%; } + textarea:hover, + input[type="color"]:hover, input[type="date"]:hover, input[type="datetime"]:hover, input[type="datetime-local"]:hover, input[type="email"]:hover, input[type="month"]:hover, input[type="number"]:hover, input[type="password"]:hover, input[type="search"]:hover, input[type="tel"]:hover, input[type="text"]:hover, input[type="time"]:hover, input[type="url"]:hover, input[type="week"]:hover, input:not([type]):hover, textarea:hover, + select[multiple=multiple]:hover { + border-color: #000; } + textarea:focus, + input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]: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, input:not([type]):focus, textarea:focus, + select[multiple=multiple]:focus { + border-color: #000; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 5px rgba(42, 63, 76, 0.7); + outline: none; } + +textarea { + resize: vertical; } + +input[type="checkbox"], +input[type="radio"] { + display: inline; + margin-right: 0.375em; } + +input[type="file"] { + padding-bottom: 0.75em; + width: 100%; } + +select { + margin-bottom: 0.375em; + padding-top: 0.3em; + padding-bottom: 0.3em; + width: 100%; } + +form p.tip { + font-size: .875em; + position: relative; + text-align: center; + margin-top: -.3em; } +form p.label { + padding: 0; + white-space: normal; } +form fieldset { + border: none; + margin-bottom: 1.5em; + padding: 0; + margin: 0; } + form fieldset legend { + font-size: 1.438em; + font-weight: 700; + width: 100%; + border-bottom: 3px solid #ddd; + margin-bottom: 1em; } + form fieldset legend + * { + -webkit-margin-top-collapse: separate; } + form fieldset legend h2 { + margin-bottom: 0; } +form li label { + display: inline-block; + font-weight: normal; } + +textarea { + height: 12em; } + +span.force-inline-label label { + display: inline; } + +.sc-button { + color: #000; + background: #d9d9d9; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button:hover, .sc-button:active { + background: #cccccc; + color: #000; + text-decoration: none; } + .sc-button:focus { + background: #cccccc; } + .sc-button[disabled] { + background: rgba(217, 217, 217, 0.6); + color: #bfbfbf; } + .sc-button[disabled]:hover { + box-shadow: none; } + .sc-button.blue { + color: #fff; + background: #334d5c; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.blue:hover, .sc-button.blue:active { + background: #2a3f4c; + color: #fff; + text-decoration: none; } + .sc-button.blue:focus { + background: #2a3f4c; } + .sc-button.blue[disabled] { + background: rgba(51, 77, 92, 0.6); + color: #21323b; } + .sc-button.blue[disabled]:hover { + box-shadow: none; } + .sc-button.green { + color: #fff; + background: #43b29d; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.green:hover, .sc-button.green:active { + background: #3c9f8d; + color: #fff; + text-decoration: none; } + .sc-button.green:focus { + background: #3c9f8d; } + .sc-button.green[disabled] { + background: rgba(67, 178, 157, 0.6); + color: #358d7c; } + .sc-button.green[disabled]:hover { + box-shadow: none; } + .sc-button.orange { + color: #fff; + background: #e2793d; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.orange:hover, .sc-button.orange:active { + background: #df6a27; + color: #fff; + text-decoration: none; } + .sc-button.orange:focus { + background: #df6a27; } + .sc-button.orange[disabled] { + background: rgba(226, 121, 61, 0.6); + color: #cd5e1f; } + .sc-button.orange[disabled]:hover { + box-shadow: none; } + .sc-button.red { + color: #fff; + background: #df4949; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.red:hover, .sc-button.red:active { + background: #db3333; + color: #fff; + text-decoration: none; } + .sc-button.red:focus { + background: #db3333; } + .sc-button.red[disabled] { + background: rgba(223, 73, 73, 0.6); + color: #d02525; } + .sc-button.red[disabled]:hover { + box-shadow: none; } + .sc-button.black { + color: #fff; + background: #1a1a1a; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.black:hover, .sc-button.black:active { + background: #0d0d0d; + color: #fff; + text-decoration: none; } + .sc-button.black:focus { + background: #0d0d0d; } + .sc-button.black[disabled] { + background: rgba(26, 26, 26, 0.6); + color: black; } + .sc-button.black[disabled]:hover { + box-shadow: none; } + .sc-button.white { + color: #555; + background: #fff; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.white:hover, .sc-button.white:active { + background: #f2f2f2; + color: #555; + text-decoration: none; } + .sc-button.white:focus { + background: #f2f2f2; } + .sc-button.white[disabled] { + background: rgba(255, 255, 255, 0.6); + color: #e6e6e6; } + .sc-button.white[disabled]:hover { + box-shadow: none; } + +.sc-button.bt-submit, .sc-button.bt-save, .sc-button.bt-create, .sc-button.bt-new { + color: #fff; + background: #43b29d; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.bt-submit:hover, .sc-button.bt-submit:active, .sc-button.bt-save:hover, .sc-button.bt-save:active, .sc-button.bt-create:hover, .sc-button.bt-create:active, .sc-button.bt-new:hover, .sc-button.bt-new:active { + background: #3c9f8d; + color: #fff; + text-decoration: none; } + .sc-button.bt-submit:focus, .sc-button.bt-save:focus, .sc-button.bt-create:focus, .sc-button.bt-new:focus { + background: #3c9f8d; } + .sc-button.bt-submit[disabled], .sc-button.bt-save[disabled], .sc-button.bt-create[disabled], .sc-button.bt-new[disabled] { + background: rgba(67, 178, 157, 0.6); + color: #358d7c; } + .sc-button.bt-submit[disabled]:hover, .sc-button.bt-save[disabled]:hover, .sc-button.bt-create[disabled]:hover, .sc-button.bt-new[disabled]:hover { + box-shadow: none; } +.sc-button.bt-reset, .sc-button.bt-delete { + color: #fff; + background: #df4949; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.bt-reset:hover, .sc-button.bt-reset:active, .sc-button.bt-delete:hover, .sc-button.bt-delete:active { + background: #db3333; + color: #fff; + text-decoration: none; } + .sc-button.bt-reset:focus, .sc-button.bt-delete:focus { + background: #db3333; } + .sc-button.bt-reset[disabled], .sc-button.bt-delete[disabled] { + background: rgba(223, 73, 73, 0.6); + color: #d02525; } + .sc-button.bt-reset[disabled]:hover, .sc-button.bt-delete[disabled]:hover { + box-shadow: none; } +.sc-button.bt-action, .sc-button.bt-edit, .sc-button.bt-update { + color: #fff; + background: #e2793d; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.bt-action:hover, .sc-button.bt-action:active, .sc-button.bt-edit:hover, .sc-button.bt-edit:active, .sc-button.bt-update:hover, .sc-button.bt-update:active { + background: #df6a27; + color: #fff; + text-decoration: none; } + .sc-button.bt-action:focus, .sc-button.bt-edit:focus, .sc-button.bt-update:focus { + background: #df6a27; } + .sc-button.bt-action[disabled], .sc-button.bt-edit[disabled], .sc-button.bt-update[disabled] { + background: rgba(226, 121, 61, 0.6); + color: #cd5e1f; } + .sc-button.bt-action[disabled]:hover, .sc-button.bt-edit[disabled]:hover, .sc-button.bt-update[disabled]:hover { + box-shadow: none; } +.sc-button.bt-show, .sc-button.bt-view { + color: #fff; + background: #334d5c; + border: medium none; + box-shadow: none; + padding: 8px 12px; + text-decoration: none; + text-align: center; + display: inline-block; + vertical-align: middle; + white-space: nowrap; + line-height: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .sc-button.bt-show:hover, .sc-button.bt-show:active, .sc-button.bt-view:hover, .sc-button.bt-view:active { + background: #2a3f4c; + color: #fff; + text-decoration: none; } + .sc-button.bt-show:focus, .sc-button.bt-view:focus { + background: #2a3f4c; } + .sc-button.bt-show[disabled], .sc-button.bt-view[disabled] { + background: rgba(51, 77, 92, 0.6); + color: #21323b; } + .sc-button.bt-show[disabled]:hover, .sc-button.bt-view[disabled]:hover { + box-shadow: none; } +.sc-button:not(.change-icon).bt-create::before, .sc-button:not(.change-icon).bt-save::before, .sc-button:not(.change-icon).bt-new::before, .sc-button:not(.change-icon).bt-delete::before, .sc-button:not(.change-icon).bt-update::before, .sc-button:not(.change-icon).bt-edit::before, .sc-button:not(.change-icon).bt-cancel::before, .sc-button:not(.change-icon).bt-view::before, .sc-button:not(.change-icon).bt-show::before { + font: normal normal normal 14px/1 FontAwesome; + margin-right: 0.5em; } +.sc-button:not(.change-icon).bt-save::before { + content: ""; } +.sc-button:not(.change-icon).bt-create::before, .sc-button:not(.change-icon).bt-new::before { + content: ""; } +.sc-button:not(.change-icon).bt-delete::before { + content: ""; } +.sc-button:not(.change-icon).bt-edit::before, .sc-button:not(.change-icon).bt-update::before { + content: ""; } +.sc-button:not(.change-icon).bt-cancel::before { + content: ""; } +.sc-button:not(.change-icon).bt-show::before, .sc-button:not(.change-icon).bt-view::before { + content: ""; } +.sc-button > i.fa { + margin-right: 0.5em; } +.sc-button.has-hidden > i.fa, .sc-button:empty > i.fa { + margin-right: 0; } +.sc-button.has-hidden:not(.change-icon).bt-create::before, .sc-button.has-hidden:not(.change-icon).bt-save::before, .sc-button.has-hidden:not(.change-icon).bt-new::before, .sc-button.has-hidden:not(.change-icon).bt-delete::before, .sc-button.has-hidden:not(.change-icon).bt-update::before, .sc-button.has-hidden:not(.change-icon).bt-edit::before, .sc-button.has-hidden:not(.change-icon).bt-cancel::before, .sc-button.has-hidden:not(.change-icon).bt-view::before, .sc-button.has-hidden:not(.change-icon).bt-show::before, .sc-button:empty:not(.change-icon).bt-create::before, .sc-button:empty:not(.change-icon).bt-save::before, .sc-button:empty:not(.change-icon).bt-new::before, .sc-button:empty:not(.change-icon).bt-delete::before, .sc-button:empty:not(.change-icon).bt-update::before, .sc-button:empty:not(.change-icon).bt-edit::before, .sc-button:empty:not(.change-icon).bt-cancel::before, .sc-button:empty:not(.change-icon).bt-view::before, .sc-button:empty:not(.change-icon).bt-show::before { + margin-right: 0; } +.sc-button.has-hidden > span.show-on-hover { + display: none; } +.sc-button.has-hidden:hover > span.show-on-hover { + display: inline-block; } +.sc-button.has-hidden:hover > i.fa { + margin-right: 0.5em; } +.sc-button.has-hidden:hover:not(.change-icon).bt-create::before, .sc-button.has-hidden:hover:not(.change-icon).bt-save::before, .sc-button.has-hidden:hover:not(.change-icon).bt-new::before, .sc-button.has-hidden:hover:not(.change-icon).bt-delete::before, .sc-button.has-hidden:hover:not(.change-icon).bt-update::before, .sc-button.has-hidden:hover:not(.change-icon).bt-edit::before, .sc-button.has-hidden:hover:not(.change-icon).bt-cancel::before, .sc-button.has-hidden:hover:not(.change-icon).bt-view::before, .sc-button.has-hidden:hover:not(.change-icon).bt-show::before { + margin-right: 0.5em; } +.sc-button.button-small { + font-size: 80%; + padding: 6px 8px; } + +.sticky-form-buttons { + margin-top: 1em; + background-color: #cabb9f; + position: sticky; + bottom: 0.5em; + text-align: center; + padding: 0.5em; + border-radius: 0; } + +.sticky-form-buttons .margin-5 { + margin-left: 5%; + margin-right: 5%; } + +.sticky-form-buttons .margin-10 { + margin-left: 10%; + margin-right: 10%; } + +/* Tabs */ +.tab-nav { + margin: 0.5em 0; + padding: 0; } + .tab-nav > li { + font-family: "Open Sans"; + display: inline-block; + width: 100%; + cursor: default; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-bottom: 0.2em; } + .tab-nav > li.title { + padding: 0.5em 0.5em 0.5em 1em; + font-weight: 900; + background-color: #334d5c; + color: #fff; } + .tab-nav > li > a { + display: block; + width: auto; + padding: 0.3em 0.3em 0.3em 0.6em; + margin: 0; + color: #334d5c; + cursor: pointer; + border: none; + background-color: #eec84a; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } + .tab-nav > li > a:hover, .tab-nav > li > a:active { + border: none; + color: #334d5c; + text-decoration: none; } + +table { + width: 100%; } + table thead { + background-color: unset; } + table thead tr th { + border: unset; + text-align: center; + padding: 0.3em; + color: #334d5c; } + table tbody tr td { + border: 1px solid #000; + text-align: left; + padding: 0.3em; + color: unset; } + table tbody tr:nth-of-type(even) { + background-color: #e6e6e6; } + table tbody tr:nth-of-type(odd) { + background-color: #fff; } + +/* +Chill is a software for social workers +Copyright (C) 2015 Champs Libres + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero 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 Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +*/ +div.timeline-item { + padding: 0.5em; + overflow: auto; + margin-bottom: 1.5em; } + div.timeline-item.odd { + background-color: #e6e6e6; } + div.timeline-item.odd .summary { + background-color: #fff; } + div.timeline-item.even { + background-color: #fff; } + div.timeline-item.even .summary { + background-color: #e6e6e6; } + div.timeline-item .summary { + margin: 1em; + overflow: auto; } + div.timeline-item h3 { + margin-top: 0em; + font-size: 1em; } + div.timeline-item h3.single-line { + margin-bottom: 0em; } + +span.entity.entity-activity.activity-reason { + font-variant: small-caps; + display: inline; + padding: .2em .6em .3em; + font-size: 88%; + font-weight: bold; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + color: white; + background-color: #dd506d; } + +.activity { + color: #43b29d; } + +.report { + color: #df4949; } + +.person { + color: #334d5c; } + +.pagination { + display: flex; + justify-content: center; + text-align: center; + margin-top: 1em; } + .pagination .link { + background-color: white; + display: inline-block; } + .pagination .link:nth-of-type(1n+2) { + border-left: 1px solid #b2b2b2; } + .pagination .link.current { + font-weight: bold; + color: white; + background-color: #334d5c; + padding: 0.4em 0.8em; } + .pagination .link a { + display: block; + padding: 0.4em 0.8em; } + .pagination .link a:hover { + color: white; + font-weight: bold; + background-color: #43b29d; } + +/* the cf title will look like a `form legend h2` */ +span.cf-title { + display: block; + font-weight: 700; + border-bottom: 3px solid #cccccc; + margin-bottom: 1em; } + +span.cf-subtitle { + display: block; + font-weight: 600; + border-bottom: 1px solid #cccccc; + margin-bottom: 1em; } + +div.chill_address div.chill_address_address { + margin: 0.7em 0; + font-size: 98%; + font-variant: small-caps; } + div.chill_address div.chill_address_address p { + display: inline-block; + margin: 0 0 0 1.5em; + text-indent: -1.5em; } + +/*ul.record_actions { + padding-left: 0; +} +ul.record_actions li { + display: inline-block; +}*/ +ul.record_actions { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding: 0.5em 0; } + ul.record_actions li { + display: inline-block; + list-style-type: none; + margin-right: 1em; + order: 99; } + ul.record_actions li:last-child { + margin-right: 0; } + ul.record_actions li.cancel { + order: 1; + margin-right: auto; } + +ul.record_actions.sticky-form-buttons { + padding-left: 1em; + padding-right: 1em; } + +td ul.record_actions li, +ul.record_actions_small li { + margin-right: 0.2em; } + +.flash_message { + margin-top: 2.5em; } + +.alert { + background: rgba(223, 73, 73, 0.2); + color: #df4949; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +.warning { + background: rgba(226, 121, 61, 0.2); + color: #e2793d; + font-weight: bold; + margin-bottom: 0.75em; + padding: 0.75em; } + +html, body { + min-height: 100%; + font-family: 'Open Sans'; } + +header { + position: relative; } + +#content_conainter { + position: relative; + min-height: calc(100% - 100px); } + +#content_conainter:before { + bottom: 0; + content: ""; + left: 0; + opacity: 0.1; + position: absolute; + right: 0; + top: 0; + z-index: -1; + background-attachment: fixed; + background-repeat: no-repeat; + background-size: cover; + background-position: center; } + +/* CUSTOM FIELDS -> */ +.cf-title { + font-size: 2em; } + +.cf-subtitle { + font-size: 1.5em; } + +/* <- CUSTOM FIELDS */ +ul.submenu.width-11-em { + min-width: 11em; } + +ul.submenu.width-15-em { + min-width: 15em; } + +.content { + padding-top: 1em; + padding-bottom: 1em; } + +.select2 { + width: 100%; } + +ul.custom_fields.choice li { + list-style: none; } + +.errors { + color: #df4949; } + +.blop label { + line-height: 1.5em; } + +.footer p { + font-family: 'Open Sans'; + font-weight: 300; } +.footer a { + color: white; + text-decoration: underline; } + +.time_compound input[type=text], .time_compound select { + width: 4em; + display: inline-block; + text-align: center; } +.time_compound .separator { + margin-left: 0.2em; + margin-right: 0.2em; } + +.open_sansbold { + font-family: 'Open Sans'; + font-weight: bold; } + +dd { + margin-left: 0; } + +dt { + font-family: 'Open Sans'; + font-weight: 600; } + +/* INPUT CLASS -> */ +div.input_with_post_text { + display: flex; + align-items: center; } + +div.input_with_post_text span.post_text { + flex: 1; + margin-left: 0.5em; } + +div.input_with_post_text input { + width: 70%; + display: inline-block; + flex: 2; } + +/* <- INPUT CLASS */ +dl.chill_report_view_data dt, +dl.chill_view_data dt { + margin-top: 1.5em; + color: #334d5c; } +dl.chill_report_view_data dd, +dl.chill_view_data dd { + padding-left: 1.5em; + margin-top: 0.2em; } + dl.chill_report_view_data dd ul, + dl.chill_view_data dd ul { + padding-left: 0; } + +blockquote.chill-user-quote { + border-left: 10px solid #eec84a; + margin: 1.5em 10px; + padding: 0.5em 10px; + quotes: "“" "”" "‘" "’"; + background-color: #e6e6e6; } + blockquote.chill-user-quote p { + display: inline; } + +.chill-no-data-statement { + font-style: italic; } + +/*# sourceMappingURL=scratch.css.map */ diff --git a/src/Bundle/ChillMain/Resources/public/css/scratch.css.map b/src/Bundle/ChillMain/Resources/public/css/scratch.css.map new file mode 100644 index 000000000..2d375842b --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/scratch.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "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", +"sources": ["../sass/contrib/normalize/_normalize.scss","../sass/contrib/fontawesome/font-awesome.scss","../sass/contrib/fontawesome/_path.scss","../sass/contrib/fontawesome/_core.scss","../sass/contrib/fontawesome/_larger.scss","../sass/contrib/fontawesome/_fixed-width.scss","../sass/contrib/fontawesome/_list.scss","../sass/contrib/fontawesome/_variables.scss","../sass/contrib/fontawesome/_bordered-pulled.scss","../sass/contrib/fontawesome/_animated.scss","../sass/contrib/fontawesome/_rotated-flipped.scss","../sass/contrib/fontawesome/_mixins.scss","../sass/contrib/fontawesome/_stacked.scss","../sass/contrib/fontawesome/_icons.scss","../sass/custom/config/_colors.scss","../sass/config/_variables.scss","../sass/scratch.scss","../sass/contrib/gridle/gridle/_silent-classes.scss","../sass/contrib/gridle/gridle/_functions.scss","../sass/contrib/gridle/gridle/_settings-mixins.scss","../sass/contrib/gridle/gridle/_mixins.scss","../sass/contrib/gridle/gridle/_generate-mixins.scss","../sass/contrib/gridle/gridle/_common-mixins.scss","../sass/modules/_typography.scss","../sass/custom/config/_variables.scss","../sass/modules/_navigation.scss","../sass/contrib/bourbon/addons/_position.scss","../sass/custom/modules/_navigation.scss","../sass/modules/_content.scss","../sass/modules/_footer.scss","../sass/modules/_alerts.scss","../sass/mixins/_alerts.scss","../sass/modules/_forms.scss","../sass/contrib/bourbon/addons/_border-radius.scss","../sass/custom/modules/_forms.scss","../sass/modules/_buttons.scss","../sass/mixins/_buttons.scss","../sass/config/_buttons.scss","../sass/config/_colors.scss","../sass/custom/modules/_buttons.scss","../sass/modules/_tabs.scss","../sass/modules/_table.scss","../sass/custom/_timeline.scss","../sass/custom/_activity.scss","../sass/custom/mixins/entity.scss","../sass/custom/_report.scss","../sass/custom/_person.scss","../sass/custom/_pagination.scss","../sass/custom/_custom-fields.scss","../sass/custom/_address.scss","../sass/custom/_record_actions.scss","../sass/custom/_flash_messages.scss","../sass/_custom.scss"], +"names": [], +"file": "scratch.css" +} diff --git a/src/Bundle/ChillMain/Resources/public/css/select2/select2-bootstrap.css b/src/Bundle/ChillMain/Resources/public/css/select2/select2-bootstrap.css new file mode 100644 index 000000000..3b83f0a22 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/select2/select2-bootstrap.css @@ -0,0 +1,87 @@ +.form-control .select2-choice { + border: 0; + border-radius: 2px; +} + +.form-control .select2-choice .select2-arrow { + border-radius: 0 2px 2px 0; +} + +.form-control.select2-container { + height: auto !important; + padding: 0; +} + +.form-control.select2-container.select2-dropdown-open { + border-color: #5897FB; + border-radius: 3px 3px 0 0; +} + +.form-control .select2-container.select2-dropdown-open .select2-choices { + border-radius: 3px 3px 0 0; +} + +.form-control.select2-container .select2-choices { + border: 0 !important; + border-radius: 3px; +} + +.control-group.warning .select2-container .select2-choice, +.control-group.warning .select2-container .select2-choices, +.control-group.warning .select2-container-active .select2-choice, +.control-group.warning .select2-container-active .select2-choices, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.warning .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.warning .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #C09853 !important; +} + +.control-group.warning .select2-container .select2-choice div { + border-left: 1px solid #C09853 !important; + background: #FCF8E3 !important; +} + +.control-group.error .select2-container .select2-choice, +.control-group.error .select2-container .select2-choices, +.control-group.error .select2-container-active .select2-choice, +.control-group.error .select2-container-active .select2-choices, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.error .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.error .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #B94A48 !important; +} + +.control-group.error .select2-container .select2-choice div { + border-left: 1px solid #B94A48 !important; + background: #F2DEDE !important; +} + +.control-group.info .select2-container .select2-choice, +.control-group.info .select2-container .select2-choices, +.control-group.info .select2-container-active .select2-choice, +.control-group.info .select2-container-active .select2-choices, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.info .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.info .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #3A87AD !important; +} + +.control-group.info .select2-container .select2-choice div { + border-left: 1px solid #3A87AD !important; + background: #D9EDF7 !important; +} + +.control-group.success .select2-container .select2-choice, +.control-group.success .select2-container .select2-choices, +.control-group.success .select2-container-active .select2-choice, +.control-group.success .select2-container-active .select2-choices, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choice, +.control-group.success .select2-dropdown-open.select2-drop-above .select2-choices, +.control-group.success .select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #468847 !important; +} + +.control-group.success .select2-container .select2-choice div { + border-left: 1px solid #468847 !important; + background: #DFF0D8 !important; +} diff --git a/src/Bundle/ChillMain/Resources/public/css/select2/select2.css b/src/Bundle/ChillMain/Resources/public/css/select2/select2.css new file mode 100644 index 000000000..915347037 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/css/select2/select2.css @@ -0,0 +1,692 @@ +/* +Version: 3.5.4 Timestamp: Sun Aug 30 13:30:32 EDT 2015 +*/ +.select2-container { + margin: 0; + position: relative; + display: inline-block; + vertical-align: middle; +} + +.select2-container, +.select2-drop, +.select2-search, +.select2-search input { + /* + Force border-box so that % widths fit the parent + container without overlap because of margin/padding. + More Info : http://www.quirksmode.org/css/box.html + */ + -webkit-box-sizing: border-box; /* webkit */ + -moz-box-sizing: border-box; /* firefox */ + box-sizing: border-box; /* css3 */ +} + +.select2-container .select2-choice { + display: block; + height: 26px; + padding: 0 0 0 8px; + overflow: hidden; + position: relative; + + border: 1px solid #aaa; + white-space: nowrap; + line-height: 26px; + color: #444; + text-decoration: none; + + border-radius: 4px; + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #fff; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.5, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 50%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); + background-image: linear-gradient(to top, #eee 0%, #fff 50%); +} + +html[dir="rtl"] .select2-container .select2-choice { + padding: 0 8px 0 0; +} + +.select2-container.select2-drop-above .select2-choice { + border-bottom-color: #aaa; + + border-radius: 0 0 4px 4px; + + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eee), color-stop(0.9, #fff)); + background-image: -webkit-linear-gradient(center bottom, #eee 0%, #fff 90%); + background-image: -moz-linear-gradient(center bottom, #eee 0%, #fff 90%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eeeeee', GradientType=0); + background-image: linear-gradient(to bottom, #eee 0%, #fff 90%); +} + +.select2-container.select2-allowclear .select2-choice .select2-chosen { + margin-right: 42px; +} + +.select2-container .select2-choice > .select2-chosen { + margin-right: 26px; + display: block; + overflow: hidden; + + white-space: nowrap; + + text-overflow: ellipsis; + float: none; + width: auto; +} + +html[dir="rtl"] .select2-container .select2-choice > .select2-chosen { + margin-left: 26px; + margin-right: 0; +} + +.select2-container .select2-choice abbr { + display: none; + width: 12px; + height: 12px; + position: absolute; + right: 24px; + top: 8px; + + font-size: 1px; + text-decoration: none; + + border: 0; + background: url('select2.png') right top no-repeat; + cursor: pointer; + outline: 0; +} + +.select2-container.select2-allowclear .select2-choice abbr { + display: inline-block; +} + +.select2-container .select2-choice abbr:hover { + background-position: right -11px; + cursor: pointer; +} + +.select2-drop-mask { + border: 0; + margin: 0; + padding: 0; + position: fixed; + left: 0; + top: 0; + min-height: 100%; + min-width: 100%; + height: auto; + width: auto; + opacity: 0; + z-index: 9998; + /* styles required for IE to work */ + background-color: #fff; + filter: alpha(opacity=0); +} + +.select2-drop { + width: 100%; + margin-top: -1px; + position: absolute; + z-index: 9999; + top: 100%; + + background: #fff; + color: #000; + border: 1px solid #aaa; + border-top: 0; + + border-radius: 0 0 4px 4px; + + -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop.select2-drop-above { + margin-top: 1px; + border-top: 1px solid #aaa; + border-bottom: 0; + + border-radius: 4px 4px 0 0; + + -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); + box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); +} + +.select2-drop-active { + border: 1px solid #5897fb; + border-top: none; +} + +.select2-drop.select2-drop-above.select2-drop-active { + border-top: 1px solid #5897fb; +} + +.select2-drop-auto-width { + border-top: 1px solid #aaa; + width: auto; +} + +.select2-container .select2-choice .select2-arrow { + display: inline-block; + width: 18px; + height: 100%; + position: absolute; + right: 0; + top: 0; + + border-left: 1px solid #aaa; + border-radius: 0 4px 4px 0; + + background-clip: padding-box; + + background: #ccc; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); + background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); + background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); + background-image: linear-gradient(to top, #ccc 0%, #eee 60%); +} + +html[dir="rtl"] .select2-container .select2-choice .select2-arrow { + left: 0; + right: auto; + + border-left: none; + border-right: 1px solid #aaa; + border-radius: 4px 0 0 4px; +} + +.select2-container .select2-choice .select2-arrow b { + display: block; + width: 100%; + height: 100%; + background: url('select2.png') no-repeat 0 1px; +} + +html[dir="rtl"] .select2-container .select2-choice .select2-arrow b { + background-position: 2px 1px; +} + +.select2-search { + display: inline-block; + width: 100%; + min-height: 26px; + margin: 0; + padding: 4px 4px 0 4px; + + position: relative; + z-index: 10000; + + white-space: nowrap; +} + +.select2-search input { + width: 100%; + height: auto !important; + min-height: 26px; + padding: 4px 20px 4px 5px; + margin: 0; + + outline: 0; + font-family: sans-serif; + font-size: 1em; + + border: 1px solid #aaa; + border-radius: 0; + + -webkit-box-shadow: none; + box-shadow: none; + + background: #fff url('select2.png') no-repeat 100% -22px; + background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat 100% -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +html[dir="rtl"] .select2-search input { + padding: 4px 5px 4px 20px; + + background: #fff url('select2.png') no-repeat -37px -22px; + background: url('select2.png') no-repeat -37px -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2.png') no-repeat -37px -22px, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat -37px -22px, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2.png') no-repeat -37px -22px, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +.select2-search input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100%; + background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, #fff), color-stop(0.99, #eee)); + background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, #fff 85%, #eee 99%); + background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(to bottom, #fff 85%, #eee 99%) 0 0; +} + +.select2-container-active .select2-choice, +.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} + +.select2-dropdown-open .select2-choice { + border-bottom-color: transparent; + -webkit-box-shadow: 0 1px 0 #fff inset; + box-shadow: 0 1px 0 #fff inset; + + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + + background-color: #eee; + background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center bottom, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center bottom, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(to top, #fff 0%, #eee 50%); +} + +.select2-dropdown-open.select2-drop-above .select2-choice, +.select2-dropdown-open.select2-drop-above .select2-choices { + border: 1px solid #5897fb; + border-top-color: transparent; + + background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), color-stop(0.5, #eee)); + background-image: -webkit-linear-gradient(center top, #fff 0%, #eee 50%); + background-image: -moz-linear-gradient(center top, #fff 0%, #eee 50%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0); + background-image: linear-gradient(to bottom, #fff 0%, #eee 50%); +} + +.select2-dropdown-open .select2-choice .select2-arrow { + background: transparent; + border-left: none; + filter: none; +} +html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow { + border-right: none; +} + +.select2-dropdown-open .select2-choice .select2-arrow b { + background-position: -18px 1px; +} + +html[dir="rtl"] .select2-dropdown-open .select2-choice .select2-arrow b { + background-position: -16px 1px; +} + +.select2-hidden-accessible { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; +} + +/* results */ +.select2-results { + max-height: 200px; + padding: 0 0 0 4px; + margin: 4px 4px 4px 0; + position: relative; + overflow-x: hidden; + overflow-y: auto; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +html[dir="rtl"] .select2-results { + padding: 0 4px 0 0; + margin: 4px 0 4px 4px; +} + +.select2-results ul.select2-result-sub { + margin: 0; + padding-left: 0; +} + +.select2-results li { + list-style: none; + display: list-item; + background-image: none; +} + +.select2-results li.select2-result-with-children > .select2-result-label { + font-weight: bold; +} + +.select2-results .select2-result-label { + padding: 3px 7px 4px; + margin: 0; + cursor: pointer; + + min-height: 1em; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.select2-results-dept-1 .select2-result-label { padding-left: 20px } +.select2-results-dept-2 .select2-result-label { padding-left: 40px } +.select2-results-dept-3 .select2-result-label { padding-left: 60px } +.select2-results-dept-4 .select2-result-label { padding-left: 80px } +.select2-results-dept-5 .select2-result-label { padding-left: 100px } +.select2-results-dept-6 .select2-result-label { padding-left: 110px } +.select2-results-dept-7 .select2-result-label { padding-left: 120px } + +.select2-results .select2-highlighted { + background: #3875d7; + color: #fff; +} + +.select2-results li em { + background: #feffde; + font-style: normal; +} + +.select2-results .select2-highlighted em { + background: transparent; +} + +.select2-results .select2-highlighted ul { + background: #fff; + color: #000; +} + +.select2-results .select2-no-results, +.select2-results .select2-searching, +.select2-results .select2-ajax-error, +.select2-results .select2-selection-limit { + background: #f4f4f4; + display: list-item; + padding-left: 5px; +} + +/* +disabled look for disabled choices in the results dropdown +*/ +.select2-results .select2-disabled.select2-highlighted { + color: #666; + background: #f4f4f4; + display: list-item; + cursor: default; +} +.select2-results .select2-disabled { + background: #f4f4f4; + display: list-item; + cursor: default; +} + +.select2-results .select2-selected { + display: none; +} + +.select2-more-results.select2-active { + background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; +} + +.select2-results .select2-ajax-error { + background: rgba(255, 50, 50, .2); +} + +.select2-more-results { + background: #f4f4f4; + display: list-item; +} + +/* disabled styles */ + +.select2-container.select2-container-disabled .select2-choice { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container.select2-container-disabled .select2-choice .select2-arrow { + background-color: #f4f4f4; + background-image: none; + border-left: 0; +} + +.select2-container.select2-container-disabled .select2-choice abbr { + display: none; +} + + +/* multiselect */ + +.select2-container-multi .select2-choices { + height: auto !important; + height: 1%; + margin: 0; + padding: 0 5px 0 0; + position: relative; + + border: 1px solid #aaa; + cursor: text; + overflow: hidden; + + background-color: #fff; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eee), color-stop(15%, #fff)); + background-image: -webkit-linear-gradient(top, #eee 1%, #fff 15%); + background-image: -moz-linear-gradient(top, #eee 1%, #fff 15%); + background-image: linear-gradient(to bottom, #eee 1%, #fff 15%); +} + +html[dir="rtl"] .select2-container-multi .select2-choices { + padding: 0 0 0 5px; +} + +.select2-locked { + padding: 3px 5px 3px 5px !important; +} + +.select2-container-multi .select2-choices { + min-height: 26px; +} + +.select2-container-multi.select2-container-active .select2-choices { + border: 1px solid #5897fb; + outline: none; + + -webkit-box-shadow: 0 0 5px rgba(0, 0, 0, .3); + box-shadow: 0 0 5px rgba(0, 0, 0, .3); +} +.select2-container-multi .select2-choices li { + float: left; + list-style: none; +} +html[dir="rtl"] .select2-container-multi .select2-choices li +{ + float: right; +} +.select2-container-multi .select2-choices .select2-search-field { + margin: 0; + padding: 0; + white-space: nowrap; +} + +.select2-container-multi .select2-choices .select2-search-field input { + padding: 5px; + margin: 1px 0; + + font-family: sans-serif; + font-size: 100%; + color: #666; + outline: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + background: transparent !important; +} + +.select2-container-multi .select2-choices .select2-search-field input.select2-active { + background: #fff url('select2-spinner.gif') no-repeat 100% !important; +} + +.select2-default { + color: #999 !important; +} + +.select2-container-multi .select2-choices .select2-search-choice { + padding: 3px 5px 3px 18px; + margin: 3px 0 3px 5px; + position: relative; + + line-height: 13px; + color: #333; + cursor: default; + border: 1px solid #aaaaaa; + + border-radius: 3px; + + -webkit-box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + box-shadow: 0 0 2px #fff inset, 0 1px 0 rgba(0, 0, 0, 0.05); + + background-clip: padding-box; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + background-color: #e4e4e4; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eee)); + background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); + background-image: linear-gradient(to bottom, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eee 100%); +} +html[dir="rtl"] .select2-container-multi .select2-choices .select2-search-choice +{ + margin: 3px 5px 3px 0; + padding: 3px 18px 3px 5px; +} +.select2-container-multi .select2-choices .select2-search-choice .select2-chosen { + cursor: default; +} +.select2-container-multi .select2-choices .select2-search-choice-focus { + background: #d4d4d4; +} + +.select2-search-choice-close { + display: block; + width: 12px; + height: 13px; + position: absolute; + right: 3px; + top: 4px; + + font-size: 1px; + outline: none; + background: url('select2.png') right top no-repeat; +} +html[dir="rtl"] .select2-search-choice-close { + right: auto; + left: 3px; +} + +.select2-container-multi .select2-search-choice-close { + left: 3px; +} + +html[dir="rtl"] .select2-container-multi .select2-search-choice-close { + left: auto; + right: 2px; +} + +.select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { + background-position: right -11px; +} +.select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { + background-position: right -11px; +} + +/* disabled styles */ +.select2-container-multi.select2-container-disabled .select2-choices { + background-color: #f4f4f4; + background-image: none; + border: 1px solid #ddd; + cursor: default; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { + padding: 3px 5px 3px 5px; + border: 1px solid #ddd; + background-image: none; + background-color: #f4f4f4; +} + +.select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; + background: none; +} +/* end multiselect */ + + +.select2-result-selectable .select2-match, +.select2-result-unselectable .select2-match { + text-decoration: underline; +} + +.select2-offscreen, .select2-offscreen:focus { + clip: rect(0 0 0 0) !important; + width: 1px !important; + height: 1px !important; + border: 0 !important; + margin: 0 !important; + padding: 0 !important; + overflow: hidden !important; + position: absolute !important; + outline: 0 !important; + left: 0px !important; + top: 0px !important; +} + +.select2-display-none { + display: none; +} + +.select2-measure-scrollbar { + position: absolute; + top: -10000px; + left: -10000px; + width: 100px; + height: 100px; + overflow: scroll; +} + +/* Retina-ize icons */ + +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 2dppx) { + .select2-search input, + .select2-search-choice-close, + .select2-container .select2-choice abbr, + .select2-container .select2-choice .select2-arrow b { + background-image: url('select2x2.png') !important; + background-repeat: no-repeat !important; + background-size: 60px 40px !important; + } + + .select2-search input { + background-position: 100% -21px !important; + } +} diff --git a/src/Bundle/ChillMain/Resources/public/css/select2/select2.png b/src/Bundle/ChillMain/Resources/public/css/select2/select2.png new file mode 100644 index 000000000..1d804ffb9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/css/select2/select2.png differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/FontAwesome.otf b/src/Bundle/ChillMain/Resources/public/fonts/FontAwesome.otf new file mode 100644 index 000000000..3ed7f8b48 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/FontAwesome.otf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Bold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Bold.ttf new file mode 100644 index 000000000..1d623bfe9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Bold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf new file mode 100644 index 000000000..89e1809d3 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf new file mode 100644 index 000000000..1d1662ff6 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf new file mode 100644 index 000000000..5decede38 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Italic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Italic.ttf new file mode 100644 index 000000000..ed36a44f8 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Italic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Light.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Light.ttf new file mode 100644 index 000000000..bb0294779 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Light.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-LightItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-LightItalic.ttf new file mode 100644 index 000000000..affa9ca50 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-LightItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Regular.ttf new file mode 100644 index 000000000..78c591099 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/MerriweatherSans-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/OFL.txt new file mode 100644 index 000000000..6665e9d99 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/Merriweather_Sans/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2013, Sorkin Type Co (www.sorkintype.com) with Reserved Font Name 'Merriweather' + +Merriweather is a trademark of Sorkin Type Co. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/OpenSans.scss b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/OpenSans.scss new file mode 100644 index 000000000..20fb380fc --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/OpenSans.scss @@ -0,0 +1,130 @@ +@font-face { + /*font-family: 'open_sanssemibold';*/ + font-family: 'Open Sans'; + src: url('opensans-semibold-webfont.eot'); + src: url('opensans-semibold-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-semibold-webfont.woff2') format('woff2'), + url('opensans-semibold-webfont.woff') format('woff'), + url('opensans-semibold-webfont.ttf') format('truetype'), + url('opensans-semibold-webfont.svg#open_sanssemibold') format('svg'); + font-weight: 600; + font-style: normal; +} + +@font-face { + /*font-family: 'open_sanssemibold_italic';*/ + font-family: 'Open Sans'; + src: url('opensans-semibolditalic-webfont.eot'); + src: url('opensans-semibolditalic-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-semibolditalic-webfont.woff2') format('woff2'), + url('opensans-semibolditalic-webfont.woff') format('woff'), + url('opensans-semibolditalic-webfont.ttf') format('truetype'), + url('opensans-semibolditalic-webfont.svg#open_sanssemibold_italic') format('svg'); + font-weight: 600; + font-style: italic; +} + +@font-face { + /*font-family: 'open_sansitalic';*/ + font-family: 'Open Sans'; + src: url('opensans-italic-webfont.eot'); + src: url('opensans-italic-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-italic-webfont.woff2') format('woff2'), + url('opensans-italic-webfont.woff') format('woff'), + url('opensans-italic-webfont.ttf') format('truetype'), + url('opensans-italic-webfont.svg#open_sansitalic') format('svg'); + font-weight: normal; + font-style: italic; +} + +@font-face { + /*font-family: 'open_sanslight';*/ + font-family: 'Open Sans'; + src: url('opensans-light-webfont.eot'); + src: url('opensans-light-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-light-webfont.woff2') format('woff2'), + url('opensans-light-webfont.woff') format('woff'), + url('opensans-light-webfont.ttf') format('truetype'), + url('opensans-light-webfont.svg#open_sanslight') format('svg'); + font-weight: 300; + font-style: normal; +} + +@font-face { + /*font-family: 'open_sanslight_italic';*/ + font-family: 'Open Sans'; + src: url('opensans-lightitalic-webfont.eot'); + src: url('opensans-lightitalic-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-lightitalic-webfont.woff2') format('woff2'), + url('opensans-lightitalic-webfont.woff') format('woff'), + url('opensans-lightitalic-webfont.ttf') format('truetype'), + url('opensans-lightitalic-webfont.svg#open_sanslight_italic') format('svg'); + font-weight: 300; + font-style: italic; +} + +@font-face { + /*font-family: 'open_sansregular';*/ + font-family: 'Open Sans'; + src: url('opensans-regular-webfont.eot'); + src: url('opensans-regular-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-regular-webfont.woff2') format('woff2'), + url('opensans-regular-webfont.woff') format('woff'), + url('opensans-regular-webfont.ttf') format('truetype'), + url('opensans-regular-webfont.svg#open_sansregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + /*font-family: 'open_sansbold';*/ + font-family: 'Open Sans'; + src: url('opensans-bold-webfont.eot'); + src: url('opensans-bold-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-bold-webfont.woff2') format('woff2'), + url('opensans-bold-webfont.woff') format('woff'), + url('opensans-bold-webfont.ttf') format('truetype'), + url('opensans-bold-webfont.svg#open_sansbold') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + /*font-family: 'open_sansbold_italic';*/ + font-family: 'Open Sans'; + src: url('opensans-bolditalic-webfont.eot'); + src: url('opensans-bolditalic-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-bolditalic-webfont.woff2') format('woff2'), + url('opensans-bolditalic-webfont.woff') format('woff'), + url('opensans-bolditalic-webfont.ttf') format('truetype'), + url('opensans-bolditalic-webfont.svg#open_sansbold_italic') format('svg'); + font-weight: bold; + font-style: italic; + +} + +@font-face { + /*font-family: 'open_sansextrabold';*/ + font-family: 'Open Sans'; + src: url('opensans-extrabold-webfont.eot'); + src: url('opensans-extrabold-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-extrabold-webfont.woff2') format('woff2'), + url('opensans-extrabold-webfont.woff') format('woff'), + url('opensans-extrabold-webfont.ttf') format('truetype'), + url('opensans-extrabold-webfont.svg#open_sansextrabold') format('svg'); + font-weight: 800; + font-style: normal; +} + +@font-face { + /*font-family: 'open_sansextrabold_italic';*/ + font-family: 'Open Sans'; + src: url('opensans-extrabolditalic-webfont.eot'); + src: url('opensans-extrabolditalic-webfont.eot?#iefix') format('embedded-opentype'), + url('opensans-extrabolditalic-webfont.woff2') format('woff2'), + url('opensans-extrabolditalic-webfont.woff') format('woff'), + url('opensans-extrabolditalic-webfont.ttf') format('truetype'), + url('opensans-extrabolditalic-webfont.svg#open_sansextrabold_italic') format('svg'); + font-weight: 800; + font-style: italic; +} \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.eot new file mode 100755 index 000000000..3bad28cde Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.svg new file mode 100755 index 000000000..1b5c1ccb7 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.ttf new file mode 100755 index 000000000..50d7100d9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff new file mode 100755 index 000000000..0c4ab055d Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff2 new file mode 100755 index 000000000..5a09b9d29 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bold-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.eot new file mode 100755 index 000000000..3b66e113f Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.svg new file mode 100755 index 000000000..7d483577f --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.ttf new file mode 100755 index 000000000..706d6155a Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff new file mode 100755 index 000000000..91670aad3 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff2 new file mode 100755 index 000000000..f1bc9f375 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-bolditalic-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.eot new file mode 100755 index 000000000..e5b9f6daf Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.svg new file mode 100755 index 000000000..e763c4a49 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.ttf new file mode 100755 index 000000000..bf4b57291 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff new file mode 100755 index 000000000..d78a7cd3f Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff2 new file mode 100755 index 000000000..1b35bb4cd Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabold-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.eot new file mode 100755 index 000000000..89ee4f217 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.svg new file mode 100755 index 000000000..6c6dce854 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.ttf new file mode 100755 index 000000000..ed6cfd660 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff new file mode 100755 index 000000000..a59fe0fc3 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff2 new file mode 100755 index 000000000..0c7d0cf26 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-extrabolditalic-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.eot new file mode 100755 index 000000000..57e8ee5f6 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.svg new file mode 100755 index 000000000..930528f49 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.ttf new file mode 100755 index 000000000..2e4dedead Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff new file mode 100755 index 000000000..2f0d79b12 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff2 new file mode 100755 index 000000000..f7e4c42af Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-italic-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.eot new file mode 100755 index 000000000..402f01df2 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.svg new file mode 100755 index 000000000..b3e3faabc --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.ttf new file mode 100755 index 000000000..0afac6e13 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff new file mode 100755 index 000000000..31b513332 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff2 new file mode 100755 index 000000000..b0f892d0f Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-light-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.eot new file mode 100755 index 000000000..a6b145cc0 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.svg new file mode 100755 index 000000000..4f3a04733 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.ttf new file mode 100755 index 000000000..929d797dd Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff new file mode 100755 index 000000000..784b29e9f Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff2 new file mode 100755 index 000000000..1b7824c30 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-lightitalic-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.eot new file mode 100755 index 000000000..9bc7cb5ed Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.svg new file mode 100755 index 000000000..3e5d0ac37 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.ttf new file mode 100755 index 000000000..4acdd3938 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff new file mode 100755 index 000000000..5b4ee0cc9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff2 new file mode 100755 index 000000000..3388b9723 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-regular-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.eot new file mode 100755 index 000000000..0187f5402 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.svg new file mode 100755 index 000000000..b94dca758 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.ttf new file mode 100755 index 000000000..800532dde Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff new file mode 100755 index 000000000..bd68ac40c Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff2 new file mode 100755 index 000000000..74f98f830 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibold-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.eot new file mode 100755 index 000000000..26f82dafd Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.svg new file mode 100755 index 000000000..a687cd087 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.svg @@ -0,0 +1,1824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.ttf new file mode 100755 index 000000000..e9c381b9a Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff new file mode 100755 index 000000000..9038a2af5 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff2 new file mode 100755 index 000000000..5ca516590 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/OpenSans/opensans-semibolditalic-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/OFL.txt new file mode 100644 index 000000000..905e1d607 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2012, vernon adams (vern@newtypography.co.uk), with Reserved Font Names 'Oxygen' +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Bold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Bold.ttf new file mode 100644 index 000000000..835ab0536 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Bold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Light.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Light.ttf new file mode 100644 index 000000000..08b9fec30 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Light.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Regular.ttf new file mode 100644 index 000000000..a66ddf1c8 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen/Oxygen-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OFL.txt new file mode 100644 index 000000000..905e1d607 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2012, vernon adams (vern@newtypography.co.uk), with Reserved Font Names 'Oxygen' +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OxygenMono-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OxygenMono-Regular.ttf new file mode 100644 index 000000000..64c165c92 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/Oxygen_Mono/OxygenMono-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.eot b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..9b6afaedc Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.svg b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..d05688e9e --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.svg @@ -0,0 +1,655 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.ttf b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..26dea7951 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..dc35ce3c2 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff2 b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..500e51725 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/fontawesome-webfont.woff2 differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Bold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Bold.ttf new file mode 100644 index 000000000..1d623bfe9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Bold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf new file mode 100644 index 000000000..89e1809d3 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-BoldItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf new file mode 100644 index 000000000..1d1662ff6 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf new file mode 100644 index 000000000..5decede38 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-ExtraBoldItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Italic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Italic.ttf new file mode 100644 index 000000000..ed36a44f8 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Italic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Light.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Light.ttf new file mode 100644 index 000000000..bb0294779 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Light.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-LightItalic.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-LightItalic.ttf new file mode 100644 index 000000000..affa9ca50 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-LightItalic.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Regular.ttf new file mode 100644 index 000000000..78c591099 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/MerriweatherSans-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/OFL.txt new file mode 100644 index 000000000..6665e9d99 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/icons/Merriweather_Sans/OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2013, Sorkin Type Co (www.sorkintype.com) with Reserved Font Name 'Merriweather' + +Merriweather is a trademark of Sorkin Type Co. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/OFL.txt new file mode 100644 index 000000000..905e1d607 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2012, vernon adams (vern@newtypography.co.uk), with Reserved Font Names 'Oxygen' +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Bold.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Bold.ttf new file mode 100644 index 000000000..835ab0536 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Bold.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Light.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Light.ttf new file mode 100644 index 000000000..08b9fec30 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Light.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Regular.ttf new file mode 100644 index 000000000..a66ddf1c8 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen/Oxygen-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OFL.txt b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OFL.txt new file mode 100644 index 000000000..905e1d607 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2012, vernon adams (vern@newtypography.co.uk), with Reserved Font Names 'Oxygen' +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OxygenMono-Regular.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OxygenMono-Regular.ttf new file mode 100644 index 000000000..64c165c92 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/Oxygen_Mono/OxygenMono-Regular.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.eot b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.eot new file mode 100644 index 000000000..41f223e4d Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.eot differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.ttf b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.ttf new file mode 100644 index 000000000..331ea3a1b Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.ttf differ diff --git a/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.woff b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.woff new file mode 100644 index 000000000..b0771de82 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/fonts/icons/entypo.woff differ diff --git a/src/Bundle/ChillMain/Resources/public/img/background/desert.jpg b/src/Bundle/ChillMain/Resources/public/img/background/desert.jpg new file mode 100644 index 000000000..6872698f2 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/background/desert.jpg differ diff --git a/src/Bundle/ChillMain/Resources/public/img/background/kids-1.jpg b/src/Bundle/ChillMain/Resources/public/img/background/kids-1.jpg new file mode 100644 index 000000000..9c55fdc52 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/background/kids-1.jpg differ diff --git a/src/Bundle/ChillMain/Resources/public/img/background/kids-2.jpg b/src/Bundle/ChillMain/Resources/public/img/background/kids-2.jpg new file mode 100644 index 000000000..8d7ff1301 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/background/kids-2.jpg differ diff --git a/src/Bundle/ChillMain/Resources/public/img/background/sun.jpg b/src/Bundle/ChillMain/Resources/public/img/background/sun.jpg new file mode 100644 index 000000000..46f476ea0 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/background/sun.jpg differ diff --git a/src/Bundle/ChillMain/Resources/public/img/favicon-32.xcf b/src/Bundle/ChillMain/Resources/public/img/favicon-32.xcf new file mode 100644 index 000000000..19b04f0a9 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/favicon-32.xcf differ diff --git a/src/Bundle/ChillMain/Resources/public/img/favicon.ico b/src/Bundle/ChillMain/Resources/public/img/favicon.ico new file mode 100644 index 000000000..baa007811 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/favicon.ico differ diff --git a/src/Bundle/ChillMain/Resources/public/img/favicon.svg b/src/Bundle/ChillMain/Resources/public/img/favicon.svg new file mode 100644 index 000000000..372c72ba3 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/img/favicon.svg @@ -0,0 +1,150 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.png b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.png new file mode 100644 index 000000000..340cbb8d8 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.png differ diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.svg b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.svg new file mode 100644 index 000000000..6472424fc --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement.svg @@ -0,0 +1,192 @@ + + + +image/svg+xmll'accompagnement social facile +CHILL + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.png b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.png new file mode 100644 index 000000000..5f6a52bfe Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.png differ diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.svg b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.svg new file mode 100644 index 000000000..80974c536 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/img/logo-chill-outil-accompagnement_white.svg @@ -0,0 +1,192 @@ + + + +image/svg+xmll'accompagnement social facile +CHILL + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.png b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.png new file mode 100644 index 000000000..6a488da86 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.png differ diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.svg b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.svg new file mode 100644 index 000000000..19d70300f --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan.svg @@ -0,0 +1,167 @@ + + + +image/svg+xmlCHILL + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.png b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.png new file mode 100644 index 000000000..eb6c936b5 Binary files /dev/null and b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.png differ diff --git a/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.svg b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.svg new file mode 100644 index 000000000..3ca5e7bf8 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/img/logo-chill-sans-slogan_white.svg @@ -0,0 +1,167 @@ + + + +image/svg+xmlCHILL + \ No newline at end of file diff --git a/src/Bundle/ChillMain/Resources/public/js/chill.js b/src/Bundle/ChillMain/Resources/public/js/chill.js new file mode 100644 index 000000000..23f339b32 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/js/chill.js @@ -0,0 +1,425 @@ +/* jslint vars: true */ +/*jslint indent: 4 */ +/* global moment, $, window */ +'use strict'; + +var chill = function() { + + /* intialiase the pikaday module */ + function initPikaday(locale) { + var i18n_trad = { + fr: { + previousMonth : 'Mois précédent', + nextMonth : 'Mois suivant', + months : ['Janvier','Février','Mars','Avril','Mai','Juin','Juillet','Août','Septembre','Octobre','Novembre','Décembre'], + weekdays : ['Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi'], + weekdaysShort : ['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'] + }, + nl: { + previousMonth : 'Vorig maand', + nextMonth : 'Volgende maand', + months : ['Januari','Februari','Maart','April','Mei','Juni','Juli','Augustus','September','Oktober','November','December'], + weekdays : ['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'], + weekdaysShort : ['Zon','Ma','Di','Wo','Do','Vri','Zat'] + } + }; + + var pikaday_options = { + format: 'D-M-YYYY', + yearRange: [parseInt(moment().format('YYYY')) - 100, parseInt(moment().format('YYYY'))], + }; + + if(locale in i18n_trad) { + pikaday_options.i18n = i18n_trad[locale]; + } + + $('.datepicker').pikaday( + pikaday_options + ); + } + + /* emulate the position:sticky */ + function emulateSticky() { + var need_emulation = false; + + $('.sticky-form-buttons').each(function(i,stick_element) { + if($(stick_element).css('position') !== 'sticky') { + need_emulation = true; + stick_element.init_offset_top = $(stick_element).offset().top; + } + }); + + function emulate() { + $('.sticky-form-buttons').each(function(i,stick_element) { + if (($(window).scrollTop() + $(window).height()) < stick_element.init_offset_top) { + //sticky at bottom + $(stick_element).css('position','fixed'); + $(stick_element).css('bottom','0'); + $(stick_element).css('top',''); + $(stick_element).css('width',$(stick_element).parent().outerWidth()); + } else if (stick_element.init_offset_top < $(window).scrollTop()) { + //sticky at top + $(stick_element).css('position','fixed'); + $(stick_element).css('top','0'); + $(stick_element).css('bottom',''); + $(stick_element).css('width',$(stick_element).parent().outerWidth()); + } else { + //no sticky + $(stick_element).css('position','initial'); + $(stick_element).css('bottom',''); + $(stick_element).css('width',''); + $(stick_element).css('top',''); + } + }); + } + + if(need_emulation) { + $(window).scroll(function() { + emulate(); + }); + + emulate(); + } + } + + + /** + * Display an alert message when the user wants to leave a page containing a given form + * in a given state. + * + * The action of displaying the form be parametrised as : + * - always display the alert message when leaving + * - only display the alert message when the form contains some modified fields. + * + * @param{string} form_id An identification string of the form + * @param{string} alert_message The alert message to display + * @param{boolean} check_unsaved_data If true display the alert message only when the form + * contains some modified fields otherwise always display the alert when leaving + * @return nothing + */ + function _generalDisplayAlertWhenLeavingForm(form_id, alert_message, check_unsaved_data) { + var form_submitted = false; + var unsaved_data = false; + + $(form_id) + .submit(function() { + form_submitted = true; + }) + .on('reset', function() { + unsaved_data = false; + }) + ; + + $.each($(form_id).find(':input'), function(i,e) { + $(e).change(function() { + unsaved_data = true; + }); + }); + + $(window).bind('beforeunload', function(){ + if((!form_submitted) && (unsaved_data || !check_unsaved_data)) { + return alert_message; + } + }); + } + + /** + * Mark the choices "not specified" as check by default. + * + * This function apply to `custom field choices` when the `required` + * option is false and `expanded` is true (checkboxes or radio buttons). + * + * @param{string} choice_name the name of the input + */ + function checkNullValuesInChoices(choice_name) { + var choices; + choices = $("input[name='"+choice_name+"']:checked"); + if (choices.size() === 0) { + $.each($("input[name='"+choice_name+"']"), function (i, e) { + if (e.value === "") { + e.checked = true; + } + }); + } + } + + /** + * Display an alert message when the user wants to leave a page containing a given + * modified form. + * + * @param{string} form_id An identification string of the form + * @param{string} alert_message The alert message to display + * @return nothing + */ + function displayAlertWhenLeavingModifiedForm(form_id, alert_message) { + _generalDisplayAlertWhenLeavingForm(form_id, alert_message, true); + } + + /** + * Display an alert message when the user wants to leave a page containing a given + * form that was not submitted. + * + * @param{string} form_id An identification string of the form + * @param{string} alert_message The alert message to display + * @return nothing + */ + function displayAlertWhenLeavingUnsubmittedForm(form_id, alert_message) { + _generalDisplayAlertWhenLeavingForm(form_id, alert_message, false); + } + + /* Enable the following behavior : when the user change the value + of an other field, its checkbox is checked. + */ + function checkOtherValueOnChange() { + $('.input-text-other-value').each(function() { + $(this).change(function() { + var checkbox = $(this).parent().find('input[type=checkbox][value=_other]')[0]; + $(checkbox).prop('checked', ($(this).val() !== '')); + }); + }); + } + + /** + * Create an interraction between two select element (the parent and the + * child) of a given form : each parent option has a category, the + * child select only display options that have the same category of the + * parent optionn + * + * The parent must have the class "chill-category-link-parent". + * + * The children must have the class "chill-category-link-child". Each option + * of the parent must have the attribute `data-link-category`, with the value of + * the connected option in parent. + * + * Example : + * + * ```html + * + * + * + * ``` + * + * TODO ECRIRE LA DOC METTRE LES TESTS DANS git : + * tester que init est ok : + - quand vide + - quand choix + * tester que quand sélection + - quand vide + - quand choix + */ + function categoryLinkParentChildSelect() { + var forms_to_link = $('form:has(select.chill-category-link-parent)'); + + forms_to_link.each(function(i,form_selector) { + var form = $(form_selector), parent_multiple; + form.old_category = null; + form.link_parent = $(form).find('.chill-category-link-parent'); + form.link_child = $(form).find('.chill-category-link-child'); + + // check if the parent allow multiple or single results + parent_multiple = $(form).find('.chill-category-link-parent').get(0).multiple; + // if we use select2, parent_multiple will be `undefined` + if (typeof parent_multiple == 'undefined') { + // currently, I do not know how to check if multiple using select2. + // we suppose that multiple is false (old behaviour) + parent_multiple = false + } + + $(form.link_parent).addClass('select2'); + $(form.link_parant).select2({allowClear: true}); // it is weird: when I fix the typo here, the whole stuff does not work anymore... + + if (parent_multiple == false) { + + form.old_category = null; + if($(form.link_parent).select2('data') !== null) { + form.old_category = ($(form.link_parent).select2('data').element[0].dataset.linkCategory); + } + + $(form.link_child).find('option') + .each(function(i,e) { + if( + ((!$(e).data('link-category')) || $(e).data('link-category') == form.old_category) && + ((!$(e).data('link-categories')) || form.old_category in $(e).data('link-categories').split(',')) + ) { + $(e).show(); + // here, we should handle the optgroup + } else { + $(e).hide(); + // here, we should handle the optgroup + } + }); + + form.link_parent.change(function() { + var new_category = ($(form.link_parent).select2('data').element[0].dataset.linkCategory); + if(new_category != form.old_category) { + $(form.link_child).find('option') + .each(function(i,e) { + if( + ((!$(e).data('link-category')) || $(e).data('link-category') == new_category) && + ((!$(e).data('link-categories')) || new_category in $(e).data('link-categories').split(',')) + ) { + $(e).show(); + // here, we should handle the optgroup + } else { + $(e).hide(); + // here, we should handle the opgroup + } + }); + $(form.link_child).find('option')[0].selected = true; + form.old_category = new_category; + } + }); + } else { + var i=0, + selected_items = $(form.link_parent).find(':selected'); + + form.old_categories = []; + for (i=0;i < selected_items.length; i++) { + form.old_categories.push(selected_items[i].value); + } + + $(form.link_child).find('option') + .each(function(i,e) { + var visible; + if(form.old_categories.indexOf(e.dataset.linkCategory) != -1) { + $(e).show(); + if (e.parentNode instanceof HTMLOptGroupElement) { + $(e.parentNode).show(); + } + } else { + $(e).hide(); + if (e.parentNode instanceof HTMLOptGroupElement) { + // we check if the other options are visible. + visible = false + for (var l=0; l < e.parentNode.children.length; l++) { + if (window.getComputedStyle(e.parentNode.children[l]).getPropertyValue('display') != 'none') { + visible = true; + } + } + // if any options are visible, we hide the optgroup + if (visible == false) { + $(e.parentNode).hide(); + } + } + } + }); + + form.link_parent.change(function() { + var new_categories = [], + selected_items = $(form.link_parent).find(':selected'), + visible; + for (i=0;i < selected_items.length; i++) { + new_categories.push(selected_items[i].value); + } + + if(new_categories != form.old_categories) { + $(form.link_child).find('option') + .each(function(i,e) { + if(new_categories.indexOf(e.dataset.linkCategory) != -1) { + $(e).show(); + // if parent is an opgroup, we show it + if (e.parentNode instanceof HTMLOptGroupElement) { + $(e.parentNode).show(); + } + } else { + $(e).hide(); + // we check if the parent is an optgroup + if (e.parentNode instanceof HTMLOptGroupElement) { + // we check if other options are visible + visible = false + for (var l=0; l < e.parentNode.children.length; l++) { + if (window.getComputedStyle(e.parentNode.children[l]).getPropertyValue('display') != 'none') { + visible = true; + } + } + // if any options are visible, we hide the optgroup + if (visible == false) { + $(e.parentNode).hide(); + } + } + } + }); + //$(form.link_child).find('option')[0].selected = true; + form.old_categories = new_categories; + } + }); + } + }); + } + + function _displayHideTargetWithCheckbox(checkbox) { + var target = checkbox.dataset.displayTarget, + hideableElements; + + hideableElements = document.querySelectorAll('[data-display-show-hide="' + target + '"]'); + + if (checkbox.checked) { + for (let i=0; i < hideableElements.length; i = i+1) { + hideableElements[i].style.display = "unset"; + } + } else { + for (let i=0; i < hideableElements.length; i = i+1) { + hideableElements[i].style.display = "none"; + } + } + + } + + /** + * create an interaction between a checkbox and element to show if the + * checkbox is checked, or hide if the checkbox is not checked. + * + * The checkbox must have the data `data-display-target` with an id, + * and the parts to show/hide must have the data `data-display-show-hide` + * with the same value. + * + * Example : + * + * ``` + * + * + *
+ * + *
+ * ``` + * + * Hint: for forms in symfony, you could use the `id` of the form element, + * accessible through `{{ form.vars.id }}`. This id should be unique. + * + * + * @returns {undefined} + */ + function listenerDisplayCheckbox() { + var elements = document.querySelectorAll("[data-display-target]"); + + for (let i=0; i < elements.length; i = i+1) { + elements[i].addEventListener("change", function(e) { + _displayHideTargetWithCheckbox(e.target); + }); + // initial display-hide + _displayHideTargetWithCheckbox(elements[i]); + } + } + + return { + initPikaday: initPikaday, + emulateSticky: emulateSticky, + checkOtherValueOnChange: checkOtherValueOnChange, + displayAlertWhenLeavingModifiedForm: displayAlertWhenLeavingModifiedForm, + displayAlertWhenLeavingUnsubmittedForm: displayAlertWhenLeavingUnsubmittedForm, + checkNullValuesInChoices: checkNullValuesInChoices, + categoryLinkParentChildSelect: categoryLinkParentChildSelect, + listenerDisplayCheckbox: listenerDisplayCheckbox, + }; +} (); + +export { chill }; diff --git a/src/Bundle/ChillMain/Resources/public/js/collection/collection.scss b/src/Bundle/ChillMain/Resources/public/js/collection/collection.scss new file mode 100644 index 000000000..ba2f9ce9e --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/js/collection/collection.scss @@ -0,0 +1,27 @@ +div.chill-collection { + ul.chill-collection__list { + list-style: none; + padding: 0; + margin-bottom: 1.5rem; + + li.chill-collection__list__entry:nth-child(2n) { + background-color: var(--chill-light-gray); + padding: 0.5rem 0; + } + + // all entries, except the last one + li.chill-collection__list__entry:nth-last-child(1n+2) { + margin-bottom: 1rem; + } + + button.chill-collection__list__remove-entry { + margin-left: 0.5rem; + } + } + + button.chill-collection__button--add { + + + } +} + diff --git a/src/Bundle/ChillMain/Resources/public/js/collection/collections.js b/src/Bundle/ChillMain/Resources/public/js/collection/collections.js new file mode 100644 index 000000000..e8e697058 --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/js/collection/collections.js @@ -0,0 +1,116 @@ +/** + * Javascript file which handle ChillCollectionType + * + * Two events are emitted by this module, both on window and on collection / ul. + * + * Collection (an UL element) and entry (a li element) are associated with those + * events. + * + * ``` + * window.addEventListener('collection-add-entry', function(e) { + * console.log(e.detail.collection); + * console.log(e.detail.entry); + * }); + * + * window.addEventListener('collection-remove-entry', function(e) { + * console.log(e.detail.collection); + * console.log(e.detail.entry); + * }); + * + * collection.addEventListener('collection-add-entry', function(e) { + * console.log(e.detail.collection); + * console.log(e.detail.entry); + * }); + * + * collection.addEventListener('collection-remove-entry', function(e) { + * console.log(e.detail.collection); + * console.log(e.detail.entry); + * }); + * ``` + */ +require('./collection.scss'); + +class CollectionEvent { + constructor(collection, entry) { + this.collection = collection; + this.entry = entry; + } +} + +/** + * + * @param {type} button + * @returns {handleAdd} + */ +var handleAdd = function(button) { + var + form_name = button.dataset.collectionAddTarget, + prototype = button.dataset.formPrototype, + collection = document.querySelector('ul[data-collection-name="'+form_name+'"]'), + entry = document.createElement('li'), + event = new CustomEvent('collection-add-entry', { detail: { collection: collection, entry: entry } }), + counter = collection.childNodes.length, + content + ; + content = prototype.replace(new RegExp('__name__', 'g'), counter); + entry.innerHTML = content; + entry.classList.add('chill-collection__list__entry'); + initializeRemove(collection, entry); + collection.appendChild(entry); + chill.initPikaday('fr'); + + collection.dispatchEvent(event); + window.dispatchEvent(event); +}; + +var initializeRemove = function(collection, entry) { + var + button = document.createElement('button'), + isPersisted = entry.dataset.collectionIsPersisted, + content = collection.dataset.collectionButtonRemoveLabel, + allowDelete = collection.dataset.collectionAllowDelete, + event = new CustomEvent('collection-remove-entry', { detail: { collection: collection, entry: entry } }) + ; + + if (allowDelete === '0' && isPersisted === '1') { + return; + } + + button.classList.add('sc-button', 'bt-delete', 'chill-collection__list__remove-entry'); + button.textContent = content; + + button.addEventListener('click', function(e) { + e.preventDefault(); + entry.remove(); + collection.dispatchEvent(event); + window.dispatchEvent(event); + }); + + entry.appendChild(button); +}; + +window.addEventListener('load', function() { + var + addButtons = document.querySelectorAll("button[data-collection-add-target]"), + collections = document.querySelectorAll("ul[data-collection-name]") + ; + + for (let i = 0; i < addButtons.length; i ++) { + let addButton = addButtons[i]; + addButton.addEventListener('click', function(e) { + e.preventDefault(); + handleAdd(e.target); + }); + } + + for (let i = 0; i < collections.length; i ++) { + let entries = collections[i].querySelectorAll(':scope > li'); + + for (let j = 0; j < entries.length; j ++) { + initializeRemove(collections[i], entries[j]); + } + } +}); + + + diff --git a/src/Bundle/ChillMain/Resources/public/js/jquery.js b/src/Bundle/ChillMain/Resources/public/js/jquery.js new file mode 100644 index 000000000..eed17778c --- /dev/null +++ b/src/Bundle/ChillMain/Resources/public/js/jquery.js @@ -0,0 +1,9210 @@ +/*! + * jQuery JavaScript Library v2.1.4 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2015-04-28T16:01Z + */ + +(function( global, factory ) { + + if ( typeof module === "object" && typeof module.exports === "object" ) { + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Support: Firefox 18+ +// Can't be in strict mode, several libs including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// + +var arr = []; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var support = {}; + + + +var + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + version = "2.1.4", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android<4.1 + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + // parseFloat NaNs numeric-cast false positives (null|true|false|"") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + // adding 1 corrects loss of precision from parseFloat (#15100) + return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0; + }, + + isPlainObject: function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.constructor && + !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + // Support: Android<4.0, iOS<6 (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call(obj) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + var script, + indirect = eval; + + code = jQuery.trim( code ); + + if ( code ) { + // If the code includes a valid, prologue position + // strict mode pragma, execute code by injecting a + // script tag into the document. + if ( code.indexOf("use strict") === 1 ) { + script = document.createElement("script"); + script.text = code; + document.head.appendChild( script ).parentNode.removeChild( script ); + } else { + // Otherwise, avoid the DOM node creation, insertion + // and removal by using an indirect global eval + indirect( code ); + } + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE9-11+ + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Support: Android<4.1 + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +}); + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + + // Support: iOS 8.2 (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.2.0-pre + * http://sizzlejs.com/ + * + * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2014-12-16 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // General-purpose constants + MAX_NEGATIVE = 1 << 31, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // http://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + characterEncoding + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + rescape = /'|\\/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }; + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + nodeType = context.nodeType; + + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + if ( !seed && documentIsHTML ) { + + // Try to shortcut find operations when possible (e.g., not under DocumentFragment) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document (jQuery #6963) + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getElementsByClassName ) { + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // QSA path + if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + nid = old = expando; + newContext = context; + newSelector = nodeType !== 1 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return !!fn( div ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( div.parentNode ) { + div.parentNode.removeChild( div ); + } + // release memory in IE + div = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = attrs.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + ( ~b.sourceIndex || MAX_NEGATIVE ) - + ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, parent, + doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + parent = doc.defaultView; + + // Support: IE>8 + // If iframe document is assigned to "document" variable and if iframe has been reloaded, + // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 + // IE6-8 do not support the defaultView property so parent will be undefined + if ( parent && parent !== parent.top ) { + // IE11 does not have attachEvent, so all must suffer + if ( parent.addEventListener ) { + parent.addEventListener( "unload", unloadHandler, false ); + } else if ( parent.attachEvent ) { + parent.attachEvent( "onunload", unloadHandler ); + } + } + + /* Support tests + ---------------------------------------------------------------------- */ + documentIsHTML = !isXML( doc ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert(function( div ) { + div.className = "i"; + return !div.getAttribute("className"); + }); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( doc.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert(function( div ) { + docElem.appendChild( div ).id = expando; + return !doc.getElementsByName || !doc.getElementsByName( expando ).length; + }); + + // ID find and filter + if ( support.getById ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [ m ] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + // Support: IE6/7 + // getElementById is not reliable as a find shortcut + delete Expr.find["ID"]; + + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + if ( documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See http://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + docElem.appendChild( div ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( div.querySelectorAll("[msallowcapture^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+ + if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push("~="); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibing-combinator selector` fails + if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push(".#.+[+~]"); + } + }); + + assert(function( div ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = doc.createElement("input"); + input.setAttribute( "type", "hidden" ); + div.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( div.querySelectorAll("[name=d]").length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + + // Choose the first element that is related to our preferred document + if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + return -1; + } + if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + return doc; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + if ( support.matchesSelector && documentIsHTML && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch (e) {} + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + (val = elem.getAttributeNode(name)) && val.specified ? + val.value : + null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( (elem = results[i++]) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + while ( (node = elem[i++]) ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[6] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[3] ) { + match[2] = match[4] || match[5] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { return true; } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + // Don't keep the element (issue #299) + input[0] = null; + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifier + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( (tokens = []) ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + }); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match + }); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (oldCache = outerCache[ dir ]) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return (newCache[ 2 ] = oldCache[ 2 ]); + } else { + // Reuse newcache so results back-propagate to previous elements + outerCache[ dir ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + len = elems.length; + + if ( outermost ) { + outermostContext = context !== document && context; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( (selector = compiled.selector || selector) ); + + results = results || []; + + // Try to minimize operations if there is no seed and only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + support.getById && context.nodeType === 9 && documentIsHTML && + Expr.relative[ tokens[1].type ] ) { + + context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert(function( div1 ) { + // Should return 1, but returns 4 (following) + return div1.compareDocumentPosition( document.createElement("div") ) & 1; +}); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert(function( div ) { + div.innerHTML = ""; + return div.firstChild.getAttribute("href") === "#" ; +}) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + }); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert(function( div ) { + div.innerHTML = ""; + div.firstChild.setAttribute( "value", "" ); + return div.firstChild.getAttribute( "value" ) === ""; +}) ) { + addHandle( "value", function( elem, name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + }); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert(function( div ) { + return div.getAttribute("disabled") == null; +}) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + (val = elem.getAttributeNode( name )) && val.specified ? + val.value : + null; + } + }); +} + +return Sizzle; + +})( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + + +var rneedsContext = jQuery.expr.match.needsContext; + +var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); + + + +var risSimple = /^.[^:#\[\.,]*$/; + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + /* jshint -W018 */ + return !!qualifier.call( elem, i, elem ) !== not; + }); + + } + + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + }); + + } + + if ( typeof qualifier === "string" ) { + if ( risSimple.test( qualifier ) ) { + return jQuery.filter( qualifier, elements, not ); + } + + qualifier = jQuery.filter( qualifier, elements ); + } + + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; + }); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 && elem.nodeType === 1 ? + jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : + jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + })); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var i, + len = this.length, + ret = [], + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = this.selector ? this.selector + " " + selector : selector; + return ret; + }, + filter: function( selector ) { + return this.pushStack( winnow(this, selector || [], false) ); + }, + not: function( selector ) { + return this.pushStack( winnow(this, selector || [], true) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +}); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + init = jQuery.fn.init = function( selector, context ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Support: Blackberry 4.6 + // gEBID returns nodes no longer in the document (#6963) + if ( elem && elem.parentNode ) { + // Inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return typeof rootjQuery.ready !== "undefined" ? + rootjQuery.ready( selector ) : + // Execute immediately if ready is not present + selector( jQuery ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.extend({ + dir: function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; + }, + + sibling: function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; + } +}); + +jQuery.fn.extend({ + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter(function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { + // Always skip document fragments + if ( cur.nodeType < 11 && (pos ? + pos.index(cur) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) ) { + + matched.push( cur ); + break; + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.unique( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +function sibling( cur, dir ) { + while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return elem.contentDocument || jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.unique( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +}); +var rnotwhite = (/\S+/g); + + + +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( list && ( !fired || stack ) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // Add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // If we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); + + +// The deferred used on DOM ready +var readyList; + +jQuery.fn.ready = function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; +}; + +jQuery.extend({ + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + jQuery( document ).off( "ready" ); + } + } +}); + +/** + * The ready event handler and self cleanup method + */ +function completed() { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + jQuery.ready(); +} + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // We once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + } else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + } + } + return readyList.promise( obj ); +}; + +// Kick off the DOM ready check even if the user does not +jQuery.ready.promise(); + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + len ? fn( elems[0], key ) : emptyGet; +}; + + +/** + * Determines whether an object can have data + */ +jQuery.acceptData = function( owner ) { + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + /* jshint -W018 */ + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + +function Data() { + // Support: Android<4, + // Old WebKit does not have Object.preventExtensions/freeze method, + // return new empty object instead with no [[set]] accessor + Object.defineProperty( this.cache = {}, 0, { + get: function() { + return {}; + } + }); + + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; +Data.accepts = jQuery.acceptData; + +Data.prototype = { + key: function( owner ) { + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return the key for a frozen object. + if ( !Data.accepts( owner ) ) { + return 0; + } + + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[ this.expando ]; + + // If not, create one + if ( !unlock ) { + unlock = Data.uid++; + + // Secure it in a non-enumerable, non-writable property + try { + descriptor[ this.expando ] = { value: unlock }; + Object.defineProperties( owner, descriptor ); + + // Support: Android<4 + // Fallback to a less secure definition + } catch ( e ) { + descriptor[ this.expando ] = unlock; + jQuery.extend( owner, descriptor ); + } + } + + // Ensure the cache object + if ( !this.cache[ unlock ] ) { + this.cache[ unlock ] = {}; + } + + return unlock; + }, + set: function( owner, data, value ) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + // Handle: [ owner, key, value ] args + if ( typeof data === "string" ) { + cache[ data ] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Fresh assignments by object are shallow copied + if ( jQuery.isEmptyObject( cache ) ) { + jQuery.extend( this.cache[ unlock ], data ); + // Otherwise, copy the properties one-by-one to the cache object + } else { + for ( prop in data ) { + cache[ prop ] = data[ prop ]; + } + } + } + return cache; + }, + get: function( owner, key ) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. A valid owner object must be provided. + var cache = this.cache[ this.key( owner ) ]; + + return key === undefined ? + cache : cache[ key ]; + }, + access: function( owner, key, value ) { + var stored; + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ((key && typeof key === "string") && value === undefined) ) { + + stored = this.get( owner, key ); + + return stored !== undefined ? + stored : this.get( owner, jQuery.camelCase(key) ); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, name, camel, + unlock = this.key( owner ), + cache = this.cache[ unlock ]; + + if ( key === undefined ) { + this.cache[ unlock ] = {}; + + } else { + // Support array or space separated string of keys + if ( jQuery.isArray( key ) ) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat( key.map( jQuery.camelCase ) ); + } else { + camel = jQuery.camelCase( key ); + // Try the string as a key before any manipulation + if ( key in cache ) { + name = [ key, camel ]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = camel; + name = name in cache ? + [ name ] : ( name.match( rnotwhite ) || [] ); + } + } + + i = name.length; + while ( i-- ) { + delete cache[ name[ i ] ]; + } + } + }, + hasData: function( owner ) { + return !jQuery.isEmptyObject( + this.cache[ owner[ this.expando ] ] || {} + ); + }, + discard: function( owner ) { + if ( owner[ this.expando ] ) { + delete this.cache[ owner[ this.expando ] ]; + } + } +}; +var data_priv = new Data(); + +var data_user = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /([A-Z])/g; + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + data_user.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend({ + hasData: function( elem ) { + return data_user.hasData( elem ) || data_priv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return data_user.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + data_user.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to data_priv methods, these can be deprecated. + _data: function( elem, name, data ) { + return data_priv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + data_priv.remove( elem, name ); + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = data_user.get( elem ); + + if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE11+ + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = jQuery.camelCase( name.slice(5) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + data_priv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + data_user.set( this, key ); + }); + } + + return access( this, function( value ) { + var data, + camelKey = jQuery.camelCase( key ); + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to get data from the cache + // with the key camelized + data = data_user.get( elem, camelKey ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, camelKey, undefined ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get( this, camelKey ); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set( this, camelKey, value ); + + // *... In the case of properties that might _actually_ + // have dashes, we need to also store a copy of that + // unchanged property. + if ( key.indexOf("-") !== -1 && data !== undefined ) { + data_user.set( this, key, value ); + } + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + data_user.remove( this, key ); + }); + } +}); + + +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = data_priv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray( data ) ) { + queue = data_priv.access( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return data_priv.get( elem, key ) || data_priv.access( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + data_priv.remove( elem, [ type + "queue", key ] ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = data_priv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var isHidden = function( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); + }; + +var rcheckableType = (/^(?:checkbox|radio)$/i); + + + +(function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Safari<=5.1 + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Safari<=5.1, Android<4.2 + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<=11+ + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; +})(); +var strundefined = typeof undefined; + + + +support.focusinBubbles = "onfocusin" in window; + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.get( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = data_priv.hasData( elem ) && data_priv.get( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnotwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + data_priv.remove( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && jQuery.acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && + jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[ type ](); + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = slice.call( arguments ), + handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or 2) have namespace(s) + // a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: Cordova 2.5 (WebKit) (#13255) + // All events should have a target; Cordova deviceready doesn't + if ( !event.target ) { + event.target = document; + } + + // Support: Safari 6.0+, Chrome<28 + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== safeActiveElement() && this.focus ) { + this.focus(); + return false; + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === safeActiveElement() && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { + this.click(); + return false; + } + }, + + // For cross-browser consistency, don't fire native .click() on links + _default: function( event ) { + return jQuery.nodeName( event.target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } +}; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + // Support: Android<4.0 + src.returnValue === false ? + returnTrue : + returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && e.preventDefault ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && e.stopPropagation ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && e.stopImmediatePropagation ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +// Support: Chrome 15+ +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// Support: Firefox, Chrome, Safari +// Create "bubbling" focus and blur events +if ( !support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this, + attaches = data_priv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + data_priv.remove( doc, fix ); + + } else { + data_priv.access( doc, fix, attaches ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); + + +var + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + + // Support: IE9 + option: [ 1, "" ], + + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] + }; + +// Support: IE9 +wrapMap.optgroup = wrapMap.option; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: 1.x compatibility +// Manipulating tables requires a tbody +function manipulationTarget( elem, content ) { + return jQuery.nodeName( elem, "table" ) && + jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? + + elem.getElementsByTagName("tbody")[0] || + elem.appendChild( elem.ownerDocument.createElement("tbody") ) : + elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + + if ( match ) { + elem.type = match[ 1 ]; + } else { + elem.removeAttribute("type"); + } + + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + data_priv.set( + elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) + ); + } +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( data_priv.hasData( src ) ) { + pdataOld = data_priv.access( src ); + pdataCur = data_priv.set( dest, pdataOld ); + events = pdataOld.events; + + if ( events ) { + delete pdataCur.handle; + pdataCur.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( data_user.hasData( src ) ) { + udataOld = data_user.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + data_user.set( dest, udataCur ); + } +} + +function getAll( context, tag ) { + var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : + context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : + []; + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], ret ) : + ret; +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = jQuery.contains( elem.ownerDocument, elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var elem, tmp, tag, wrap, contains, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: QtWebKit, PhantomJS + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; + }, + + cleanData: function( elems ) { + var data, elem, type, key, + special = jQuery.event.special, + i = 0; + + for ( ; (elem = elems[ i ]) !== undefined; i++ ) { + if ( jQuery.acceptData( elem ) ) { + key = elem[ data_priv.expando ]; + + if ( key && (data = data_priv.cache[ key ]) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + if ( data_priv.cache[ key ] ) { + // Discard any remaining `private` data + delete data_priv.cache[ key ]; + } + } + } + // Discard any remaining `user` data + delete data_user.cache[ elem[ data_user.expando ] ]; + } + } +}); + +jQuery.fn.extend({ + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each(function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + }); + }, null, value, arguments.length ); + }, + + append: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip( arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + remove: function( selector, keepData /* Internal Use Only */ ) { + var elem, + elems = selector ? jQuery.filter( selector, this ) : this, + i = 0; + + for ( ; (elem = elems[i]) != null; i++ ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var arg = arguments[ 0 ]; + + // Make the changes, replacing each context element with the new content + this.domManip( arguments, function( elem ) { + arg = this.parentNode; + + jQuery.cleanData( getAll( this ) ); + + if ( arg ) { + arg.replaceChild( elem, this ); + } + }); + + // Force removal if there was no new content (e.g., from empty arguments) + return arg && (arg.length || arg.nodeType) ? this : this.remove(); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, callback ) { + + // Flatten any nested arrays + args = concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[ 0 ], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + self.domManip( args, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + // Support: QtWebKit + // jQuery.merge because push.apply(_, arraylike) throws + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( this[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl ) { + jQuery._evalUrl( node.src ); + } + } else { + jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); + } + } + } + } + } + } + + return this; + } +}); + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: QtWebKit + // .get() because push.apply(_, arraylike) throws + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + + +var iframe, + elemdisplay = {}; + +/** + * Retrieve the actual display of a element + * @param {String} name nodeName of the element + * @param {Object} doc Document object + */ +// Called only from within defaultDisplay +function actualDisplay( name, doc ) { + var style, + elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), + + // getDefaultComputedStyle might be reliably used only on attached element + display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? + + // Use of this method is a temporary fix (more like optimization) until something better comes along, + // since it was removed from specification and supported only in FF + style.display : jQuery.css( elem[ 0 ], "display" ); + + // We don't have any data stored on the element, + // so use "detach" method as fast way to get rid of the element + elem.detach(); + + return display; +} + +/** + * Try to determine the default display value of an element + * @param {String} nodeName + */ +function defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + + // Use the already-created iframe if possible + iframe = (iframe || jQuery( "