mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-11-04 11:18:25 +00:00 
			
		
		
		
	Compare commits
	
		
			17 Commits
		
	
	
		
			feature/en
			...
			refactorin
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1f9b4ddd79 | ||
| 
						 | 
					a9bdb1fe3b | ||
| 
						 | 
					92b6f4e206 | ||
| 
						 | 
					c019da9bcf | ||
| 
						 | 
					5146419e9a | ||
| 
						 | 
					11036e84f6 | ||
| 
						 | 
					1f488109e3 | ||
| 
						 | 
					d2a93f0763 | ||
| 
						 | 
					c5940c1263 | ||
| 
						 | 
					7f602ffa47 | ||
| 
						 | 
					ac9d0242ad | ||
| 
						 | 
					e9a7a05fb7 | ||
| 
						 | 
					174d873398 | ||
| 
						 | 
					55695b7d2c | ||
| 
						 | 
					ca3cb787d7 | ||
| 
						 | 
					84bb56664a | ||
| 
						 | 
					f26eb06276 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,8 +1,8 @@
 | 
			
		||||
.composer/*
 | 
			
		||||
composer.phar
 | 
			
		||||
composer.lock
 | 
			
		||||
 | 
			
		||||
docs/build/
 | 
			
		||||
.php_cs.cache
 | 
			
		||||
 | 
			
		||||
###> symfony/framework-bundle ###
 | 
			
		||||
/.env.local
 | 
			
		||||
@@ -19,4 +19,3 @@ docs/build/
 | 
			
		||||
/phpunit.xml
 | 
			
		||||
.phpunit.result.cache
 | 
			
		||||
###< phpunit/phpunit ###
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,14 @@ before_script:
 | 
			
		||||
  - PGPASSWORD=$POSTGRES_PASSWORD psql -U $POSTGRES_USER -h db -c "CREATE EXTENSION IF NOT EXISTS unaccent; CREATE EXTENSION IF NOT EXISTS pg_trgm;"
 | 
			
		||||
  # Install and run Composer
 | 
			
		||||
  - curl -sS https://getcomposer.org/installer | php
 | 
			
		||||
  - php -d memory_limit=2G composer.phar install
 | 
			
		||||
  - php composer.phar install
 | 
			
		||||
  - php tests/app/bin/console doctrine:migrations:migrate -n
 | 
			
		||||
  - php -d memory_limit=2G tests/app/bin/console doctrine:fixtures:load -n
 | 
			
		||||
  - echo "before_script finished"
 | 
			
		||||
  - php tests/app/bin/console doctrine:fixtures:load -n
 | 
			
		||||
 | 
			
		||||
# Bring in any services we need http://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-a-service
 | 
			
		||||
# See http://docs.gitlab.com/ee/ci/services/README.html for examples.
 | 
			
		||||
services:
 | 
			
		||||
  - name: postgis/postgis:12-3.1-alpine
 | 
			
		||||
  - name: postgres:12
 | 
			
		||||
    alias: db
 | 
			
		||||
  - name: redis
 | 
			
		||||
    alias: redis
 | 
			
		||||
@@ -39,4 +38,4 @@ variables:
 | 
			
		||||
# Run our tests
 | 
			
		||||
test:
 | 
			
		||||
  script:
 | 
			
		||||
    - php -d memory_limit=3G bin/phpunit --colors=never
 | 
			
		||||
    - bin/phpunit --colors=never
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										661
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										661
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,661 +0,0 @@
 | 
			
		||||
                    GNU AFFERO GENERAL PUBLIC LICENSE
 | 
			
		||||
                       Version 3, 19 November 2007
 | 
			
		||||
 | 
			
		||||
 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 | 
			
		||||
 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.
 | 
			
		||||
 | 
			
		||||
    <one line to give the program's name and a brief idea of what it does.>
 | 
			
		||||
    Copyright (C) <year>  <name of author>
 | 
			
		||||
 | 
			
		||||
    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 <https://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
<https://www.gnu.org/licenses/>.
 | 
			
		||||
							
								
								
									
										123
									
								
								composer.json
									
									
									
									
									
								
							
							
						
						
									
										123
									
								
								composer.json
									
									
									
									
									
								
							@@ -1,69 +1,9 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "chill-project/chill-bundles",
 | 
			
		||||
    "license": "AGPL-3.0-only",
 | 
			
		||||
    "type": "library",
 | 
			
		||||
    "description": "Most used bundles for chill-project",
 | 
			
		||||
    "keywords": [
 | 
			
		||||
        "chill",
 | 
			
		||||
        "social worker"
 | 
			
		||||
    ],
 | 
			
		||||
    "license": "AGPL-3.0-only",
 | 
			
		||||
    "require": {
 | 
			
		||||
        "champs-libres/async-uploader-bundle": "dev-sf4",
 | 
			
		||||
        "composer/package-versions-deprecated": "^1.10",
 | 
			
		||||
        "doctrine/doctrine-bundle": "^2.1",
 | 
			
		||||
        "doctrine/doctrine-migrations-bundle": "^3.0",
 | 
			
		||||
        "doctrine/orm": "^2.7",
 | 
			
		||||
        "drupol/php-conventions": "^5.0",
 | 
			
		||||
        "erusev/parsedown": "^1.7",
 | 
			
		||||
        "graylog2/gelf-php": "^1.5",
 | 
			
		||||
        "knplabs/knp-menu": "^3.1",
 | 
			
		||||
        "knplabs/knp-menu-bundle": "^3.0",
 | 
			
		||||
        "knplabs/knp-time-bundle": "^1.12",
 | 
			
		||||
        "league/csv": "^9.6",
 | 
			
		||||
        "phpoffice/phpspreadsheet": "^1.16",
 | 
			
		||||
        "sensio/framework-extra-bundle": "^5.5",
 | 
			
		||||
        "symfony/asset": "4.*",
 | 
			
		||||
        "symfony/browser-kit": "^5.2",
 | 
			
		||||
        "symfony/css-selector": "^5.2",
 | 
			
		||||
        "symfony/expression-language": "4.*",
 | 
			
		||||
        "symfony/form": "4.*",
 | 
			
		||||
        "symfony/intl": "4.*",
 | 
			
		||||
        "symfony/monolog-bundle": "^3.5",
 | 
			
		||||
        "symfony/security-bundle": "4.*",
 | 
			
		||||
        "symfony/serializer": "^5.2",
 | 
			
		||||
        "symfony/swiftmailer-bundle": "^3.5",
 | 
			
		||||
        "symfony/templating": "4.*",
 | 
			
		||||
        "symfony/translation": "4.*",
 | 
			
		||||
        "symfony/twig-bundle": "^4.4",
 | 
			
		||||
        "symfony/validator": "4.*",
 | 
			
		||||
        "symfony/webpack-encore-bundle": "^1.11",
 | 
			
		||||
        "symfony/workflow": "4.*",
 | 
			
		||||
        "symfony/yaml": "4.*",
 | 
			
		||||
        "twig/extra-bundle": "^2.12 || ^3.0",
 | 
			
		||||
        "twig/intl-extra": "^3.0",
 | 
			
		||||
        "twig/markdown-extra": "^3.3",
 | 
			
		||||
        "twig/twig": "^2.12 || ^3.0"
 | 
			
		||||
    },
 | 
			
		||||
    "conflict": {
 | 
			
		||||
        "symfony/symfony": "*"
 | 
			
		||||
    },
 | 
			
		||||
    "require-dev": {
 | 
			
		||||
        "doctrine/doctrine-fixtures-bundle": "^3.3",
 | 
			
		||||
        "fakerphp/faker": "^1.13",
 | 
			
		||||
        "nelmio/alice": "^3.8",
 | 
			
		||||
        "phpunit/phpunit": "^7.0",
 | 
			
		||||
        "symfony/debug-bundle": "^5.1",
 | 
			
		||||
        "symfony/dotenv": "^5.1",
 | 
			
		||||
        "symfony/maker-bundle": "^1.20",
 | 
			
		||||
        "symfony/phpunit-bridge": "^5.2",
 | 
			
		||||
        "symfony/stopwatch": "^5.1",
 | 
			
		||||
        "symfony/var-dumper": "4.*",
 | 
			
		||||
        "symfony/web-profiler-bundle": "^5.0"
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
        "bin-dir": "bin",
 | 
			
		||||
        "vendor-dir": "tests/app/vendor"
 | 
			
		||||
    },
 | 
			
		||||
    "keywords": ["chill", "social worker"],
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "Chill\\ActivityBundle\\": "src/Bundle/ChillActivityBundle",
 | 
			
		||||
@@ -73,7 +13,7 @@
 | 
			
		||||
            "Chill\\EventBundle\\": "src/Bundle/ChillEventBundle",
 | 
			
		||||
            "Chill\\FamilyMemberBundle\\": "src/Bundle/ChillFamilyMemberBundle",
 | 
			
		||||
            "Chill\\MainBundle\\": "src/Bundle/ChillMainBundle",
 | 
			
		||||
            "Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle",
 | 
			
		||||
            "Chill\\PersonBundle\\": "src/Bundle/ChillPersonBundle/src",
 | 
			
		||||
            "Chill\\ReportBundle\\": "src/Bundle/ChillReportBundle",
 | 
			
		||||
            "Chill\\TaskBundle\\": "src/Bundle/ChillTaskBundle",
 | 
			
		||||
            "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle"
 | 
			
		||||
@@ -81,13 +21,68 @@
 | 
			
		||||
    },
 | 
			
		||||
    "autoload-dev": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "App\\": "tests/app/src/"
 | 
			
		||||
          "App\\": "tests/app/src/"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "require": {
 | 
			
		||||
        "champs-libres/async-uploader-bundle": "dev-sf4",
 | 
			
		||||
        "graylog2/gelf-php": "^1.5",
 | 
			
		||||
        "symfony/form": "4.*",
 | 
			
		||||
        "symfony/twig-bundle": "^4.4",
 | 
			
		||||
        "twig/extra-bundle": "^2.12|^3.0",
 | 
			
		||||
        "twig/twig": "^2.12|^3.0",
 | 
			
		||||
        "composer/package-versions-deprecated": "^1.10",
 | 
			
		||||
        "doctrine/doctrine-bundle": "^2.1",
 | 
			
		||||
        "doctrine/doctrine-migrations-bundle": "^3.0",
 | 
			
		||||
        "doctrine/orm": "^2.7",
 | 
			
		||||
        "symfony/asset": "4.*",
 | 
			
		||||
        "symfony/monolog-bundle": "^3.5",
 | 
			
		||||
        "symfony/security-bundle": "4.*",
 | 
			
		||||
        "symfony/translation": "4.*",
 | 
			
		||||
        "symfony/validator": "4.*",
 | 
			
		||||
        "sensio/framework-extra-bundle": "^5.5",
 | 
			
		||||
        "symfony/yaml": "4.*",
 | 
			
		||||
        "symfony/webpack-encore-bundle": "^1.11",
 | 
			
		||||
        "knplabs/knp-menu": "^3.1",
 | 
			
		||||
        "knplabs/knp-menu-bundle": "^3.0",
 | 
			
		||||
        "symfony/templating": "4.*",
 | 
			
		||||
        "twig/intl-extra": "^3.0",
 | 
			
		||||
        "symfony/workflow": "4.*",
 | 
			
		||||
        "symfony/expression-language": "4.*",
 | 
			
		||||
        "knplabs/knp-time-bundle": "^1.12",
 | 
			
		||||
        "symfony/intl": "4.*",
 | 
			
		||||
        "symfony/swiftmailer-bundle": "^3.5",
 | 
			
		||||
        "league/csv": "^9.6",
 | 
			
		||||
        "phpoffice/phpspreadsheet": "^1.16",
 | 
			
		||||
        "symfony/browser-kit": "^5.2",
 | 
			
		||||
        "symfony/css-selector": "^5.2",
 | 
			
		||||
        "twig/markdown-extra": "^3.3",
 | 
			
		||||
        "erusev/parsedown": "^1.7",
 | 
			
		||||
        "symfony/serializer": "^5.2"
 | 
			
		||||
    },
 | 
			
		||||
    "conflict": {
 | 
			
		||||
        "symfony/symfony": "*"
 | 
			
		||||
    },
 | 
			
		||||
    "require-dev": {
 | 
			
		||||
        "fakerphp/faker": "^1.13",
 | 
			
		||||
        "phpunit/phpunit": "^7.0",
 | 
			
		||||
        "symfony/dotenv": "^5.1",
 | 
			
		||||
        "symfony/maker-bundle": "^1.20",
 | 
			
		||||
        "doctrine/doctrine-fixtures-bundle": "^3.3",
 | 
			
		||||
        "symfony/stopwatch": "^5.1",
 | 
			
		||||
        "symfony/web-profiler-bundle": "^5.0",
 | 
			
		||||
        "symfony/var-dumper": "4.*",
 | 
			
		||||
        "symfony/debug-bundle": "^5.1",
 | 
			
		||||
        "symfony/phpunit-bridge": "^5.2"
 | 
			
		||||
    },
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "auto-scripts": {
 | 
			
		||||
            "cache:clear": "symfony-cmd",
 | 
			
		||||
            "assets:install %PUBLIC_DIR%": "symfony-cmd"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "config": {
 | 
			
		||||
      "vendor-dir": "tests/app/vendor",
 | 
			
		||||
      "bin-dir": "bin"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ use Chill\MainBundle\Export\FormatterInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
@@ -25,95 +25,94 @@ class CountPerson implements ExportInterface
 | 
			
		||||
     * @var EntityManagerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $entityManager;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function __construct(
 | 
			
		||||
            EntityManagerInterface $em
 | 
			
		||||
            )
 | 
			
		||||
    {
 | 
			
		||||
        $this->entityManager = $em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getType()
 | 
			
		||||
    {
 | 
			
		||||
        return Declarations::PERSON_TYPE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getDescription()
 | 
			
		||||
    {
 | 
			
		||||
        return "Count peoples by various parameters.";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getTitle()
 | 
			
		||||
    {
 | 
			
		||||
        return "Count peoples";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function requiredRole()
 | 
			
		||||
    {
 | 
			
		||||
        return new Role(PersonVoter::STATS);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function initiateQuery(array $requiredModifiers,  array $acl, array $data = array())
 | 
			
		||||
    {
 | 
			
		||||
        // we gather all center the user choose.
 | 
			
		||||
        $centers = array_map(function($el) { return $el['center']; }, $acl);
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        $qb = $this->entityManager->createQueryBuilder();
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        $qb->select('COUNT(person.id) AS export_result')
 | 
			
		||||
                ->from('ChillPersonBundle:Person', 'person')
 | 
			
		||||
                ->join('person.center', 'center')
 | 
			
		||||
                ->andWhere('center IN (:authorized_centers)')
 | 
			
		||||
                ->setParameter('authorized_centers', $centers);
 | 
			
		||||
                ;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        
 | 
			
		||||
        return $qb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getResult($qb, $data)
 | 
			
		||||
    {
 | 
			
		||||
        return $qb->getQuery()->getResult(Query::HYDRATE_SCALAR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getQueryKeys($data)
 | 
			
		||||
    {
 | 
			
		||||
        // this array match the result keys in the query. We have only
 | 
			
		||||
        // one column.
 | 
			
		||||
        return array('export_result');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getLabels($key, array $values, $data)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        // the Closure which will be executed by the formatter.
 | 
			
		||||
        
 | 
			
		||||
        // the Closure which will be executed by the formatter. 
 | 
			
		||||
        return function($value) {
 | 
			
		||||
            switch($value) {
 | 
			
		||||
                case '_header':
 | 
			
		||||
                  // we have to process specifically the '_header' string,
 | 
			
		||||
                  // we have to process specifically the '_header' string, 
 | 
			
		||||
                  // which will be used by the formatter to show a column title
 | 
			
		||||
                  return $this->getTitle();
 | 
			
		||||
                default:
 | 
			
		||||
                  // for all value, we do not process them and return them
 | 
			
		||||
                  // immediatly
 | 
			
		||||
                  return $value;
 | 
			
		||||
            };
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function getAllowedFormattersTypes()
 | 
			
		||||
    {
 | 
			
		||||
        return array(FormatterInterface::TYPE_TABULAR);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder) {
 | 
			
		||||
        // this export does not add any form
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function supportsModifiers()
 | 
			
		||||
    {
 | 
			
		||||
        // explain the export manager which formatters and filters are allowed
 | 
			
		||||
        return array(Declarations::PERSON_TYPE, Declarations::PERSON_IMPLIED_IN);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,747 +0,0 @@
 | 
			
		||||
.. Copyright (C)  2014 Champs Libres Cooperative SCRLFS
 | 
			
		||||
   Permission is granted to copy, distribute and/or modify this document
 | 
			
		||||
   under the terms of the GNU Free Documentation License, Version 1.3
 | 
			
		||||
   or any later version published by the Free Software Foundation;
 | 
			
		||||
   with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
 | 
			
		||||
   A copy of the license is included in the section entitled "GNU
 | 
			
		||||
   Free Documentation License".
 | 
			
		||||
 | 
			
		||||
.. _api:
 | 
			
		||||
 | 
			
		||||
API
 | 
			
		||||
###
 | 
			
		||||
 | 
			
		||||
Chill provides a basic framework to build REST api.
 | 
			
		||||
 | 
			
		||||
Basic configuration
 | 
			
		||||
*******************
 | 
			
		||||
 | 
			
		||||
Configure a route
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
Follow those steps to build a REST api:
 | 
			
		||||
 | 
			
		||||
1. Create your model;
 | 
			
		||||
2. Configure the API;
 | 
			
		||||
 | 
			
		||||
You can also:
 | 
			
		||||
 | 
			
		||||
* hook into the controller to customize some steps;
 | 
			
		||||
* add more route and steps
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
    Useful links:
 | 
			
		||||
 | 
			
		||||
    * `How to use annotation to configure serialization <https://symfony.com/doc/current/serializer.html>`_
 | 
			
		||||
    * `How to create your custom normalizer <https://symfony.com/doc/current/serializer/custom_normalizer.html>`_
 | 
			
		||||
 | 
			
		||||
Auto-loading the routes
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
Ensure that those lines are present in your file `app/config/routing.yml`:
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_cruds:
 | 
			
		||||
       resource: 'chill_main_crud_route_loader:load'
 | 
			
		||||
       type: service
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Create your model
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
Create your model on the usual way:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
   use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository;
 | 
			
		||||
   use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
    * @ORM\Entity(repositoryClass=OriginRepository::class)
 | 
			
		||||
    * @ORM\Table(name="chill_person_accompanying_period_origin")
 | 
			
		||||
    */
 | 
			
		||||
   class Origin
 | 
			
		||||
   {
 | 
			
		||||
       /**
 | 
			
		||||
        * @ORM\Id
 | 
			
		||||
        * @ORM\GeneratedValue
 | 
			
		||||
        * @ORM\Column(type="integer")
 | 
			
		||||
        */
 | 
			
		||||
       private $id;
 | 
			
		||||
 | 
			
		||||
       /**
 | 
			
		||||
        * @ORM\Column(type="json")
 | 
			
		||||
        */
 | 
			
		||||
       private $label;
 | 
			
		||||
 | 
			
		||||
       /**
 | 
			
		||||
        * @ORM\Column(type="date_immutable", nullable=true)
 | 
			
		||||
        */
 | 
			
		||||
       private $noActiveAfter;
 | 
			
		||||
 | 
			
		||||
       // .. getters and setters
 | 
			
		||||
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Configure api
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Configure the api using Yaml (see the full configuration: :ref:`api_full_configuration`):
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   # config/packages/chill_main.yaml
 | 
			
		||||
   chill_main:
 | 
			
		||||
       apis:
 | 
			
		||||
           accompanying_period_origin:
 | 
			
		||||
               base_path: '/api/1.0/person/accompanying-period/origin'
 | 
			
		||||
               class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
 | 
			
		||||
               name: accompanying_period_origin
 | 
			
		||||
               base_role: 'ROLE_USER'
 | 
			
		||||
               actions:
 | 
			
		||||
                   _index:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
                   _entity:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
 | 
			
		||||
   If you are working on a shared bundle (aka "The chill bundles"), you should define your configuration inside the class :code:`ChillXXXXBundleExtension`, using the "prependConfig" feature:
 | 
			
		||||
 | 
			
		||||
   .. code-block:: php
 | 
			
		||||
 | 
			
		||||
      namespace Chill\PersonBundle\DependencyInjection;
 | 
			
		||||
 | 
			
		||||
      use Symfony\Component\DependencyInjection\ContainerBuilder;
 | 
			
		||||
      use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
 | 
			
		||||
      use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
      /**
 | 
			
		||||
       * Class ChillPersonExtension
 | 
			
		||||
       * Loads and manages your bundle configuration
 | 
			
		||||
       *
 | 
			
		||||
       * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
 | 
			
		||||
       * @package Chill\PersonBundle\DependencyInjection
 | 
			
		||||
       */
 | 
			
		||||
      class ChillPersonExtension extends Extension implements PrependExtensionInterface
 | 
			
		||||
      {
 | 
			
		||||
          public function prepend(ContainerBuilder $container)
 | 
			
		||||
          {
 | 
			
		||||
              $this->prependCruds($container);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          /**
 | 
			
		||||
           * @param ContainerBuilder $container
 | 
			
		||||
           */
 | 
			
		||||
          protected function prependCruds(ContainerBuilder $container)
 | 
			
		||||
          {
 | 
			
		||||
              $container->prependExtensionConfig('chill_main', [
 | 
			
		||||
                  'apis' => [
 | 
			
		||||
                      [
 | 
			
		||||
                          'class' => \Chill\PersonBundle\Entity\AccompanyingPeriod\Origin::class,
 | 
			
		||||
                          'name' => 'accompanying_period_origin',
 | 
			
		||||
                          'base_path' => '/api/1.0/person/accompanying-period/origin',
 | 
			
		||||
                          'controller' => \Chill\PersonBundle\Controller\OpeningApiController::class,
 | 
			
		||||
                          'base_role' => 'ROLE_USER',
 | 
			
		||||
                          'actions' => [
 | 
			
		||||
                              '_index' => [
 | 
			
		||||
                                  'methods' => [ 
 | 
			
		||||
                                      Request::METHOD_GET => true,
 | 
			
		||||
                                      Request::METHOD_HEAD => true
 | 
			
		||||
                                  ],
 | 
			
		||||
                              ],
 | 
			
		||||
                              '_entity' => [
 | 
			
		||||
                                  'methods' => [ 
 | 
			
		||||
                                      Request::METHOD_GET => true,
 | 
			
		||||
                                      Request::METHOD_HEAD => true
 | 
			
		||||
                                  ]
 | 
			
		||||
                              ],
 | 
			
		||||
                          ]
 | 
			
		||||
                      ]
 | 
			
		||||
                  ]
 | 
			
		||||
              ]);
 | 
			
		||||
          }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
The :code:`_index` and :code:`_entity` action
 | 
			
		||||
*********************************************
 | 
			
		||||
 | 
			
		||||
The :code:`_index` and :code:`_entity` action are default actions:
 | 
			
		||||
 | 
			
		||||
* they will call a specific method in the default controller;
 | 
			
		||||
* they will generate defined routes:
 | 
			
		||||
 | 
			
		||||
Index:
 | 
			
		||||
   Name: :code:`chill_api_single_accompanying_period_origin__index`
 | 
			
		||||
 | 
			
		||||
   Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}`
 | 
			
		||||
 | 
			
		||||
Entity:
 | 
			
		||||
   Name: :code:`chill_api_single_accompanying_period_origin__entity`
 | 
			
		||||
 | 
			
		||||
   Path: :code:`/api/1.0/person/accompanying-period/origin/{id}.{_format}`
 | 
			
		||||
 | 
			
		||||
Role
 | 
			
		||||
****
 | 
			
		||||
 | 
			
		||||
By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account.
 | 
			
		||||
 | 
			
		||||
For index action, the role will be called with :code:`NULL` as :code:`$subject`. The retrieved entity will be the subject for single queries.
 | 
			
		||||
 | 
			
		||||
You can also define a role for each method. In this case, this role is used for the given method, and, if any, the base role is taken into account.
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   # config/packages/chill_main.yaml
 | 
			
		||||
   chill_main:
 | 
			
		||||
       apis:
 | 
			
		||||
           accompanying_period_origin:
 | 
			
		||||
               base_path: '/api/1.0/person/bla/bla'
 | 
			
		||||
               class: 'Chill\PersonBundle\Entity\Blah'
 | 
			
		||||
               name: bla
 | 
			
		||||
               actions:
 | 
			
		||||
                   _entity:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
                       roles:
 | 
			
		||||
                           GET: MY_ROLE_SEE
 | 
			
		||||
                           HEAD: MY ROLE_SEE
 | 
			
		||||
 | 
			
		||||
Customize the controller
 | 
			
		||||
************************
 | 
			
		||||
 | 
			
		||||
You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Controller;
 | 
			
		||||
 | 
			
		||||
   use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
 | 
			
		||||
   class OpeningApiController extends ApiController
 | 
			
		||||
   {
 | 
			
		||||
       protected function customizeQuery(string $action, Request $request, $qb): void
 | 
			
		||||
       {
 | 
			
		||||
           $qb->where($qb->expr()->gt('e.noActiveAfter', ':now'))
 | 
			
		||||
               ->orWhere($qb->expr()->isNull('e.noActiveAfter'));
 | 
			
		||||
           $qb->setParameter('now', new \DateTime('now'));
 | 
			
		||||
       }     
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
And set your controller in configuration:
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_main:
 | 
			
		||||
       apis:
 | 
			
		||||
           accompanying_period_origin:
 | 
			
		||||
               base_path: '/api/1.0/person/accompanying-period/origin'
 | 
			
		||||
               class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin'
 | 
			
		||||
               name: accompanying_period_origin
 | 
			
		||||
               # add a controller
 | 
			
		||||
               controller: 'Chill\PersonBundle\Controller\OpeningApiController'
 | 
			
		||||
               base_role: 'ROLE_USER'
 | 
			
		||||
               actions:
 | 
			
		||||
                   _index:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
                   _entity:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
 | 
			
		||||
Create your own actions
 | 
			
		||||
***********************
 | 
			
		||||
 | 
			
		||||
You can add your own actions:
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_main:
 | 
			
		||||
       apis:
 | 
			
		||||
           -
 | 
			
		||||
               class: Chill\PersonBundle\Entity\AccompanyingPeriod
 | 
			
		||||
               name: accompanying_course
 | 
			
		||||
               base_path: /api/1.0/person/accompanying-course
 | 
			
		||||
               controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
 | 
			
		||||
               actions:
 | 
			
		||||
                   # add a custom participation:
 | 
			
		||||
                   participation:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           POST: true
 | 
			
		||||
                           DELETE: true
 | 
			
		||||
                           GET: false
 | 
			
		||||
                           HEAD: false
 | 
			
		||||
                           PUT: false
 | 
			
		||||
                       roles:
 | 
			
		||||
                           POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           GET: null
 | 
			
		||||
                           HEAD: null
 | 
			
		||||
                           PUT: null
 | 
			
		||||
                       single-collection: single
 | 
			
		||||
 | 
			
		||||
The key :code:`single-collection` with value :code:`single` will add a :code:`/{id}/ + "action name"` (in this example, :code:`/{id}/participation`) into the path, after the base path. If the value is :code:`collection`, no id will be set, but the action name will be append to the path.
 | 
			
		||||
 | 
			
		||||
Then, create the corresponding action into your controller:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Controller;
 | 
			
		||||
 | 
			
		||||
   use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
   use Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Exception\BadRequestException;
 | 
			
		||||
   use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
   use Symfony\Component\Validator\Validator\ValidatorInterface;
 | 
			
		||||
   use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent;
 | 
			
		||||
   use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
 | 
			
		||||
   class AccompanyingCourseApiController extends ApiController
 | 
			
		||||
   {
 | 
			
		||||
       protected EventDispatcherInterface $eventDispatcher;
 | 
			
		||||
 | 
			
		||||
       protected ValidatorInterface $validator;
 | 
			
		||||
 | 
			
		||||
       public function __construct(EventDispatcherInterface $eventDispatcher, $validator)
 | 
			
		||||
       {
 | 
			
		||||
           $this->eventDispatcher = $eventDispatcher;
 | 
			
		||||
           $this->validator = $validator;
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       public function participationApi($id, Request $request, $_format)
 | 
			
		||||
       {
 | 
			
		||||
           /** @var AccompanyingPeriod $accompanyingPeriod */ 
 | 
			
		||||
           $accompanyingPeriod = $this->getEntity('participation', $id, $request);
 | 
			
		||||
           $person = $this->getSerializer()
 | 
			
		||||
               ->deserialize($request->getContent(), Person::class, $_format, []);
 | 
			
		||||
 | 
			
		||||
           if (NULL === $person) {
 | 
			
		||||
               throw new BadRequestException('person id not found');
 | 
			
		||||
           }
 | 
			
		||||
 | 
			
		||||
           $this->onPostCheckACL('participation', $request, $accompanyingPeriod, $_format);
 | 
			
		||||
 | 
			
		||||
           switch ($request->getMethod()) {
 | 
			
		||||
               case Request::METHOD_POST:
 | 
			
		||||
                   $participation = $accompanyingPeriod->addPerson($person);
 | 
			
		||||
                   break;
 | 
			
		||||
               case Request::METHOD_DELETE:
 | 
			
		||||
                   $participation = $accompanyingPeriod->removePerson($person);
 | 
			
		||||
                   break;
 | 
			
		||||
               default:
 | 
			
		||||
                   throw new BadRequestException("This method is not supported");
 | 
			
		||||
           }
 | 
			
		||||
 | 
			
		||||
           $errors = $this->validator->validate($accompanyingPeriod);
 | 
			
		||||
 | 
			
		||||
           if ($errors->count() > 0) {
 | 
			
		||||
               // only format accepted
 | 
			
		||||
               return $this->json($errors);
 | 
			
		||||
           }
 | 
			
		||||
 | 
			
		||||
           $this->getDoctrine()->getManager()->flush();
 | 
			
		||||
 | 
			
		||||
           return $this->json($participation);
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
Managing association
 | 
			
		||||
********************
 | 
			
		||||
 | 
			
		||||
ManyToOne association
 | 
			
		||||
=====================
 | 
			
		||||
 | 
			
		||||
In ManyToOne association, you can add associated entities using the :code:`PATCH` request. By default, the serializer deserialize entities only with their id and discriminator type, if any.
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
   curl -X 'PATCH' \
 | 
			
		||||
     'http://localhost:8001/api/1.0/person/accompanying-course/2668.json' \
 | 
			
		||||
     -H 'accept: */*' \
 | 
			
		||||
     -H 'Content-Type: application/json' \
 | 
			
		||||
      # see the data sent to the server: \
 | 
			
		||||
     -d '{
 | 
			
		||||
     "type": "accompanying_period",
 | 
			
		||||
     "id": 2668,
 | 
			
		||||
     "origin": { "id": 11 }
 | 
			
		||||
   }'
 | 
			
		||||
 | 
			
		||||
ManyToMany associations
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
In OneToMany association, you can easily create route for adding and removing entities, using :code:`POST` and :code:`DELETE` requests.
 | 
			
		||||
 | 
			
		||||
Prepare your entity, creating the methods :code:`addYourEntity` and :code:`removeYourEntity`:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Entity;
 | 
			
		||||
 | 
			
		||||
   use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
   use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
   use Doctrine\Common\Collections\Collection;
 | 
			
		||||
   use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
   use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
   use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
 | 
			
		||||
 | 
			
		||||
   /**
 | 
			
		||||
    * AccompanyingPeriod Class
 | 
			
		||||
    *
 | 
			
		||||
    * @ORM\Entity
 | 
			
		||||
    * @ORM\Table(name="chill_person_accompanying_period")
 | 
			
		||||
    * @DiscriminatorMap(typeProperty="type", mapping={
 | 
			
		||||
    *  "accompanying_period"=AccompanyingPeriod::class
 | 
			
		||||
    *  })
 | 
			
		||||
    */
 | 
			
		||||
   class AccompanyingPeriod
 | 
			
		||||
   {
 | 
			
		||||
       /**
 | 
			
		||||
        * @var Collection
 | 
			
		||||
        * @ORM\ManyToMany(
 | 
			
		||||
        *     targetEntity=Scope::class,
 | 
			
		||||
        *     cascade={}
 | 
			
		||||
        *     )
 | 
			
		||||
        * @Groups({"read"})
 | 
			
		||||
        */
 | 
			
		||||
       private $scopes;
 | 
			
		||||
 | 
			
		||||
       public function addScope(Scope $scope): self
 | 
			
		||||
       {
 | 
			
		||||
           $this->scopes[] = $scope;
 | 
			
		||||
 | 
			
		||||
           return $this;
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
       public function removeScope(Scope $scope): void
 | 
			
		||||
       {
 | 
			
		||||
           $this->scopes->removeElement($scope);
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Create your route into the configuration:
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
   chill_main:
 | 
			
		||||
       apis:
 | 
			
		||||
           -
 | 
			
		||||
               class: Chill\PersonBundle\Entity\AccompanyingPeriod
 | 
			
		||||
               name: accompanying_course
 | 
			
		||||
               base_path: /api/1.0/person/accompanying-course
 | 
			
		||||
               controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
 | 
			
		||||
               actions:
 | 
			
		||||
                   scope:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           POST: true
 | 
			
		||||
                           DELETE: true
 | 
			
		||||
                           GET: false
 | 
			
		||||
                           HEAD: false
 | 
			
		||||
                           PUT: false
 | 
			
		||||
                           PATCH: false
 | 
			
		||||
                       roles:
 | 
			
		||||
                           POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           GET: null
 | 
			
		||||
                           HEAD: null
 | 
			
		||||
                           PUT: null
 | 
			
		||||
                           PATCH: null
 | 
			
		||||
                       controller_action: null
 | 
			
		||||
                       path: null
 | 
			
		||||
                       single-collection: single
 | 
			
		||||
 | 
			
		||||
This will create a new route, which will accept two methods: DELETE and POST:
 | 
			
		||||
 | 
			
		||||
.. code-block:: raw
 | 
			
		||||
 | 
			
		||||
   +--------------+---------------------------------------------------------------------------------------+
 | 
			
		||||
   | Property     | Value                                                                                 |
 | 
			
		||||
   +--------------+---------------------------------------------------------------------------------------+
 | 
			
		||||
   | Route Name   | chill_api_single_accompanying_course_scope                                            |
 | 
			
		||||
   | Path         | /api/1.0/person/accompanying-course/{id}/scope.{_format}                              |
 | 
			
		||||
   | Path Regex   | {^/api/1\.0/person/accompanying\-course/(?P<id>[^/]++)/scope\.(?P<_format>[^/]++)$}sD |
 | 
			
		||||
   | Host         | ANY                                                                                   |
 | 
			
		||||
   | Host Regex   |                                                                                       |
 | 
			
		||||
   | Scheme       | ANY                                                                                   |
 | 
			
		||||
   | Method       | POST|DELETE                                                                           |
 | 
			
		||||
   | Requirements | {id}: \d+                                                                             |
 | 
			
		||||
   | Class        | Symfony\Component\Routing\Route                                                       |
 | 
			
		||||
   | Defaults     | _controller: csapi_accompanying_course_controller:scopeApi                            |
 | 
			
		||||
   | Options      | compiler_class: Symfony\Component\Routing\RouteCompiler                               |
 | 
			
		||||
   +--------------+---------------------------------------------------------------------------------------+
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Then, create the controller action. Call the method:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Controller;
 | 
			
		||||
 | 
			
		||||
   use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
   use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
   use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
 | 
			
		||||
   class MyController extends ApiController
 | 
			
		||||
   {
 | 
			
		||||
       public function scopeApi($id, Request $request, string $_format): Response
 | 
			
		||||
       {
 | 
			
		||||
           return $this->addRemoveSomething('scope', $id, $request, $_format, 'scope', Scope::class, [ 'groups' => [ 'read' ] ]);
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
This will allow to add a scope by his id, and delete them.
 | 
			
		||||
 | 
			
		||||
Curl requests:
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
   # add a scope with id 5
 | 
			
		||||
   curl -X 'POST' \
 | 
			
		||||
     'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \
 | 
			
		||||
     -H 'accept: */*' \
 | 
			
		||||
     -H 'Content-Type: application/json' \
 | 
			
		||||
     -d '{
 | 
			
		||||
     "type": "scope",
 | 
			
		||||
     "id": 5
 | 
			
		||||
   }'
 | 
			
		||||
 | 
			
		||||
   # remove a scope with id 5
 | 
			
		||||
   curl -X 'DELETE' \
 | 
			
		||||
     'http://localhost:8001/api/1.0/person/accompanying-course/2868/scope.json' \
 | 
			
		||||
     -H 'accept: */*' \
 | 
			
		||||
     -H 'Content-Type: application/json' \
 | 
			
		||||
     -d '{
 | 
			
		||||
     "id": 5,
 | 
			
		||||
     "type": "scope"
 | 
			
		||||
   }'
 | 
			
		||||
 | 
			
		||||
Deserializing an association where multiple types are allowed
 | 
			
		||||
=============================================================
 | 
			
		||||
 | 
			
		||||
Sometimes, multiples types are allowed as association to one entity:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Entity\AccompanyingPeriod;
 | 
			
		||||
 | 
			
		||||
   use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
   use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
   use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
 | 
			
		||||
   class Resource
 | 
			
		||||
   {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
       /**
 | 
			
		||||
        * @ORM\ManyToOne(targetEntity=ThirdParty::class)
 | 
			
		||||
        * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
        */
 | 
			
		||||
       private $thirdParty;
 | 
			
		||||
 | 
			
		||||
       /**
 | 
			
		||||
        * @ORM\ManyToOne(targetEntity=Person::class)
 | 
			
		||||
        * @ORM\JoinColumn(nullable=true)
 | 
			
		||||
        */
 | 
			
		||||
       private $person;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
       /**
 | 
			
		||||
        *
 | 
			
		||||
        * @param $resource Person|ThirdParty
 | 
			
		||||
        */
 | 
			
		||||
       public function setResource($resource): self
 | 
			
		||||
       {
 | 
			
		||||
          // ...
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
       
 | 
			
		||||
       /**
 | 
			
		||||
        * @return ThirdParty|Person
 | 
			
		||||
        * @Groups({"read", "write"})
 | 
			
		||||
        */
 | 
			
		||||
       public function getResource()
 | 
			
		||||
       {
 | 
			
		||||
           return $this->person ?? $this->thirdParty;
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
This is not well taken into account by the Symfony serializer natively.
 | 
			
		||||
 | 
			
		||||
You must, then, create your own CustomNormalizer. You can help yourself using this:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   namespace Chill\PersonBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
   use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
   use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
   use Chill\PersonBundle\Entity\AccompanyingPeriod\Resource;
 | 
			
		||||
   use Chill\PersonBundle\Repository\AccompanyingPeriod\ResourceRepository;
 | 
			
		||||
   use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
   use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
 | 
			
		||||
   use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
 | 
			
		||||
   use Symfony\Component\Serializer\Normalizer\ObjectToPopulateTrait;
 | 
			
		||||
   use Symfony\Component\Serializer\Exception;
 | 
			
		||||
   use Chill\MainBundle\Serializer\Normalizer\DiscriminatedObjectDenormalizer;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   class AccompanyingPeriodResourceNormalizer implements DenormalizerInterface, DenormalizerAwareInterface
 | 
			
		||||
   {
 | 
			
		||||
       use DenormalizerAwareTrait;
 | 
			
		||||
       use ObjectToPopulateTrait;
 | 
			
		||||
 | 
			
		||||
       public function __construct(ResourceRepository $repository)
 | 
			
		||||
       {
 | 
			
		||||
           $this->repository = $repository;
 | 
			
		||||
       }
 | 
			
		||||
 | 
			
		||||
       public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
       {
 | 
			
		||||
           // .. snipped for brevity
 | 
			
		||||
 | 
			
		||||
           if ($resource === NULL) {
 | 
			
		||||
               $resource = new Resource();
 | 
			
		||||
           }
 | 
			
		||||
 | 
			
		||||
           if (\array_key_exists('resource', $data)) {
 | 
			
		||||
               $res = $this->denormalizer->denormalize(
 | 
			
		||||
                   $data['resource'],
 | 
			
		||||
                   // call for a "multiple type"
 | 
			
		||||
                   DiscriminatedObjectDenormalizer::TYPE,
 | 
			
		||||
                   $format,
 | 
			
		||||
                   // into the context, we add the list of allowed types:
 | 
			
		||||
                   [ 
 | 
			
		||||
                       DiscriminatedObjectDenormalizer::ALLOWED_TYPES => 
 | 
			
		||||
                       [ 
 | 
			
		||||
                           Person::class, ThirdParty::class
 | 
			
		||||
                       ]
 | 
			
		||||
                   ]
 | 
			
		||||
               );
 | 
			
		||||
 | 
			
		||||
               $resource->setResource($res);
 | 
			
		||||
           } 
 | 
			
		||||
 | 
			
		||||
           return $resource;
 | 
			
		||||
       }
 | 
			
		||||
       
 | 
			
		||||
 | 
			
		||||
       public function supportsDenormalization($data, string $type, string $format = null)
 | 
			
		||||
       {
 | 
			
		||||
           return $type === Resource::class;
 | 
			
		||||
       }  
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
Serialization for collection
 | 
			
		||||
****************************
 | 
			
		||||
 | 
			
		||||
A specific model has been defined for returning collection:
 | 
			
		||||
 | 
			
		||||
.. code-block:: json
 | 
			
		||||
 | 
			
		||||
   {
 | 
			
		||||
       "count": 49,
 | 
			
		||||
       "results": [
 | 
			
		||||
       ],
 | 
			
		||||
       "pagination": {
 | 
			
		||||
           "more": true,
 | 
			
		||||
           "next": "/api/1.0/search.json&q=xxxx......&page=2",
 | 
			
		||||
           "previous": null,
 | 
			
		||||
           "first": 0,
 | 
			
		||||
           "items_per_page": 1
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
Where this is relevant, this model should be re-used in custom controller actions.
 | 
			
		||||
 | 
			
		||||
In custom actions, this can be achieved quickly by assembling results into a :code:`Chill\MainBundle\Serializer\Model\Collection`. The pagination information is given by using :code:`Paginator` (see :ref:`Pagination <pagination-ref>`).
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
   use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
   use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
 | 
			
		||||
   class MyController extends AbstractController
 | 
			
		||||
   {
 | 
			
		||||
 | 
			
		||||
       protected function serializeCollection(PaginatorInterface $paginator, $entities): Response
 | 
			
		||||
       {
 | 
			
		||||
           $model = new Collection($entities, $paginator);
 | 
			
		||||
 | 
			
		||||
           return $this->json($model, Response::HTTP_OK, [], $context);
 | 
			
		||||
       }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _api_full_configuration:
 | 
			
		||||
 | 
			
		||||
Full configuration example
 | 
			
		||||
**************************
 | 
			
		||||
 | 
			
		||||
.. code-block:: yaml
 | 
			
		||||
 | 
			
		||||
       apis:
 | 
			
		||||
           -
 | 
			
		||||
               class: Chill\PersonBundle\Entity\AccompanyingPeriod
 | 
			
		||||
               name: accompanying_course
 | 
			
		||||
               base_path: /api/1.0/person/accompanying-course
 | 
			
		||||
               controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController
 | 
			
		||||
               actions:
 | 
			
		||||
                   _entity:
 | 
			
		||||
                       roles:
 | 
			
		||||
                           GET: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           HEAD: null
 | 
			
		||||
                           POST: null
 | 
			
		||||
                           DELETE: null
 | 
			
		||||
                           PUT: null
 | 
			
		||||
                       controller_action: null
 | 
			
		||||
                       path: null
 | 
			
		||||
                       single-collection: single
 | 
			
		||||
                       methods:
 | 
			
		||||
                           GET: true
 | 
			
		||||
                           HEAD: true
 | 
			
		||||
                           POST: false
 | 
			
		||||
                           DELETE: false
 | 
			
		||||
                           PUT: false
 | 
			
		||||
                   participation:
 | 
			
		||||
                       methods:
 | 
			
		||||
                           POST: true
 | 
			
		||||
                           DELETE: true
 | 
			
		||||
                           GET: false
 | 
			
		||||
                           HEAD: false
 | 
			
		||||
                           PUT: false
 | 
			
		||||
                       roles:
 | 
			
		||||
                           POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE
 | 
			
		||||
                           GET: null
 | 
			
		||||
                           HEAD: null
 | 
			
		||||
                           PUT: null
 | 
			
		||||
                       controller_action: null
 | 
			
		||||
                       # the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty.
 | 
			
		||||
                       requirements:         []
 | 
			
		||||
                       path: null
 | 
			
		||||
                       single-collection: single
 | 
			
		||||
               base_role: null
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -16,7 +16,6 @@ As Chill rely on the `symfony <http://symfony.com>`_ framework, reading the fram
 | 
			
		||||
 | 
			
		||||
    Instructions to create a new bundle <create-a-new-bundle.rst>
 | 
			
		||||
    CRUD (Create - Update - Delete) for one entity <crud.rst>
 | 
			
		||||
    Helpers for building a REST API <api.rst>
 | 
			
		||||
    Routing <routing.rst>
 | 
			
		||||
    Menus <menus.rst>
 | 
			
		||||
    Forms <forms.rst>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,8 +7,6 @@
 | 
			
		||||
   Free Documentation License".
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. _pagination-ref:
 | 
			
		||||
 | 
			
		||||
Pagination
 | 
			
		||||
##########
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +15,7 @@ The Bundle :code:`Chill\MainBundle` provides a **Pagination** api which allow yo
 | 
			
		||||
A simple example
 | 
			
		||||
****************
 | 
			
		||||
 | 
			
		||||
In the controller, get the :code:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance.
 | 
			
		||||
In the controller, get the :class:`Chill\Main\Pagination\PaginatorFactory` from the `Container` and use this :code:`PaginatorFactory` to create a :code:`Paginator` instance.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.. literalinclude:: pagination/example.php
 | 
			
		||||
 
 | 
			
		||||
@@ -8,21 +8,21 @@ use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
class ItemController extends Controller {
 | 
			
		||||
 | 
			
		||||
    public function yourAction()
 | 
			
		||||
    public function yourAction() 
 | 
			
		||||
    {
 | 
			
		||||
        $em = $this->getDoctrine()->getManager();
 | 
			
		||||
        // first, get the number of total item are available
 | 
			
		||||
        $total = $em
 | 
			
		||||
             ->createQuery("SELECT COUNT (item.id) FROM ChillMyBundle:Item item")
 | 
			
		||||
             ->getSingleScalarResult();
 | 
			
		||||
 | 
			
		||||
       
 | 
			
		||||
        // get the PaginatorFactory
 | 
			
		||||
        $paginatorFactory = $this->get('chill_main.paginator_factory');
 | 
			
		||||
 | 
			
		||||
        // create a pagination instance. This instance is only valid for
 | 
			
		||||
        
 | 
			
		||||
        // create a pagination instance. This instance is only valid for 
 | 
			
		||||
        // the current route and parameters
 | 
			
		||||
        $paginator = $paginatorFactory->create($total);
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        // launch your query on item. Limit the query to the results
 | 
			
		||||
        // for the current page using the paginator
 | 
			
		||||
        $items = $em->createQuery("SELECT item FROM ChillMyBundle:Item item WHERE <your clause>")
 | 
			
		||||
@@ -30,12 +30,12 @@ class ItemController extends Controller {
 | 
			
		||||
            ->setFirstResult($paginator->getCurrentPage()->getFirstItemNumber())
 | 
			
		||||
            // use the paginator to get the number of items to display
 | 
			
		||||
            ->setMaxResults($paginator->getItemsPerPage());
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
        return $this->render('ChillMyBundle:Item:list.html.twig', array(
 | 
			
		||||
                'items' => $items,
 | 
			
		||||
                'paginator' => $paginator
 | 
			
		||||
                )
 | 
			
		||||
        );
 | 
			
		||||
                );
 | 
			
		||||
            
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ The has the following signature :
 | 
			
		||||
         * 
 | 
			
		||||
         * @param string $context
 | 
			
		||||
         * @param mixed[] $args the argument to the context.
 | 
			
		||||
         * @return TimelineSingleQuery
 | 
			
		||||
         * @return string[]
 | 
			
		||||
         * @throw  \LogicException if the context is not supported
 | 
			
		||||
         */
 | 
			
		||||
        public function fetchQuery($context, array $args);
 | 
			
		||||
@@ -163,16 +163,18 @@ The has the following signature :
 | 
			
		||||
The `fetchQuery` function
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
The fetchQuery function help to build the UNION query to gather events. This function should return an instance of :code:`TimelineSingleQuery`. For you convenience, this object may be build using an associative array with the following keys:
 | 
			
		||||
The fetchQuery function help to build the UNION query to gather events. This function should return an associative array MUST have the following key :
 | 
			
		||||
 | 
			
		||||
* `id` : the name of the id column
 | 
			
		||||
* `type`: a string to indicate the type
 | 
			
		||||
* `date`: the name of the datetime column, used to order entities by date
 | 
			
		||||
* `FROM`: the FROM clause. May contains JOIN instructions
 | 
			
		||||
* `WHERE`: the WHERE clause;
 | 
			
		||||
* `parameters`: the parameters to pass to the query
 | 
			
		||||
* `FROM` (in capital) : the FROM clause. May contains JOIN instructions
 | 
			
		||||
 | 
			
		||||
The parameters should be replaced into the query by :code:`?`. They will be replaced into the query using prepared statements.
 | 
			
		||||
Those key are optional:
 | 
			
		||||
 | 
			
		||||
* `WHERE` (in capital) : the WHERE clause. 
 | 
			
		||||
 | 
			
		||||
 Where relevant, the data must be quoted to avoid SQL injection.
 | 
			
		||||
 | 
			
		||||
`$context` and `$args` are defined by the bundle which will call the timeline rendering. You may use them to build a different query depending on this context.
 | 
			
		||||
 | 
			
		||||
@@ -184,15 +186,6 @@ For instance, if the context is `'person'`, the args will be this array :
 | 
			
		||||
        'person' => $person //a \Chill\PersonBundle\Entity\Person entity
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
For the context :code:`center`, the args will be:
 | 
			
		||||
 | 
			
		||||
.. code-block:: php
 | 
			
		||||
 | 
			
		||||
    array(
 | 
			
		||||
        'centers' => [ ]  // an array of \Chill\MainBundle\Entity\Center entities
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
You should find in the bundle documentation which contexts are arguments the bundle defines.
 | 
			
		||||
 | 
			
		||||
.. note::
 | 
			
		||||
@@ -206,12 +199,13 @@ Example of an implementation :
 | 
			
		||||
    namespace Chill\ReportBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
    use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
    use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
    use Doctrine\ORM\EntityManager;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provide report for inclusion in timeline
 | 
			
		||||
     *
 | 
			
		||||
     * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
     * @author Champs Libres <info@champs-libres.coop>
 | 
			
		||||
     */
 | 
			
		||||
    class TimelineReportProvider implements TimelineProviderInterface
 | 
			
		||||
    {
 | 
			
		||||
@@ -233,17 +227,16 @@ Example of an implementation :
 | 
			
		||||
                
 | 
			
		||||
                $metadata = $this->em->getClassMetadata('ChillReportBundle:Report');
 | 
			
		||||
                
 | 
			
		||||
                return TimelineSingleQuery::fromArray([
 | 
			
		||||
                return array(
 | 
			
		||||
                   'id' => $metadata->getColumnName('id'),
 | 
			
		||||
                   'type' => 'report',
 | 
			
		||||
                   'date' => $metadata->getColumnName('date'),
 | 
			
		||||
                   'FROM' => $metadata->getTableName(),
 | 
			
		||||
                   'WHERE' => sprintf('%s = ?',
 | 
			
		||||
                   'WHERE' => sprintf('%s = %d',
 | 
			
		||||
                         $metadata
 | 
			
		||||
                            ->getAssociationMapping('person')['joinColumns'][0]['name'])
 | 
			
		||||
                         )
 | 
			
		||||
                   'parameters' => [ $args['person']->getId() ]
 | 
			
		||||
                ]);
 | 
			
		||||
                            ->getAssociationMapping('person')['joinColumns'][0]['name'],
 | 
			
		||||
                         $args['person']->getId())
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        //....
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,7 @@ Chill will be available at ``http://localhost:8001.`` Currently, there isn't any
 | 
			
		||||
 | 
			
		||||
.. code-block:: bash
 | 
			
		||||
 | 
			
		||||
   docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load --purge-with-truncate
 | 
			
		||||
   docker-compose exec --user $(id -u) php bin/console doctrine:fixtures:load
 | 
			
		||||
 | 
			
		||||
There are several users available:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								grumphp.yml
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								grumphp.yml
									
									
									
									
									
								
							@@ -1,22 +0,0 @@
 | 
			
		||||
imports:
 | 
			
		||||
  - { resource: tests/app/vendor/drupol/php-conventions/config/php73/grumphp.yml }
 | 
			
		||||
 | 
			
		||||
parameters:
 | 
			
		||||
  tasks.license.name: AGPL-3.0
 | 
			
		||||
  tasks.license.holder: Champs-Libres
 | 
			
		||||
  tasks.phpstan.configuration: phpstan.neon
 | 
			
		||||
  tasks.phpstan.ignore_patterns:
 | 
			
		||||
    - /src\/Bundle\/(.*)\/migrations/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/Resources/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/Tests/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/Test/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/config/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/translations/
 | 
			
		||||
    - /src\/Bundle\/(.*)\/DataFixtures/
 | 
			
		||||
    - /^docs/
 | 
			
		||||
    - /^tests/
 | 
			
		||||
  skip_tasks:
 | 
			
		||||
    - composer_require_checker
 | 
			
		||||
    - phpcsfixer
 | 
			
		||||
    - phpcs
 | 
			
		||||
    - twigcs
 | 
			
		||||
							
								
								
									
										23979
									
								
								phpstan-baseline.neon
									
									
									
									
									
								
							
							
						
						
									
										23979
									
								
								phpstan-baseline.neon
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,3 +0,0 @@
 | 
			
		||||
includes:
 | 
			
		||||
	- phpstan-baseline.neon
 | 
			
		||||
 | 
			
		||||
@@ -18,20 +18,12 @@
 | 
			
		||||
       <testsuite name="MainBundle">
 | 
			
		||||
         <directory suffix="Test.php">src/Bundle/ChillMainBundle/Tests/</directory>
 | 
			
		||||
       </testsuite>
 | 
			
		||||
       <!--
 | 
			
		||||
       <testsuite name="PersonBundle">
 | 
			
		||||
         <directory suffix="Test.php">src/Bundle/ChillPersonBundle/Tests/</directory>
 | 
			
		||||
         <!-- test for export will be runned later -->
 | 
			
		||||
         <exclude>src/Bundle/ChillPersonBundle/Tests/Export/*</exclude>
 | 
			
		||||
         <!-- we are rewriting accompanying periods... Work in progress -->
 | 
			
		||||
         <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/AccompanyingPeriodControllerTest.php</exclude>
 | 
			
		||||
         <!-- we are rewriting address, Work in progress -->
 | 
			
		||||
         <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonAddressControllerTest.php</exclude>
 | 
			
		||||
         <!-- find a solution to create multiple configs -->
 | 
			
		||||
         <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonControllerUpdateWithHiddenFieldsTest.php</exclude>
 | 
			
		||||
         <!-- temporarily removed, the time to find a fix -->
 | 
			
		||||
         <exclude>src/Bundle/ChillPersonBundle/Tests/Controller/PersonDuplicateControllerViewTest.php</exclude>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
    </testsuites>
 | 
			
		||||
      -->
 | 
			
		||||
      </testsuites>
 | 
			
		||||
 | 
			
		||||
    <listeners>
 | 
			
		||||
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2022
									
								
								psalm-baseline.xml
									
									
									
									
									
								
							
							
						
						
									
										2022
									
								
								psalm-baseline.xml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								psalm.xml
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								psalm.xml
									
									
									
									
									
								
							@@ -1,16 +0,0 @@
 | 
			
		||||
<?xml version="1.0"?>
 | 
			
		||||
<psalm
 | 
			
		||||
    errorLevel="7"
 | 
			
		||||
    resolveFromConfigFile="true"
 | 
			
		||||
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
    xmlns="https://getpsalm.org/schema/config"
 | 
			
		||||
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
 | 
			
		||||
    errorBaseline="psalm-baseline.xml"
 | 
			
		||||
>
 | 
			
		||||
    <projectFiles>
 | 
			
		||||
        <directory name="src" />
 | 
			
		||||
        <ignoreFiles>
 | 
			
		||||
            <directory name="tests/" />
 | 
			
		||||
        </ignoreFiles>
 | 
			
		||||
    </projectFiles>
 | 
			
		||||
</psalm>
 | 
			
		||||
@@ -52,6 +52,7 @@ class ChillActivityExtension extends Extension implements PrependExtensionInterf
 | 
			
		||||
        $loader->load('services/export.yaml');
 | 
			
		||||
        $loader->load('services/repositories.yaml');
 | 
			
		||||
        $loader->load('services/fixtures.yaml');
 | 
			
		||||
        $loader->load('services/menu.yaml');
 | 
			
		||||
        $loader->load('services/controller.yaml');
 | 
			
		||||
        $loader->load('services/form.yaml');
 | 
			
		||||
        $loader->load('services/templating.yaml');
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ use Chill\MainBundle\Validator\Constraints\Entity\UserCircleConsistency;
 | 
			
		||||
 * Class Activity
 | 
			
		||||
 *
 | 
			
		||||
 * @package Chill\ActivityBundle\Entity
 | 
			
		||||
 * @ORM\Entity(repositoryClass="Chill\ActivityBundle\Repository\ActivityRepository")
 | 
			
		||||
 * @ORM\Entity()
 | 
			
		||||
 * @ORM\Table(name="activity")
 | 
			
		||||
 * @ORM\HasLifecycleCallbacks()
 | 
			
		||||
 * @UserCircleConsistency(
 | 
			
		||||
 
 | 
			
		||||
@@ -1,169 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2021, Champs Libres Cooperative SCRLFS,
 | 
			
		||||
 * <http://www.champs-libres.coop>, <info@champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\ActivityBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\ActivityBundle\Entity\Activity;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\ActivityBundle\Repository\ActivityRepository;
 | 
			
		||||
use Chill\ActivityBundle\Security\Authorization\ActivityVoter;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Doctrine\ORM\QueryBuilder;
 | 
			
		||||
use Doctrine\ORM\Query\Expr\Orx;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
final class ActivityACLAwareRepository 
 | 
			
		||||
{
 | 
			
		||||
    private AuthorizationHelper $authorizationHelper;
 | 
			
		||||
 | 
			
		||||
    private TokenStorageInterface $tokenStorage;
 | 
			
		||||
 | 
			
		||||
    private ActivityRepository $repository;
 | 
			
		||||
 | 
			
		||||
    private EntityManagerInterface $em;
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        AuthorizationHelper $authorizationHelper,
 | 
			
		||||
        TokenStorageInterface $tokenStorage,
 | 
			
		||||
        ActivityRepository $repository,
 | 
			
		||||
        EntityManagerInterface $em
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->authorizationHelper = $authorizationHelper;
 | 
			
		||||
        $this->tokenStorage = $tokenStorage;
 | 
			
		||||
        $this->repository = $repository;
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function queryTimelineIndexer(string $context, array $args = []): array
 | 
			
		||||
    {
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
 | 
			
		||||
        $from = $this->getFromClauseCenter($args);
 | 
			
		||||
        [$where, $parameters] = $this->getWhereClause($context, $args);
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
           'id' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('id'),
 | 
			
		||||
           'type' => 'activity',
 | 
			
		||||
           'date' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('date'),
 | 
			
		||||
           'FROM' => $from,
 | 
			
		||||
           'WHERE' => $where,
 | 
			
		||||
           'parameters' => $parameters
 | 
			
		||||
       ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getFromClauseCenter(array $args): string
 | 
			
		||||
    {
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        return $metadataActivity->getTableName().' JOIN '
 | 
			
		||||
            .$metadataPerson->getTableName().' ON '
 | 
			
		||||
            .$metadataPerson->getTableName().'.'.
 | 
			
		||||
                $associationMapping['joinColumns'][0]['referencedColumnName']
 | 
			
		||||
            .' = '
 | 
			
		||||
            .$associationMapping['joinColumns'][0]['name']
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getWhereClause(string $context, array $args): array
 | 
			
		||||
    {
 | 
			
		||||
        $where = '';
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
        
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $activityToPerson = $metadataActivity->getAssociationMapping('person')['joinColumns'][0]['name'];
 | 
			
		||||
        $activityToScope  = $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'];
 | 
			
		||||
        $personToCenter = $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // acls:
 | 
			
		||||
        $role = new Role(ActivityVoter::SEE);
 | 
			
		||||
        $reachableCenters = $this->authorizationHelper->getReachableCenters($this->tokenStorage->getToken()->getUser(), 
 | 
			
		||||
                $role);
 | 
			
		||||
        
 | 
			
		||||
        if (count($reachableCenters) === 0) {
 | 
			
		||||
            // insert a dummy condition
 | 
			
		||||
            return 'FALSE = TRUE';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($context === 'person') { 
 | 
			
		||||
            // we start with activities having the person_id linked to person 
 | 
			
		||||
            $where .= sprintf('%s = ? AND ', $activityToPerson);
 | 
			
		||||
            $parameters[] = $person->getId();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // we add acl (reachable center and scopes)
 | 
			
		||||
        $where .= '('; // first loop for the for centers
 | 
			
		||||
        $centersI = 0; // like centers#i
 | 
			
		||||
        foreach ($reachableCenters as $center) {
 | 
			
		||||
            // we pass if not in centers
 | 
			
		||||
            if (!\in_array($center, $args['centers'])) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            // we get all the reachable scopes for this center
 | 
			
		||||
            $reachableScopes = $this->authorizationHelper->getReachableScopes($this->tokenStorage->getToken()->getUser(), $role, $center);
 | 
			
		||||
            // we get the ids for those scopes
 | 
			
		||||
            $reachablesScopesId = array_map(
 | 
			
		||||
                function(Scope $scope) { return $scope->getId(); }, 
 | 
			
		||||
                $reachableScopes
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // if not the first center
 | 
			
		||||
            if ($centersI > 0) {
 | 
			
		||||
                $where .= ') OR (';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // condition for the center
 | 
			
		||||
            $where .= sprintf(' %s.%s = ? ', $metadataPerson->getTableName(), $personToCenter);
 | 
			
		||||
            $parameters[] = $center->getId();
 | 
			
		||||
 | 
			
		||||
            // begin loop for scopes
 | 
			
		||||
            $where .= ' AND (';
 | 
			
		||||
            $scopesI = 0; //like scope#i
 | 
			
		||||
 | 
			
		||||
            foreach ($reachablesScopesId as $scopeId) {
 | 
			
		||||
                if ($scopesI > 0) {
 | 
			
		||||
                    $where .= ' OR ';
 | 
			
		||||
                }
 | 
			
		||||
                $where .= sprintf(' %s.%s = ? ', $metadataActivity->getTableName(), $activityToScope);
 | 
			
		||||
                $parameters[] = $scopeId;
 | 
			
		||||
                $scopesI ++;
 | 
			
		||||
            }
 | 
			
		||||
            // close loop for scopes
 | 
			
		||||
            $where .= ') ';
 | 
			
		||||
            $centersI++;
 | 
			
		||||
        }
 | 
			
		||||
        // close loop for centers
 | 
			
		||||
        $where .= ')';
 | 
			
		||||
        
 | 
			
		||||
        return [$where, $parameters];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,42 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2021, Champs Libres Cooperative SCRLFS,
 | 
			
		||||
 * <http://www.champs-libres.coop>, <info@champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\ActivityBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\ActivityBundle\Entity\Activity;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @method AccompanyingPeriodParticipation|null find($id, $lockMode = null, $lockVersion = null)
 | 
			
		||||
 * @method AccompanyingPeriodParticipation|null findOneBy(array $criteria, array $orderBy = null)
 | 
			
		||||
 * @method AccompanyingPeriodParticipation[]    findAll()
 | 
			
		||||
 * @method AccompanyingPeriodParticipation[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 | 
			
		||||
 */
 | 
			
		||||
class ActivityRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, Activity::class);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
{% import 'ChillActivityBundle:ActivityReason:macro.html.twig' as m %}
 | 
			
		||||
 | 
			
		||||
<div>
 | 
			
		||||
  <h3>{{ activity.date|format_date('long') }}<span class="activity"> / {{ 'Activity'|trans }}</span>{% if 'person' != context %} / {{ activity.person|chill_entity_render_box({'addLink': true}) }}{% endif %}</h3>
 | 
			
		||||
    <h3>{{ activity.date|format_date('long') }}<span class="activity"> / {{ 'Activity'|trans }}</span></h3>
 | 
			
		||||
    <div class="statement">
 | 
			
		||||
        <span class="statement">{{ '%user% has done an %activity_type%'|trans(
 | 
			
		||||
           {
 | 
			
		||||
             '%user%' : activity.user,
 | 
			
		||||
             '%user%' : user,
 | 
			
		||||
             '%activity_type%': activity.type.name|localize_translatable_string,
 | 
			
		||||
             '%date%' : activity.date|format_date('long') }
 | 
			
		||||
        ) }}</span>
 | 
			
		||||
@@ -29,13 +29,13 @@
 | 
			
		||||
 | 
			
		||||
    <ul class="record_actions">
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="{{ path('chill_activity_activity_show', { 'person_id': activity.person.id, 'id': activity.id} ) }}" class="sc-button bt-view">
 | 
			
		||||
            <a href="{{ path('chill_activity_activity_show', { 'person_id': person.id, 'id': activity.id} ) }}" class="sc-button bt-view">
 | 
			
		||||
                {{ 'Show the activity'|trans }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
        {% if is_granted('CHILL_ACTIVITY_UPDATE', activity) %}
 | 
			
		||||
        <li>
 | 
			
		||||
            <a href="{{ path('chill_activity_activity_edit', { 'person_id': activity.person.id, 'id': activity.id} ) }}" class="sc-button bt-edit">
 | 
			
		||||
            <a href="{{ path('chill_activity_activity_edit', { 'person_id': person.id, 'id': activity.id} ) }}" class="sc-button bt-edit">
 | 
			
		||||
                {{ 'Edit the activity'|trans }}
 | 
			
		||||
            </a>
 | 
			
		||||
        </li>
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@
 | 
			
		||||
namespace Chill\ActivityBundle\Timeline;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Chill\ActivityBundle\Repository\ActivityACLAwareRepository;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
@@ -29,13 +28,13 @@ use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Doctrine\ORM\Mapping\ClassMetadata;
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\ActivityBundle\Entity\Activity;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provide activity for inclusion in timeline
 | 
			
		||||
 *
 | 
			
		||||
*/
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 * @author Champs Libres <info@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
@@ -56,10 +55,6 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
     * @var \Chill\MainBundle\Entity\User 
 | 
			
		||||
     */
 | 
			
		||||
    protected $user;
 | 
			
		||||
 | 
			
		||||
    protected ActivityACLAwareRepository $aclAwareRepository;
 | 
			
		||||
 | 
			
		||||
    private const SUPPORTED_CONTEXTS = [ 'center', 'person'];
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * TimelineActivityProvider constructor.
 | 
			
		||||
@@ -71,13 +66,11 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        EntityManager $em,
 | 
			
		||||
        AuthorizationHelper $helper,
 | 
			
		||||
        TokenStorageInterface $storage,
 | 
			
		||||
        ActivityACLAwareRepository $aclAwareRepository
 | 
			
		||||
        TokenStorageInterface $storage
 | 
			
		||||
    )
 | 
			
		||||
    {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->helper = $helper;
 | 
			
		||||
        $this->aclAwareRepository = $aclAwareRepository;
 | 
			
		||||
        
 | 
			
		||||
        if (!$storage->getToken()->getUser() instanceof \Chill\MainBundle\Entity\User)
 | 
			
		||||
        {
 | 
			
		||||
@@ -93,69 +86,67 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
     */
 | 
			
		||||
    public function fetchQuery($context, array $args)
 | 
			
		||||
    {
 | 
			
		||||
        if ('center' === $context) {
 | 
			
		||||
            return TimelineSingleQuery::fromArray($this->aclAwareRepository
 | 
			
		||||
                ->queryTimelineIndexer($context, $args));
 | 
			
		||||
        }
 | 
			
		||||
        $this->checkContext($context);
 | 
			
		||||
        
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
 | 
			
		||||
        [$where, $parameters] =  $this->getWhereClauseForPerson($args['person']);
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata('ChillActivityBundle:Activity');
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person');
 | 
			
		||||
        
 | 
			
		||||
        return TimelineSingleQuery::fromArray([
 | 
			
		||||
        return array(
 | 
			
		||||
           'id' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('id'),
 | 
			
		||||
           'type' => 'activity',
 | 
			
		||||
           'date' => $metadataActivity->getTableName()
 | 
			
		||||
                .'.'.$metadataActivity->getColumnName('date'),
 | 
			
		||||
           'FROM' => $this->getFromClausePerson($args['person']),
 | 
			
		||||
           'WHERE' => $where,
 | 
			
		||||
           'parameters' => $parameters
 | 
			
		||||
       ]);
 | 
			
		||||
           'FROM' => $this->getFromClause($metadataActivity, $metadataPerson),
 | 
			
		||||
           'WHERE' => $this->getWhereClause($metadataActivity, $metadataPerson,
 | 
			
		||||
                   $args['person'])
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getWhereClauseForPerson(Person $person)
 | 
			
		||||
    private function getWhereClause(ClassMetadata $metadataActivity, 
 | 
			
		||||
            ClassMetadata $metadataPerson, Person $person)
 | 
			
		||||
    {
 | 
			
		||||
        $parameters = [];
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        $role = new Role('CHILL_ACTIVITY_SEE');
 | 
			
		||||
        $reachableScopes =  $this->helper->getReachableScopes($this->user,
 | 
			
		||||
            $role, $person->getCenter());
 | 
			
		||||
        $whereClause = sprintf(' {activity.person_id} = ? AND {activity.scope_id} IN ({scopes_ids}) ');
 | 
			
		||||
        $scopes_ids = [];
 | 
			
		||||
 | 
			
		||||
        // first parameter: activity.person_id 
 | 
			
		||||
        $parameters[] = $person->getId();
 | 
			
		||||
 | 
			
		||||
        // loop on reachable scopes 
 | 
			
		||||
        foreach ($reachableScopes as $scope) {
 | 
			
		||||
            if (\in_array($scope->getId(), $scopes_ids)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $scopes_ids[] = '?'; 
 | 
			
		||||
            $parameters[] = $scope->getId();
 | 
			
		||||
        $reachableCenters = $this->helper->getReachableCenters($this->user, 
 | 
			
		||||
                $role);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        if (count($reachableCenters) === 0) {
 | 
			
		||||
            return 'FALSE = TRUE';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            \strtr(
 | 
			
		||||
                $whereClause,
 | 
			
		||||
                [
 | 
			
		||||
                    '{activity.person_id}' => $associationMapping['joinColumns'][0]['name'],
 | 
			
		||||
                    '{activity.scope_id}' => $metadataActivity->getTableName().'.'.
 | 
			
		||||
        
 | 
			
		||||
        // we start with activities having the person_id linked to person 
 | 
			
		||||
        // (currently only context "person" is supported)
 | 
			
		||||
        $whereClause = sprintf('%s = %d',
 | 
			
		||||
                 $associationMapping['joinColumns'][0]['name'],
 | 
			
		||||
                 $person->getId());
 | 
			
		||||
        
 | 
			
		||||
        // we add acl (reachable center and scopes)
 | 
			
		||||
        $centerAndScopeLines = array();
 | 
			
		||||
        foreach ($reachableCenters as $center) {
 | 
			
		||||
            $reachablesScopesId = array_map(
 | 
			
		||||
                    function(Scope $scope) { return $scope->getId(); },
 | 
			
		||||
                    $this->helper->getReachableScopes($this->user, $role, 
 | 
			
		||||
                        $person->getCenter())
 | 
			
		||||
                );
 | 
			
		||||
                    
 | 
			
		||||
            $centerAndScopeLines[] = sprintf('(%s = %d AND %s IN (%s))',
 | 
			
		||||
                    $metadataPerson->getTableName().'.'.
 | 
			
		||||
                        $metadataPerson->getAssociationMapping('center')['joinColumns'][0]['name'],
 | 
			
		||||
                    $center->getId(),
 | 
			
		||||
                    $metadataActivity->getTableName().'.'.
 | 
			
		||||
                        $metadataActivity->getAssociationMapping('scope')['joinColumns'][0]['name'],
 | 
			
		||||
                    '{scopes_ids}' => \implode(", ", $scopes_ids)
 | 
			
		||||
,
 | 
			
		||||
                ]
 | 
			
		||||
            ),
 | 
			
		||||
            $parameters
 | 
			
		||||
        ];
 | 
			
		||||
                    implode(',', $reachablesScopesId));
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        $whereClause .= ' AND ('.implode(' OR ', $centerAndScopeLines).')';
 | 
			
		||||
        
 | 
			
		||||
        return $whereClause;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private function getFromClausePerson()
 | 
			
		||||
    private function getFromClause(ClassMetadata $metadataActivity,
 | 
			
		||||
            ClassMetadata $metadataPerson)
 | 
			
		||||
    {
 | 
			
		||||
        $metadataActivity = $this->em->getClassMetadata(Activity::class);
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata(Person::class);
 | 
			
		||||
        $associationMapping = $metadataActivity->getAssociationMapping('person');
 | 
			
		||||
        
 | 
			
		||||
        return $metadataActivity->getTableName().' JOIN '
 | 
			
		||||
@@ -166,14 +157,14 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
            .$associationMapping['joinColumns'][0]['name']
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function getEntities(array $ids)
 | 
			
		||||
    {
 | 
			
		||||
        $activities = $this->em->getRepository(Activity::class)
 | 
			
		||||
        $activities = $this->em->getRepository('ChillActivityBundle:Activity')
 | 
			
		||||
              ->findBy(array('id' => $ids));
 | 
			
		||||
        
 | 
			
		||||
        $result = array();
 | 
			
		||||
@@ -192,13 +183,14 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
    {
 | 
			
		||||
        $this->checkContext($context);
 | 
			
		||||
        
 | 
			
		||||
        return [
 | 
			
		||||
        return array(
 | 
			
		||||
           'template' => 'ChillActivityBundle:Timeline:activity_person_context.html.twig',
 | 
			
		||||
           'template_data' => [
 | 
			
		||||
           'template_data' => array(
 | 
			
		||||
              'activity' => $entity,
 | 
			
		||||
              'context' => $context
 | 
			
		||||
            ]
 | 
			
		||||
        ];
 | 
			
		||||
              'person' => $args['person'],
 | 
			
		||||
              'user' => $entity->getUser()
 | 
			
		||||
           )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -218,7 +210,7 @@ class TimelineActivityProvider implements TimelineProviderInterface
 | 
			
		||||
     */
 | 
			
		||||
    private function checkContext($context)
 | 
			
		||||
    {
 | 
			
		||||
        if (FALSE === \in_array($context, self::SUPPORTED_CONTEXTS)) {
 | 
			
		||||
        if ($context !== 'person') {
 | 
			
		||||
            throw new \LogicException("The context '$context' is not "
 | 
			
		||||
                  . "supported. Currently only 'person' is supported");
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,14 +22,6 @@ services:
 | 
			
		||||
            - '@doctrine.orm.entity_manager'
 | 
			
		||||
            - '@chill.main.security.authorization.helper'
 | 
			
		||||
            - '@security.token_storage'
 | 
			
		||||
            - '@Chill\ActivityBundle\Repository\ActivityACLAwareRepository'
 | 
			
		||||
        public: true
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: chill.timeline, context: 'person' }
 | 
			
		||||
            - { name: chill.timeline, context: 'center' }
 | 
			
		||||
 | 
			
		||||
    Chill\ActivityBundle\Menu\:
 | 
			
		||||
        autowire: true
 | 
			
		||||
        autoconfigure: true
 | 
			
		||||
        resource: '../Menu/'
 | 
			
		||||
        tags: ['chill.menu_builder']
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/Bundle/ChillActivityBundle/config/services/menu.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Bundle/ChillActivityBundle/config/services/menu.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
services:
 | 
			
		||||
    Chill\ActivityBundle\Menu\MenuBuilder:
 | 
			
		||||
        arguments:
 | 
			
		||||
            $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
 | 
			
		||||
            $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
 | 
			
		||||
            $translator: '@Symfony\Component\Translation\TranslatorInterface'
 | 
			
		||||
        tags:
 | 
			
		||||
            - { name: 'chill.menu_builder' }
 | 
			
		||||
@@ -1,32 +1,18 @@
 | 
			
		||||
---
 | 
			
		||||
services:
 | 
			
		||||
    chill_activity.repository.activity_type:
 | 
			
		||||
        class: Doctrine\ORM\EntityRepository
 | 
			
		||||
        factory: ['@doctrine.orm.entity_manager', getRepository]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - 'Chill\ActivityBundle\Entity\ActivityType'
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
    chill_activity.repository.reason:
 | 
			
		||||
        class: Doctrine\ORM\EntityRepository
 | 
			
		||||
        factory: ['@doctrine.orm.entity_manager', getRepository]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - 'Chill\ActivityBundle\Entity\ActivityReason'
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
    chill_activity.repository.reason_category:
 | 
			
		||||
        class: Doctrine\ORM\EntityRepository
 | 
			
		||||
        factory: ['@doctrine.orm.entity_manager', getRepository]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - 'Chill\ActivityBundle\Entity\ActivityReasonCategory'
 | 
			
		||||
 | 
			
		||||
    Chill\ActivityBundle\Repository\ActivityRepository:
 | 
			
		||||
        tags: [doctrine.repository_service]
 | 
			
		||||
        arguments:
 | 
			
		||||
            - '@Doctrine\Persistence\ManagerRegistry'
 | 
			
		||||
 | 
			
		||||
    Chill\ActivityBundle\Repository\ActivityACLAwareRepository:
 | 
			
		||||
        arguments:
 | 
			
		||||
            $tokenStorage: '@Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface'
 | 
			
		||||
            $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper'
 | 
			
		||||
            $repository: '@Chill\ActivityBundle\Repository\ActivityRepository'
 | 
			
		||||
            $em: '@Doctrine\ORM\EntityManagerInterface'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,6 @@ namespace Chill\EventBundle\Timeline;
 | 
			
		||||
use Chill\EventBundle\Entity\Event;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineProviderInterface;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineSingleQuery;
 | 
			
		||||
use Doctrine\ORM\EntityManager;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
 | 
			
		||||
@@ -89,14 +88,13 @@ class TimelineEventProvider implements TimelineProviderInterface
 | 
			
		||||
        $metadataParticipation = $this->em->getClassMetadata('ChillEventBundle:Participation');
 | 
			
		||||
        $metadataPerson = $this->em->getClassMetadata('ChillPersonBundle:Person');
 | 
			
		||||
        
 | 
			
		||||
        $query = TimelineSingleQuery::fromArray([
 | 
			
		||||
        $query = array(
 | 
			
		||||
            'id' => $metadataEvent->getTableName().'.'.$metadataEvent->getColumnName('id'),
 | 
			
		||||
            'type' => 'event',
 | 
			
		||||
            'date' => $metadataEvent->getTableName().'.'.$metadataEvent->getColumnName('date'),
 | 
			
		||||
            'FROM' => $this->getFromClause($metadataEvent, $metadataParticipation, $metadataPerson),
 | 
			
		||||
            'WHERE' => $this->getWhereClause($metadataEvent, $metadataParticipation, $metadataPerson, $args['person']),
 | 
			
		||||
            'parameters' => []
 | 
			
		||||
        ]);
 | 
			
		||||
            'WHERE' => $this->getWhereClause($metadataEvent, $metadataParticipation, $metadataPerson, $args['person'])
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
@@ -240,4 +238,4 @@ class TimelineEventProvider implements TimelineProviderInterface
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,77 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2018 Champs Libres Cooperative <info@champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
namespace Chill\MainBundle\CRUD\CompilerPass;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
use Chill\MainBundle\Routing\MenuComposer;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Definition;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class CRUDControllerCompilerPass implements CompilerPassInterface
 | 
			
		||||
{
 | 
			
		||||
    public function process(ContainerBuilder $container)
 | 
			
		||||
    {
 | 
			
		||||
        $crudConfig = $container->getParameter('chill_main_crud_route_loader_config');
 | 
			
		||||
        $apiConfig = $container->getParameter('chill_main_api_route_loader_config');
 | 
			
		||||
 | 
			
		||||
        foreach ($crudConfig as $crudEntry) {
 | 
			
		||||
            $this->configureCrudController($container, $crudEntry, 'crud');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($apiConfig as $crudEntry) {
 | 
			
		||||
            $this->configureCrudController($container, $crudEntry, 'api');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add a controller for each definition, and add a methodCall to inject crud configuration to controller
 | 
			
		||||
     */
 | 
			
		||||
    private function configureCrudController(ContainerBuilder $container, array $crudEntry, string $apiOrCrud): void
 | 
			
		||||
    {
 | 
			
		||||
        $controllerClass = $crudEntry['controller'];
 | 
			
		||||
 | 
			
		||||
        $controllerServiceName = 'cs'.$apiOrCrud.'_'.$crudEntry['name'].'_controller';
 | 
			
		||||
 | 
			
		||||
        if ($container->hasDefinition($controllerClass)) {
 | 
			
		||||
            $controller = $container->getDefinition($controllerClass);
 | 
			
		||||
            $container->removeDefinition($controllerClass);
 | 
			
		||||
            $alreadyDefined = true;
 | 
			
		||||
        } else {
 | 
			
		||||
            $controller = new Definition($controllerClass);
 | 
			
		||||
            $alreadyDefined = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $controller->addTag('controller.service_arguments');
 | 
			
		||||
        if (FALSE === $alreadyDefined) {
 | 
			
		||||
            $controller->setAutoconfigured(true);
 | 
			
		||||
            $controller->setPublic(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $param = 'chill_main_'.$apiOrCrud.'_config_'.$crudEntry['name'];
 | 
			
		||||
        $container->setParameter($param, $crudEntry);
 | 
			
		||||
        $controller->addMethodCall('setCrudConfig', ['%'.$param.'%']);
 | 
			
		||||
 | 
			
		||||
        $container->setDefinition($controllerServiceName, $controller);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -1,251 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\CRUD\Controller;
 | 
			
		||||
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactory;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
 | 
			
		||||
class AbstractCRUDController extends AbstractController
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * The crud configuration 
 | 
			
		||||
     *
 | 
			
		||||
     * This configuration si defined by `chill_main['crud']` or `chill_main['apis']`
 | 
			
		||||
     *
 | 
			
		||||
     * @var array
 | 
			
		||||
     */
 | 
			
		||||
    protected array $crudConfig = [];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get the instance of the entity with the given id
 | 
			
		||||
     * 
 | 
			
		||||
     * @param string $id
 | 
			
		||||
     * @return object
 | 
			
		||||
     * @throw Symfony\Component\HttpKernel\Exception\NotFoundHttpException if the object is not found
 | 
			
		||||
     */
 | 
			
		||||
    protected function getEntity($action, $id, Request $request): object
 | 
			
		||||
    {
 | 
			
		||||
        $e = $this->getDoctrine()
 | 
			
		||||
            ->getRepository($this->getEntityClass())
 | 
			
		||||
            ->find($id);
 | 
			
		||||
 | 
			
		||||
        if (NULL === $e) {
 | 
			
		||||
            throw $this->createNotFoundException(sprintf("The object %s for id %s is not found", $this->getEntityClass(), $id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Count the number of entities
 | 
			
		||||
     *
 | 
			
		||||
     * By default, count all entities. You can customize the query by 
 | 
			
		||||
     * using the method `customizeQuery`.
 | 
			
		||||
     * 
 | 
			
		||||
     * @param string $action
 | 
			
		||||
     * @param Request $request
 | 
			
		||||
     * @return int
 | 
			
		||||
     */
 | 
			
		||||
    protected function countEntities(string $action, Request $request, $_format): int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->buildQueryEntities($action, $request)
 | 
			
		||||
            ->select('COUNT(e)')
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getSingleScalarResult()
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Query the entity.
 | 
			
		||||
     * 
 | 
			
		||||
     * By default, get all entities. You can customize the query by using the
 | 
			
		||||
     * method `customizeQuery`.
 | 
			
		||||
     * 
 | 
			
		||||
     * The method `orderEntity` is called internally to order entities.
 | 
			
		||||
     * 
 | 
			
		||||
     * It returns, by default, a query builder.
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    protected function queryEntities(string $action, Request $request, string $_format, 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, $_format);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add ordering fields in the query build by self::queryEntities
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    protected function orderQuery(string $action, $query, Request $request, PaginatorInterface $paginator, $_format)
 | 
			
		||||
    {
 | 
			
		||||
        return $query;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Build the base query for listing all entities.
 | 
			
		||||
     *
 | 
			
		||||
     * This method is used internally by `countEntities` `queryEntities`
 | 
			
		||||
     * 
 | 
			
		||||
     * This base query does not contains any `WHERE` or `SELECT` clauses. You
 | 
			
		||||
     * can add some by using the method `customizeQuery`.
 | 
			
		||||
     *
 | 
			
		||||
     * The alias for the entity is "e".
 | 
			
		||||
     * 
 | 
			
		||||
     * @param string $action
 | 
			
		||||
     * @param Request $request
 | 
			
		||||
     * @return QueryBuilder
 | 
			
		||||
     */
 | 
			
		||||
    protected function buildQueryEntities(string $action, Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        $qb = $this->getDoctrine()->getManager()
 | 
			
		||||
            ->createQueryBuilder()
 | 
			
		||||
            ->select('e')
 | 
			
		||||
            ->from($this->getEntityClass(), 'e')
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
        $this->customizeQuery($action, $request, $qb);
 | 
			
		||||
 | 
			
		||||
        return $qb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function customizeQuery(string $action, Request $request, $query): void {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the result of the query
 | 
			
		||||
     */
 | 
			
		||||
    protected function getQueryResult(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query) 
 | 
			
		||||
    {
 | 
			
		||||
        return $query->getQuery()->getResult();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onPreIndex(string $action, Request $request, string $_format): ?Response
 | 
			
		||||
    { 
 | 
			
		||||
        return null; 
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * method used by indexAction
 | 
			
		||||
     */
 | 
			
		||||
    protected function onPreIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator): ?Response
 | 
			
		||||
    { 
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * method used by indexAction
 | 
			
		||||
     */
 | 
			
		||||
    protected function onPostIndexBuildQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $query): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * method used by indexAction
 | 
			
		||||
     */
 | 
			
		||||
    protected function onPostIndexFetchQuery(string $action, Request $request, string $_format, int $totalItems, PaginatorInterface $paginator, $entities): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the complete FQDN of the class
 | 
			
		||||
     * 
 | 
			
		||||
     * @return string the complete fqdn of the class
 | 
			
		||||
     */
 | 
			
		||||
    protected function getEntityClass(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->crudConfig['class'];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * called on post fetch entity 
 | 
			
		||||
     */
 | 
			
		||||
    protected function onPostFetchEntity(string $action, Request $request, $entity, $_format): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called on post check ACL
 | 
			
		||||
     */
 | 
			
		||||
    protected function onPostCheckACL(string $action, Request $request, string $_format, $entity): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.
 | 
			
		||||
     * 
 | 
			
		||||
     * @throws \Symfony\Component\Security\Core\Exception\AccessDeniedHttpException
 | 
			
		||||
     */
 | 
			
		||||
    protected function checkACL(string $action, Request $request, string $_format, $entity = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->denyAccessUnlessGranted($this->getRoleFor($action, $request, $entity, $_format), $entity);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     * @return string the crud name
 | 
			
		||||
     */
 | 
			
		||||
    protected function getCrudName(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->crudConfig['name'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getActionConfig(string $action)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->crudConfig['actions'][$action];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Set the crud configuration
 | 
			
		||||
     *
 | 
			
		||||
     * Used by the container to inject configuration for this crud.
 | 
			
		||||
     */
 | 
			
		||||
    public function setCrudConfig(array $config): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->crudConfig = $config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return PaginatorFactory
 | 
			
		||||
     */
 | 
			
		||||
    protected function getPaginatorFactory(): PaginatorFactory
 | 
			
		||||
    {
 | 
			
		||||
        return $this->container->get('chill_main.paginator_factory');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getValidator(): ValidatorInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('validator');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,517 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\CRUD\Controller;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
use Symfony\Component\Serializer\SerializerInterface;
 | 
			
		||||
use Chill\MainBundle\Serializer\Model\Collection;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 | 
			
		||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
 | 
			
		||||
 | 
			
		||||
class ApiController extends AbstractCRUDController
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.  Serialize the entity and return the result. The serialization context is given by `getSerializationContext`
 | 
			
		||||
     * 
 | 
			
		||||
     */
 | 
			
		||||
    protected function entityGet(string $action, Request $request, $id, $_format = 'html'): Response
 | 
			
		||||
    {
 | 
			
		||||
        $entity = $this->getEntity($action, $id, $request, $_format);
 | 
			
		||||
        
 | 
			
		||||
        $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
 | 
			
		||||
        
 | 
			
		||||
        if ($postFetch instanceof Response) {
 | 
			
		||||
            return $postFetch;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->checkACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostCheckACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $this->onBeforeSerialize($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($_format === 'json') {
 | 
			
		||||
            $context = $this->getContextForSerialization($action, $request, $_format, $entity);
 | 
			
		||||
 | 
			
		||||
            return $this->json($entity, Response::HTTP_OK, [], $context);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This format is not implemented");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function onBeforeSerialize(string $action, Request $request, $_format, $entity): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base method for handling api action
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function entityApi(Request $request, $id, $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        switch ($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_GET:
 | 
			
		||||
            case Request::METHOD_HEAD:
 | 
			
		||||
                return $this->entityGet('_entity', $request, $id, $_format);
 | 
			
		||||
            case Request::METHOD_PUT:
 | 
			
		||||
            case Request::METHOD_PATCH:
 | 
			
		||||
                return $this->entityPut('_entity', $request, $id, $_format);
 | 
			
		||||
            case Request::METHOD_POST:
 | 
			
		||||
                return $this->entityPostAction('_entity', $request, $id, $_format);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    public function entityPost(Request $request, $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        switch($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_POST:
 | 
			
		||||
                return $this->entityPostAction('_entity', $request, $_format);
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \Symfony\Component\HttpFoundation\Exception\BadRequestException("This method is not implemented");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function entityPostAction($action, Request $request, string $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        $entity = $this->createEntity($action, $request);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $entity = $this->deserialize($action, $request, $_format, $entity);
 | 
			
		||||
        } catch (NotEncodableValueException $e) {
 | 
			
		||||
            throw new BadRequestException("invalid json", 400, $e);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $errors = $this->validate($action, $request, $_format, $entity);
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($errors->count() > 0) {
 | 
			
		||||
            $response = $this->json($errors);
 | 
			
		||||
            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
 | 
			
		||||
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->checkACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostCheckACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->getDoctrine()->getManager()->persist($entity);
 | 
			
		||||
        $this->getDoctrine()->getManager()->flush();
 | 
			
		||||
 | 
			
		||||
        $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        $response = $this->onBeforeSerialize($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->json(
 | 
			
		||||
            $entity,
 | 
			
		||||
            Response::HTTP_OK,
 | 
			
		||||
            [], 
 | 
			
		||||
            $this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    public function entityPut($action, Request $request, $id, string $_format): Response
 | 
			
		||||
    {
 | 
			
		||||
        $entity = $this->getEntity($action, $id, $request, $_format);
 | 
			
		||||
        
 | 
			
		||||
        $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
 | 
			
		||||
        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, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostCheckACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $this->onBeforeSerialize($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            $entity = $this->deserialize($action, $request, $_format, $entity);
 | 
			
		||||
        } catch (NotEncodableValueException $e) {
 | 
			
		||||
            throw new BadRequestException("invalid json", 400, $e);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $errors = $this->validate($action, $request, $_format, $entity);
 | 
			
		||||
 | 
			
		||||
        $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($errors->count() > 0) {
 | 
			
		||||
            $response = $this->json($errors);
 | 
			
		||||
            $response->setStatusCode(Response::HTTP_UNPROCESSABLE_ENTITY);
 | 
			
		||||
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->getDoctrine()->getManager()->flush();
 | 
			
		||||
 | 
			
		||||
        $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->json(
 | 
			
		||||
            $entity,
 | 
			
		||||
            Response::HTTP_OK,
 | 
			
		||||
            [], 
 | 
			
		||||
            $this->getContextForSerializationPostAlter($action, $request, $_format, $entity)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onAfterValidation(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected function onAfterFlush(string $action, Request $request, string $_format, $entity, ConstraintViolationListInterface $errors, array $more = []): ?Response
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getValidationGroups(string $action, Request $request, string $_format, $entity): ?array
 | 
			
		||||
    {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function validate(string $action, Request $request, string $_format, $entity, array $more = []): ConstraintViolationListInterface
 | 
			
		||||
    {
 | 
			
		||||
        $validationGroups = $this->getValidationGroups($action, $request, $_format, $entity);
 | 
			
		||||
        
 | 
			
		||||
        return $this->getValidator()->validate($entity, null, $validationGroups);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deserialize the content of the request into the class associated with the curd
 | 
			
		||||
     */
 | 
			
		||||
    protected function deserialize(string $action, Request $request, string $_format, $entity = null): object
 | 
			
		||||
    {
 | 
			
		||||
        $default = [];
 | 
			
		||||
 | 
			
		||||
        if (NULL !== $entity) {
 | 
			
		||||
            $default[AbstractNormalizer::OBJECT_TO_POPULATE] = $entity;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $context = \array_merge(
 | 
			
		||||
            $default,
 | 
			
		||||
            $this->getContextForSerialization($action, $request, $_format, $entity)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return $this->getSerializer()->deserialize($request->getContent(), $this->getEntityClass(), $_format, $context);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Base action for indexing entities
 | 
			
		||||
     */
 | 
			
		||||
    public function indexApi(Request $request, string $_format)
 | 
			
		||||
    {
 | 
			
		||||
        switch ($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_GET:
 | 
			
		||||
            case REQUEST::METHOD_HEAD:
 | 
			
		||||
                return $this->indexApiAction('_index', $request, $_format);
 | 
			
		||||
            default:
 | 
			
		||||
                throw $this->createNotFoundException("This method is not supported");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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.  Serialize the entities in a Collection, using `SerializeCollection`
 | 
			
		||||
     * 
 | 
			
		||||
     * @param string $action
 | 
			
		||||
     * @param Request $request
 | 
			
		||||
     * @return type
 | 
			
		||||
     */
 | 
			
		||||
    protected function indexApiAction($action, Request $request, $_format)
 | 
			
		||||
    {
 | 
			
		||||
        $this->onPreIndex($action, $request, $_format);
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->checkACL($action, $request, $_format);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!isset($entity)) {
 | 
			
		||||
            $entity = '';
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostCheckACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $totalItems = $this->countEntities($action, $request, $_format);
 | 
			
		||||
        $paginator = $this->getPaginatorFactory()->create($totalItems);
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPreIndexBuildQuery($action, $request, $_format, $totalItems, 
 | 
			
		||||
            $paginator);
 | 
			
		||||
        
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $query = $this->queryEntities($action, $request, $_format, $paginator);
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostIndexBuildQuery($action, $request, $_format, $totalItems, 
 | 
			
		||||
            $paginator, $query);
 | 
			
		||||
        
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $entities = $this->getQueryResult($action, $request, $_format, $totalItems, $paginator, $query);
 | 
			
		||||
        
 | 
			
		||||
        $response = $this->onPostIndexFetchQuery($action, $request, $_format, $totalItems, 
 | 
			
		||||
            $paginator, $entities);
 | 
			
		||||
        
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $this->serializeCollection($action, $request, $_format, $paginator, $entities);    
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Add or remove an associated entity, using `add` and `remove` methods.
 | 
			
		||||
     *
 | 
			
		||||
     * This method:
 | 
			
		||||
     *
 | 
			
		||||
     * 1. Fetch the base entity (throw 404 if not found)
 | 
			
		||||
     * 2. checkACL, 
 | 
			
		||||
     * 3. run onPostCheckACL, return response if any,
 | 
			
		||||
     * 4. deserialize posted data into the entity given by $postedDataType, with the context in $postedDataContext
 | 
			
		||||
     * 5. run 'add+$property' for POST method, or 'remove+$property' for DELETE method
 | 
			
		||||
     * 6. validate the base entity (not the deserialized one). Groups are fetched from getValidationGroups, validation is perform by `validate`
 | 
			
		||||
     * 7. run onAfterValidation
 | 
			
		||||
     * 8. if errors, return a 422 response with errors
 | 
			
		||||
     * 9. flush the data 
 | 
			
		||||
     * 10. run onAfterFlush
 | 
			
		||||
     * 11. return a 202 response for DELETE with empty body, or HTTP 200 for post with serialized posted entity
 | 
			
		||||
     *
 | 
			
		||||
     * @param string action
 | 
			
		||||
     * @param mixed id
 | 
			
		||||
     * @param Request $request
 | 
			
		||||
     * @param string $_format
 | 
			
		||||
     * @param string $property the name of the property. This will be used to make a `add+$property` and `remove+$property` method
 | 
			
		||||
     * @param string $postedDataType the type of the posted data (the content)
 | 
			
		||||
     * @param string $postedDataContext a context to deserialize posted data (the content)
 | 
			
		||||
     * @throw BadRequestException if unable to deserialize the posted data
 | 
			
		||||
     * @throw BadRequestException if the method is not POST or DELETE
 | 
			
		||||
     *
 | 
			
		||||
     */ 
 | 
			
		||||
    protected function addRemoveSomething(string $action, $id, Request $request, string $_format, string $property, string $postedDataType, $postedDataContext = []): Response
 | 
			
		||||
    {
 | 
			
		||||
        $entity = $this->getEntity($action, $id, $request);
 | 
			
		||||
 | 
			
		||||
        $postFetch = $this->onPostFetchEntity($action, $request, $entity, $_format);
 | 
			
		||||
        if ($postFetch instanceof Response) {
 | 
			
		||||
            return $postFetch;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $this->checkACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $this->onPostCheckACL($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $this->onBeforeSerialize($action, $request, $_format, $entity);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            $postedData = $this->getSerializer()->deserialize($request->getContent(), $postedDataType, $_format, $postedDataContext);
 | 
			
		||||
        } catch (\Symfony\Component\Serializer\Exception\UnexpectedValueException $e) {
 | 
			
		||||
            throw new BadRequestException(sprintf("Unable to deserialize posted ".
 | 
			
		||||
                "data: %s", $e->getMessage()), 0, $e);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch ($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_DELETE:
 | 
			
		||||
                // oups... how to use property accessor to remove element ?
 | 
			
		||||
                $entity->{'remove'.\ucfirst($property)}($postedData);
 | 
			
		||||
                break;
 | 
			
		||||
            case Request::METHOD_POST:
 | 
			
		||||
                $entity->{'add'.\ucfirst($property)}($postedData);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                throw new BadRequestException("this method is not supported");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $errors = $this->validate($action, $request, $_format, $entity, [$postedData]);
 | 
			
		||||
 | 
			
		||||
        $response = $this->onAfterValidation($action, $request, $_format, $entity, $errors, [$postedData]);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($errors->count() > 0) {
 | 
			
		||||
            // only format accepted
 | 
			
		||||
            return $this->json($errors, 422);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->getDoctrine()->getManager()->flush();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        $response = $this->onAfterFlush($action, $request, $_format, $entity, $errors, [$postedData]);
 | 
			
		||||
        if ($response instanceof Response) {
 | 
			
		||||
            return $response;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch ($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_DELETE:
 | 
			
		||||
                return $this->json('', Response::HTTP_OK);
 | 
			
		||||
            case Request::METHOD_POST:
 | 
			
		||||
                return $this->json(
 | 
			
		||||
                    $postedData,
 | 
			
		||||
                    Response::HTTP_OK,
 | 
			
		||||
                    [], 
 | 
			
		||||
                    $this->getContextForSerializationPostAlter($action, $request, $_format, $entity, [$postedData])
 | 
			
		||||
                );
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Serialize collections
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    protected function serializeCollection(string $action, Request $request, string $_format, PaginatorInterface $paginator, $entities): Response
 | 
			
		||||
    {
 | 
			
		||||
        $model = new Collection($entities, $paginator);
 | 
			
		||||
 | 
			
		||||
        $context = $this->getContextForSerialization($action, $request, $_format, $entities);
 | 
			
		||||
 | 
			
		||||
        return $this->json($model, Response::HTTP_OK, [], $context);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    protected function getContextForSerialization(string $action, Request $request, string $_format, $entity): array
 | 
			
		||||
    {
 | 
			
		||||
        switch ($request->getMethod()) {
 | 
			
		||||
            case Request::METHOD_GET:
 | 
			
		||||
                return [ 'groups' => [ 'read' ]];
 | 
			
		||||
            case Request::METHOD_PUT:
 | 
			
		||||
            case Request::METHOD_PATCH:
 | 
			
		||||
            case Request::METHOD_POST:
 | 
			
		||||
                return [ 'groups' => [ 'write' ]];
 | 
			
		||||
            default:
 | 
			
		||||
                throw new \LogicException("get context for serialization is not implemented for this method");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the context for serialization post alter query (in case of 
 | 
			
		||||
     * PATCH, PUT, or POST method)
 | 
			
		||||
     *
 | 
			
		||||
     * This is called **after** the entity was altered.
 | 
			
		||||
     */
 | 
			
		||||
    protected function getContextForSerializationPostAlter(string $action, Request $request, string $_format, $entity, array $more = []): array
 | 
			
		||||
    {
 | 
			
		||||
        return [ 'groups' => [ 'read' ]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get the role given from the config.
 | 
			
		||||
     */
 | 
			
		||||
    protected function getRoleFor(string $action, Request $request, $entity, $_format): string
 | 
			
		||||
    {
 | 
			
		||||
        $actionConfig = $this->getActionConfig($action);
 | 
			
		||||
 | 
			
		||||
        if (NULL !== $actionConfig['roles'][$request->getMethod()]) {
 | 
			
		||||
            return $actionConfig['roles'][$request->getMethod()];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->crudConfig['base_role']) {
 | 
			
		||||
            return $this->crudConfig['base_role'];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new \RuntimeException(sprintf("the config does not have any role for the ".
 | 
			
		||||
            "method %s nor a global role for the whole action. Add those to your ".
 | 
			
		||||
            "configuration or override the required method", $request->getMethod()));
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getSerializer(): SerializerInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get('serializer');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -23,9 +23,6 @@ namespace Chill\MainBundle\CRUD\Routing;
 | 
			
		||||
use Symfony\Component\Config\Loader\Loader;
 | 
			
		||||
use Symfony\Component\Routing\Route;
 | 
			
		||||
use Symfony\Component\Routing\RouteCollection;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\CRUDController;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class CRUDRoutesLoader
 | 
			
		||||
@@ -35,34 +32,24 @@ use Chill\MainBundle\CRUD\Controller\CRUDController;
 | 
			
		||||
 */
 | 
			
		||||
class CRUDRoutesLoader extends Loader
 | 
			
		||||
{
 | 
			
		||||
    protected array $crudConfig = [];
 | 
			
		||||
 | 
			
		||||
    protected array $apiCrudConfig = [];
 | 
			
		||||
    /**
 | 
			
		||||
     * @var array
 | 
			
		||||
     */
 | 
			
		||||
    protected $config = [];
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @var bool
 | 
			
		||||
     */
 | 
			
		||||
    private $isLoaded = false;
 | 
			
		||||
 | 
			
		||||
    private const ALL_SINGLE_METHODS = [
 | 
			
		||||
        Request::METHOD_GET,
 | 
			
		||||
        Request::METHOD_POST,
 | 
			
		||||
        Request::METHOD_PUT,
 | 
			
		||||
        Request::METHOD_DELETE
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    private const ALL_INDEX_METHODS = [ Request::METHOD_GET, Request::METHOD_HEAD ];
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * CRUDRoutesLoader constructor.
 | 
			
		||||
     *
 | 
			
		||||
     * @param $crudConfig the config from cruds
 | 
			
		||||
     * @param $apicrudConfig the config from api_crud
 | 
			
		||||
     * @param $config
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(array $crudConfig, array $apiConfig)
 | 
			
		||||
    public function __construct($config)
 | 
			
		||||
    {
 | 
			
		||||
        $this->crudConfig = $crudConfig;
 | 
			
		||||
        $this->apiConfig = $apiConfig;
 | 
			
		||||
        $this->config = $config;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
@@ -76,153 +63,53 @@ class CRUDRoutesLoader extends Loader
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Load routes for CRUD and CRUD Api
 | 
			
		||||
     * @return RouteCollection
 | 
			
		||||
     */
 | 
			
		||||
    public function load($resource, $type = null): RouteCollection
 | 
			
		||||
    public function load($resource, $type = null)
 | 
			
		||||
    {
 | 
			
		||||
    
 | 
			
		||||
        if (true === $this->isLoaded) {
 | 
			
		||||
            throw new \RuntimeException('Do not add the "CRUD" loader twice');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        $collection = new RouteCollection();
 | 
			
		||||
        
 | 
			
		||||
        foreach ($this->crudConfig as $crudConfig) {
 | 
			
		||||
            $collection->addCollection($this->loadCrudConfig($crudConfig));
 | 
			
		||||
        foreach ($this->config as $config) {
 | 
			
		||||
            $collection->addCollection($this->loadConfig($config));
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($this->apiConfig as $crudConfig) {
 | 
			
		||||
            $collection->addCollection($this->loadApi($crudConfig));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        return $collection;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Load routes for CRUD (without api)
 | 
			
		||||
     *
 | 
			
		||||
     * @param $crudConfig
 | 
			
		||||
     * @param $config
 | 
			
		||||
     * @return RouteCollection
 | 
			
		||||
     */
 | 
			
		||||
    protected function loadCrudConfig($crudConfig): RouteCollection
 | 
			
		||||
    protected function loadConfig($config): RouteCollection
 | 
			
		||||
    {
 | 
			
		||||
        $collection = new RouteCollection();
 | 
			
		||||
        $controller ='cscrud_'.$crudConfig['name'].'_controller';
 | 
			
		||||
 | 
			
		||||
        foreach ($crudConfig['actions'] as $name => $action) {
 | 
			
		||||
            // defaults (controller name)
 | 
			
		||||
        foreach ($config['actions'] as $name => $action) {
 | 
			
		||||
            $defaults = [
 | 
			
		||||
                '_controller' => $controller.':'.($action['controller_action'] ?? $name)
 | 
			
		||||
                '_controller' => 'cscrud_'.$config['name'].'_controller'.':'.($action['controller_action'] ?? $name)
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            if ($name === 'index') {
 | 
			
		||||
                $path = "{_locale}".$crudConfig['base_path'];
 | 
			
		||||
                $path = "{_locale}".$config['base_path'];
 | 
			
		||||
                $route = new Route($path, $defaults);
 | 
			
		||||
            } elseif ($name === 'new') {
 | 
			
		||||
                $path = "{_locale}".$crudConfig['base_path'].'/'.$name;
 | 
			
		||||
                $path = "{_locale}".$config['base_path'].'/'.$name;
 | 
			
		||||
                $route = new Route($path, $defaults);
 | 
			
		||||
            } else {
 | 
			
		||||
                $path = "{_locale}".$crudConfig['base_path'].($action['path'] ?? '/{id}/'.$name);
 | 
			
		||||
                $path = "{_locale}".$config['base_path'].($action['path'] ?? '/{id}/'.$name);
 | 
			
		||||
                $requirements = $action['requirements'] ?? [
 | 
			
		||||
                    '{id}' => '\d+'
 | 
			
		||||
                ];
 | 
			
		||||
                $route = new Route($path, $defaults, $requirements);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            $collection->add('chill_crud_'.$crudConfig['name'].'_'.$name, $route);
 | 
			
		||||
            $collection->add('chill_crud_'.$config['name'].'_'.$name, $route);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return $collection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load routes for api single
 | 
			
		||||
     *
 | 
			
		||||
     * @param $crudConfig
 | 
			
		||||
     * @return RouteCollection
 | 
			
		||||
     */
 | 
			
		||||
    protected function loadApi(array $crudConfig): RouteCollection
 | 
			
		||||
    {
 | 
			
		||||
        $collection = new RouteCollection();
 | 
			
		||||
        $controller ='csapi_'.$crudConfig['name'].'_controller';
 | 
			
		||||
 | 
			
		||||
        foreach ($crudConfig['actions'] as $name => $action) {
 | 
			
		||||
            // filter only on single actions
 | 
			
		||||
            $singleCollection = $action['single-collection'] ?? $name === '_entity' ? 'single' : NULL;
 | 
			
		||||
            if ('collection' === $singleCollection) {
 | 
			
		||||
//                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // compute default action
 | 
			
		||||
            switch ($name) {
 | 
			
		||||
                case '_entity':
 | 
			
		||||
                    $controllerAction = 'entityApi';
 | 
			
		||||
                    break;
 | 
			
		||||
                case '_index':
 | 
			
		||||
                    $controllerAction = 'indexApi';
 | 
			
		||||
                    break;
 | 
			
		||||
                default:
 | 
			
		||||
                    $controllerAction = $name.'Api';
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $defaults = [
 | 
			
		||||
                '_controller' => $controller.':'.($action['controller_action'] ?? $controllerAction)
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            // path are rewritten
 | 
			
		||||
            // if name === 'default', we rewrite it to nothing :-)
 | 
			
		||||
            $localName = \in_array($name, [ '_entity', '_index' ]) ? '' : '/'.$name;
 | 
			
		||||
            if ('collection' === $action['single-collection'] || '_index' === $name) {
 | 
			
		||||
                $localPath = $action['path'] ?? $localName.'.{_format}';
 | 
			
		||||
            } else {
 | 
			
		||||
                $localPath = $action['path'] ?? '/{id}'.$localName.'.{_format}';
 | 
			
		||||
            }
 | 
			
		||||
            $path = $crudConfig['base_path'].$localPath;
 | 
			
		||||
 | 
			
		||||
            $requirements = $action['requirements'] ?? [ '{id}' => '\d+' ];
 | 
			
		||||
 | 
			
		||||
            $methods = \array_keys(\array_filter($action['methods'], function($value, $key) { return $value; },
 | 
			
		||||
                ARRAY_FILTER_USE_BOTH));
 | 
			
		||||
 | 
			
		||||
            if (count($methods) === 0) {
 | 
			
		||||
                throw new \RuntimeException("The api configuration named \"{$crudConfig['name']}\", action \"{$name}\", ".
 | 
			
		||||
                    "does not have any allowed methods. You should remove this action from the config ".
 | 
			
		||||
                    "or allow, at least, one method"); 
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ('_entity' === $name && \in_array(Request::METHOD_POST, $methods)) {
 | 
			
		||||
                unset($methods[\array_search(Request::METHOD_POST, $methods)]);
 | 
			
		||||
                $entityPostRoute = $this->createEntityPostRoute($name, $crudConfig, $action, 
 | 
			
		||||
                    $controller);
 | 
			
		||||
                $collection->add("chill_api_single_{$crudConfig['name']}_{$name}_create", 
 | 
			
		||||
                    $entityPostRoute);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (count($methods) === 0) {
 | 
			
		||||
                // the only method was POST,
 | 
			
		||||
                // continue to next
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $route = new Route($path, $defaults, $requirements);
 | 
			
		||||
            $route->setMethods($methods);
 | 
			
		||||
            
 | 
			
		||||
            $collection->add('chill_api_single_'.$crudConfig['name'].'_'.$name, $route);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $collection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createEntityPostRoute(string $name, $crudConfig, array $action, $controller): Route
 | 
			
		||||
    {
 | 
			
		||||
        $localPath = $action['path'].'.{_format}';
 | 
			
		||||
        $defaults = [
 | 
			
		||||
            '_controller' => $controller.':'.($action['controller_action'] ?? 'entityPost')
 | 
			
		||||
        ];
 | 
			
		||||
        $path = $crudConfig['base_path'].$localPath;
 | 
			
		||||
        $requirements = $action['requirements'] ?? [];
 | 
			
		||||
        $route = new Route($path, $defaults, $requirements);
 | 
			
		||||
        $route->setMethods([ Request::METHOD_POST ]);
 | 
			
		||||
 | 
			
		||||
        return $route;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ use Chill\MainBundle\DependencyInjection\CompilerPass\NotificationCounterCompile
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\MenuCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\ACLFlagsCompilerPass;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\CompilerPass\GroupingCenterCompilerPass;
 | 
			
		||||
use Chill\MainBundle\CRUD\CompilerPass\CRUDControllerCompilerPass;
 | 
			
		||||
use Chill\MainBundle\Templating\Entity\CompilerPass as RenderEntityCompilerPass;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -34,6 +33,5 @@ class ChillMainBundle extends Bundle
 | 
			
		||||
        $container->addCompilerPass(new ACLFlagsCompilerPass());
 | 
			
		||||
        $container->addCompilerPass(new GroupingCenterCompilerPass());
 | 
			
		||||
        $container->addCompilerPass(new RenderEntityCompilerPass());
 | 
			
		||||
        $container->addCompilerPass(new CRUDControllerCompilerPass());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class AddressReferenceAPIController
 | 
			
		||||
 *
 | 
			
		||||
 * @package Chill\MainBundle\Controller
 | 
			
		||||
 * @author Champs Libres
 | 
			
		||||
 */
 | 
			
		||||
class AddressReferenceAPIController extends ApiController
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    protected function customizeQuery(string $action, Request $request, $qb): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($request->query->has('postal_code')) {
 | 
			
		||||
 | 
			
		||||
            $qb->where('e.postcode = :postal_code')
 | 
			
		||||
               ->setParameter('postal_code', $request->query->get('postal_code'));
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\CRUD\Controller\ApiController;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class PostalCodeAPIController
 | 
			
		||||
 *
 | 
			
		||||
 * @package Chill\MainBundle\Controller
 | 
			
		||||
 * @author Champs Libres
 | 
			
		||||
 */
 | 
			
		||||
class PostalCodeAPIController extends ApiController
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    protected function customizeQuery(string $action, Request $request, $qb): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($request->query->has('country')) {
 | 
			
		||||
 | 
			
		||||
            $qb->where('e.country = :country')
 | 
			
		||||
               ->setParameter('country', $request->query->get('country'));
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -22,7 +22,6 @@
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Serializer\Model\Collection;
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Chill\MainBundle\Search\UnknowSearchDomainException;
 | 
			
		||||
@@ -35,7 +34,6 @@ use Symfony\Component\HttpFoundation\JsonResponse;
 | 
			
		||||
use Chill\MainBundle\Search\SearchProvider;
 | 
			
		||||
use Symfony\Contracts\Translation\TranslatorInterface;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactory;
 | 
			
		||||
use Chill\MainBundle\Search\SearchApi;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class SearchController
 | 
			
		||||
@@ -44,24 +42,32 @@ use Chill\MainBundle\Search\SearchApi;
 | 
			
		||||
 */
 | 
			
		||||
class SearchController extends AbstractController
 | 
			
		||||
{
 | 
			
		||||
    protected SearchProvider $searchProvider;
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var SearchProvider
 | 
			
		||||
     */
 | 
			
		||||
    protected $searchProvider;
 | 
			
		||||
    
 | 
			
		||||
    protected TranslatorInterface $translator;
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var TranslatorInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $translator;
 | 
			
		||||
    
 | 
			
		||||
    protected PaginatorFactory $paginatorFactory;
 | 
			
		||||
 | 
			
		||||
    protected SearchApi $searchApi;
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var PaginatorFactory
 | 
			
		||||
     */
 | 
			
		||||
    protected $paginatorFactory;
 | 
			
		||||
    
 | 
			
		||||
    function __construct(
 | 
			
		||||
        SearchProvider $searchProvider, 
 | 
			
		||||
        TranslatorInterface $translator,
 | 
			
		||||
        PaginatorFactory $paginatorFactory,
 | 
			
		||||
        SearchApi $searchApi
 | 
			
		||||
        PaginatorFactory $paginatorFactory
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->searchProvider = $searchProvider;
 | 
			
		||||
        $this->translator = $translator;
 | 
			
		||||
        $this->paginatorFactory = $paginatorFactory;
 | 
			
		||||
        $this->searchApi = $searchApi;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
@@ -146,19 +152,6 @@ class SearchController extends AbstractController
 | 
			
		||||
              array('results' => $results, 'pattern' => $pattern)
 | 
			
		||||
              );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function searchApi(Request $request, $_format): JsonResponse
 | 
			
		||||
    {
 | 
			
		||||
        //TODO this is an incomplete implementation
 | 
			
		||||
        $query = $request->query->get('q', '');
 | 
			
		||||
 | 
			
		||||
        $results = $this->searchApi->getResults($query, 0, 150);
 | 
			
		||||
        $paginator = $this->paginatorFactory->create(count($results));
 | 
			
		||||
 | 
			
		||||
        $collection = new Collection($results, $paginator);
 | 
			
		||||
 | 
			
		||||
        return $this->json($collection);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    public function advancedSearchListAction(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2015 Champs-Libres Coopérative <info@champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <http://www.gnu.org/licenses/>.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Controller;
 | 
			
		||||
 | 
			
		||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
 | 
			
		||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
use Chill\MainBundle\Timeline\TimelineBuilder;
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorFactory;
 | 
			
		||||
use Chill\PersonBundle\Security\Authorization\PersonVoter;
 | 
			
		||||
use Chill\MainBundle\Security\Authorization\AuthorizationHelper;
 | 
			
		||||
use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Symfony\Component\Routing\Annotation\Route;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
 | 
			
		||||
class TimelineCenterController extends AbstractController
 | 
			
		||||
{
 | 
			
		||||
    
 | 
			
		||||
    protected TimelineBuilder $timelineBuilder;
 | 
			
		||||
    
 | 
			
		||||
    protected PaginatorFactory $paginatorFactory;
 | 
			
		||||
 | 
			
		||||
    private Security $security;
 | 
			
		||||
    
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        TimelineBuilder $timelineBuilder,
 | 
			
		||||
        PaginatorFactory $paginatorFactory,
 | 
			
		||||
        Security $security
 | 
			
		||||
    ) {
 | 
			
		||||
        $this->timelineBuilder = $timelineBuilder;
 | 
			
		||||
        $this->paginatorFactory = $paginatorFactory;
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @Route("/{_locale}/center/timeline",
 | 
			
		||||
     *    name="chill_center_timeline",
 | 
			
		||||
     *    methods={"GET"}
 | 
			
		||||
     *  )
 | 
			
		||||
     */ 
 | 
			
		||||
    public function centerAction(Request $request)
 | 
			
		||||
    {
 | 
			
		||||
        // collect reachable center for each group
 | 
			
		||||
        $user = $this->security->getUser();
 | 
			
		||||
        $centers = [];
 | 
			
		||||
        foreach ($user->getGroupCenters() as $group) {
 | 
			
		||||
            $centers[] = $group->getCenter();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 === count($centers)) {
 | 
			
		||||
            throw $this->createNotFoundException();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $nbItems = $this->timelineBuilder->countItems('center', 
 | 
			
		||||
            [ 'centers' => $centers ]
 | 
			
		||||
            );
 | 
			
		||||
        
 | 
			
		||||
        $paginator = $this->paginatorFactory->create($nbItems);
 | 
			
		||||
        
 | 
			
		||||
        return $this->render('@ChillMain/Timeline/index.html.twig', array
 | 
			
		||||
            (
 | 
			
		||||
                'timeline' => $this->timelineBuilder->getTimelineHTML(
 | 
			
		||||
                    'center', 
 | 
			
		||||
                    [ 'centers' => $centers ],
 | 
			
		||||
                    $paginator->getCurrentPage()->getFirstItemNumber(),
 | 
			
		||||
                    $paginator->getItemsPerPage()
 | 
			
		||||
                    ),
 | 
			
		||||
                'nb_items' => $nbItems,
 | 
			
		||||
                'paginator' => $paginator
 | 
			
		||||
            )
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,95 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\DataFixtures\ORM;
 | 
			
		||||
 | 
			
		||||
use Doctrine\Common\DataFixtures\AbstractFixture;
 | 
			
		||||
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerInterface;
 | 
			
		||||
use Doctrine\Persistence\ObjectManager;
 | 
			
		||||
use Chill\MainBundle\DataFixtures\ORM\LoadPostalCodes;
 | 
			
		||||
use Chill\MainBundle\Entity\AddressReference;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\Point;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Load reference addresses into database
 | 
			
		||||
 *
 | 
			
		||||
 * @author Champs Libres
 | 
			
		||||
 */
 | 
			
		||||
class LoadAddressReferences extends AbstractFixture implements ContainerAwareInterface, OrderedFixtureInterface {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    protected $faker;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->faker = \Faker\Factory::create('fr_FR');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var ContainerInterface
 | 
			
		||||
     */
 | 
			
		||||
    private $container;
 | 
			
		||||
 | 
			
		||||
    public function setContainer(ContainerInterface $container = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->container = $container;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getOrder() {
 | 
			
		||||
        return 51;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a random point
 | 
			
		||||
     *
 | 
			
		||||
     * @return Point
 | 
			
		||||
     */
 | 
			
		||||
    private function getRandomPoint()
 | 
			
		||||
    {
 | 
			
		||||
        $lonBrussels = 4.35243;
 | 
			
		||||
        $latBrussels = 50.84676;
 | 
			
		||||
        $lon = $lonBrussels + 0.01 * rand(-5, 5);
 | 
			
		||||
        $lat = $latBrussels + 0.01 * rand(-5, 5);
 | 
			
		||||
        return Point::fromLonLat($lon, $lat);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a random reference address
 | 
			
		||||
     *
 | 
			
		||||
     * @return AddressReference
 | 
			
		||||
     */
 | 
			
		||||
    private function getRandomAddressReference()
 | 
			
		||||
    {
 | 
			
		||||
        $ar= new AddressReference();
 | 
			
		||||
 | 
			
		||||
        $ar->setRefId($this->faker->numerify('ref-id-######'));
 | 
			
		||||
        $ar->setStreet($this->faker->streetName);
 | 
			
		||||
        $ar->setStreetNumber(rand(0,199));
 | 
			
		||||
        $ar ->setPoint($this->getRandomPoint());
 | 
			
		||||
        $ar->setPostcode($this->getReference(
 | 
			
		||||
            LoadPostalCodes::$refs[array_rand(LoadPostalCodes::$refs)]
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        $ar->setMunicipalityCode($ar->getPostcode()->getCode());
 | 
			
		||||
 | 
			
		||||
        return $ar
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function load(ObjectManager $manager) {
 | 
			
		||||
 | 
			
		||||
        echo "loading some reference address... \n";
 | 
			
		||||
 | 
			
		||||
        for ($i=0; $i<10; $i++) {
 | 
			
		||||
            $ar = $this->getRandomAddressReference();
 | 
			
		||||
            $manager->persist($ar);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $manager->flush();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -55,11 +55,11 @@ class LoadCenters extends AbstractFixture implements OrderedFixtureInterface
 | 
			
		||||
    public function load(ObjectManager $manager)
 | 
			
		||||
    {
 | 
			
		||||
        foreach (static::$centers as $new) {
 | 
			
		||||
            $center = new Center();
 | 
			
		||||
            $center->setName($new['name']);
 | 
			
		||||
            $centerA = new Center();
 | 
			
		||||
            $centerA->setName($new['name']);
 | 
			
		||||
 | 
			
		||||
            $manager->persist($center);
 | 
			
		||||
            $this->addReference($new['ref'], $center);
 | 
			
		||||
            $manager->persist($centerA);
 | 
			
		||||
            $this->addReference($new['ref'], $centerA);
 | 
			
		||||
            static::$refs[] = $new['ref'];
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 
 | 
			
		||||
@@ -35,9 +35,6 @@ use Chill\MainBundle\Doctrine\DQL\OverlapsI;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Definition;
 | 
			
		||||
use Symfony\Component\DependencyInjection\Reference;
 | 
			
		||||
use Chill\MainBundle\Doctrine\DQL\Replace;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Type\NativeDateIntervalType;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Type\PointType;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ChillMainExtension
 | 
			
		||||
@@ -136,7 +133,7 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        $loader->load('services/search.yaml');
 | 
			
		||||
        $loader->load('services/serializer.yaml');
 | 
			
		||||
 | 
			
		||||
        $this->configureCruds($container, $config['cruds'], $config['apis'], $loader);
 | 
			
		||||
        $this->configureCruds($container, $config['cruds'], $loader);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -169,49 +166,32 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        $container->prependExtensionConfig('twig', $twigConfig);
 | 
			
		||||
 | 
			
		||||
        //add DQL function to ORM (default entity_manager)
 | 
			
		||||
        $container
 | 
			
		||||
            ->prependExtensionConfig(
 | 
			
		||||
                'doctrine',
 | 
			
		||||
                [
 | 
			
		||||
                    'orm' => [
 | 
			
		||||
                        'dql' => [
 | 
			
		||||
                            'string_functions' => [
 | 
			
		||||
                                '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,
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                    ],
 | 
			
		||||
                ],
 | 
			
		||||
        );
 | 
			
		||||
        $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',
 | 
			
		||||
                [
 | 
			
		||||
                   'dbal' => [
 | 
			
		||||
                        // This is mandatory since we are using postgis as database.
 | 
			
		||||
                        'mapping_types' => [
 | 
			
		||||
                            'geometry' => 'string',
 | 
			
		||||
                        ],
 | 
			
		||||
                        'types' => [
 | 
			
		||||
                            'dateinterval' => [
 | 
			
		||||
                                'class' => NativeDateIntervalType::class
 | 
			
		||||
                            ],
 | 
			
		||||
                            'point' => [
 | 
			
		||||
                                'class' => PointType::class
 | 
			
		||||
                            ]
 | 
			
		||||
                        ]
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
        );
 | 
			
		||||
        $container->prependExtensionConfig('doctrine', array(
 | 
			
		||||
           'dbal' => [
 | 
			
		||||
               'types' => [
 | 
			
		||||
                   'dateinterval' => \Chill\MainBundle\Doctrine\Type\NativeDateIntervalType::class
 | 
			
		||||
               ]
 | 
			
		||||
           ]
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        //add current route to chill main
 | 
			
		||||
        $container->prependExtensionConfig('chill_main', array(
 | 
			
		||||
@@ -227,125 +207,54 @@ class ChillMainExtension extends Extension implements PrependExtensionInterface,
 | 
			
		||||
        $container->prependExtensionConfig('monolog', array(
 | 
			
		||||
            'channels' => array('chill')
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        //add crud api
 | 
			
		||||
        $this->prependCruds($container);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Load parameter for configuration and set parameters for api
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     * @param array $config the config under 'cruds' key
 | 
			
		||||
     * @return null
 | 
			
		||||
     */
 | 
			
		||||
    protected function configureCruds(
 | 
			
		||||
        ContainerBuilder $container,
 | 
			
		||||
        array $crudConfig,
 | 
			
		||||
        array $apiConfig,
 | 
			
		||||
        Loader\YamlFileLoader $loader
 | 
			
		||||
    ): void
 | 
			
		||||
    protected function configureCruds(ContainerBuilder $container, $config, Loader\YamlFileLoader $loader)
 | 
			
		||||
    {
 | 
			
		||||
        if (count($crudConfig) === 0) {
 | 
			
		||||
        if (count($config) === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $loader->load('services/crud.yaml');
 | 
			
		||||
 | 
			
		||||
        $container->setParameter('chill_main_crud_route_loader_config', $crudConfig);
 | 
			
		||||
        $container->setParameter('chill_main_api_route_loader_config', $apiConfig);
 | 
			
		||||
        $container->setParameter('chill_main_crud_route_loader_config', $config);
 | 
			
		||||
 | 
			
		||||
        // Note: the controller are loaded inside compiler pass
 | 
			
		||||
    }
 | 
			
		||||
        $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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ContainerBuilder $container
 | 
			
		||||
     */
 | 
			
		||||
    protected function prependCruds(ContainerBuilder $container)
 | 
			
		||||
    {
 | 
			
		||||
        $container->prependExtensionConfig('chill_main', [
 | 
			
		||||
            'apis' => [
 | 
			
		||||
                [
 | 
			
		||||
                    'class' => \Chill\MainBundle\Entity\Address::class,
 | 
			
		||||
                    'name' => 'address',
 | 
			
		||||
                    'base_path' => '/api/1.0/main/address',
 | 
			
		||||
                    'base_role' => 'ROLE_USER',
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        '_index' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                        '_entity' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_POST => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ]
 | 
			
		||||
                        ],
 | 
			
		||||
                    ]
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    'controller' => \Chill\MainBundle\Controller\AddressReferenceAPIController::class,
 | 
			
		||||
                    'class' => \Chill\MainBundle\Entity\AddressReference::class,
 | 
			
		||||
                    'name' => 'address_reference',
 | 
			
		||||
                    'base_path' => '/api/1.0/main/address-reference',
 | 
			
		||||
                    'base_role' => 'ROLE_USER',
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        '_index' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                        '_entity' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ]
 | 
			
		||||
                        ],
 | 
			
		||||
                    ]
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    'controller' => \Chill\MainBundle\Controller\PostalCodeAPIController::class,
 | 
			
		||||
                    'class' => \Chill\MainBundle\Entity\PostalCode::class,
 | 
			
		||||
                    'name' => 'postal_code',
 | 
			
		||||
                    'base_path' => '/api/1.0/main/postal-code',
 | 
			
		||||
                    'base_role' => 'ROLE_USER',
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        '_index' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                        '_entity' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ]
 | 
			
		||||
                        ],
 | 
			
		||||
                    ]
 | 
			
		||||
                ],
 | 
			
		||||
                [
 | 
			
		||||
                    'class' => \Chill\MainBundle\Entity\Country::class,
 | 
			
		||||
                    'name' => 'country',
 | 
			
		||||
                    'base_path' => '/api/1.0/main/country',
 | 
			
		||||
                    'base_role' => 'ROLE_USER',
 | 
			
		||||
                    'actions' => [
 | 
			
		||||
                        '_index' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ],
 | 
			
		||||
                        ],
 | 
			
		||||
                        '_entity' => [
 | 
			
		||||
                            'methods' => [
 | 
			
		||||
                                Request::METHOD_GET => true,
 | 
			
		||||
                                Request::METHOD_HEAD => true
 | 
			
		||||
                            ]
 | 
			
		||||
                        ],
 | 
			
		||||
                    ]
 | 
			
		||||
                ]
 | 
			
		||||
            ]
 | 
			
		||||
        ]);
 | 
			
		||||
        $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.'%']);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ use Chill\MainBundle\DependencyInjection\Widget\Factory\WidgetFactoryInterface;
 | 
			
		||||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
 | 
			
		||||
use Chill\MainBundle\DependencyInjection\Widget\AddWidgetConfigurationTrait;
 | 
			
		||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Request;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -141,7 +140,7 @@ class Configuration implements ConfigurationInterface
 | 
			
		||||
                                        ->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")
 | 
			
		||||
                                            ->example("'action'")
 | 
			
		||||
                                        ->end()
 | 
			
		||||
                                        ->scalarNode('path')
 | 
			
		||||
                                            ->defaultNull()
 | 
			
		||||
@@ -169,80 +168,6 @@ class Configuration implements ConfigurationInterface
 | 
			
		||||
                    ->end()
 | 
			
		||||
 | 
			
		||||
                ->end()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                ->arrayNode('apis')
 | 
			
		||||
                    ->defaultValue([])
 | 
			
		||||
                    ->arrayPrototype()
 | 
			
		||||
                        ->children()
 | 
			
		||||
                            ->scalarNode('class')->cannotBeEmpty()->isRequired()->end()
 | 
			
		||||
                            ->scalarNode('controller')
 | 
			
		||||
                                ->cannotBeEmpty()
 | 
			
		||||
                                ->defaultValue(\Chill\MainBundle\CRUD\Controller\ApiController::class)
 | 
			
		||||
                            ->end()
 | 
			
		||||
                            ->scalarNode('name')->cannotBeEmpty()->isRequired()->end()
 | 
			
		||||
                            ->scalarNode('base_path')->cannotBeEmpty()->isRequired()->end()
 | 
			
		||||
                            ->scalarNode('base_role')->defaultNull()->end()
 | 
			
		||||
                            ->arrayNode('actions')
 | 
			
		||||
                                ->useAttributeAsKey('name')
 | 
			
		||||
                                ->arrayPrototype()
 | 
			
		||||
                                    ->children()
 | 
			
		||||
                                        ->scalarNode('controller_action')
 | 
			
		||||
                                            ->defaultNull()
 | 
			
		||||
                                            ->info('the method name to call in the controller. Will be set to the concatenation '.
 | 
			
		||||
                                                'of action name + \'Api\' if left empty.')
 | 
			
		||||
                                            ->example("showApi")
 | 
			
		||||
                                        ->end()
 | 
			
		||||
                                        ->scalarNode('path')
 | 
			
		||||
                                            ->defaultNull()
 | 
			
		||||
                                            ->info('the path that will be **appended** after the base path. Do not forget to add ' .
 | 
			
		||||
                                                'arguments for the method. By default, will set to the action name, including an `{id}` '.
 | 
			
		||||
                                                'parameter. A suffix of action name will be appended, except if the action name '.
 | 
			
		||||
                                                'is "_entity".')
 | 
			
		||||
                                            ->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()
 | 
			
		||||
                                        ->enumNode('single-collection')
 | 
			
		||||
                                            ->values(['single', 'collection'])
 | 
			
		||||
                                            ->defaultValue('single')
 | 
			
		||||
                                            ->info('indicates if the returned object is a single element or a collection. '.
 | 
			
		||||
                                                    'If the action name is `_index`, this value will always be considered as '.
 | 
			
		||||
                                                    '`collection`')
 | 
			
		||||
                                        ->end()
 | 
			
		||||
                                        ->arrayNode('methods')
 | 
			
		||||
                                            ->addDefaultsIfNotSet()
 | 
			
		||||
                                            ->info('the allowed methods')
 | 
			
		||||
                                            ->children()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_GET)->defaultTrue()->end()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_HEAD)->defaultTrue()->end()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_POST)->defaultFalse()->end()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_DELETE)->defaultFalse()->end()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_PUT)->defaultFalse()->end()
 | 
			
		||||
                                                ->booleanNode(Request::METHOD_PATCH)->defaultFalse()->end()
 | 
			
		||||
                                            ->end()
 | 
			
		||||
                                        ->end()
 | 
			
		||||
                                        ->arrayNode('roles')
 | 
			
		||||
                                            ->addDefaultsIfNotSet()
 | 
			
		||||
                                            ->info("The role require for each http method")
 | 
			
		||||
                                            ->children()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_GET)->defaultNull()->end()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_HEAD)->defaultNull()->end()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_POST)->defaultNull()->end()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_DELETE)->defaultNull()->end()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_PUT)->defaultNull()->end()
 | 
			
		||||
                                                ->scalarNode(Request::METHOD_PATCH)->defaultNull()->end()
 | 
			
		||||
                                            ->end()
 | 
			
		||||
                                        ->end()
 | 
			
		||||
                                    ->end()
 | 
			
		||||
                                ->end()
 | 
			
		||||
                            ->end()
 | 
			
		||||
                        ->end()
 | 
			
		||||
                    ->end()
 | 
			
		||||
 | 
			
		||||
                ->end()
 | 
			
		||||
               ->end() // end of root/children
 | 
			
		||||
            ->end() // end of root
 | 
			
		||||
        ;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Event;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Doctrine\Common\EventSubscriber;
 | 
			
		||||
use Doctrine\ORM\Events;
 | 
			
		||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\TrackCreationInterface;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Security;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TrackCreateUpdateSubscriber implements EventSubscriber
 | 
			
		||||
{
 | 
			
		||||
    private Security $security;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Security $security
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(Security $security)
 | 
			
		||||
    {
 | 
			
		||||
        $this->security = $security;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function getSubscribedEvents()
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            Events::prePersist,
 | 
			
		||||
            Events::preUpdate
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function prePersist(LifecycleEventArgs $args): void
 | 
			
		||||
    {
 | 
			
		||||
        $object = $args->getObject();
 | 
			
		||||
 | 
			
		||||
        if ($object instanceof TrackCreationInterface
 | 
			
		||||
            && $this->security->getUser() instanceof User) {
 | 
			
		||||
            $object->setCreatedBy($this->security->getUser());
 | 
			
		||||
            $object->setCreatedAt(new \DateTimeImmutable('now'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->onUpdate($object);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function preUpdate(LifecycleEventArgs $args): void
 | 
			
		||||
    {
 | 
			
		||||
        $object = $args->getObject();
 | 
			
		||||
 | 
			
		||||
        $this->onUpdate($object);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function onUpdate(object $object): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($object instanceof TrackUpdateInterface 
 | 
			
		||||
            && $this->security->getUser() instanceof User) {
 | 
			
		||||
            $object->setUpdatedBy($this->security->getUser());
 | 
			
		||||
            $object->setUpdatedAt(new \DateTimeImmutable('now'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Model;
 | 
			
		||||
 | 
			
		||||
use \JsonSerializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description of Point
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class Point implements JsonSerializable {
 | 
			
		||||
    private ?float $lat = null;
 | 
			
		||||
    private ?float $lon = null;
 | 
			
		||||
    public static string $SRID = '4326';
 | 
			
		||||
 | 
			
		||||
    private function __construct(?float $lon, ?float $lat)
 | 
			
		||||
    {
 | 
			
		||||
        $this->lat = $lat;
 | 
			
		||||
        $this->lon = $lon;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toGeoJson(): string
 | 
			
		||||
    {
 | 
			
		||||
        $array = $this->toArrayGeoJson();
 | 
			
		||||
        return \json_encode($array);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function jsonSerialize(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->toArrayGeoJson();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toArrayGeoJson(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            "type" => "Point",
 | 
			
		||||
            "coordinates" => [ $this->lon, $this->lat ]
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function toWKT(): string
 | 
			
		||||
    {
 | 
			
		||||
        return 'SRID='.self::$SRID.';POINT('.$this->lon.' '.$this->lat.')';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param type $geojson
 | 
			
		||||
     * @return Point
 | 
			
		||||
     */
 | 
			
		||||
    public static function fromGeoJson(string $geojson): Point
 | 
			
		||||
    {
 | 
			
		||||
        $a = json_decode($geojson);
 | 
			
		||||
        //check if the geojson string is correct
 | 
			
		||||
        if (NULL === $a or !isset($a->type) or !isset($a->coordinates)){
 | 
			
		||||
            throw PointException::badJsonString($geojson);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($a->type != 'Point'){
 | 
			
		||||
            throw PointException::badGeoType();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $lat = $a->coordinates[1];
 | 
			
		||||
        $lon = $a->coordinates[0];
 | 
			
		||||
 | 
			
		||||
        return Point::fromLonLat($lon, $lat);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromLonLat(float $lon, float $lat): Point
 | 
			
		||||
    {
 | 
			
		||||
        if (($lon > -180 && $lon < 180) && ($lat > -90 && $lat < 90))
 | 
			
		||||
        {
 | 
			
		||||
            return new Point($lon, $lat);
 | 
			
		||||
        } else {
 | 
			
		||||
            throw PointException::badCoordinates($lon, $lat);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromArrayGeoJson(array $array): Point
 | 
			
		||||
    {
 | 
			
		||||
        if ($array['type'] == 'Point' &&
 | 
			
		||||
                isset($array['coordinates']))
 | 
			
		||||
        {
 | 
			
		||||
            return self::fromLonLat($array['coordinates'][0], $array['coordinates'][1]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLat(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLon(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->lon;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1,27 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Model;
 | 
			
		||||
 | 
			
		||||
use \Exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Description of PointException
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class PointException extends Exception {
 | 
			
		||||
 | 
			
		||||
    public static function badCoordinates($lon, $lat): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self("Input coordinates are not valid in the used coordinate system (longitude = $lon , latitude = $lat)");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function badJsonString($str): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self("The JSON string is not valid: $str");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function badGeoType(): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self("The geoJSON object type is not valid");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Model;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
 | 
			
		||||
interface TrackCreationInterface
 | 
			
		||||
{
 | 
			
		||||
    public function setCreatedBy(User $user): self;
 | 
			
		||||
 | 
			
		||||
    public function setCreatedAt(\DateTimeInterface $datetime): self;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Model;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
 | 
			
		||||
interface TrackUpdateInterface
 | 
			
		||||
{
 | 
			
		||||
    public function setUpdatedBy(User $user): self;
 | 
			
		||||
 | 
			
		||||
    public function setUpdatedAt(\DateTimeInterface $datetime): self;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Doctrine\Type;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\Point;
 | 
			
		||||
use Doctrine\DBAL\Types\Type;
 | 
			
		||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\PointException;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Type for Doctrine to implement the Geography Point type
 | 
			
		||||
 * implemented by Postgis on postgis+postgresql databases
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class PointType extends Type {
 | 
			
		||||
 | 
			
		||||
    const POINT = 'point';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $fieldDeclaration
 | 
			
		||||
     * @param AbstractPlatform $platform
 | 
			
		||||
     * @return type
 | 
			
		||||
     */
 | 
			
		||||
    public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
 | 
			
		||||
    {
 | 
			
		||||
        return 'geometry(POINT,'.Point::$SRID.')';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param type $value
 | 
			
		||||
     * @param AbstractPlatform $platform
 | 
			
		||||
     * @return Point
 | 
			
		||||
     */
 | 
			
		||||
    public function convertToPHPValue($value, AbstractPlatform $platform)
 | 
			
		||||
    {
 | 
			
		||||
        if ($value === NULL){
 | 
			
		||||
            return NULL;
 | 
			
		||||
        } else {
 | 
			
		||||
            return Point::fromGeoJson($value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getName()
 | 
			
		||||
    {
 | 
			
		||||
        return self::POINT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function convertToDatabaseValue($value, AbstractPlatform $platform)
 | 
			
		||||
    {
 | 
			
		||||
        if ($value === NULL){
 | 
			
		||||
            return NULL;
 | 
			
		||||
        } else {
 | 
			
		||||
            return $value->toWKT();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function canRequireSQLConversion()
 | 
			
		||||
    {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function convertToPHPValueSQL($sqlExpr, $platform)
 | 
			
		||||
    {
 | 
			
		||||
        return 'ST_AsGeoJSON('.$sqlExpr.') ';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
 | 
			
		||||
    {
 | 
			
		||||
        return $sqlExpr;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -4,9 +4,6 @@ namespace Chill\MainBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\Point;
 | 
			
		||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Address
 | 
			
		||||
@@ -23,7 +20,6 @@ class Address
 | 
			
		||||
     * @ORM\Id
 | 
			
		||||
     * @ORM\Column(name="id", type="integer")
 | 
			
		||||
     * @ORM\GeneratedValue(strategy="AUTO")
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $id;
 | 
			
		||||
 | 
			
		||||
@@ -31,82 +27,23 @@ class Address
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $street = '';
 | 
			
		||||
    private $streetAddress1 = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $streetNumber = '';
 | 
			
		||||
    private $streetAddress2 = '';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var PostalCode
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $postcode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=16, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $floor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=16, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $corridor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=16, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $steps;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $buildingName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=16, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $flat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $distribution;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $extra;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates when the address starts validation. Used to build an history
 | 
			
		||||
     * of address. By default, the current date.
 | 
			
		||||
@@ -114,62 +51,29 @@ class Address
 | 
			
		||||
     * @var \DateTime
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="date")
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $validFrom;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates when the address ends. Used to build an history
 | 
			
		||||
     * of address.
 | 
			
		||||
     *
 | 
			
		||||
     * @var \DateTime|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="date", nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $validTo;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * True if the address is a "no address", aka homeless person, ...
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     *
 | 
			
		||||
     * @var bool
 | 
			
		||||
     */
 | 
			
		||||
    private $isNoAddress = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A geospatial field storing the coordinates of the Address
 | 
			
		||||
     *
 | 
			
		||||
     * @var Point|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="point", nullable=true)
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     */
 | 
			
		||||
    private $point;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A ThirdParty reference for person's addresses that are linked to a third party
 | 
			
		||||
     *
 | 
			
		||||
     * @var ThirdParty|null
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity="Chill\ThirdPartyBundle\Entity\ThirdParty")
 | 
			
		||||
     * @groups({"write"})
 | 
			
		||||
     * @ORM\JoinColumn(nullable=true, onDelete="SET NULL")
 | 
			
		||||
     */
 | 
			
		||||
    private $linkedToThirdParty;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * A list of metadata, added by customizable fields
 | 
			
		||||
     *
 | 
			
		||||
     * 
 | 
			
		||||
     * @var array
 | 
			
		||||
     */
 | 
			
		||||
    private $customs = [];
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validFrom = new \DateTime();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get id
 | 
			
		||||
     *
 | 
			
		||||
@@ -181,7 +85,7 @@ class Address
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set streetAddress1 (legacy function)
 | 
			
		||||
     * Set streetAddress1
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $streetAddress1
 | 
			
		||||
     *
 | 
			
		||||
@@ -189,23 +93,23 @@ class Address
 | 
			
		||||
     */
 | 
			
		||||
    public function setStreetAddress1($streetAddress1)
 | 
			
		||||
    {
 | 
			
		||||
        $this->street = $streetAddress1 === NULL ? '' : $streetAddress1;
 | 
			
		||||
        $this->streetAddress1 = $streetAddress1 === NULL ? '' : $streetAddress1;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get streetAddress1 (legacy function)
 | 
			
		||||
     * Get streetAddress1
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function getStreetAddress1()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->street;
 | 
			
		||||
        return $this->streetAddress1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set streetAddress2 (legacy function)
 | 
			
		||||
     * Set streetAddress2
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $streetAddress2
 | 
			
		||||
     *
 | 
			
		||||
@@ -213,19 +117,19 @@ class Address
 | 
			
		||||
     */
 | 
			
		||||
    public function setStreetAddress2($streetAddress2)
 | 
			
		||||
    {
 | 
			
		||||
        $this->streetNumber = $streetAddress2 === NULL ? '' : $streetAddress2;
 | 
			
		||||
        $this->streetAddress2 = $streetAddress2 === NULL ? '' : $streetAddress2;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get streetAddress2 (legacy function)
 | 
			
		||||
     * Get streetAddress2
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function getStreetAddress2()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->streetNumber;
 | 
			
		||||
        return $this->streetAddress2;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -251,7 +155,7 @@ class Address
 | 
			
		||||
    {
 | 
			
		||||
        return $this->postcode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return \DateTime
 | 
			
		||||
     */
 | 
			
		||||
@@ -269,19 +173,19 @@ class Address
 | 
			
		||||
        $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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
@@ -292,9 +196,9 @@ class Address
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set IsNoAddress
 | 
			
		||||
     *
 | 
			
		||||
     * 
 | 
			
		||||
     * Indicate true if the address is a fake address (homeless, ...)
 | 
			
		||||
     *
 | 
			
		||||
     * 
 | 
			
		||||
     * @param bool $isNoAddress
 | 
			
		||||
     * @return $this
 | 
			
		||||
     */
 | 
			
		||||
@@ -303,10 +207,10 @@ class Address
 | 
			
		||||
        $this->isNoAddress = $isNoAddress;
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Get customs informations in the address
 | 
			
		||||
     *
 | 
			
		||||
     * 
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getCustoms(): array
 | 
			
		||||
@@ -316,27 +220,27 @@ class Address
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     */
 | 
			
		||||
@@ -348,18 +252,18 @@ class Address
 | 
			
		||||
                ->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")
 | 
			
		||||
@@ -367,7 +271,7 @@ class Address
 | 
			
		||||
                ->addViolation();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Address $original
 | 
			
		||||
     * @return Address
 | 
			
		||||
@@ -382,149 +286,5 @@ class Address
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getStreet(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->street;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setStreet(string $street): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->street = $street;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getStreetNumber(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->streetNumber;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setStreetNumber(string $streetNumber): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->streetNumber = $streetNumber;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFloor(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->floor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setFloor(?string $floor): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->floor = $floor;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getCorridor(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->corridor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setCorridor(?string $corridor): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->corridor = $corridor;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSteps(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->steps;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setSteps(?string $steps): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->steps = $steps;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getBuildingName(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->buildingName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setBuildingName(?string $buildingName): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->buildingName = $buildingName;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFlat(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->flat;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setFlat(?string $flat): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->flat = $flat;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDistribution(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->distribution;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setDistribution(?string $distribution): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->distribution = $distribution;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getExtra(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->extra;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setExtra(?string $extra): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->extra = $extra;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getValidTo(): ?\DateTimeInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->validTo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setValidTo(\DateTimeInterface $validTo): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->validTo = $validTo;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPoint(): ?Point
 | 
			
		||||
    {
 | 
			
		||||
        return $this->point;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPoint(?Point $point): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->point = $point;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getLinkedToThirdParty()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->linkedToThirdParty;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setLinkedToThirdParty($linkedToThirdParty): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->linkedToThirdParty = $linkedToThirdParty;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,173 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\Point;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @ORM\Entity()
 | 
			
		||||
 * @ORM\Table(name="chill_main_address_reference")
 | 
			
		||||
 * @ORM\HasLifecycleCallbacks()
 | 
			
		||||
 */
 | 
			
		||||
class AddressReference
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Id
 | 
			
		||||
     * @ORM\GeneratedValue
 | 
			
		||||
     * @ORM\Column(type="integer")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=255)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $refId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $street;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $streetNumber;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var PostalCode
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\PostalCode")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $postcode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $municipalityCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @ORM\Column(type="string", length=255, nullable=true)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $source;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A geospatial field storing the coordinates of the Address
 | 
			
		||||
     *
 | 
			
		||||
     * @var Point
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="point")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $point;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function getId(): ?int
 | 
			
		||||
    {
 | 
			
		||||
        return $this->id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRefId(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->refId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setRefId(string $refId): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->refId = $refId;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getStreet(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->street;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setStreet(?string $street): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->street = $street;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getStreetNumber(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->streetNumber;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setStreetNumber(?string $streetNumber): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->streetNumber = $streetNumber;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Set postcode
 | 
			
		||||
     *
 | 
			
		||||
     * @param PostalCode $postcode
 | 
			
		||||
     *
 | 
			
		||||
     * @return Address
 | 
			
		||||
     */
 | 
			
		||||
    public function setPostcode(PostalCode $postcode = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->postcode = $postcode;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get postcode
 | 
			
		||||
     *
 | 
			
		||||
     * @return PostalCode
 | 
			
		||||
     */
 | 
			
		||||
    public function getPostcode()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->postcode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getMunicipalityCode(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->municipalityCode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setMunicipalityCode(?string $municipalityCode): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->municipalityCode = $municipalityCode;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSource(): ?string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->source;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setSource(?string $source): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->source = $source;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPoint(): ?Point
 | 
			
		||||
    {
 | 
			
		||||
        return $this->point;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPoint(?Point $point): self
 | 
			
		||||
    {
 | 
			
		||||
        $this->point = $point;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
namespace Chill\MainBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Country
 | 
			
		||||
@@ -21,7 +20,6 @@ class Country
 | 
			
		||||
     * @ORM\Id
 | 
			
		||||
     * @ORM\Column(name="id", type="integer")
 | 
			
		||||
     * @ORM\GeneratedValue(strategy="AUTO")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $id;
 | 
			
		||||
 | 
			
		||||
@@ -29,16 +27,13 @@ class Country
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="json_array")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    private $name;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=3)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $countryCode;
 | 
			
		||||
 | 
			
		||||
@@ -46,7 +41,7 @@ class Country
 | 
			
		||||
    /**
 | 
			
		||||
     * Get id
 | 
			
		||||
     *
 | 
			
		||||
     * @return integer
 | 
			
		||||
     * @return integer 
 | 
			
		||||
     */
 | 
			
		||||
    public function getId()
 | 
			
		||||
    {
 | 
			
		||||
@@ -62,20 +57,20 @@ class Country
 | 
			
		||||
    public function setName($name)
 | 
			
		||||
    {
 | 
			
		||||
        $this->name = $name;
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get name
 | 
			
		||||
     *
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @return string 
 | 
			
		||||
     */
 | 
			
		||||
    public function getName()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
@@ -95,12 +90,12 @@ class Country
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $countryCode
 | 
			
		||||
     * @param string $countryCode            
 | 
			
		||||
     */
 | 
			
		||||
    public function setCountryCode($countryCode)
 | 
			
		||||
    {
 | 
			
		||||
        $this->countryCode = $countryCode;
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
namespace Chill\MainBundle\Entity;
 | 
			
		||||
 | 
			
		||||
use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * PostalCode
 | 
			
		||||
@@ -26,7 +25,6 @@ class PostalCode
 | 
			
		||||
     * @ORM\Id
 | 
			
		||||
     * @ORM\Column(name="id", type="integer")
 | 
			
		||||
     * @ORM\GeneratedValue(strategy="AUTO")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $id;
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +32,6 @@ class PostalCode
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=255, name="label")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $name;
 | 
			
		||||
 | 
			
		||||
@@ -42,7 +39,6 @@ class PostalCode
 | 
			
		||||
     * @var string
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="string", length=100)
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $code;
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +46,6 @@ class PostalCode
 | 
			
		||||
     * @var Country
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\ManyToOne(targetEntity="Chill\MainBundle\Entity\Country")
 | 
			
		||||
     * @groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $country;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -24,16 +24,13 @@ use Doctrine\ORM\Mapping as ORM;
 | 
			
		||||
use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Chill\MainBundle\Entity\RoleScope;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\Groups;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @ORM\Entity()
 | 
			
		||||
 * @ORM\Table(name="scopes")
 | 
			
		||||
 * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region")
 | 
			
		||||
 * @DiscriminatorMap(typeProperty="type", mapping={
 | 
			
		||||
 *    "scope"=Scope::class
 | 
			
		||||
 * })
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class Scope
 | 
			
		||||
{
 | 
			
		||||
@@ -43,7 +40,6 @@ class Scope
 | 
			
		||||
     * @ORM\Id
 | 
			
		||||
     * @ORM\Column(name="id", type="integer")
 | 
			
		||||
     * @ORM\GeneratedValue(strategy="AUTO")
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $id;
 | 
			
		||||
    
 | 
			
		||||
@@ -53,7 +49,6 @@ class Scope
 | 
			
		||||
     * @var array
 | 
			
		||||
     *
 | 
			
		||||
     * @ORM\Column(type="json_array")
 | 
			
		||||
     * @Groups({"read"})
 | 
			
		||||
     */
 | 
			
		||||
    private $name = [];
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,6 @@ use Doctrine\Common\Collections\Collection;
 | 
			
		||||
use Doctrine\Common\Collections\ArrayCollection;
 | 
			
		||||
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
 | 
			
		||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * User
 | 
			
		||||
@@ -15,9 +14,6 @@ use Symfony\Component\Serializer\Annotation\DiscriminatorMap;
 | 
			
		||||
 * @ORM\Entity(repositoryClass="Chill\MainBundle\Repository\UserRepository")
 | 
			
		||||
 * @ORM\Table(name="users")
 | 
			
		||||
 * @ORM\Cache(usage="NONSTRICT_READ_WRITE", region="acl_cache_region")
 | 
			
		||||
 * @DiscriminatorMap(typeProperty="type", mapping={
 | 
			
		||||
 *    "user"=User::class
 | 
			
		||||
 * })
 | 
			
		||||
 */
 | 
			
		||||
class User implements AdvancedUserInterface {
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,8 @@ 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
 | 
			
		||||
 * 
 | 
			
		||||
 * - `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
 | 
			
		||||
@@ -45,10 +45,10 @@ class AddressType extends AbstractType
 | 
			
		||||
    public function buildForm(FormBuilderInterface $builder, array $options)
 | 
			
		||||
    {
 | 
			
		||||
        $builder
 | 
			
		||||
              ->add('street', TextType::class, array(
 | 
			
		||||
              ->add('streetAddress1', TextType::class, array(
 | 
			
		||||
                 'required' => !$options['has_no_address'] // true if has no address is false
 | 
			
		||||
              ))
 | 
			
		||||
              ->add('streetNumber', TextType::class, array(
 | 
			
		||||
              ->add('streetAddress2', TextType::class, array(
 | 
			
		||||
                 'required' => false
 | 
			
		||||
              ))
 | 
			
		||||
              ->add('postCode', PostalCodeType::class, array(
 | 
			
		||||
@@ -57,7 +57,7 @@ class AddressType extends AbstractType
 | 
			
		||||
                 'required' => !$options['has_no_address'] // true if has no address is false
 | 
			
		||||
              ))
 | 
			
		||||
            ;
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        if ($options['has_valid_from']) {
 | 
			
		||||
            $builder
 | 
			
		||||
              ->add('validFrom', DateType::class, array(
 | 
			
		||||
@@ -67,7 +67,7 @@ class AddressType extends AbstractType
 | 
			
		||||
                 )
 | 
			
		||||
              );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        if ($options['has_no_address']) {
 | 
			
		||||
            $builder
 | 
			
		||||
                ->add('isNoAddress', ChoiceType::class, [
 | 
			
		||||
@@ -79,12 +79,12 @@ class AddressType extends AbstractType
 | 
			
		||||
                    'label' => 'address.address_homeless'
 | 
			
		||||
                ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        if ($options['null_if_empty'] === TRUE) {
 | 
			
		||||
            $builder->setDataMapper(new AddressDataMapper());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public function configureOptions(OptionsResolver $resolver)
 | 
			
		||||
    {
 | 
			
		||||
        $resolver
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Repository;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\AddressReference;
 | 
			
		||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
 | 
			
		||||
use Doctrine\Persistence\ManagerRegistry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @method AddressReference|null find($id, $lockMode = null, $lockVersion = null)
 | 
			
		||||
 * @method AddressReference|null findOneBy(array $criteria, array $orderBy = null)
 | 
			
		||||
 * @method AddressReference[]    findAll()
 | 
			
		||||
 * @method AddressReference[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 | 
			
		||||
 */
 | 
			
		||||
class AddressReferenceRepository extends ServiceEntityRepository
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(ManagerRegistry $registry)
 | 
			
		||||
    {
 | 
			
		||||
        parent::__construct($registry, AddressReference::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // /**
 | 
			
		||||
    //  * @return AddressReference[] Returns an array of AddressReference objects
 | 
			
		||||
    //  */
 | 
			
		||||
    /*
 | 
			
		||||
    public function findByExampleField($value)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createQueryBuilder('a')
 | 
			
		||||
            ->andWhere('a.exampleField = :val')
 | 
			
		||||
            ->setParameter('val', $value)
 | 
			
		||||
            ->orderBy('a.id', 'ASC')
 | 
			
		||||
            ->setMaxResults(10)
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getResult()
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
    public function findOneBySomeField($value): ?AddressReference
 | 
			
		||||
    {
 | 
			
		||||
        return $this->createQueryBuilder('a')
 | 
			
		||||
            ->andWhere('a.exampleField = :val')
 | 
			
		||||
            ->setParameter('val', $value)
 | 
			
		||||
            ->getQuery()
 | 
			
		||||
            ->getOneOrNullResult()
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
}
 | 
			
		||||
@@ -23,12 +23,12 @@
 | 
			
		||||
// @import "bootstrap/scss/button-group";
 | 
			
		||||
// @import "bootstrap/scss/input-group";
 | 
			
		||||
// @import "bootstrap/scss/custom-forms";
 | 
			
		||||
@import "bootstrap/scss/nav";
 | 
			
		||||
// @import "bootstrap/scss/nav";
 | 
			
		||||
// @import "bootstrap/scss/navbar";
 | 
			
		||||
// @import "bootstrap/scss/card";
 | 
			
		||||
// @import "bootstrap/scss/breadcrumb";
 | 
			
		||||
// @import "bootstrap/scss/pagination";
 | 
			
		||||
@import "bootstrap/scss/badge";
 | 
			
		||||
// @import "bootstrap/scss/badge";
 | 
			
		||||
// @import "bootstrap/scss/jumbotron";
 | 
			
		||||
// @import "bootstrap/scss/alert";
 | 
			
		||||
// @import "bootstrap/scss/progress";
 | 
			
		||||
@@ -41,7 +41,7 @@
 | 
			
		||||
// @import "bootstrap/scss/popover";
 | 
			
		||||
// @import "bootstrap/scss/carousel";
 | 
			
		||||
// @import "bootstrap/scss/spinners";
 | 
			
		||||
@import "bootstrap/scss/utilities";
 | 
			
		||||
// @import "bootstrap/scss/utilities";
 | 
			
		||||
// @import "bootstrap/scss/print";
 | 
			
		||||
 | 
			
		||||
@import "custom";
 | 
			
		||||
 
 | 
			
		||||
@@ -9,12 +9,6 @@ div.chill_address {
 | 
			
		||||
            margin: 0 0 0 1.5em;
 | 
			
		||||
            text-indent: -1.5em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &.chill_address_address--multiline {
 | 
			
		||||
            p {
 | 
			
		||||
                display: block;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,6 @@ ul.record_actions li {
 | 
			
		||||
ul.record_actions, ul.record_actions_column {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
 | 
			
		||||
    &.record_actions--left {
 | 
			
		||||
      justify-content: flex-start;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    padding: 0.5em 0;
 | 
			
		||||
    flex-wrap: wrap-reverse;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
        @include button($green, $white);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.bt-reset, &.bt-delete, &.bt-remove {
 | 
			
		||||
    &.bt-reset, &.bt-delete {
 | 
			
		||||
        @include button($red, $white);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +24,6 @@
 | 
			
		||||
        &.bt-save::before,
 | 
			
		||||
        &.bt-new::before,
 | 
			
		||||
        &.bt-delete::before,
 | 
			
		||||
        &.bt-remove::before,
 | 
			
		||||
        &.bt-update::before,
 | 
			
		||||
        &.bt-edit::before,
 | 
			
		||||
        &.bt-cancel::before,
 | 
			
		||||
@@ -57,12 +56,7 @@
 | 
			
		||||
            // add a trash
 | 
			
		||||
            content: "";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
       &.bt-remove::before {
 | 
			
		||||
           // add a times
 | 
			
		||||
           content: "";
 | 
			
		||||
       }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
        &.bt-edit::before, &.bt-update::before {
 | 
			
		||||
            // add a pencil
 | 
			
		||||
            content: "";
 | 
			
		||||
@@ -100,7 +94,6 @@
 | 
			
		||||
            &.bt-save::before,
 | 
			
		||||
            &.bt-new::before,
 | 
			
		||||
            &.bt-delete::before,
 | 
			
		||||
            &.bt-remove::before,
 | 
			
		||||
            &.bt-update::before,
 | 
			
		||||
            &.bt-edit::before,
 | 
			
		||||
            &.bt-cancel::before,
 | 
			
		||||
@@ -130,7 +123,6 @@
 | 
			
		||||
            &.bt-save::before,
 | 
			
		||||
            &.bt-new::before,
 | 
			
		||||
            &.bt-delete::before,
 | 
			
		||||
            &.bt-remove::before,
 | 
			
		||||
            &.bt-update::before,
 | 
			
		||||
            &.bt-edit::before,
 | 
			
		||||
            &.bt-cancel::before,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,57 +1,21 @@
 | 
			
		||||
/*
 | 
			
		||||
 * NOTE 2021.04
 | 
			
		||||
 * scss/chillmain.scss is the main sass file for the new chill.2
 | 
			
		||||
 * scss/chill.scss is the main sass file for the new chill.2
 | 
			
		||||
 * scratch will be replaced by bootstrap, please avoid to edit in modules/scratch/_custom.scss
 | 
			
		||||
 *
 | 
			
		||||
 * when possible, try to use bootstrap html class
 | 
			
		||||
 * when possible, try to use bootstrap class naming
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*  [hack]  /!\ Contourne le positionnement problématique du div#content_conainter suivant,
 | 
			
		||||
 *  car sa position: relative le place au-dessus du bandeau et les liens sont incliquables  */
 | 
			
		||||
div.subheader {
 | 
			
		||||
  height: 130px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Specific rules
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// [scratch] un bouton 'disabled' non clickable
 | 
			
		||||
.sc-button {
 | 
			
		||||
   &.disabled {
 | 
			
		||||
      cursor: default;
 | 
			
		||||
      &.bt-remove {
 | 
			
		||||
         background-color: #d9d9d9;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// [debug] un affichage discret pour le debug
 | 
			
		||||
.discret {
 | 
			
		||||
   color: grey;
 | 
			
		||||
   margin-right: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// reserre la hauteur des rangées de tableau (ul.record_actions prennait trop de place)
 | 
			
		||||
table {
 | 
			
		||||
   ul.record_actions {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      padding: 0.5em;
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 *  ACCOMPANYING_COURSE
 | 
			
		||||
 *  Header custom for Accompanying Course
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
div#header-accompanying_course-name {
 | 
			
		||||
  background: none repeat scroll 0 0 #718596;
 | 
			
		||||
  color: #FFF;
 | 
			
		||||
  h1 {
 | 
			
		||||
    margin: 0.4em 0;
 | 
			
		||||
  }
 | 
			
		||||
  padding-top: 1em;
 | 
			
		||||
  padding-bottom: 1em;
 | 
			
		||||
 | 
			
		||||
  span {
 | 
			
		||||
    a {
 | 
			
		||||
      color: white;
 | 
			
		||||
@@ -61,6 +25,7 @@ div#header-accompanying_course-name {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div#header-accompanying_course-details {
 | 
			
		||||
  background: none repeat scroll 0 0 #718596ab;
 | 
			
		||||
  color: #FFF;
 | 
			
		||||
@@ -68,196 +33,25 @@ div#header-accompanying_course-details {
 | 
			
		||||
  padding-bottom: 1em;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
*  FLEX RESPONSIVE TABLE/BLOCK PRESENTATION
 | 
			
		||||
*/
 | 
			
		||||
div.flex-bloc,
 | 
			
		||||
div.flex-table {
 | 
			
		||||
   h2, h3, h4, dl, p {
 | 
			
		||||
      margin: 0;
 | 
			
		||||
   }
 | 
			
		||||
   h2, h3, h4 {
 | 
			
		||||
      color: var(--chill-blue);
 | 
			
		||||
   }
 | 
			
		||||
/*  /!\  Contourne le positionnement problématique du div#content_conainter suivant,
 | 
			
		||||
 *  car sa position: relative le place au-dessus du bandeau et les liens sont incliquables  */
 | 
			
		||||
div.subheader {
 | 
			
		||||
  height: 130px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Bloc appearance
 | 
			
		||||
*/
 | 
			
		||||
div.flex-bloc {
 | 
			
		||||
   box-sizing: border-box;
 | 
			
		||||
   display: flex;
 | 
			
		||||
   flex-direction: row;
 | 
			
		||||
   flex-wrap: wrap;
 | 
			
		||||
   align-items: stretch;
 | 
			
		||||
   align-content: stretch;
 | 
			
		||||
 | 
			
		||||
   div.item-bloc {
 | 
			
		||||
      flex-grow: 0; flex-shrink: 1; flex-basis: 50%;
 | 
			
		||||
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      border: 1px solid #000;
 | 
			
		||||
      padding: 1em;
 | 
			
		||||
 | 
			
		||||
      border-top: 0;
 | 
			
		||||
      &:nth-child(1), &:nth-child(2) {
 | 
			
		||||
         border-top: 1px solid #000;
 | 
			
		||||
      }
 | 
			
		||||
      border-left: 0;
 | 
			
		||||
      &:nth-child(odd) {
 | 
			
		||||
         border-left: 1px solid #000;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //background-color: #e6e6e6;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
 | 
			
		||||
      div.item-row {
 | 
			
		||||
         flex-grow: 1; flex-shrink: 1; flex-basis: auto;
 | 
			
		||||
         display: flex;
 | 
			
		||||
         flex-direction: column;
 | 
			
		||||
 | 
			
		||||
         div.item-col {
 | 
			
		||||
            &:first-child {
 | 
			
		||||
               flex-grow: 0; flex-shrink: 0; flex-basis: auto;
 | 
			
		||||
            }
 | 
			
		||||
            &:last-child {
 | 
			
		||||
               flex-grow: 1; flex-shrink: 1; flex-basis: auto;
 | 
			
		||||
               display: flex;
 | 
			
		||||
 | 
			
		||||
               .list-content { // ul, dl, or div
 | 
			
		||||
               }
 | 
			
		||||
               ul.record_actions {
 | 
			
		||||
                  margin: 0;
 | 
			
		||||
                  align-self: flex-end;
 | 
			
		||||
                  flex-grow: 1; flex-shrink: 0; flex-basis: auto;
 | 
			
		||||
                  li {
 | 
			
		||||
                     margin-right: 5px;
 | 
			
		||||
                  }
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
   @media only screen and (max-width: 945px) { margin: auto -0.2em; }
 | 
			
		||||
   @media only screen and (max-width: 935px) { margin: auto -0.5em; }
 | 
			
		||||
   @media only screen and (max-width: 920px) { margin: auto -0.9em; }
 | 
			
		||||
   @media only screen and (max-width: 900px) {
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      margin: auto 0;
 | 
			
		||||
      div.item-bloc {
 | 
			
		||||
         border-left: 1px solid #000;
 | 
			
		||||
         &:nth-child(2) {
 | 
			
		||||
            border-top: 0;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
div.vue-component {
 | 
			
		||||
  padding: 1.5em;
 | 
			
		||||
  margin: 2em 0;
 | 
			
		||||
  border: 2px dashed grey;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  &:before {
 | 
			
		||||
    content: "vuejs component";
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 1.5em;
 | 
			
		||||
    top: -0.9em;
 | 
			
		||||
    background-color: white;
 | 
			
		||||
    color: grey;
 | 
			
		||||
    padding: 0 0.3em;
 | 
			
		||||
  }
 | 
			
		||||
  dd { margin-left: 1em; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Table appearance
 | 
			
		||||
*/
 | 
			
		||||
div.flex-table {
 | 
			
		||||
   display: flex;
 | 
			
		||||
   flex-direction: column;
 | 
			
		||||
   align-items: stretch;
 | 
			
		||||
   align-content: stretch;
 | 
			
		||||
 | 
			
		||||
   div.item-bloc {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: column;
 | 
			
		||||
      padding: 1em;
 | 
			
		||||
      border: 1px solid #000;
 | 
			
		||||
      border-top: 0;
 | 
			
		||||
      &:first-child {
 | 
			
		||||
         border-top: 1px solid #000;
 | 
			
		||||
      }
 | 
			
		||||
      &:nth-child(even) {
 | 
			
		||||
         background-color: #e6e6e6;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      div.item-row {
 | 
			
		||||
         display: flex;
 | 
			
		||||
         flex-direction: row;
 | 
			
		||||
         &:not(:first-child) {
 | 
			
		||||
            margin-top: 0.5em;
 | 
			
		||||
            border-top: 1px dotted #0000004f;
 | 
			
		||||
            padding-top: 0.5em;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         div.item-col {
 | 
			
		||||
            &:first-child {
 | 
			
		||||
               flex-grow: 0; flex-shrink: 0; flex-basis: 33%;
 | 
			
		||||
            }
 | 
			
		||||
            &:last-child {
 | 
			
		||||
               flex-grow: 1; flex-shrink: 1; flex-basis: auto;
 | 
			
		||||
               display: flex;
 | 
			
		||||
               justify-content: flex-end;
 | 
			
		||||
 | 
			
		||||
               .list-content { // ul, dl, or div
 | 
			
		||||
               }
 | 
			
		||||
               ul.record_actions {
 | 
			
		||||
                  margin: 0;
 | 
			
		||||
                  align-self: flex-start;
 | 
			
		||||
                  flex-grow: 1; flex-shrink: 0; flex-basis: auto;
 | 
			
		||||
                  li {
 | 
			
		||||
                     margin-right: 5px;
 | 
			
		||||
                  }
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
         @media only screen and (max-width: 900px) {
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            div.item-col {
 | 
			
		||||
               &:last-child {
 | 
			
		||||
                  ul.record_actions {
 | 
			
		||||
                     align-self: flex-end;
 | 
			
		||||
                  }
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         // neutralize
 | 
			
		||||
         div.chill_address div.chill_address_address p { text-indent: 0; }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Address form
 | 
			
		||||
*/
 | 
			
		||||
div.address_form {
 | 
			
		||||
   display: flex;
 | 
			
		||||
   flex-direction: column;
 | 
			
		||||
   div.address_form__header {
 | 
			
		||||
 | 
			
		||||
   }
 | 
			
		||||
   div.address_form__select {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      flex-direction: row;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
 | 
			
		||||
      div.address_form__select__body {
 | 
			
		||||
         display: flex;
 | 
			
		||||
         flex-direction: column;
 | 
			
		||||
         flex-grow: 1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      div.address_form__select__map {
 | 
			
		||||
         margin: 0px 20px;
 | 
			
		||||
         div#address_map {
 | 
			
		||||
            height:400px;
 | 
			
		||||
            width:400px;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
   div.address_form__more {
 | 
			
		||||
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
 | 
			
		||||
   <div v-if="address.address">
 | 
			
		||||
      {{ address.address.street }}, {{ address.address.streetNumber }}
 | 
			
		||||
   </div>
 | 
			
		||||
   <div v-if="address.city">
 | 
			
		||||
      {{ address.city.code }} {{ address.city.name }}
 | 
			
		||||
   </div>
 | 
			
		||||
   <div v-if="address.country">
 | 
			
		||||
      {{ address.country.name }}
 | 
			
		||||
   </div>
 | 
			
		||||
 | 
			
		||||
   <add-address
 | 
			
		||||
      @addNewAddress="addNewAddress">
 | 
			
		||||
   </add-address>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { mapState } from 'vuex';
 | 
			
		||||
 | 
			
		||||
import AddAddress from '../_components/AddAddress.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'App',
 | 
			
		||||
   components: {
 | 
			
		||||
      AddAddress
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      address() {
 | 
			
		||||
         return this.$store.state.address;
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      addNewAddress({ address, modal }) {
 | 
			
		||||
         console.log('@@@ CLICK button addNewAdress', address);
 | 
			
		||||
 | 
			
		||||
         let newAddress = {
 | 
			
		||||
            'isNoAddress': address.isNoAddress,
 | 
			
		||||
            'street': address.selected.address.street,
 | 
			
		||||
            'streetNumber': address.selected.address.streetNumber,
 | 
			
		||||
            'postcode': {'id': address.selected.city.id },
 | 
			
		||||
            'floor': address.floor,
 | 
			
		||||
            'corridor': address.corridor,
 | 
			
		||||
            'steps': address.steps,
 | 
			
		||||
            'flat': address.flat,
 | 
			
		||||
            'buildingName': address.buildingName,
 | 
			
		||||
            'distribution': address.distribution,
 | 
			
		||||
            'extra': address.extra
 | 
			
		||||
         };
 | 
			
		||||
 | 
			
		||||
         if (address.selected.address.point !== undefined){
 | 
			
		||||
            newAddress = Object.assign(newAddress, {'point': address.selected.address.point.coordinates});
 | 
			
		||||
         }
 | 
			
		||||
 | 
			
		||||
         this.$store.dispatch('addAddress', newAddress);
 | 
			
		||||
         modal.showModal = false;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
import { createApp } from 'vue'
 | 
			
		||||
import { _createI18n } from 'ChillMainAssets/vuejs/_js/i18n'
 | 
			
		||||
import { addressMessages } from './js/i18n'
 | 
			
		||||
import { store } from './store'
 | 
			
		||||
 | 
			
		||||
import App from './App.vue';
 | 
			
		||||
 | 
			
		||||
const i18n = _createI18n(addressMessages);
 | 
			
		||||
 | 
			
		||||
const app = createApp({
 | 
			
		||||
   template: `<app></app>`,
 | 
			
		||||
})
 | 
			
		||||
.use(store)
 | 
			
		||||
.use(i18n)
 | 
			
		||||
.component('app', App)
 | 
			
		||||
.mount('#address');
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
const addressMessages = {
 | 
			
		||||
   fr: {
 | 
			
		||||
      add_an_address_title: 'Ajouter une adresse',
 | 
			
		||||
      select_an_address_title: 'Sélectionner une adresse',
 | 
			
		||||
      fill_an_address: 'Compléter l\'adresse',
 | 
			
		||||
      select_country: 'Choisir le pays',
 | 
			
		||||
      select_city: 'Choisir une localité',
 | 
			
		||||
      select_address: 'Choisir une adresse',
 | 
			
		||||
      create_address: 'Appuyer sur "Entrée" pour créer une nouvelle adresse',
 | 
			
		||||
      isNoAddress: 'Pas d\'adresse complète',
 | 
			
		||||
      floor: 'Étage',
 | 
			
		||||
      corridor: 'Couloir',
 | 
			
		||||
      steps: 'Escalier',
 | 
			
		||||
      flat: 'Appartement',
 | 
			
		||||
      buildingName: 'Nom du batiment',
 | 
			
		||||
      extra: 'Complément d\'adresse',
 | 
			
		||||
      distribution: 'Service particulier de distribution'
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
   addressMessages
 | 
			
		||||
};
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
import 'es6-promise/auto';
 | 
			
		||||
import { createStore } from 'vuex';
 | 
			
		||||
 | 
			
		||||
import { postAddress } from '../../_api/AddAddress'
 | 
			
		||||
 | 
			
		||||
const debug = process.env.NODE_ENV !== 'production';
 | 
			
		||||
 | 
			
		||||
const store = createStore({
 | 
			
		||||
   strict: debug,
 | 
			
		||||
   state: {
 | 
			
		||||
      address: {},
 | 
			
		||||
      errorMsg: []
 | 
			
		||||
   },
 | 
			
		||||
   getters: {
 | 
			
		||||
   },
 | 
			
		||||
   mutations: {
 | 
			
		||||
      catchError(state, error) {
 | 
			
		||||
         state.errorMsg.push(error);
 | 
			
		||||
      },
 | 
			
		||||
      addAddress(state, address) {
 | 
			
		||||
         console.log('@M addAddress address', address);
 | 
			
		||||
         state.address = address;
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   actions: {
 | 
			
		||||
      addAddress({ commit }, payload) {
 | 
			
		||||
         console.log('@A addAddress payload', payload);
 | 
			
		||||
         //commit('addAddress', payload); // à remplacer par la suite
 | 
			
		||||
 | 
			
		||||
         //fetch POST qui envoie l'adresse, et récupère la confirmation que c'est ok.
 | 
			
		||||
         //La confirmation est l'adresse elle-même.
 | 
			
		||||
 | 
			
		||||
         postAddress(payload)
 | 
			
		||||
            .then(address => new Promise((resolve, reject) => {
 | 
			
		||||
               commit('addAddress', address);
 | 
			
		||||
               resolve();
 | 
			
		||||
            }))
 | 
			
		||||
            .catch((error) => {
 | 
			
		||||
               commit('catchError', error);
 | 
			
		||||
            });
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export { store };
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
* Endpoint chill_api_single_country__index
 | 
			
		||||
* method GET, get Country Object
 | 
			
		||||
* @returns {Promise} a promise containing all Country object
 | 
			
		||||
*/
 | 
			
		||||
const fetchCountries = () => {
 | 
			
		||||
   console.log('<<< fetching countries');
 | 
			
		||||
 | 
			
		||||
   const url = `/api/1.0/main/country.json?item_per_page=1000`;
 | 
			
		||||
   return fetch(url)
 | 
			
		||||
      .then(response => {
 | 
			
		||||
         if (response.ok) { return response.json(); }
 | 
			
		||||
         throw Error('Error with request resource response');
 | 
			
		||||
      });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Endpoint chill_api_single_postal_code__index
 | 
			
		||||
* method GET, get Country Object
 | 
			
		||||
* @returns {Promise} a promise containing all Postal Code objects filtered with country
 | 
			
		||||
*/
 | 
			
		||||
const fetchCities = (country) => {
 | 
			
		||||
   console.log('<<< fetching cities for', country);
 | 
			
		||||
   const url = `/api/1.0/main/postal-code.json?item_per_page=1000&country=${country.id}`;
 | 
			
		||||
   return fetch(url)
 | 
			
		||||
      .then(response => {
 | 
			
		||||
         if (response.ok) { return response.json(); }
 | 
			
		||||
         throw Error('Error with request resource response');
 | 
			
		||||
      });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Endpoint chill_api_single_address_reference__index
 | 
			
		||||
* method GET, get AddressReference Object
 | 
			
		||||
* @returns {Promise} a promise containing all AddressReference objects filtered with postal code
 | 
			
		||||
*/
 | 
			
		||||
const fetchReferenceAddresses = (postalCode) => {
 | 
			
		||||
    console.log('<<< fetching references addresses for', postalCode);
 | 
			
		||||
   //TODO deal with huge number of addresses... we should do suggestion...
 | 
			
		||||
    const url = `/api/1.0/main/address-reference.json?item_per_page=1000&postal_code=${postalCode.id}`;
 | 
			
		||||
    return fetch(url)
 | 
			
		||||
       .then(response => {
 | 
			
		||||
          if (response.ok) { return response.json(); }
 | 
			
		||||
          throw Error('Error with request resource response');
 | 
			
		||||
       });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
* Endpoint chill_api_single_address__entity__create
 | 
			
		||||
* method POST, post Address Object
 | 
			
		||||
* @returns {Promise}
 | 
			
		||||
*/
 | 
			
		||||
const postAddress = (address) => {
 | 
			
		||||
   console.log(address);
 | 
			
		||||
   const url = `/api/1.0/main/address.json?`;
 | 
			
		||||
   const body = address;
 | 
			
		||||
 | 
			
		||||
   return fetch(url, {
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
      headers: {
 | 
			
		||||
         'Content-Type': 'application/json;charset=utf-8'
 | 
			
		||||
         },
 | 
			
		||||
      body: JSON.stringify(body)
 | 
			
		||||
   }).then(response => {
 | 
			
		||||
      if (response.ok) { return response.json(); }
 | 
			
		||||
      throw Error('Error with request resource response');
 | 
			
		||||
   });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
   fetchCountries,
 | 
			
		||||
   fetchCities,
 | 
			
		||||
   fetchReferenceAddresses,
 | 
			
		||||
   postAddress
 | 
			
		||||
};
 | 
			
		||||
@@ -1,205 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <button class="sc-button bt-create centered mt-4" @click="openModal">
 | 
			
		||||
      {{ $t('add_an_address_title') }}
 | 
			
		||||
   </button>
 | 
			
		||||
 | 
			
		||||
   <teleport to="body">
 | 
			
		||||
      <modal v-if="modal.showModal"
 | 
			
		||||
         v-bind:modalDialogClass="modal.modalDialogClass"
 | 
			
		||||
         @close="modal.showModal = false">
 | 
			
		||||
 | 
			
		||||
         <template v-slot:header>
 | 
			
		||||
            <h3 class="modal-title">{{ $t('add_an_address_title') }}</h3>
 | 
			
		||||
         </template>
 | 
			
		||||
 | 
			
		||||
         <template v-slot:body>
 | 
			
		||||
            <div class="address_form">
 | 
			
		||||
 | 
			
		||||
               <div class="address_form__header">
 | 
			
		||||
                  <h4>{{ $t('select_an_address_title') }}</h4>
 | 
			
		||||
               </div>
 | 
			
		||||
 | 
			
		||||
               <div class="address_form__select">
 | 
			
		||||
 | 
			
		||||
                  <div class="address_form__select__body">
 | 
			
		||||
                     <label for="isNoAddress">
 | 
			
		||||
                        <input type="checkbox"
 | 
			
		||||
                           name="isNoAddress"
 | 
			
		||||
                           v-bind:placeholder="$t('isNoAddress')"
 | 
			
		||||
                           v-model="isNoAddress"
 | 
			
		||||
                           v-bind:value="value"/>
 | 
			
		||||
                        {{ $t('isNoAddress') }}
 | 
			
		||||
                     </label>
 | 
			
		||||
 | 
			
		||||
                     <country-selection
 | 
			
		||||
                        v-bind:address="address"
 | 
			
		||||
                        v-bind:getCities="getCities">
 | 
			
		||||
                     </country-selection>
 | 
			
		||||
 | 
			
		||||
                     <city-selection
 | 
			
		||||
                        v-bind:address="address"
 | 
			
		||||
                        v-bind:getReferenceAddresses="getReferenceAddresses">
 | 
			
		||||
                     </city-selection>
 | 
			
		||||
 | 
			
		||||
                     <address-selection
 | 
			
		||||
                        v-bind:address="address"
 | 
			
		||||
                        v-bind:updateMapCenter="updateMapCenter">
 | 
			
		||||
                     </address-selection>
 | 
			
		||||
                  </div>
 | 
			
		||||
 | 
			
		||||
                  <div class="address_form__select__map">
 | 
			
		||||
                     <address-map
 | 
			
		||||
                        v-bind:address="address"
 | 
			
		||||
                        ref="addressMap">
 | 
			
		||||
                     </address-map>
 | 
			
		||||
                  </div>
 | 
			
		||||
               </div>
 | 
			
		||||
 | 
			
		||||
               <div class="address_form__more">
 | 
			
		||||
                  <address-more
 | 
			
		||||
                     v-if="!isNoAddress"
 | 
			
		||||
                     v-bind:address="address">
 | 
			
		||||
                  </address-more>
 | 
			
		||||
               </div>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
         </template>
 | 
			
		||||
 | 
			
		||||
         <template v-slot:footer>
 | 
			
		||||
            <button class="sc-button green"
 | 
			
		||||
               @click.prevent="$emit('addNewAddress', { address, modal })">
 | 
			
		||||
               <i class="fa fa-plus fa-fw"></i>{{ $t('action.add')}}
 | 
			
		||||
            </button>
 | 
			
		||||
         </template>
 | 
			
		||||
 | 
			
		||||
      </modal>
 | 
			
		||||
   </teleport>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Modal from './Modal';
 | 
			
		||||
import { fetchCountries, fetchCities, fetchReferenceAddresses } from '../_api/AddAddress'
 | 
			
		||||
import CountrySelection from './AddAddress/CountrySelection';
 | 
			
		||||
import CitySelection from './AddAddress/CitySelection';
 | 
			
		||||
import AddressSelection from './AddAddress/AddressSelection';
 | 
			
		||||
import AddressMap from './AddAddress/AddressMap';
 | 
			
		||||
import AddressMore from './AddAddress/AddressMore'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'AddAddresses',
 | 
			
		||||
   components: {
 | 
			
		||||
      Modal,
 | 
			
		||||
      CountrySelection,
 | 
			
		||||
      CitySelection,
 | 
			
		||||
      AddressSelection,
 | 
			
		||||
      AddressMap,
 | 
			
		||||
      AddressMore
 | 
			
		||||
   },
 | 
			
		||||
   props: [
 | 
			
		||||
   ],
 | 
			
		||||
   emits: ['addNewAddress'],
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         modal: {
 | 
			
		||||
            showModal: false,
 | 
			
		||||
            modalDialogClass: "modal-dialog-scrollable modal-xl"
 | 
			
		||||
         },
 | 
			
		||||
         address: {
 | 
			
		||||
            loaded: {
 | 
			
		||||
               countries: [],
 | 
			
		||||
               cities: [],
 | 
			
		||||
               addresses: [],
 | 
			
		||||
            },
 | 
			
		||||
            selected: {
 | 
			
		||||
               country: {},
 | 
			
		||||
               city: {},
 | 
			
		||||
               address: {},
 | 
			
		||||
            },
 | 
			
		||||
            addressMap: {
 | 
			
		||||
               center : [48.8589, 2.3469], // Note: LeafletJs demands [lat, lon] cfr https://macwright.com/lonlat/
 | 
			
		||||
               zoom: 12
 | 
			
		||||
            },
 | 
			
		||||
            isNoAddress: false,
 | 
			
		||||
            floor: null,
 | 
			
		||||
            corridor: null,
 | 
			
		||||
            steps: null,
 | 
			
		||||
            floor: null,
 | 
			
		||||
            flat: null,
 | 
			
		||||
            buildingName: null,
 | 
			
		||||
            extra: null,
 | 
			
		||||
            distribution: null,
 | 
			
		||||
         },
 | 
			
		||||
         errorMsg: {}
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      isNoAddress: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.isNoAddress = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.isNoAddress;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   mounted() {
 | 
			
		||||
       this.getCountries();
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      openModal() {
 | 
			
		||||
         this.modal.showModal = true;
 | 
			
		||||
         this.resetAll();
 | 
			
		||||
         //this.$nextTick(function() {
 | 
			
		||||
         //   this.$refs.search.focus(); // positionner le curseur à l'ouverture de la modale
 | 
			
		||||
         //})
 | 
			
		||||
      },
 | 
			
		||||
      getCountries() {
 | 
			
		||||
         console.log('getCountries');
 | 
			
		||||
         fetchCountries().then(countries => new Promise((resolve, reject) => {
 | 
			
		||||
             this.address.loaded.countries = countries.results;
 | 
			
		||||
             resolve()
 | 
			
		||||
          }))
 | 
			
		||||
          .catch((error) => {
 | 
			
		||||
             this.errorMsg.push(error.message);
 | 
			
		||||
          });
 | 
			
		||||
      },
 | 
			
		||||
      getCities(country) {
 | 
			
		||||
         console.log('getCities for', country.name);
 | 
			
		||||
         fetchCities(country).then(cities => new Promise((resolve, reject) => {
 | 
			
		||||
            this.address.loaded.cities = cities.results;
 | 
			
		||||
            resolve()
 | 
			
		||||
         }))
 | 
			
		||||
         .catch((error) => {
 | 
			
		||||
            this.errorMsg.push(error.message);
 | 
			
		||||
         });
 | 
			
		||||
      },
 | 
			
		||||
      getReferenceAddresses(city) {
 | 
			
		||||
         console.log('getReferenceAddresses for', city.name);
 | 
			
		||||
         fetchReferenceAddresses(city).then(addresses => new Promise((resolve, reject) => {
 | 
			
		||||
               console.log('addresses', addresses);
 | 
			
		||||
               this.address.loaded.addresses = addresses.results;
 | 
			
		||||
               resolve();
 | 
			
		||||
            }))
 | 
			
		||||
            .catch((error) => {
 | 
			
		||||
               this.errorMsg.push(error.message);
 | 
			
		||||
            });
 | 
			
		||||
      },
 | 
			
		||||
      updateMapCenter(point) {
 | 
			
		||||
         console.log('point', point);
 | 
			
		||||
         this.address.addressMap.center[0] = point.coordinates[1];  // TODO use reverse()
 | 
			
		||||
         this.address.addressMap.center[1] = point.coordinates[0];
 | 
			
		||||
         this.$refs.addressMap.update(); // cast child methods
 | 
			
		||||
      },
 | 
			
		||||
      resetAll() {
 | 
			
		||||
         console.log('reset all selected');
 | 
			
		||||
         this.address.loaded.addresses = [];
 | 
			
		||||
         this.address.selected.address = {};
 | 
			
		||||
         this.address.loaded.cities = [];
 | 
			
		||||
         this.address.selected.city = {};
 | 
			
		||||
         this.address.selected.country = {};
 | 
			
		||||
         console.log('cities and addresses', this.address.loaded.cities, this.address.loaded.addresses);
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,50 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div class="container">
 | 
			
		||||
      <div id='address_map'></div>
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
 | 
			
		||||
import L from 'leaflet';
 | 
			
		||||
import markerIconPng from 'leaflet/dist/images/marker-icon.png'
 | 
			
		||||
import 'leaflet/dist/leaflet.css';
 | 
			
		||||
 | 
			
		||||
let map;
 | 
			
		||||
let marker;
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'AddressMap',
 | 
			
		||||
   props: ['address'],
 | 
			
		||||
   computed: {
 | 
			
		||||
      center() {
 | 
			
		||||
         return this.address.addressMap.center;
 | 
			
		||||
      },
 | 
			
		||||
   },
 | 
			
		||||
   methods:{
 | 
			
		||||
      init() {
 | 
			
		||||
         map = L.map('address_map').setView([48.8589, 2.3469], 12);
 | 
			
		||||
 | 
			
		||||
         L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
 | 
			
		||||
            attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
 | 
			
		||||
         }).addTo(map);
 | 
			
		||||
 | 
			
		||||
         const markerIcon = L.icon({
 | 
			
		||||
            iconUrl: markerIconPng,
 | 
			
		||||
            iconAnchor: [12, 41],
 | 
			
		||||
         });
 | 
			
		||||
 | 
			
		||||
         marker = L.marker([48.8589, 2.3469], {icon: markerIcon}).addTo(map);
 | 
			
		||||
 | 
			
		||||
      },
 | 
			
		||||
      update() {
 | 
			
		||||
         console.log('update map with : ', this.address.addressMap.center)
 | 
			
		||||
         marker.setLatLng(this.address.addressMap.center);
 | 
			
		||||
         map.setView(this.address.addressMap.center, 12);
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   mounted(){
 | 
			
		||||
      this.init()
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,112 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div>
 | 
			
		||||
      <h4>{{ $t('fill_an_address') }}</h4>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="floor"
 | 
			
		||||
         :placeholder="$t('floor')"
 | 
			
		||||
         v-model="floor"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="corridor"
 | 
			
		||||
         :placeholder="$t('corridor')"
 | 
			
		||||
         v-model="corridor"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="steps"
 | 
			
		||||
         :placeholder="$t('steps')"
 | 
			
		||||
         v-model="steps"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="flat"
 | 
			
		||||
         :placeholder="$t('flat')"
 | 
			
		||||
         v-model="flat"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="buildingName"
 | 
			
		||||
         :placeholder="$t('buildingName')"
 | 
			
		||||
         v-model="buildingName"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="extra"
 | 
			
		||||
         :placeholder="$t('extra')"
 | 
			
		||||
         v-model="extra"/>
 | 
			
		||||
      <input 
 | 
			
		||||
         type="text"
 | 
			
		||||
         name="distribution"
 | 
			
		||||
         :placeholder="$t('distribution')"
 | 
			
		||||
         v-model="distribution"/>
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
   name: "AddressMore",
 | 
			
		||||
   props: ['address'],
 | 
			
		||||
   computed: {
 | 
			
		||||
      floor: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.floor = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.floor;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      corridor: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.corridor = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.corridor;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      steps: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.steps = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.steps;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      flat: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.flat = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.flat;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      buildingName: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.buildingName = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.buildingName;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      extra: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.extra = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.extra;
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      distribution: {
 | 
			
		||||
         set(value) {
 | 
			
		||||
            console.log('value', value);
 | 
			
		||||
            this.address.distribution = value;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.address.distribution;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div class="container">
 | 
			
		||||
      <VueMultiselect
 | 
			
		||||
         v-model="value"
 | 
			
		||||
         @select="selectAddress"
 | 
			
		||||
         name="field"
 | 
			
		||||
         track-by="id"
 | 
			
		||||
         label="value"
 | 
			
		||||
         :custom-label="transName"
 | 
			
		||||
         :taggable="true"
 | 
			
		||||
         :multiple="false"
 | 
			
		||||
         @tag="addAddress"
 | 
			
		||||
         :placeholder="$t('select_address')"
 | 
			
		||||
         :tagPlaceholder="$t('create_address')"
 | 
			
		||||
         :options="addresses">
 | 
			
		||||
      </VueMultiselect>
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VueMultiselect from 'vue-multiselect';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'AddressSelection',
 | 
			
		||||
   components: { VueMultiselect },
 | 
			
		||||
   props: ['address', 'updateMapCenter'],
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         value: null
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      addresses() {
 | 
			
		||||
         return this.address.loaded.addresses;
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      transName(value) {
 | 
			
		||||
         return value.streetNumber === undefined ? value.street : `${value.street}, ${value.streetNumber}`
 | 
			
		||||
      },
 | 
			
		||||
      selectAddress(value) {
 | 
			
		||||
         this.address.selected.address = value;
 | 
			
		||||
         this.updateMapCenter(value.point);
 | 
			
		||||
      },
 | 
			
		||||
      addAddress (newAddress) {
 | 
			
		||||
         const address = {
 | 
			
		||||
            street: newAddress
 | 
			
		||||
         };
 | 
			
		||||
         this.value = address;
 | 
			
		||||
         this.address.selected.address = address;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div class="container">
 | 
			
		||||
      <VueMultiselect
 | 
			
		||||
         v-model="value"
 | 
			
		||||
         @select="selectCity"
 | 
			
		||||
         name="field"
 | 
			
		||||
         track-by="id"
 | 
			
		||||
         label="value"
 | 
			
		||||
         :custom-label="transName"
 | 
			
		||||
         :placeholder="$t('select_city')"
 | 
			
		||||
         :options="cities">
 | 
			
		||||
      </VueMultiselect>
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VueMultiselect from 'vue-multiselect';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'CitySelection',
 | 
			
		||||
   components: { VueMultiselect },
 | 
			
		||||
   props: ['address', 'getReferenceAddresses'],
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         value: null
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      transName(value) {
 | 
			
		||||
         return `${value.code}-${value.name}`
 | 
			
		||||
      },
 | 
			
		||||
      selectCity(value) {
 | 
			
		||||
         this.address.selected.city = value;
 | 
			
		||||
         this.getReferenceAddresses(value);
 | 
			
		||||
      },
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      cities() {
 | 
			
		||||
         return this.address.loaded.cities;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div class="container">
 | 
			
		||||
      <VueMultiselect
 | 
			
		||||
         v-model="value"
 | 
			
		||||
         name="field"
 | 
			
		||||
         track-by="id"
 | 
			
		||||
         label="name"
 | 
			
		||||
         :custom-label="transName"
 | 
			
		||||
         :placeholder="$t('select_country')"
 | 
			
		||||
         :options="countries"
 | 
			
		||||
         @select="selectCountry">
 | 
			
		||||
      </VueMultiselect>
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VueMultiselect from 'vue-multiselect';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'CountrySelection',
 | 
			
		||||
   components: { VueMultiselect },
 | 
			
		||||
   props: ['address', 'getCities'],
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         value: this.address.loaded.countries.filter(c => c.countryCode === 'FR')[0]
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      init() {
 | 
			
		||||
         if (this.value !== undefined) {
 | 
			
		||||
            this.getCities(this.value);
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      transName ({ name }) {
 | 
			
		||||
         return name.fr //TODO multilang
 | 
			
		||||
      },
 | 
			
		||||
      selectCountry(value) {
 | 
			
		||||
         console.log(value);
 | 
			
		||||
         this.address.selected.country = value;
 | 
			
		||||
         this.getCities(value);
 | 
			
		||||
      },
 | 
			
		||||
   },
 | 
			
		||||
   mounted(){
 | 
			
		||||
      this.init()
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      countries() {
 | 
			
		||||
         const countries = this.address.loaded.countries;
 | 
			
		||||
         let orderedCountries = [];
 | 
			
		||||
         orderedCountries.push(...countries.filter(c => c.countryCode === 'FR'))
 | 
			
		||||
         orderedCountries.push(...countries.filter(c => c.countryCode === 'BE'))
 | 
			
		||||
         orderedCountries.push(...countries.filter(c => c.countryCode !== 'FR').filter(c => c.countryCode !== 'BE'))
 | 
			
		||||
         return orderedCountries;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <transition name="modal">
 | 
			
		||||
      <div class="modal-mask">
 | 
			
		||||
         <!-- :: styles bootstrap :: -->
 | 
			
		||||
         <div class="modal-dialog" :class="modalDialogClass">
 | 
			
		||||
            <div class="modal-content">
 | 
			
		||||
               <div class="modal-header">
 | 
			
		||||
                  <slot name="header"></slot>
 | 
			
		||||
                  <button class="close sc-button grey" @click="$emit('close')">
 | 
			
		||||
                     <i class="fa fa-times" aria-hidden="true"></i></button>
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="body-head">
 | 
			
		||||
                  <slot name="body-head"></slot>
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="modal-body">
 | 
			
		||||
                  <slot name="body"></slot>
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="modal-footer">
 | 
			
		||||
                  <button class="sc-button cancel" @click="$emit('close')">{{ $t('action.close') }}</button>
 | 
			
		||||
                  <slot name="footer"></slot>
 | 
			
		||||
               </div>
 | 
			
		||||
            </div>
 | 
			
		||||
         </div>
 | 
			
		||||
         <!-- :: end styles bootstrap :: -->
 | 
			
		||||
      </div>
 | 
			
		||||
   </transition>
 | 
			
		||||
</template> 
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
/*
 | 
			
		||||
 *    This Modal component is a mix between Vue3 modal implementation 
 | 
			
		||||
 *    [+] with 'v-if:showModal' directive:parameter, html scope is added/removed not just shown/hidden
 | 
			
		||||
 *    [+] with slot we can pass content from parent component
 | 
			
		||||
 *    [+] some classes are passed from parent component
 | 
			
		||||
 *    and Bootstrap 4.6 _modal.scss module
 | 
			
		||||
 *    [+] using bootstrap css classes, the modal have a responsive behaviour, 
 | 
			
		||||
 *    [+] modal design can be configured using css classes (size, scroll)
 | 
			
		||||
 */
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'Modal',
 | 
			
		||||
   props: ['modalDialogClass'],
 | 
			
		||||
   emits: ['close']
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
   .modal-mask {
 | 
			
		||||
      position: fixed;
 | 
			
		||||
      z-index: 9998;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      left: 0;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      background-color: rgba(0, 0, 0, 0.75);
 | 
			
		||||
      display: table;
 | 
			
		||||
      transition: opacity 0.3s ease;
 | 
			
		||||
   }
 | 
			
		||||
   .modal-header .close { // bootstrap classes, override sc-button 0 radius
 | 
			
		||||
      border-top-right-radius: 0.3rem;
 | 
			
		||||
   }
 | 
			
		||||
   /*
 | 
			
		||||
   * The following styles are auto-applied to elements with
 | 
			
		||||
   * transition="modal" when their visibility is toggled
 | 
			
		||||
   * by Vue.js.
 | 
			
		||||
   *
 | 
			
		||||
   * You can easily play with the modal transition by editing
 | 
			
		||||
   * these styles.
 | 
			
		||||
   */
 | 
			
		||||
   .modal-enter {
 | 
			
		||||
      opacity: 0;
 | 
			
		||||
   }
 | 
			
		||||
   .modal-leave-active {
 | 
			
		||||
      opacity: 0;
 | 
			
		||||
   }
 | 
			
		||||
   .modal-enter .modal-container,
 | 
			
		||||
   .modal-leave-active .modal-container {
 | 
			
		||||
      -webkit-transform: scale(1.1);
 | 
			
		||||
      transform: scale(1.1);
 | 
			
		||||
   }
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,153 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
  
 | 
			
		||||
   <a class="sc-button" target="_blank"
 | 
			
		||||
      :class="classAction"
 | 
			
		||||
      :title="$t(titleAction)"
 | 
			
		||||
      @click="openModal">
 | 
			
		||||
      {{ buttonText }}
 | 
			
		||||
   </a>
 | 
			
		||||
  
 | 
			
		||||
   <teleport to="body">
 | 
			
		||||
      <modal v-if="modal.showModal"
 | 
			
		||||
         :modalDialogClass="modal.modalDialogClass"
 | 
			
		||||
         @close="modal.showModal = false">
 | 
			
		||||
         
 | 
			
		||||
         <template v-slot:header>
 | 
			
		||||
            <h3 class="modal-title">{{ $t(titleModal) }}</h3>
 | 
			
		||||
         </template>
 | 
			
		||||
         
 | 
			
		||||
         <template v-slot:body v-if="type === 'person'">
 | 
			
		||||
            <on-the-fly-person
 | 
			
		||||
               v-bind:id="id"
 | 
			
		||||
               v-bind:type="type"
 | 
			
		||||
               v-bind:action="action"
 | 
			
		||||
               ref="castPerson">
 | 
			
		||||
            </on-the-fly-person>
 | 
			
		||||
         </template>
 | 
			
		||||
         
 | 
			
		||||
         <template v-slot:body v-else-if="type === 'thirdparty'">
 | 
			
		||||
            <on-the-fly-thirdparty
 | 
			
		||||
               v-bind:id="id"
 | 
			
		||||
               v-bind:type="type"
 | 
			
		||||
               v-bind:action="action"
 | 
			
		||||
               ref="castThirdparty">
 | 
			
		||||
            </on-the-fly-thirdparty>
 | 
			
		||||
         </template>
 | 
			
		||||
         
 | 
			
		||||
         <template v-slot:body v-else>
 | 
			
		||||
            <on-the-fly-create
 | 
			
		||||
               v-bind:action="action"
 | 
			
		||||
               ref="castNew">
 | 
			
		||||
            </on-the-fly-create>
 | 
			
		||||
         </template>
 | 
			
		||||
            
 | 
			
		||||
         <template v-slot:footer>
 | 
			
		||||
            <button v-if="action === 'show'"
 | 
			
		||||
               @click="changeActionTo('edit')"
 | 
			
		||||
               class="sc-button bt-update"> <!-- @click.prevent="$emit('..', ..)" -->
 | 
			
		||||
            </button>
 | 
			
		||||
            <button v-else
 | 
			
		||||
               class="sc-button bt-save"
 | 
			
		||||
               @click="saveAction"
 | 
			
		||||
               > <!--  
 | 
			
		||||
               -->
 | 
			
		||||
               {{ $t('action.save')}}
 | 
			
		||||
            </button>
 | 
			
		||||
         </template>
 | 
			
		||||
         
 | 
			
		||||
      </modal>
 | 
			
		||||
   </teleport>
 | 
			
		||||
   
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
 | 
			
		||||
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
 | 
			
		||||
import OnTheFlyThirdparty from './OnTheFly/ThirdParty.vue';
 | 
			
		||||
import OnTheFlyCreate from './OnTheFly/Create.vue';
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: 'OnTheFly',
 | 
			
		||||
   components: {
 | 
			
		||||
      Modal,
 | 
			
		||||
      OnTheFlyPerson,
 | 
			
		||||
      OnTheFlyThirdparty,
 | 
			
		||||
      OnTheFlyCreate
 | 
			
		||||
   },
 | 
			
		||||
   props: ['type', 'id', 'action', 'buttonText'],
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         modal: {
 | 
			
		||||
            showModal: false,
 | 
			
		||||
            modalDialogClass: "modal-dialog-scrollable modal-xl"
 | 
			
		||||
         },
 | 
			
		||||
         action: this.action
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      action() {
 | 
			
		||||
         return this.$data.action;
 | 
			
		||||
      },
 | 
			
		||||
      classAction() {
 | 
			
		||||
         switch (this.action) {
 | 
			
		||||
            case 'show':
 | 
			
		||||
               return 'bt-show';
 | 
			
		||||
            case 'edit':
 | 
			
		||||
               return 'bt-update';
 | 
			
		||||
            case 'create':
 | 
			
		||||
               return 'bt-create';
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      titleAction() {
 | 
			
		||||
         switch (this.action) {
 | 
			
		||||
            case 'show':
 | 
			
		||||
               return 'action.show';
 | 
			
		||||
            case 'edit':
 | 
			
		||||
               return 'action.edit';
 | 
			
		||||
            case 'create':
 | 
			
		||||
               return 'action.create';
 | 
			
		||||
         }
 | 
			
		||||
      },
 | 
			
		||||
      titleModal() {
 | 
			
		||||
         switch (this.action) {
 | 
			
		||||
            case 'show':
 | 
			
		||||
               return 'onthefly.show.' + this.type;
 | 
			
		||||
            case 'edit':
 | 
			
		||||
               return 'onthefly.edit.' + this.type;
 | 
			
		||||
            case 'create':
 | 
			
		||||
               return 'onthefly.create.title';
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      openModal() {
 | 
			
		||||
         this.modal.showModal = true;
 | 
			
		||||
         this.$nextTick(function() {
 | 
			
		||||
            //this.$refs.search.focus();
 | 
			
		||||
         })
 | 
			
		||||
      },
 | 
			
		||||
      changeActionTo(action) {
 | 
			
		||||
         // [BUG] clic first on show item button; in modal clic edit button; close modal; clic again on show item button
 | 
			
		||||
         this.$data.action = action; 
 | 
			
		||||
      },
 | 
			
		||||
      saveAction() {
 | 
			
		||||
         console.log('saveAction');
 | 
			
		||||
         if (this.type === 'person') {
 | 
			
		||||
            this.$refs.castPerson.postData();
 | 
			
		||||
         } else if (this.type === 'thirdparty') {
 | 
			
		||||
            this.$refs.castThirdparty.postData();
 | 
			
		||||
         } else {
 | 
			
		||||
            // saveAction() ==cast=to==> child.castByType() ==cast=to==> grand-child.postData()
 | 
			
		||||
            this.$refs.castNew.castByType();
 | 
			
		||||
         }
 | 
			
		||||
         this.modal.showModal = false;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css" scoped>
 | 
			
		||||
a {
 | 
			
		||||
   cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,84 +0,0 @@
 | 
			
		||||
<template>   
 | 
			
		||||
   <ul class="nav nav-tabs">
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
         <a class="nav-link" :class="{ active: isActive('person') }">
 | 
			
		||||
            <label for="person">
 | 
			
		||||
               <input type="radio" name="person" v-model="radioType" value="person">
 | 
			
		||||
               {{ $t('onthefly.create.person') }}
 | 
			
		||||
            </label>
 | 
			
		||||
         </a>
 | 
			
		||||
      </li>
 | 
			
		||||
      <li class="nav-item">
 | 
			
		||||
         <a class="nav-link" :class="{ active: isActive('thirdparty') }">
 | 
			
		||||
            <label for="thirdparty">
 | 
			
		||||
               <input type="radio" name="thirdparty" v-model="radioType" value="thirdparty">
 | 
			
		||||
               {{ $t('onthefly.create.thirdparty') }}
 | 
			
		||||
            </label>
 | 
			
		||||
         </a>
 | 
			
		||||
      </li>
 | 
			
		||||
   </ul>
 | 
			
		||||
   
 | 
			
		||||
   <div class="my-4">
 | 
			
		||||
      <on-the-fly-person
 | 
			
		||||
         v-if="type === 'person'"
 | 
			
		||||
         v-bind:action="action"
 | 
			
		||||
         ref="castPerson">
 | 
			
		||||
      </on-the-fly-person>
 | 
			
		||||
      
 | 
			
		||||
      <on-the-fly-thirdparty
 | 
			
		||||
         v-if="type === 'thirdparty'"
 | 
			
		||||
         v-bind:action="action"
 | 
			
		||||
         ref="castThirdparty">
 | 
			
		||||
      </on-the-fly-thirdparty>
 | 
			
		||||
   </div>      
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import OnTheFlyPerson from 'ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue';
 | 
			
		||||
import OnTheFlyThirdparty from './ThirdParty.vue'; // TODO move in ChillThirdpartyAssets
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
   name: "OnTheFlyCreate",
 | 
			
		||||
   props: ['action'], 
 | 
			
		||||
   components: {
 | 
			
		||||
      OnTheFlyPerson,
 | 
			
		||||
      OnTheFlyThirdparty
 | 
			
		||||
   },
 | 
			
		||||
   data() {
 | 
			
		||||
      return {
 | 
			
		||||
         type: 'person' //by default
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   computed: {
 | 
			
		||||
      radioType: {
 | 
			
		||||
         set(type) {
 | 
			
		||||
            this.type = type;
 | 
			
		||||
         },
 | 
			
		||||
         get() {
 | 
			
		||||
            return this.type;
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   },
 | 
			
		||||
   methods: {
 | 
			
		||||
      isActive(tab) {
 | 
			
		||||
         return (this.type === tab) ? true : false; 
 | 
			
		||||
      },
 | 
			
		||||
      castByType() {
 | 
			
		||||
         console.log('saveActionByType');
 | 
			
		||||
         if (this.type === 'person') {
 | 
			
		||||
            this.$refs.castPerson.postData();
 | 
			
		||||
         } else if (this.type === 'thirdparty') {
 | 
			
		||||
            this.$refs.castThirdparty.postData();
 | 
			
		||||
         } else {
 | 
			
		||||
            throw Error('Invalid type of entity');
 | 
			
		||||
         }
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css" scoped>
 | 
			
		||||
label {
 | 
			
		||||
   cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
<template>
 | 
			
		||||
   <div v-if="action === 'show'">
 | 
			
		||||
      show
 | 
			
		||||
      thirdparty
 | 
			
		||||
      {{ id }}
 | 
			
		||||
   </div>
 | 
			
		||||
   <div v-else-if="action === 'edit' || action === 'create'">
 | 
			
		||||
      {{ action }}
 | 
			
		||||
      thirdparty 
 | 
			
		||||
      {{ id }}
 | 
			
		||||
   </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
   name: "OnTheFlyThirdParty",
 | 
			
		||||
   props: ['id', 'type', 'action']
 | 
			
		||||
}
 | 
			
		||||
// TODO move in ChillThirdpartyAssets
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="css" scoped>
 | 
			
		||||
</style>
 | 
			
		||||
@@ -1,79 +0,0 @@
 | 
			
		||||
import { createI18n } from 'vue-i18n'
 | 
			
		||||
 | 
			
		||||
const datetimeFormats = {
 | 
			
		||||
   fr: {
 | 
			
		||||
      short: {
 | 
			
		||||
         year: "numeric",
 | 
			
		||||
         month: "numeric",
 | 
			
		||||
         day: "numeric"
 | 
			
		||||
      },
 | 
			
		||||
      text: {
 | 
			
		||||
         year: "numeric",
 | 
			
		||||
         month: "long",
 | 
			
		||||
         day: "numeric",
 | 
			
		||||
      },
 | 
			
		||||
      long: {
 | 
			
		||||
         year: "numeric",
 | 
			
		||||
         month: "numeric",
 | 
			
		||||
         day: "numeric",
 | 
			
		||||
         hour: "numeric",
 | 
			
		||||
         minute: "numeric",
 | 
			
		||||
         hour12: false
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
const messages = {
 | 
			
		||||
   fr: {
 | 
			
		||||
      action: {
 | 
			
		||||
         actions: "Actions",
 | 
			
		||||
         show: "Voir",
 | 
			
		||||
         edit: "Modifier",
 | 
			
		||||
         create: "Créer",
 | 
			
		||||
         remove: "Enlever",
 | 
			
		||||
         delete: "Supprimer",
 | 
			
		||||
         save: "Enregistrer",
 | 
			
		||||
         add: "Ajouter",
 | 
			
		||||
         show_modal: "Ouvrir une modale",
 | 
			
		||||
         ok: "OK",
 | 
			
		||||
         cancel: "Annuler",
 | 
			
		||||
         close: "Fermer",
 | 
			
		||||
         back: "Retour",
 | 
			
		||||
         check_all: "cocher tout",
 | 
			
		||||
         reset: "réinitialiser"
 | 
			
		||||
      },
 | 
			
		||||
      nav: {
 | 
			
		||||
         next: "Suivant",
 | 
			
		||||
         previous: "Précédent",
 | 
			
		||||
         top: "Haut",
 | 
			
		||||
         bottom: "Bas",
 | 
			
		||||
      },
 | 
			
		||||
      onthefly: {
 | 
			
		||||
         show: {
 | 
			
		||||
            person: "Détails de l'usager",
 | 
			
		||||
            thirdparty: "Détails du tiers"
 | 
			
		||||
         },
 | 
			
		||||
         edit: {
 | 
			
		||||
            person: "Modifier un usager",
 | 
			
		||||
            thirdparty: "Modifier un tiers"
 | 
			
		||||
         },
 | 
			
		||||
         create: {
 | 
			
		||||
            button: "Créer \"{q}\"",
 | 
			
		||||
            title: "Créer à la volée…",
 | 
			
		||||
            person: "un nouvel usager",
 | 
			
		||||
            thirdparty: "un nouveau tiers"
 | 
			
		||||
         },         
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _createI18n = (appMessages) => {
 | 
			
		||||
   Object.assign(messages.fr, appMessages.fr);
 | 
			
		||||
   return createI18n({
 | 
			
		||||
      locale: 'fr',
 | 
			
		||||
      fallbackLocale: 'fr',
 | 
			
		||||
      datetimeFormats,
 | 
			
		||||
      messages,
 | 
			
		||||
   })
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { _createI18n }
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
<div class="chill_address">
 | 
			
		||||
    {% if options['has_no_address'] == true and address.isNoAddress == true %}
 | 
			
		||||
        <div class="chill_address_is_noaddress">{{ 'address.consider homeless'|trans }}</div>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    <div class="chill_address_address {% if options['multiline'] %}chill_address_address--multiline{% endif %}">
 | 
			
		||||
    {% if address.street is not empty %}<p class="street street1">{{ address.street }}</p>{% endif %}
 | 
			
		||||
    {% if address.streetNumber is not empty %}<p class="street street2">{{ address.streetNumber }}</p>{% endif %}
 | 
			
		||||
    {% if address.postCode is not empty %}
 | 
			
		||||
    <p class="postalCode"><span class="code">{{ address.postCode.code }}</span> <span class="name">{{ address.postCode.name }}</span></p>
 | 
			
		||||
    <p class="country">{{ address.postCode.country.name|localize_translatable_string }}</p>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{%- if options['with_valid_from'] == true -%}
 | 
			
		||||
<span class="address_since">{{ 'Since %date%'|trans( { '%date%' : address.validFrom|format_date('long') } ) }}</span>
 | 
			
		||||
{%- endif -%}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,14 +1,13 @@
 | 
			
		||||
{%- macro _render(address, options) -%}
 | 
			
		||||
    {%- set options = { 'with_valid_from' : true  }|merge(options|default({})) -%}
 | 
			
		||||
    {%- set options = { 'has_no_address'  : false }|merge(options|default({})) -%}
 | 
			
		||||
    {%- set options = { 'with_icon' : false }|merge(options|default({})) -%}
 | 
			
		||||
    <div class="chill_address">
 | 
			
		||||
        {% if options['has_no_address'] == true and address.isNoAddress == true %}
 | 
			
		||||
            <div class="chill_address_is_noaddress">{{ 'address.consider homeless'|trans }}</div>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        <div class="chill_address_address">{% if options['with_icon'] == true %}<i class="fa fa-fw fa-map-marker"></i>{% endif %}
 | 
			
		||||
        {% if address.street is not empty %}<p class="street street1">{{ address.street }}</p>{% endif %}
 | 
			
		||||
        {% if address.streetNumber is not empty %}<p class="street street2">{{ address.streetNumber }}</p>{% endif %}
 | 
			
		||||
        <div class="chill_address_address">
 | 
			
		||||
        {% if address.streetAddress1 is not empty %}<p class="street street1">{{ address.streetAddress1 }}</p>{% endif %}
 | 
			
		||||
        {% if address.streetAddress2 is not empty %}<p class="street street2">{{ address.streetAddress2 }}</p>{% endif %}
 | 
			
		||||
        {% if address.postCode is not empty %}
 | 
			
		||||
        <p class="postalCode"><span class="code">{{ address.postCode.code }}</span> <span class="name">{{ address.postCode.name }}</span></p>
 | 
			
		||||
        <p class="country">{{ address.postCode.country.name|localize_translatable_string }}</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<footer class="footer">
 | 
			
		||||
    <p>{{ 'This program is free software: you can redistribute it and/or modify it under the terms of the <strong>GNU Affero General Public License</strong>'|trans|raw }}
 | 
			
		||||
        <br/> <a name="bottom" href="https://{{ app.request.locale }}.wikibooks.org/wiki/Chill" target="_blank">{{ 'User manual'|trans }}</a></p>
 | 
			
		||||
</footer>
 | 
			
		||||
        <br/> <a href="https://{{ app.request.locale }}.wikibooks.org/wiki/Chill" target="_blank">{{ 'User manual'|trans }}</a></p>
 | 
			
		||||
</footer>
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
<img name="top" class="logo" src="{{ asset('build/images/logo-chill-sans-slogan_white.png') }}">
 | 
			
		||||
<img class="logo" src="{{ asset('build/images/logo-chill-sans-slogan_white.png') }}">
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
<div class="timeline">
 | 
			
		||||
    {% for result in results %}
 | 
			
		||||
        <div class="timeline-item {% if loop.index0 is even %}even{% else %}odd{% endif %}">
 | 
			
		||||
            {% include result.template with result.template_data %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
@@ -1,15 +1,7 @@
 | 
			
		||||
{% extends "@ChillMain/layout.html.twig"  %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
  <div id="container content">
 | 
			
		||||
    <div class="grid-8 centered">
 | 
			
		||||
      <h1>{{ 'Global timeline'|trans }}</h1>
 | 
			
		||||
 | 
			
		||||
      {{ timeline|raw }}
 | 
			
		||||
      
 | 
			
		||||
      {% if nb_items > paginator.getItemsPerPage %}
 | 
			
		||||
      {{ chill_pagination(paginator) }}
 | 
			
		||||
      {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% endblock content %}
 | 
			
		||||
<div class="timeline">
 | 
			
		||||
    {% for result in results %}
 | 
			
		||||
        <div class="timeline-item {% if loop.index0 is even %}even{% else %}odd{% endif %}">
 | 
			
		||||
            {% include result.template with result.template_data %}
 | 
			
		||||
        </div>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -68,14 +68,6 @@ class SectionMenuBuilder implements LocalMenuBuilderInterface
 | 
			
		||||
                'icons' => ['home'],
 | 
			
		||||
                'order' => 0
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
        $menu->addChild($this->translator->trans('Global timeline'), [
 | 
			
		||||
            'route' => 'chill_center_timeline',
 | 
			
		||||
        ])
 | 
			
		||||
            ->setExtras([
 | 
			
		||||
                'order' => 10
 | 
			
		||||
            ]
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        if ($this->authorizationChecker->isGranted(ChillExportVoter::EXPORT)) {
 | 
			
		||||
            $menu->addChild($this->translator->trans('Export Menu'), [
 | 
			
		||||
 
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Search\Model;
 | 
			
		||||
 | 
			
		||||
class Result
 | 
			
		||||
{
 | 
			
		||||
    private float $relevance;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * mixed an arbitrary result
 | 
			
		||||
     */
 | 
			
		||||
    private $result;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param float $relevance
 | 
			
		||||
     * @param $result
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct(float $relevance, $result)
 | 
			
		||||
    {
 | 
			
		||||
        $this->relevance = $relevance;
 | 
			
		||||
        $this->result = $result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getRelevance(): float
 | 
			
		||||
    {
 | 
			
		||||
        return $this->relevance;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getResult()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Search;
 | 
			
		||||
 | 
			
		||||
use Chill\PersonBundle\Entity\Person;
 | 
			
		||||
use Chill\ThirdPartyBundle\Entity\ThirdParty;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Chill\MainBundle\Search\SearchProvider;
 | 
			
		||||
use Symfony\Component\VarDumper\Resources\functions\dump;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *  ***Warning*** This is an incomplete implementation ***Warning***
 | 
			
		||||
 */
 | 
			
		||||
class SearchApi
 | 
			
		||||
{
 | 
			
		||||
    private EntityManagerInterface $em;
 | 
			
		||||
    private SearchProvider $search;
 | 
			
		||||
 | 
			
		||||
    public function __construct(EntityManagerInterface $em, SearchProvider $search)
 | 
			
		||||
    {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->search = $search;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return Model/Result[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getResults(string $query, int $offset, int $maxResult): array
 | 
			
		||||
    {
 | 
			
		||||
        // **warning again**: this is an incomplete implementation 
 | 
			
		||||
        $results = [];
 | 
			
		||||
 | 
			
		||||
        foreach ($this->getPersons($query) as $p) {
 | 
			
		||||
            $results[] = new Model\Result((float)\rand(0, 100) / 100, $p);
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($this->getThirdParties($query) as $t) {
 | 
			
		||||
            $results[] = new Model\Result((float)\rand(0, 100) / 100, $t);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        \usort($results, function(Model\Result $a, Model\Result $b) {
 | 
			
		||||
            return ($a->getRelevance() <=> $b->getRelevance()) * -1;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return $results;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function countResults(string $query): int
 | 
			
		||||
    {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getThirdParties(string $query)
 | 
			
		||||
    {
 | 
			
		||||
        $thirdPartiesIds = $this->em->createQuery('SELECT t.id FROM '.ThirdParty::class.' t')
 | 
			
		||||
            ->getScalarResult();
 | 
			
		||||
        $nbResults = rand(0, 15);
 | 
			
		||||
 | 
			
		||||
        if ($nbResults === 1) {
 | 
			
		||||
            $nbResults++;
 | 
			
		||||
        } elseif ($nbResults === 0) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
        $ids = \array_map(function ($e) use ($thirdPartiesIds) { return $thirdPartiesIds[$e]['id'];}, 
 | 
			
		||||
            \array_rand($thirdPartiesIds, $nbResults));
 | 
			
		||||
 | 
			
		||||
        $a = $this->em->getRepository(ThirdParty::class)
 | 
			
		||||
            ->findById($ids);
 | 
			
		||||
        return $a;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getPersons(string $query)
 | 
			
		||||
    {
 | 
			
		||||
        $params = [
 | 
			
		||||
            SearchInterface::SEARCH_PREVIEW_OPTION => false
 | 
			
		||||
            ];
 | 
			
		||||
        $search = $this->search->getResultByName($query, 'person_regular', 0, 50, $params, 'json');
 | 
			
		||||
        $ids = \array_map(function($r) { return $r['id']; }, $search['results']);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (count($ids) === 0) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->em->getRepository(Person::class)
 | 
			
		||||
            ->findById($ids)
 | 
			
		||||
            ;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -32,34 +32,34 @@ use Chill\MainBundle\Entity\GroupCenter;
 | 
			
		||||
use Chill\MainBundle\Entity\RoleScope;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper for authorizations. 
 | 
			
		||||
 * 
 | 
			
		||||
 * Helper for authorizations.
 | 
			
		||||
 *
 | 
			
		||||
 * Provides methods for user and entities information.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 */
 | 
			
		||||
class AuthorizationHelper
 | 
			
		||||
class AuthorizationHelper implements AuthorizationHelperInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var RoleHierarchyInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $roleHierarchy;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The role in a hierarchy, given by the parameter 
 | 
			
		||||
     * The role in a hierarchy, given by the parameter
 | 
			
		||||
     * `security.role_hierarchy.roles` from the container.
 | 
			
		||||
     *
 | 
			
		||||
     * @var string[]
 | 
			
		||||
     */
 | 
			
		||||
    protected $hierarchy;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @var EntityManagerInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $em;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        RoleHierarchyInterface $roleHierarchy,
 | 
			
		||||
        $hierarchy,
 | 
			
		||||
@@ -69,10 +69,10 @@ class AuthorizationHelper
 | 
			
		||||
        $this->hierarchy     = $hierarchy;
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if a user is active on this center
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return bool
 | 
			
		||||
@@ -81,21 +81,21 @@ class AuthorizationHelper
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter) {
 | 
			
		||||
            if ($center->getId() === $groupCenter->getCenter()->getId()) {
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Determines if the user has access to the given entity.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * if the entity implements Chill\MainBundle\Entity\HasScopeInterface,
 | 
			
		||||
     * the scope is taken into account.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param HasCenterInterface $entity the entity may also implement HasScopeInterface
 | 
			
		||||
     * @param string|Role $attribute
 | 
			
		||||
@@ -103,13 +103,15 @@ class AuthorizationHelper
 | 
			
		||||
     */
 | 
			
		||||
    public function userHasAccess(User $user, HasCenterInterface $entity, $attribute)
 | 
			
		||||
    {
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $center = $entity->getCenter();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (!$this->userCanReachCenter($user, $center)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $role = ($attribute instanceof Role) ? $attribute : new Role($attribute);
 | 
			
		||||
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
            //filter on center
 | 
			
		||||
            if ($groupCenter->getCenter()->getId() === $entity->getCenter()->getId()) {
 | 
			
		||||
@@ -117,7 +119,8 @@ class AuthorizationHelper
 | 
			
		||||
                //iterate on roleScopes
 | 
			
		||||
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                    //check that the role allow to reach the required role
 | 
			
		||||
                    if ($this->isRoleReached($attribute, $roleScope->getRole())) {
 | 
			
		||||
                    if ($this->isRoleReached($role,
 | 
			
		||||
                          new Role($roleScope->getRole()))){
 | 
			
		||||
                        //if yes, we have a right on something...
 | 
			
		||||
                        // perform check on scope if necessary
 | 
			
		||||
                        if ($entity instanceof HasScopeInterface) {
 | 
			
		||||
@@ -134,35 +137,33 @@ class AuthorizationHelper
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get reachable Centers for the given user, role,
 | 
			
		||||
     * and optionnaly Scope
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param string|Role $role
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param null|Scope $scope
 | 
			
		||||
     * @return Center[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCenters(User $user, $role, Scope $scope = null)
 | 
			
		||||
    public function getReachableCenters(User $user, Role $role, Scope $scope = null)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
        $centers = array();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
            $permissionGroup = $groupCenter->getPermissionsGroup();
 | 
			
		||||
            //iterate on roleScopes
 | 
			
		||||
            foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                //check that the role is in the reachable roles
 | 
			
		||||
                if ($this->isRoleReached($role, $roleScope->getRole())) {
 | 
			
		||||
                if ($this->isRoleReached($role,
 | 
			
		||||
                      new Role($roleScope->getRole()))) {
 | 
			
		||||
                    if ($scope === null) {
 | 
			
		||||
                        $centers[] = $groupCenter->getCenter();
 | 
			
		||||
                        break 1;
 | 
			
		||||
@@ -170,43 +171,19 @@ class AuthorizationHelper
 | 
			
		||||
                        if ($scope->getId() == $roleScope->getScope()->getId()){
 | 
			
		||||
                            $centers[] = $groupCenter->getCenter();
 | 
			
		||||
                            break 1;
 | 
			
		||||
                        }      
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $centers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Filter an array of centers, return only center which are reachable
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user The user
 | 
			
		||||
     * @param array $centers a list of centers which are going to be filtered
 | 
			
		||||
     * @param string|Center $role
 | 
			
		||||
     */
 | 
			
		||||
    public function filterReachableCenters(User $user, array $centers, $role): array
 | 
			
		||||
    {
 | 
			
		||||
        $results = [];
 | 
			
		||||
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($centers as $center) {
 | 
			
		||||
            if ($this->userCanReachCenter($user, $center, $role)) {
 | 
			
		||||
                $results[] = $center;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $results;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all reachable scope for a given user, center and role
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated Use getReachableCircles
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
@@ -214,30 +191,23 @@ class AuthorizationHelper
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableScopes(User $user, $role, Center $center)
 | 
			
		||||
    public function getReachableScopes(User $user, Role $role, Center $center)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->getReachableCircles($user, $role, $center);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all reachable circle for a given user, center and role
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param string|Role $role
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCircles(User $user, $role, Center $center)
 | 
			
		||||
    public function getReachableCircles(User $user, Role $role, Center $center)
 | 
			
		||||
    {
 | 
			
		||||
        if ($role instanceof Role) {
 | 
			
		||||
            $role = $role->getRole();
 | 
			
		||||
        }
 | 
			
		||||
        $scopes = array();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($user->getGroupCenters() as $groupCenter){
 | 
			
		||||
            if ($center->getId() === $groupCenter->getCenter()->getId()) {
 | 
			
		||||
                //iterate on permissionGroup
 | 
			
		||||
@@ -245,18 +215,20 @@ class AuthorizationHelper
 | 
			
		||||
                //iterate on roleScopes
 | 
			
		||||
                foreach($permissionGroup->getRoleScopes() as $roleScope) {
 | 
			
		||||
                    //check that the role is in the reachable roles
 | 
			
		||||
                    if ($this->isRoleReached($role, $roleScope->getRole())) {
 | 
			
		||||
                    if ($this->isRoleReached($role,
 | 
			
		||||
                          new Role($roleScope->getRole()))) {
 | 
			
		||||
 | 
			
		||||
                        $scopes[] = $roleScope->getScope();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $scopes;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @param Scope $circle
 | 
			
		||||
@@ -267,7 +239,7 @@ class AuthorizationHelper
 | 
			
		||||
        $parents = $this->getParentRoles($role);
 | 
			
		||||
        $parents[] = $role;
 | 
			
		||||
        $parentRolesString = \array_map(function(Role $r) { return $r->getRole(); }, $parents);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $qb = $this->em->createQueryBuilder();
 | 
			
		||||
        $qb
 | 
			
		||||
            ->select('u')
 | 
			
		||||
@@ -278,37 +250,37 @@ class AuthorizationHelper
 | 
			
		||||
            ->where('gc.center = :center')
 | 
			
		||||
            ->andWhere($qb->expr()->in('rs.role', $parentRolesString))
 | 
			
		||||
            ;
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $qb->setParameter('center', $center);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if ($circle !== null) {
 | 
			
		||||
            $qb->andWhere('rs.scope = :circle')
 | 
			
		||||
                ->setParameter('circle', $circle)
 | 
			
		||||
                ;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $qb->getQuery()->getResult();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Test if a parent role may give access to a given child role
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param Role $childRole The role we want to test if he is reachable
 | 
			
		||||
     * @param Role $parentRole The role which should give access to $childRole
 | 
			
		||||
     * @return boolean true if the child role is granted by parent role
 | 
			
		||||
     */
 | 
			
		||||
    protected function isRoleReached($childRole, $parentRole)
 | 
			
		||||
    protected function isRoleReached(Role $childRole, Role $parentRole)
 | 
			
		||||
    {
 | 
			
		||||
        $reachableRoles = $this->roleHierarchy
 | 
			
		||||
                ->getReachableRoleNames([$parentRole]);
 | 
			
		||||
        
 | 
			
		||||
                ->getReachableRoles([$parentRole]);
 | 
			
		||||
 | 
			
		||||
        return in_array($childRole, $reachableRoles);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all the role which give access to the given role. Only the role 
 | 
			
		||||
     * Return all the role which give access to the given role. Only the role
 | 
			
		||||
     * which are registered into Chill are taken into account.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @return Role[] the role which give access to the given $role
 | 
			
		||||
     */
 | 
			
		||||
@@ -319,18 +291,18 @@ class AuthorizationHelper
 | 
			
		||||
        $roles = \array_map(
 | 
			
		||||
                function($string) {
 | 
			
		||||
                    return new Role($string);
 | 
			
		||||
                }, 
 | 
			
		||||
                },
 | 
			
		||||
                \array_keys($this->hierarchy)
 | 
			
		||||
                );
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        foreach ($roles as $r) {
 | 
			
		||||
            $childRoles = $this->roleHierarchy->getReachableRoleNames([$r->getRole()]);
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (\in_array($role, $childRoles)) {
 | 
			
		||||
                $parentRoles[] = $r;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return $parentRoles;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,87 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Security\Authorization;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\User;
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\MainBundle\Entity\HasCenterInterface;
 | 
			
		||||
use Symfony\Component\Security\Core\Role\Role;
 | 
			
		||||
use Chill\MainBundle\Entity\Scope;
 | 
			
		||||
 | 
			
		||||
interface AuthorizationHelperInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if a user is active on this center
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function userCanReachCenter(User $user, Center $center);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * Determines if the user has access to the given entity.
 | 
			
		||||
     *
 | 
			
		||||
     * if the entity implements Chill\MainBundle\Entity\HasScopeInterface,
 | 
			
		||||
     * the scope is taken into account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param HasCenterInterface $entity the entity may also implement HasScopeInterface
 | 
			
		||||
     * @param string|Role $attribute
 | 
			
		||||
     * @return boolean true if the user has access
 | 
			
		||||
     */
 | 
			
		||||
    public function userHasAccess(User $user, HasCenterInterface $entity, $attribute);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get reachable Centers for the given user, role,
 | 
			
		||||
     * and optionnaly Scope
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param null|Scope $scope
 | 
			
		||||
     * @return Center[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCenters(User $user, Role $role, Scope $scope = null);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all reachable scope for a given user, center and role
 | 
			
		||||
     *
 | 
			
		||||
     * @deprecated Use getReachableCircles
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableScopes(User $user, Role $role, Center $center);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all reachable circle for a given user, center and role
 | 
			
		||||
     *
 | 
			
		||||
     * @param User $user
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @return Scope[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getReachableCircles(User $user, Role $role, Center $center);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @param Center $center
 | 
			
		||||
     * @param Scope $circle
 | 
			
		||||
     * @return Users
 | 
			
		||||
     */
 | 
			
		||||
    public function findUsersReaching(Role $role, Center $center, Scope $circle = null);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return all the role which give access to the given role. Only the role
 | 
			
		||||
     * which are registered into Chill are taken into account.
 | 
			
		||||
     *
 | 
			
		||||
     * @param Role $role
 | 
			
		||||
     * @return Role[] the role which give access to the given $role
 | 
			
		||||
     */
 | 
			
		||||
    public function getParentRoles(Role $role);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Model;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Pagination\PaginatorInterface;
 | 
			
		||||
 | 
			
		||||
class Collection
 | 
			
		||||
{
 | 
			
		||||
    private PaginatorInterface $paginator;
 | 
			
		||||
 | 
			
		||||
    private $items;
 | 
			
		||||
 | 
			
		||||
    public function __construct($items, PaginatorInterface $paginator)
 | 
			
		||||
    {
 | 
			
		||||
        $this->items = $items;
 | 
			
		||||
        $this->paginator = $paginator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPaginator(): PaginatorInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->paginator;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getItems()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->items;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\Address;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
 | 
			
		||||
class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    use NormalizerAwareTrait;
 | 
			
		||||
 | 
			
		||||
    public function normalize($address, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        $data['address_id'] = $address->getId();
 | 
			
		||||
        $data['text'] = $address->getStreet().', '.$address->getBuildingName();
 | 
			
		||||
        $data['postcode']['name'] = $address->getPostCode()->getName();
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null)
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof Address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -20,32 +20,19 @@
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Entity\Center;
 | 
			
		||||
use Chill\MainBundle\Repository\CenterRepository;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
 | 
			
		||||
class CenterNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    private CenterRepository $repository;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function __construct(CenterRepository $repository)
 | 
			
		||||
    {
 | 
			
		||||
        $this->repository = $repository;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function normalize($center, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var Center $center */
 | 
			
		||||
        return [
 | 
			
		||||
            'id' => $center->getId(),
 | 
			
		||||
            'type' => 'center',
 | 
			
		||||
            'name' => $center->getName()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
@@ -54,30 +41,4 @@ class CenterNormalizer implements NormalizerInterface, DenormalizerInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof Center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        if (FALSE === \array_key_exists('type', $data)) {
 | 
			
		||||
            throw new InvalidArgumentException('missing "type" key in data');
 | 
			
		||||
        }
 | 
			
		||||
        if ('center' !== $data['type']) {
 | 
			
		||||
            throw new InvalidArgumentException('type should be equal to "center"');
 | 
			
		||||
        }
 | 
			
		||||
        if (FALSE === \array_key_exists('id', $data)) {
 | 
			
		||||
            throw new InvalidArgumentException('missing "id" key in data');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $center = $this->repository->find($data['id']);
 | 
			
		||||
 | 
			
		||||
        if (null === $center) {
 | 
			
		||||
            throw new UnexpectedValueException("The type with id {$data['id']} does not exists");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $center;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsDenormalization($data, string $type, string $format = null)
 | 
			
		||||
    {
 | 
			
		||||
        return $type === Center::class;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Serializer\Model\Collection;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerAwareTrait;
 | 
			
		||||
 | 
			
		||||
class CollectionNormalizer implements NormalizerInterface, NormalizerAwareInterface
 | 
			
		||||
{
 | 
			
		||||
    use NormalizerAwareTrait; 
 | 
			
		||||
 | 
			
		||||
    public function supportsNormalization($data, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof Collection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function normalize($collection, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        /** @var $collection Collection */
 | 
			
		||||
        /** @var $collection Chill\MainBundle\Pagination\PaginatorInterface */ 
 | 
			
		||||
        $paginator = $collection->getPaginator();
 | 
			
		||||
 | 
			
		||||
        $data['count'] = $paginator->getTotalItems();
 | 
			
		||||
        $pagination['first'] = $paginator->getCurrentPageFirstItemNumber();
 | 
			
		||||
        $pagination['items_per_page'] = $paginator->getItemsPerPage();
 | 
			
		||||
        $pagination['next']  = $paginator->hasNextPage() ? 
 | 
			
		||||
            $paginator->getNextPage()->generateUrl() : null;
 | 
			
		||||
        $pagination['previous'] = $paginator->hasPreviousPage() ?
 | 
			
		||||
            $paginator->getPreviousPage()->generateUrl() : null;
 | 
			
		||||
        $pagination['more'] = $paginator->hasNextPage();
 | 
			
		||||
        $data['pagination'] = $pagination;
 | 
			
		||||
 | 
			
		||||
        // normalize results
 | 
			
		||||
        $data['results'] = $this->normalizer->normalize($collection->getItems(),
 | 
			
		||||
            $format, $context);
 | 
			
		||||
 | 
			
		||||
        return $data;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,16 +20,15 @@
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 | 
			
		||||
 | 
			
		||||
class DateNormalizer implements NormalizerInterface, DenormalizerInterface
 | 
			
		||||
class DateNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    public function normalize($date, string $format = null, array $context = array())
 | 
			
		||||
    {
 | 
			
		||||
        /** @var \DateTimeInterface $date */
 | 
			
		||||
        return [
 | 
			
		||||
            'datetime' => $date->format(\DateTimeInterface::ISO8601)
 | 
			
		||||
            'datetime' => $date->format(\DateTimeInterface::ISO8601),
 | 
			
		||||
            'u' => $date->getTimestamp()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -37,24 +36,4 @@ class DateNormalizer implements NormalizerInterface, DenormalizerInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $data instanceof \DateTimeInterface;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        switch ($type) {
 | 
			
		||||
            case \DateTime::class:
 | 
			
		||||
                return \DateTime::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']);
 | 
			
		||||
            case \DateTimeInterface::class:
 | 
			
		||||
            case \DateTimeImmutable::class:
 | 
			
		||||
            default:
 | 
			
		||||
                return \DateTimeImmutable::createFromFormat(\DateTimeInterface::ISO8601, $data['datetime']);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsDenormalization($data, string $type, string $format = null): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $type === \DateTimeInterface::class ||
 | 
			
		||||
            $type === \DateTime::class ||
 | 
			
		||||
            $type === \DateTimeImmutable::class ||
 | 
			
		||||
            (\is_array($data) && array_key_exists('datetime', $data));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,71 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\RuntimeException;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Denormalize an object given a list of supported class
 | 
			
		||||
 */
 | 
			
		||||
class DiscriminatedObjectDenormalizer implements ContextAwareDenormalizerInterface, DenormalizerAwareInterface
 | 
			
		||||
{
 | 
			
		||||
    use DenormalizerAwareTrait;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The type to set for enabling this type
 | 
			
		||||
     */
 | 
			
		||||
    public const TYPE = '@multi';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Should be present in context and contains an array of
 | 
			
		||||
     * allowed types. 
 | 
			
		||||
     */
 | 
			
		||||
    public const ALLOWED_TYPES = 'denormalize_multi.allowed_types';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($context[self::ALLOWED_TYPES] as $localType) {
 | 
			
		||||
            if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
 | 
			
		||||
                try {
 | 
			
		||||
                    return $this->denormalizer->denormalize($data, $localType, $format, $context);               } catch (RuntimeException $e) {
 | 
			
		||||
                    $lastException = $e;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new RuntimeException(sprintf("Could not find any denormalizer for those ".
 | 
			
		||||
            "ALLOWED_TYPES: %s", \implode(", ", $context[self::ALLOWED_TYPES])));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function supportsDenormalization($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        if (self::TYPE !== $type) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (0 === count($context[self::ALLOWED_TYPES] ?? [])) {
 | 
			
		||||
            throw new \LogicException("The context should contains a list of 
 | 
			
		||||
                allowed types");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        foreach ($context[self::ALLOWED_TYPES] as $localType) {
 | 
			
		||||
            if ($this->denormalizer->supportsDenormalization($data, $localType, $format)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@@ -1,71 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
use Doctrine\ORM\EntityManagerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
 | 
			
		||||
use Doctrine\ORM\Mapping\ClassMetadata;
 | 
			
		||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface as SerializerMetadata;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DoctrineExistingEntityNormalizer implements DenormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
    private EntityManagerInterface $em;
 | 
			
		||||
 | 
			
		||||
    private ClassMetadataFactoryInterface $serializerMetadataFactory;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public function __construct(EntityManagerInterface $em, ClassMetadataFactoryInterface $serializerMetadataFactory)
 | 
			
		||||
    {
 | 
			
		||||
        $this->em = $em;
 | 
			
		||||
        $this->serializerMetadataFactory = $serializerMetadataFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
        if (\array_key_exists(AbstractNormalizer::OBJECT_TO_POPULATE, $context)) {
 | 
			
		||||
            return $context[AbstractNormalizer::OBJECT_TO_POPULATE];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->em->getRepository($type)
 | 
			
		||||
            ->find($data['id']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function supportsDenormalization($data, string $type, string $format = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (FALSE === \is_array($data)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (FALSE === \array_key_exists('id', $data)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        } 
 | 
			
		||||
 | 
			
		||||
        if (FALSE === $this->em->getClassMetadata($type) instanceof ClassMetadata) {
 | 
			
		||||
            return false; 
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // does have serializer metadata, and class discriminator ?
 | 
			
		||||
        if ($this->serializerMetadataFactory->hasMetadataFor($type)) {
 | 
			
		||||
 | 
			
		||||
            $classDiscriminator = $this->serializerMetadataFactory
 | 
			
		||||
                ->getMetadataFor($type)->getClassDiscriminatorMapping();
 | 
			
		||||
 | 
			
		||||
            if ($classDiscriminator) {
 | 
			
		||||
                $typeProperty = $classDiscriminator->getTypeProperty();
 | 
			
		||||
 | 
			
		||||
                // check that only 2 keys
 | 
			
		||||
                // that the second key is property
 | 
			
		||||
                // and that the type match the class for given type property 
 | 
			
		||||
                return count($data) === 2
 | 
			
		||||
                    && \array_key_exists($typeProperty, $data) 
 | 
			
		||||
                    && $type === $classDiscriminator->getClassForType($data[$typeProperty]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // we do not have any class discriminator. Check that the id is the only one key
 | 
			
		||||
        return count($data) === 1;
 | 
			
		||||
    }
 | 
			
		||||
}    
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Serializer\Normalizer;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
 | 
			
		||||
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
 | 
			
		||||
 | 
			
		||||
use Chill\MainBundle\Doctrine\Model\Point;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PointNormalizer implements DenormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    public function supportsDenormalization($data, string $type, string $format = null) : bool
 | 
			
		||||
    {
 | 
			
		||||
        return $type === Point::class;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function denormalize($data, string $type, string $format = null, array $context = [])
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if (!is_array($data)) {
 | 
			
		||||
            throw new InvalidArgumentException('point data is not an array. It should be an array of 2 coordinates.');
 | 
			
		||||
        } else {
 | 
			
		||||
            if (count($data) !== 2) {
 | 
			
		||||
                throw new InvalidArgumentException('point data is not an array of 2 elements. It should be an array of 2 coordinates.');
 | 
			
		||||
            } else {
 | 
			
		||||
                return Point::fromLonLat($data[0], $data[1]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -24,7 +24,7 @@ use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @internal we keep this normalizer, because the property 'text' may be replace by a rendering in the future
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class UserNormalizer implements NormalizerInterface
 | 
			
		||||
{
 | 
			
		||||
@@ -32,10 +32,8 @@ class UserNormalizer implements NormalizerInterface
 | 
			
		||||
    {
 | 
			
		||||
        /** @var User $user */
 | 
			
		||||
        return [
 | 
			
		||||
            'type' => 'user',
 | 
			
		||||
            'id' => $user->getId(),
 | 
			
		||||
            'username' => $user->getUsername(),
 | 
			
		||||
            'text' => $user->getUsername()
 | 
			
		||||
            'username' => $user->getUsername()
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Templating\Entity;
 | 
			
		||||
 | 
			
		||||
use Symfony\Component\Templating\EngineInterface;
 | 
			
		||||
use Chill\MainBundle\Entity\Address;
 | 
			
		||||
 | 
			
		||||
class AddressRender implements ChillEntityRenderInterface
 | 
			
		||||
{
 | 
			
		||||
    private EngineInterface $templating;
 | 
			
		||||
 | 
			
		||||
    public const DEFAULT_OPTIONS = [
 | 
			
		||||
        'with_valid_from' => true,
 | 
			
		||||
        'has_no_address' => false,
 | 
			
		||||
        'multiline' => true,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function __construct(EngineInterface $templating)
 | 
			
		||||
    {
 | 
			
		||||
        $this->templating = $templating;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function supports($entity, array $options): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $entity instanceof Address;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Address addr
 | 
			
		||||
     */
 | 
			
		||||
    public function renderString($addr, array $options): string
 | 
			
		||||
    {
 | 
			
		||||
        $lines = [];
 | 
			
		||||
        if (!empty($addr->getStreet())) {
 | 
			
		||||
            $lines[0] = $addr->getStreet();
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($addr->getStreetNumber())) {
 | 
			
		||||
            $lines[0] .= ", ".$addr->getStreetNumber();
 | 
			
		||||
        }
 | 
			
		||||
        if (!empty($addr->getPostcode())) {
 | 
			
		||||
            $lines[1] = \strtr("{postcode} {label}", [
 | 
			
		||||
                '{postcode}' => $addr->getPostcode()->getCode(),
 | 
			
		||||
                '{label}' => $addr->getPostcode()->getName()
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return implode(" - ", $lines);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     * @param Address addr
 | 
			
		||||
     */
 | 
			
		||||
    public function renderBox($addr, array $options): string
 | 
			
		||||
    {
 | 
			
		||||
        $options = \array_merge(self::DEFAULT_OPTIONS, $options); 
 | 
			
		||||
 | 
			
		||||
        return $this->templating
 | 
			
		||||
            ->render('@ChillMain/Address/entity_render.html.twig', [
 | 
			
		||||
                'address' => $addr,
 | 
			
		||||
                'options' => $options
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Chill is a software for social workers
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2014-2019, Champs Libres Cooperative SCRLFS, 
 | 
			
		||||
 * Copyright (C) 2014-2019, Champs Libres Cooperative SCRLFS,
 | 
			
		||||
 * <http://www.champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software: you can redistribute it and/or modify
 | 
			
		||||
@@ -28,18 +28,18 @@ use Twig\TwigFilter;
 | 
			
		||||
 *
 | 
			
		||||
 * @package Chill\MainBundle\Templating\Entity
 | 
			
		||||
 */
 | 
			
		||||
class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
class ChillEntityRenderExtension extends AbstractExtension implements ChillEntityRenderExtensionInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ChillEntityRenderInterface 
 | 
			
		||||
     * @var ChillEntityRenderInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected $renders = [];
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @var ChillEntityRender
 | 
			
		||||
     */
 | 
			
		||||
    protected $defaultRender;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * ChillEntityRenderExtension constructor.
 | 
			
		||||
     */
 | 
			
		||||
@@ -47,7 +47,7 @@ class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
    {
 | 
			
		||||
        $this->defaultRender = new ChillEntityRender();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array|TwigFilter[]
 | 
			
		||||
     */
 | 
			
		||||
@@ -62,7 +62,7 @@ class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
            ])
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $entity
 | 
			
		||||
     * @param array $options
 | 
			
		||||
@@ -76,7 +76,7 @@ class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
        return $this->getRender($entity, $options)
 | 
			
		||||
            ->renderString($entity, $options);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $entity
 | 
			
		||||
     * @param array $options
 | 
			
		||||
@@ -90,7 +90,7 @@ class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
        return $this->getRender($entity, $options)
 | 
			
		||||
            ->renderBox($entity, $options);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ChillEntityRenderInterface $render
 | 
			
		||||
     */
 | 
			
		||||
@@ -98,7 +98,7 @@ class ChillEntityRenderExtension extends AbstractExtension
 | 
			
		||||
    {
 | 
			
		||||
        $this->renders[] = $render;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $entity
 | 
			
		||||
     * @param $options
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,33 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Chill\MainBundle\Templating\Entity;
 | 
			
		||||
 | 
			
		||||
use Twig\Extension\ExtensionInterface;
 | 
			
		||||
use Twig\TwigFilter;
 | 
			
		||||
 | 
			
		||||
interface ChillEntityRenderExtensionInterface extends ExtensionInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array|TwigFilter[]
 | 
			
		||||
     */
 | 
			
		||||
    public function getFilters();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $entity
 | 
			
		||||
     * @param array $options
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function renderString($entity, array $options = []): string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param $entity
 | 
			
		||||
     * @param array $options
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function renderBox($entity, array $options = []): string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ChillEntityRenderInterface $render
 | 
			
		||||
     */
 | 
			
		||||
    public function addRender(ChillEntityRenderInterface $render);
 | 
			
		||||
}
 | 
			
		||||
@@ -23,36 +23,36 @@ use Symfony\Component\HttpFoundation\RequestStack;
 | 
			
		||||
use Symfony\Component\Translation\Translator;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * This helper helps to find the string in current locale from translatable_strings
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * @author Julien Fastré <julien.fastre@champs-libres.coop>
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
class TranslatableStringHelper
 | 
			
		||||
class TranslatableStringHelper implements TranslatableStringHelperInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @var RequestStack
 | 
			
		||||
     */
 | 
			
		||||
    private $requestStack;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private $fallbackLocales;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    public function __construct(RequestStack $requestStack, Translator $translator)
 | 
			
		||||
    {
 | 
			
		||||
        $this->requestStack = $requestStack;
 | 
			
		||||
        $this->fallbackLocales = $translator->getFallbackLocales();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * return the string in current locale if it exists.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * If it does not exists; return the name in the first language available.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Return a blank string if any strings are available.
 | 
			
		||||
     * Return NULL if $translatableString is NULL
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $translatableStrings
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
@@ -61,32 +61,32 @@ class TranslatableStringHelper
 | 
			
		||||
        if (NULL === $translatableStrings) {
 | 
			
		||||
            return NULL;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        $language = $this->requestStack->getCurrentRequest()->getLocale();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (isset($translatableStrings[$language])) {
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            return $translatableStrings[$language];
 | 
			
		||||
        } else {
 | 
			
		||||
            foreach ($this->fallbackLocales as $locale) {
 | 
			
		||||
                if (array_key_exists($locale, $translatableStrings)) {
 | 
			
		||||
                    
 | 
			
		||||
 | 
			
		||||
                    return $translatableStrings[$locale];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        // no fallback translation... trying the  first available
 | 
			
		||||
        $langs = array_keys($translatableStrings);
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        if (count($langs) === 0) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
        return $translatableStrings[$langs[0]];
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user