mirror of
				https://gitlab.com/Chill-Projet/chill-bundles.git
				synced 2025-10-31 01:08:26 +00:00 
			
		
		
		
	Merge branch 'master' of gitlab.com:Chill-Projet/chill-bundles
This commit is contained in:
		| @@ -22,7 +22,8 @@ | ||||
|             "Chill\\ThirdPartyBundle\\": "src/Bundle/ChillThirdPartyBundle", | ||||
|             "Chill\\AsideActivityBundle\\": "src/Bundle/ChillAsideActivityBundle/src", | ||||
|             "Chill\\DocGeneratorBundle\\": "src/Bundle/ChillDocGeneratorBundle", | ||||
|             "Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle" | ||||
|             "Chill\\CalendarBundle\\": "src/Bundle/ChillCalendarBundle", | ||||
|             "Chill\\WopiBundle\\": "src/Bundle/ChillWopiBundle/src" | ||||
|         } | ||||
|     }, | ||||
|     "autoload-dev": { | ||||
| @@ -30,8 +31,12 @@ | ||||
|             "App\\": "tests/app/src/" | ||||
|         } | ||||
|     }, | ||||
|     "minimum-stability": "dev", | ||||
|     "prefer-stable": true, | ||||
|     "require": { | ||||
|         "champs-libres/async-uploader-bundle": "dev-sf4", | ||||
|         "champs-libres/wopi-bundle": "dev-master", | ||||
|         "nyholm/psr7": "^1.4", | ||||
|         "graylog2/gelf-php": "^1.5", | ||||
|         "symfony/form": "4.*", | ||||
|         "symfony/twig-bundle": "^4.4", | ||||
|   | ||||
| @@ -87,7 +87,8 @@ class CalendarController extends AbstractController | ||||
|             // $view = 'ChillCalendarBundle:Calendar:listByUser.html.twig'; | ||||
|         } elseif ($accompanyingPeriod instanceof AccompanyingPeriod) { | ||||
|             $calendarItems = $em->getRepository(Calendar::class)->findBy( | ||||
|                 ['accompanyingPeriod' => $accompanyingPeriod] | ||||
|                 ['accompanyingPeriod' => $accompanyingPeriod], | ||||
|                 ['startDate' => 'DESC'] | ||||
|             ); | ||||
|  | ||||
|             $view = 'ChillCalendarBundle:Calendar:listByAccompanyingCourse.html.twig'; | ||||
| @@ -139,10 +140,9 @@ class CalendarController extends AbstractController | ||||
|  | ||||
|             $this->addFlash('success', $this->get('translator')->trans('Success : calendar item created!')); | ||||
|  | ||||
|             $params = $this->buildParamsToUrl($user, $accompanyingPeriod); //TODO useful? | ||||
|             $params['id'] = $entity->getId(); | ||||
|             $params = $this->buildParamsToUrl($user, $accompanyingPeriod); | ||||
|  | ||||
|             return $this->redirectToRoute('chill_calendar_calendar_show', $params); | ||||
|             return $this->redirectToRoute('chill_calendar_calendar', $params); | ||||
|         } elseif ($form->isSubmitted() and !$form->isValid()) { | ||||
|             $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); | ||||
|         } | ||||
| @@ -241,8 +241,7 @@ class CalendarController extends AbstractController | ||||
|             $this->addFlash('success', $this->get('translator')->trans('Success : calendar item updated!')); | ||||
|  | ||||
|             $params = $this->buildParamsToUrl($user, $accompanyingPeriod); | ||||
|             $params['id'] = $id; | ||||
|             return $this->redirectToRoute('chill_calendar_calendar_show', $params); | ||||
|             return $this->redirectToRoute('chill_calendar_calendar', $params); | ||||
|         } elseif ($form->isSubmitted() and !$form->isValid()) { | ||||
|             $this->addFlash('error', $this->get('translator')->trans('This form contains errors')); | ||||
|         } | ||||
| @@ -304,7 +303,7 @@ class CalendarController extends AbstractController | ||||
|                 $em->flush(); | ||||
|  | ||||
|                 $this->addFlash('success', $this->get('translator') | ||||
|                       ->trans("The calendar has been successfully removed.")); | ||||
|                       ->trans("The calendar item has been successfully removed.")); | ||||
|  | ||||
|                 $params = $this->buildParamsToUrl($user, $accompanyingPeriod); | ||||
|                 return $this->redirectToRoute('chill_calendar_calendar', $params); | ||||
|   | ||||
| @@ -48,13 +48,13 @@ class CalendarType extends AbstractType | ||||
|             ->add('comment', CommentType::class, [ | ||||
|                 'required' => false | ||||
|             ]) | ||||
|             ->add('cancelReason', EntityType::class, [ | ||||
|                 'required' => false, | ||||
|                 'class' => CancelReason::class, | ||||
|                 'choice_label' => function (CancelReason $entity) { | ||||
|                     return $entity->getCanceledBy(); | ||||
|                 }, | ||||
|             ]) | ||||
|             // ->add('cancelReason', EntityType::class, [ | ||||
|             //     'required' => false, | ||||
|             //     'class' => CancelReason::class, | ||||
|             //     'choice_label' => function (CancelReason $entity) { | ||||
|             //         return $entity->getCanceledBy(); | ||||
|             //     }, | ||||
|             // ]) | ||||
|             ->add('sendSMS', ChoiceType::class, [ | ||||
|                 'required' => false, | ||||
|                 'choices' => [ | ||||
|   | ||||
| @@ -0,0 +1 @@ | ||||
| require('./scss/calendar.scss'); | ||||
| @@ -0,0 +1,10 @@ | ||||
| div#calendarControls { | ||||
|     height: 50%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: flex-end; | ||||
| } | ||||
|  | ||||
| div#fullCalendar{ | ||||
|  | ||||
| } | ||||
| @@ -1,39 +1,35 @@ | ||||
| <template> | ||||
|   <concerned-groups></concerned-groups> | ||||
|   <calendar-user-selector  | ||||
|     v-bind:users="users" | ||||
|     v-bind:calendarEvents="calendarEvents"  | ||||
|     v-bind:updateEventsSource="updateEventsSource" | ||||
|     v-bind:showMyCalendar="showMyCalendar" | ||||
|     v-bind:toggleMyCalendar="toggleMyCalendar" > | ||||
|   </calendar-user-selector> | ||||
|   <h2 class="chill-red">{{ $t('choose_your_date') }}</h2> | ||||
|   <FullCalendar ref="fullCalendar" :options="calendarOptions"> | ||||
|     <template v-slot:eventContent='arg'> | ||||
|       <b>{{ arg.timeText }}</b> | ||||
|     </template> | ||||
|   </FullCalendar> | ||||
|   <teleport to="#calendarControls"> | ||||
|     <calendar-user-selector  | ||||
|       v-bind:users="users" | ||||
|       v-bind:calendarEvents="calendarEvents"  | ||||
|       v-bind:updateEventsSource="updateEventsSource" | ||||
|       v-bind:showMyCalendar="showMyCalendar" | ||||
|       v-bind:toggleMyCalendar="toggleMyCalendar" | ||||
|       v-bind:toggleWeekends="toggleWeekends" > | ||||
|     </calendar-user-selector> | ||||
|   </teleport> | ||||
|   <teleport to="#fullCalendar"> | ||||
|     <FullCalendar ref="fullCalendar" :options="calendarOptions"> | ||||
|       <template v-slot:eventContent='arg'> | ||||
|         <b>{{ arg.timeText }}</b> | ||||
|         <i> {{ arg.event.title }}</i> | ||||
|       </template> | ||||
|     </FullCalendar> | ||||
|   </teleport> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import ConcernedGroups from 'ChillActivityAssets/vuejs/Activity/components/ConcernedGroups.vue'; | ||||
| import CalendarUserSelector from '../_components/CalendarUserSelector/CalendarUserSelector.vue'; | ||||
| import '@fullcalendar/core/vdom' // solves problem with Vite | ||||
| import '@fullcalendar/core/vdom'; // solves problem with Vite | ||||
| import frLocale from '@fullcalendar/core/locales/fr'; | ||||
| import FullCalendar from '@fullcalendar/vue3' | ||||
| import dayGridPlugin from '@fullcalendar/daygrid' | ||||
| import interactionPlugin from '@fullcalendar/interaction' | ||||
| import timeGridPlugin from '@fullcalendar/timegrid' | ||||
| import listPlugin from '@fullcalendar/list'; | ||||
|  | ||||
| const currentEvent = { | ||||
|    events: [{ | ||||
|       title: 'my_event', | ||||
|       start: window.startDate, | ||||
|       end: window.endDate | ||||
|    }], | ||||
|    id: window.mainUser | ||||
| }; | ||||
| import FullCalendar from '@fullcalendar/vue3'; | ||||
| import dayGridPlugin from '@fullcalendar/daygrid'; | ||||
| import interactionPlugin from '@fullcalendar/interaction'; | ||||
| import timeGridPlugin from '@fullcalendar/timegrid'; | ||||
| // import listPlugin from '@fullcalendar/list'; | ||||
|  | ||||
| export default { | ||||
|   name: "App", | ||||
| @@ -54,12 +50,23 @@ export default { | ||||
|          loaded: [], | ||||
|          selected: [], | ||||
|          user: [], | ||||
|          current: currentEvent | ||||
|          current: { | ||||
|             events: [{ | ||||
|                 title: 'plage prévue', | ||||
|                 start: window.startDate, | ||||
|                 end: window.endDate | ||||
|             }], | ||||
|             id: window.mainUser, | ||||
|             color: '#bbbbbb' | ||||
|           } | ||||
|       }, | ||||
|       showMyCalendar: true, | ||||
|       selectedEvent: null, | ||||
|       previousSelectedEvent: null, | ||||
|       previousSelectedEventColor: null, | ||||
|       showMyCalendar: false, | ||||
|       calendarOptions: { | ||||
|         locale: frLocale, | ||||
|         plugins: [ dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin ], | ||||
|         plugins: [ dayGridPlugin, interactionPlugin, timeGridPlugin ], | ||||
|         initialView: 'timeGridWeek', | ||||
|         initialDate: window.startDate !== undefined ? window.startDate : new Date(), | ||||
|         eventSource: [], | ||||
| @@ -71,6 +78,7 @@ export default { | ||||
|       //   eventMouseLeave: this.onEventMouseLeave, | ||||
|         selectMirror: true, | ||||
|         editable: true, | ||||
|         weekends: false, | ||||
|         headerToolbar: { | ||||
|           left: 'prev,next today', | ||||
|           center: 'title', | ||||
| @@ -81,36 +89,54 @@ export default { | ||||
|   }, | ||||
|   methods: { | ||||
|     init() { | ||||
|       console.log(window.startDate) | ||||
|       this.updateEventsSource() | ||||
|       this.updateEventsSource(); | ||||
|     }, | ||||
|     toggleMyCalendar(value) { | ||||
|       this.showMyCalendar = value; | ||||
|     }, | ||||
|     toggleWeekends: function() { | ||||
|       this.calendarOptions.weekends = !this.calendarOptions.weekends; | ||||
|     }, | ||||
|     updateEventsSource() { | ||||
|       this.calendarOptions.eventSources = []; | ||||
|       this.calendarOptions.eventSources.push(...this.calendarEvents.selected); | ||||
|       console.log(this.calendarOptions.eventSources) | ||||
|       if (window.startDate !== undefined) { | ||||
|          this.calendarOptions.eventSources.push(currentEvent); | ||||
|          this.calendarOptions.eventSources.push(this.calendarEvents.current); | ||||
|       } | ||||
|       if (this.showMyCalendar) { | ||||
|           this.calendarOptions.eventSources.push(this.calendarEvents.user); | ||||
|       } | ||||
|       console.log(this.calendarOptions.eventSources) | ||||
|     }, | ||||
|     unSelectPreviousEvent(event) { | ||||
|       if (event) { | ||||
|         if (typeof event.setProp === 'function') { | ||||
|           event.setProp('backgroundColor', this.previousSelectedEventColor); | ||||
|           event.setProp('borderColor', this.previousSelectedEventColor); | ||||
|           event.setProp('textColor','#444444'); | ||||
|           event.setProp('title',''); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     onDateSelect(payload) { | ||||
|       console.log(payload) | ||||
|       this.unSelectPreviousEvent(this.selectedEvent); | ||||
|       Object.assign(payload, {users: this.users}); | ||||
|       Object.assign(payload, {title: 'Choisir cette plage'}); //TODO does not display | ||||
|       //payload.event.setProp('title', 'Choisir cette plage'); | ||||
|       this.$store.dispatch('createEvent', payload); | ||||
|     }, | ||||
|     onEventChange(payload) { | ||||
|       console.log(this.calendarOptions.eventSources) | ||||
|       this.$store.dispatch('updateEvent', payload); | ||||
|     }, | ||||
|     onEventClick(payload) { | ||||
|       this.previousSelectedEvent = this.selectedEvent; | ||||
|       this.previousSelectedEventColor = payload.event.extendedProps.sourceColor; | ||||
|       this.selectedEvent = payload.event; | ||||
|       this.unSelectPreviousEvent(this.previousSelectedEvent); | ||||
|       payload.event.setProp('backgroundColor','#3788d8'); | ||||
|       payload.event.setProp('borderColor','#3788d8'); | ||||
|       payload.event.setProp('title', 'Choisir cette plage'); | ||||
|       payload.event.setProp('textColor','#ffffff'); | ||||
|       //this.$store.dispatch('updateEvent', payload); | ||||
|     }, | ||||
|     onEventMouseEnter(payload) { | ||||
|       payload.event.setProp('borderColor','#444444'); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <template> | ||||
|   <h2 class="chill-red">{{ $t('choose_your_calendar_user') }}</h2> | ||||
|   <div> | ||||
|   <div class="calendar__controls"> | ||||
|      <h2 class="chill-red">{{ $t('choose_your_calendar_user') }}</h2> | ||||
|     <VueMultiselect | ||||
|         name="field" | ||||
|         id="calendarUserSelector" | ||||
| @@ -23,6 +23,11 @@ | ||||
|     <input type="checkbox" id="myCalendar" class="form-check-input" v-model="showMyCalendarWidget" /> | ||||
|     <label class="form-check-label" for="myCalendar">{{ $t('show_my_calendar') }}</label> | ||||
|   </div> | ||||
|   <div class="form-check"> | ||||
|     <input type="checkbox" id="weekends" class="form-check-input" @click="toggleWeekends" /> | ||||
|     <label class="form-check-label" for="weekends">{{ $t('show_weekends') }}</label> | ||||
|   </div> | ||||
|    | ||||
| </template> | ||||
| <script> | ||||
|  | ||||
| @@ -48,7 +53,7 @@ const COLORS = [ /* from https://colorbrewer2.org/#type=qualitative&scheme=Set3& | ||||
| export default { | ||||
|   name: 'CalendarUserSelector', | ||||
|   components: { VueMultiselect }, | ||||
|   props: ['users', 'updateEventsSource', 'calendarEvents', 'showMyCalendar', 'toggleMyCalendar'], | ||||
|   props: ['users', 'updateEventsSource', 'calendarEvents', 'showMyCalendar', 'toggleMyCalendar', 'toggleWeekends'], | ||||
|   data() { | ||||
|     return { | ||||
|       errorMsg: [], | ||||
| @@ -95,7 +100,8 @@ export default { | ||||
|               ({ | ||||
|                 start: i.startDate.datetime, | ||||
|                 end: i.endDate.datetime, | ||||
|                 calendarRangeId: i.id | ||||
|                 calendarRangeId: i.id, | ||||
|                 sourceColor: u.color | ||||
|                 //display: 'background' // can be an option for the disponibility  | ||||
|               }) | ||||
|             ); | ||||
| @@ -103,15 +109,13 @@ export default { | ||||
|               events: arr, | ||||
|               color: u.color, | ||||
|               textColor: '#444444', | ||||
|               editable: false, | ||||
|               id: u.id | ||||
|             }) | ||||
|           }) | ||||
|  | ||||
|           this.users.loaded = users; | ||||
|           this.options = users; | ||||
|  | ||||
|           console.log(users) | ||||
|           console.log(calendarEvents) | ||||
|            | ||||
|           this.calendarEvents.loaded = calendarEvents; | ||||
|           whoami().then(me => new Promise((resolve, reject) => { | ||||
| @@ -130,7 +134,8 @@ export default { | ||||
|               let calendarEventsCurrentUser = { | ||||
|                   events: events, | ||||
|                   color: 'darkblue', | ||||
|                   id: 1000 | ||||
|                   id: 1000, | ||||
|                   editable: false | ||||
|               }; | ||||
|               this.calendarEvents.user = calendarEventsCurrentUser; | ||||
|  | ||||
| @@ -196,3 +201,12 @@ export default { | ||||
|  | ||||
| <style src="vue-multiselect/dist/vue-multiselect.css"></style> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
|   div.calendar__controls { | ||||
|     background-color: 'black'; | ||||
|     height: 50%; | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
|     justify-content: flex-end; | ||||
|   } | ||||
| </style> | ||||
|   | ||||
| @@ -2,7 +2,8 @@ const calendarUserSelectorMessages = { | ||||
|     fr: { | ||||
|       choose_your_calendar_user: "Afficher les plages de disponibilités", | ||||
|       select_user: "Sélectionnez des calendriers", | ||||
|       show_my_calendar: "Affichez mon calendrier" | ||||
|       show_my_calendar: "Afficher mon calendrier", | ||||
|       show_weekends: "Afficher les week-ends" | ||||
|     } | ||||
|  }; | ||||
|   | ||||
|   | ||||
| @@ -40,10 +40,6 @@ | ||||
|  | ||||
| .. location | ||||
|  | ||||
| {%- if form.cancelReason is defined -%} | ||||
|     {{ form_row(form.cancelReason) }} | ||||
| {% endif %} | ||||
|  | ||||
| {%- if form.comment is defined -%} | ||||
|     {{ form_row(form.comment) }} | ||||
| {% endif %} | ||||
| @@ -52,8 +48,7 @@ | ||||
|     {{ form_row(form.sendSMS) }} | ||||
| {% endif %} | ||||
|  | ||||
|  | ||||
| ..calendarRange | ||||
| <div id="fullCalendar"></div> | ||||
|  | ||||
| <ul class="record_actions sticky-form-buttons"> | ||||
|   <li class="cancel"> | ||||
|   | ||||
| @@ -36,3 +36,7 @@ | ||||
|     {{ parent() }} | ||||
|     {{ encore_entry_link_tags('vue_calendar') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block block_post_menu %} | ||||
|     <div id="calendarControls"></div> | ||||
| {% endblock %} | ||||
| @@ -24,22 +24,25 @@ | ||||
|         <div class="item-bloc"> | ||||
|             <div class="item-row main"> | ||||
|                 <div class="item-col"> | ||||
|                                          | ||||
|                     {% if calendar.startDate %} | ||||
|                         <h3>{{ calendar.startDate|format_datetime('long') }} </h3> | ||||
|                     {% endif %} | ||||
|                     | ||||
|                     {% if calendar.endDate %} | ||||
|                         <h3>{{ calendar.endDate|format_datetime('long') }}</h3> | ||||
|                     {% endif %} | ||||
|                      | ||||
|  | ||||
|                     <div class="duration"> | ||||
|                         <p> | ||||
|                             <i class="fa fa-fw fa-hourglass-end"></i> | ||||
|                             {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}} | ||||
|                         </p> | ||||
|                     </div> | ||||
|  | ||||
|                     {% if calendar.startDate and calendar.endDate %} | ||||
|                         {% if calendar.endDate.diff(calendar.startDate).days >= 1 %} | ||||
|                             <h3>{{ "From the day"|trans }} {{ calendar.startDate|format_datetime('medium', 'short') }} </h3> | ||||
|                             <h3>{{ "to the day"|trans }} {{ calendar.endDate|format_datetime('medium', 'short') }}</h3> | ||||
|                         {% else %} | ||||
|                             <h3>{{ calendar.startDate|format_date('full') }} </h3> | ||||
|                             <h3>{{ calendar.startDate|format_datetime('none', 'short', locale='fr') }} - {{ calendar.endDate|format_datetime('none', 'short', locale='fr') }}</h3> | ||||
|  | ||||
|                             <div class="duration"> | ||||
|                                 <p> | ||||
|                                     <i class="fa fa-fw fa-hourglass-end"></i> | ||||
|                                     {{ calendar.endDate.diff(calendar.startDate)|date("%H:%M")}} | ||||
|                                 </p> | ||||
|                             </div> | ||||
|                         {% endif %} | ||||
|  | ||||
|                     {% endif %} | ||||
|  | ||||
|                     {% if context == 'user' and calendar.accompanyingPeriod is not empty %} | ||||
|                       <div class="accompanyingPeriodLink" style="margin-top: 1rem"> | ||||
| @@ -66,7 +69,7 @@ | ||||
|                          | ||||
|                         {% if calendar.mainUser is not empty %} | ||||
|                         <li> | ||||
|                             <b>{{ 'main user concerned'|trans }}{{ calendar.mainUser.usernameCanonical }}</b> | ||||
|                             <b>{{ 'main user concerned'|trans }}: {{ calendar.mainUser.usernameCanonical }}</b> | ||||
|                         </li> | ||||
|                         {% endif %} | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <h1>{{ "Calendar item creation"|trans ~ ' :' }}</h1> | ||||
| <h1>{{ "Calendar item creation"|trans }}</h1> | ||||
|  | ||||
| {{ form_start(form) }} | ||||
| {{ form_errors(form) }} | ||||
| @@ -34,16 +34,8 @@ | ||||
|     {{ form_row(form.endDate) }} | ||||
| {% endif %} | ||||
|  | ||||
| {%- if form.calendarRange is defined -%} | ||||
|     {{ form_row(form.calendarRange) }} | ||||
| {% endif %} | ||||
|  | ||||
| .. location | ||||
|  | ||||
| {%- if form.cancelReason is defined -%} | ||||
|     {{ form_row(form.cancelReason) }} | ||||
| {% endif %} | ||||
|  | ||||
| {%- if form.comment is defined -%} | ||||
|     {{ form_row(form.comment) }} | ||||
| {% endif %} | ||||
| @@ -52,19 +44,16 @@ | ||||
|     {{ form_row(form.sendSMS) }} | ||||
| {% endif %} | ||||
|  | ||||
|  | ||||
| ..calendarRange | ||||
|  | ||||
|  | ||||
| <div id="fullCalendar"></div> | ||||
|  | ||||
| <ul class="record_actions sticky-form-buttons"> | ||||
|   <li class="cancel"> | ||||
|     <a  | ||||
|       class="btn btn-cancel" | ||||
|       {%- if context == 'person' -%}  | ||||
|       href="{{ chill_return_path_or('chill_activity_activity_list', { 'person_id': person.id } )}}" | ||||
|       href="{{ chill_return_path_or('chill_calendar_calendar', { 'person_id': person.id } )}}" | ||||
|       {%- else -%} | ||||
|       href="{{ chill_return_path_or('chill_activity_activity_list', { 'accompanying_period_id': accompanyingCourse.id } )}}" | ||||
|       href="{{ chill_return_path_or('chill_calendar_calendar', { 'accompanying_period_id': accompanyingCourse.id } )}}" | ||||
|       {%- endif -%} | ||||
|       > | ||||
|       {{ 'Cancel'|trans|chill_return_path_label }} | ||||
|   | ||||
| @@ -34,4 +34,6 @@ | ||||
|     <link rel="stylesheet" href="{{ asset('build/vue_calendar.css') }}"/> | ||||
| {% endblock %} | ||||
|  | ||||
|  | ||||
| {% block block_post_menu %} | ||||
|     <div id="calendarControls"></div> | ||||
| {% endblock %} | ||||
|   | ||||
| @@ -11,11 +11,18 @@ | ||||
|  | ||||
| <h2 class="chill-red">{{ 'Calendar data'|trans }}</h2> | ||||
|  | ||||
|     <dt class="inline">{{ 'start date'|trans }}</dt> | ||||
|     <dd>{{ entity.startDate|format_datetime('long') }}</dd> | ||||
|  | ||||
|     <dt class="inline">{{ 'end date'|trans }}</dt> | ||||
|     <dd>{{ entity.endDate|format_datetime('long') }}</dd> | ||||
|     {% if entity.endDate.diff(entity.startDate).days >= 1 %} | ||||
|         <dt>{{ "From the day"|trans }} {{ entity.startDate|format_datetime('medium', 'short') }} | ||||
|         {{ "to the day"|trans }} {{ entity.endDate|format_datetime('medium', 'short') }} | ||||
|         </dt> | ||||
|         <dd></dd> | ||||
|     {% else %} | ||||
|         <dt> | ||||
|         {{ entity.startDate|format_date('full') }}, | ||||
|         {{ entity.startDate|format_datetime('none', 'short', locale='fr') }} - {{ entity.endDate|format_datetime('none', 'short', locale='fr') }} | ||||
|         </dt> | ||||
|         <dd></dd> | ||||
|     {% endif %} | ||||
|  | ||||
|     <dt class="inline">{{ 'cancel reason'|trans }}</dt> | ||||
|     <dd> | ||||
|   | ||||
							
								
								
									
										81
									
								
								src/Bundle/ChillCalendarBundle/chill.api.specs.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/Bundle/ChillCalendarBundle/chill.api.specs.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | ||||
| --- | ||||
| openapi: "3.0.0" | ||||
| info: | ||||
|   version: "1.0.0" | ||||
|   title: "Chill api" | ||||
|   description: "Api documentation for chill. Currently, work in progress" | ||||
| servers: | ||||
|   - url: "/api" | ||||
|     description: "Your current dev server" | ||||
|  | ||||
| paths: | ||||
|   /1.0/calendar/calendar.json: | ||||
|     get: | ||||
|       tags: | ||||
|         - calendar | ||||
|       summary: Return a list of all calendar items | ||||
|       responses: | ||||
|         200: | ||||
|           description: "ok" | ||||
|  | ||||
|   /1.0/calendar/calendar/{id}.json: | ||||
|     get: | ||||
|       tags: | ||||
|         - calendar | ||||
|       summary: Return an calendar item by id | ||||
|       parameters: | ||||
|         - name: id | ||||
|           in: path | ||||
|           required: true | ||||
|           description: The calendar id | ||||
|           schema: | ||||
|             type: integer | ||||
|             format: integer | ||||
|             minimum: 1 | ||||
|       responses: | ||||
|         200: | ||||
|           description: "ok" | ||||
|         404: | ||||
|           description: "not found" | ||||
|         401: | ||||
|           description: "Unauthorized" | ||||
|  | ||||
|   /1.0/calendar/calendar-range.json: | ||||
|     get: | ||||
|       tags: | ||||
|         - calendar | ||||
|       summary: Return a list of all calendar range items | ||||
|       responses: | ||||
|         200: | ||||
|           description: "ok" | ||||
|  | ||||
|   /1.0/calendar/calendar-range/{id}.json: | ||||
|     get: | ||||
|       tags: | ||||
|         - calendar | ||||
|       summary: Return an calendar-range item by id | ||||
|       parameters: | ||||
|         - name: id | ||||
|           in: path | ||||
|           required: true | ||||
|           description: The calendar-range id | ||||
|           schema: | ||||
|             type: integer | ||||
|             format: integer | ||||
|             minimum: 1 | ||||
|       responses: | ||||
|         200: | ||||
|           description: "ok" | ||||
|         404: | ||||
|           description: "not found" | ||||
|         401: | ||||
|           description: "Unauthorized" | ||||
|    | ||||
|   /1.0/calendar/calendar-range-available.json: | ||||
|     get: | ||||
|       tags: | ||||
|         - calendar | ||||
|       summary: Return a list of available calendar range items. Available means calendar-range not being taken by a calendar entity | ||||
|       responses: | ||||
|         200: | ||||
|           description: "ok" | ||||
| @@ -1,10 +1,10 @@ | ||||
| // this file loads all assets from the Chill calendar bundle | ||||
| module.exports = function(encore, entries) { | ||||
|     //entries.push(__dirname + '/Resources/public/index.js'); | ||||
|     entries.push(__dirname + '/Resources/public/chill/index.js'); | ||||
|      | ||||
|     encore.addAliases({ | ||||
|         ChillCalendarAssets: __dirname + '/Resources/public' | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     encore.addEntry('vue_calendar', __dirname + '/Resources/public/vuejs/Calendar/index.js'); | ||||
| }; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ Are you sure you want to remove the calendar item?: Êtes-vous sûr de vouloir s | ||||
| Concerned groups: Parties concernées | ||||
| Calendar data: Données du rendez-vous | ||||
| Update calendar: Modifier le rendez-vous | ||||
| main user concerned: Utilisateur principal | ||||
| main user concerned: Utilisateur concerné | ||||
| Main user: Utilisateur principal | ||||
| Calendar item creation: Création du rendez-vous | ||||
| start date: début du rendez-vous | ||||
| @@ -18,3 +18,8 @@ sendSMS: Envoi d'un SMS | ||||
| Send s m s: Envoi d'un SMS ?  | ||||
| Cancel reason: Motif d'annulation | ||||
| Add a new calendar: Ajouter un nouveau rendez-vous | ||||
| "Success : calendar item updated!": "Rendez-vous mis à jour" | ||||
| "Success : calendar item created!": "Rendez-vous créé" | ||||
| The calendar item has been successfully removed.: Le rendez-vous a été supprimé | ||||
| From the day: Du | ||||
| to the day: au | ||||
| @@ -3,6 +3,7 @@ | ||||
| namespace Chill\DocGeneratorBundle\Context; | ||||
|  | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; | ||||
| use Chill\PersonBundle\Entity\SocialWork\SocialAction; | ||||
|  | ||||
| /** | ||||
| @@ -21,11 +22,22 @@ class HouseholdMemberSelectionContext implements DocGeneratorContextInterface | ||||
|      * Generate the form that display | ||||
|      */ | ||||
|     public function getForm($entity) { | ||||
|         // TODO Get FormFactory and create a form | ||||
|         throw new \Exception("No implemented yet", 1); | ||||
|  | ||||
|         $choices = []; | ||||
|         if(get_class($entity) == AccompanyingPeriodWorkEvaluation::class) { | ||||
|             foreach ($entity->getAccompanyingPeriodWork()->getPersons() as $person) { | ||||
|                 $choices[$person->getId()] = $person->getName(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // access to the house hold for the AccompanyingPeriod pr the SocialAction | ||||
|         // and configure the form to select member of the family | ||||
|         $builder->add('members', ChoiceType::class, [ | ||||
|             'choices' => $choices, | ||||
|             'placeholder' => 'Choose a person', | ||||
|             'label' => 'Person to add' | ||||
|         ]); | ||||
|  | ||||
|         return $builder; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
| @@ -41,17 +53,37 @@ class HouseholdMemberSelectionContext implements DocGeneratorContextInterface | ||||
|      * Get the data that will be injected to the generated document | ||||
|      */ | ||||
|     public function getData($entity): array { | ||||
|         $ret = []; | ||||
|         $datas = array( | ||||
|             'setValues' => array(), | ||||
|             'cloneRowAndSetValues' => array() | ||||
|         ); | ||||
|  | ||||
|         if(get_class($entity) == AccompanyingPeriod::class) { | ||||
|             // TODO mettre ça dans un service | ||||
|             $ret = ['AccompanyingPeriod' =>  $entity]; | ||||
|         } elseif(get_class($entity) == SocialAction::class) { | ||||
|             $ret = ['SocialAction' => $entity]; | ||||
|         $persons = $entity->getAccompanyingPeriodWork()->getPersons(); | ||||
|  | ||||
|         if(sizeof($persons) > 0) { | ||||
|             $firstPerson = $persons[0]; | ||||
|  | ||||
|             $datas['setValues'][] = array( | ||||
|                 'firstPersonFirstName' => $firstPerson->getFirstName(), | ||||
|                 'firstPersonLastName' => $firstPerson->getLastName(),); | ||||
|         } | ||||
|  | ||||
|         // TODO AJOUTER LES DONNES DU FORMULAIRE | ||||
|  | ||||
|         return $ret; | ||||
|         if(get_class($entity) == AccompanyingPeriodWorkEvaluation::class) { | ||||
|             $values = array(); | ||||
|             foreach($entity->getAccompanyingPeriodWork()->getPersons() as $person) { | ||||
|                 $i = 1; | ||||
|                 $values[] = array( | ||||
|                     'personRowId' => $i, | ||||
|                     'personFirstName' => $person->getFirstName(), | ||||
|                     'personLastName' => $person->getLastName(), | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             $datas['cloneRowAndSetValues'][] = array( | ||||
|                 'personRowId', $values); | ||||
|         } | ||||
|  | ||||
|         return $datas; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,13 @@ | ||||
| namespace Chill\DocGeneratorBundle\Controller; | ||||
|  | ||||
| use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluationDocument; | ||||
| use Chill\PersonBundle\Entity\SocialWork\Evaluation; | ||||
| use GuzzleHttp\Exception\TransferException; | ||||
| use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | ||||
| use Symfony\Component\HttpFoundation\Request; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpFoundation\JsonResponse; | ||||
| use Symfony\Component\HttpFoundation\HeaderUtils; | ||||
| @@ -54,9 +60,12 @@ class DocGeneratorTemplateController extends AbstractController | ||||
|      * ) | ||||
|      */ | ||||
|     public function generateDocFromTemplateAction( | ||||
|         TempUrlOpenstackGenerator $tempUrlGenerator, | ||||
|         DocGeneratorTemplate $template, string $entityClassName, int $entityId): Response | ||||
|     { | ||||
|         \ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface $tempUrlGenerator, | ||||
|         DocGeneratorTemplate $template, | ||||
|         string $entityClassName, | ||||
|         int $entityId, | ||||
|         Request $request | ||||
|     ): Response { | ||||
|         $getUrlGen = $tempUrlGenerator->generate( | ||||
|             'GET', | ||||
|             $template->getFile()); | ||||
| @@ -68,14 +77,20 @@ class DocGeneratorTemplateController extends AbstractController | ||||
|  | ||||
|         if ($template->getContext() == HouseholdMemberSelectionContext::class) { | ||||
|             $context = new HouseholdMemberSelectionContext(); | ||||
|             // $datas = $context->getData($entity); | ||||
|             $datas = []; | ||||
|             $datas = $context->getData($entity); | ||||
|         } else { | ||||
|             throw new \Exception("Not implemented", 1); | ||||
|         } | ||||
|  | ||||
|         $templateProcessor = new TemplateProcessor($tmpfname); | ||||
|         $templateProcessor->setValues(array('firstname' => 'John', 'lastname' => 'Doe')); | ||||
|  | ||||
|         foreach ($datas['setValues'] as $setValuesConf) { | ||||
|             $templateProcessor->setValues($setValuesConf); | ||||
|         } | ||||
|  | ||||
|         foreach ($datas['cloneRowAndSetValues'] as $cloneRowAndSetValues) { | ||||
|             $templateProcessor->cloneRowAndSetValues($cloneRowAndSetValues[0], $cloneRowAndSetValues[1]); | ||||
|         } | ||||
|  | ||||
|         $tmpfname2 = tempnam(sys_get_temp_dir(), 'DOC_GENERATED'); | ||||
|         $templateProcessor->saveAs($tmpfname2); | ||||
| @@ -84,7 +99,7 @@ class DocGeneratorTemplateController extends AbstractController | ||||
|  | ||||
|         $fileContent = fopen($tmpfname2, 'r'); // the generated file content | ||||
|  | ||||
|         $genDocName = 'doc_'.sprintf( '%010d', rand()).'.docx'; | ||||
|         $genDocName = 'doc_' . sprintf('%010d', rand()) . '.docx'; | ||||
|  | ||||
|         $getUrlGen = $tempUrlGenerator->generate( | ||||
|             'PUT', | ||||
| @@ -94,25 +109,41 @@ class DocGeneratorTemplateController extends AbstractController | ||||
|  | ||||
|         $client = new Client(); | ||||
|  | ||||
|         $putResponse = $client->request('PUT', $getUrlGen->{'url'}, [ | ||||
|             'body' => $fileContent | ||||
|         ]); | ||||
|         try { | ||||
|             $putResponse = $client->request('PUT', $getUrlGen->{'url'}, [ | ||||
|                 'body' => $fileContent | ||||
|             ]); | ||||
|  | ||||
|         if ($putResponse->getStatusCode() == 201) { | ||||
|             return new JsonResponse( | ||||
|                 array( | ||||
|                     "msg" => "Document créé", | ||||
|                     "id" => $genDocName, | ||||
|                     "response" => array( | ||||
|                         "reasonPhrase" => $putResponse->getReasonPhrase(), | ||||
|                         "statusCode" => $putResponse->getStatusCode()))); | ||||
|             if ($putResponse->getStatusCode() == 201) { | ||||
|                 $em = $this->getDoctrine()->getManager(); | ||||
|                 $storedObject = new StoredObject(); | ||||
|                 $storedObject | ||||
|                     // currently, only docx is supported | ||||
|                     ->setType('application/vnd.openxmlformats-officedocument.wordprocessingml.document') | ||||
|                     ->setFilename($genDocName); | ||||
|  | ||||
|                 $em->persist($storedObject); | ||||
|  | ||||
|                 // Only for evaluation | ||||
|                 if ($entity instanceof AccompanyingPeriodWorkEvaluation) { | ||||
|                     $doc = new AccompanyingPeriodWorkEvaluationDocument(); | ||||
|                     $doc | ||||
|                         ->setStoredObject($storedObject) | ||||
|                         ->setTemplate($template) | ||||
|                         ; | ||||
|                     $entity->addDocument($doc); | ||||
|                     $em->persist($doc); | ||||
|                 } | ||||
|  | ||||
|                 $em->flush(); | ||||
|  | ||||
|                 return $this->redirectToRoute('chill_wopi_file_edit', [ | ||||
|                     'fileId' => $genDocName, | ||||
|                     'returnPath' => $request->query->get('returnPath', "/") | ||||
|                 ]); | ||||
|             } | ||||
|         } catch (TransferException $e) { | ||||
|             throw $e; | ||||
|         } | ||||
|  | ||||
|         return new JsonResponse( | ||||
|             array( | ||||
|                 "msg" => "PBM", | ||||
|                 "response" => array( | ||||
|                     "reasonPhrase" => $putResponse->getReasonPhrase(), | ||||
|                     "statusCode" => $putResponse->getStatusCode()))); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| namespace Chill\DocGeneratorBundle\DataFixtures\ORM; | ||||
|  | ||||
| use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation; | ||||
| use Doctrine\Common\DataFixtures\AbstractFixture; | ||||
| use Doctrine\Persistence\ObjectManager; | ||||
| use Doctrine\Common\DataFixtures\DependentFixtureInterface; | ||||
| @@ -23,15 +24,15 @@ class LoadDocGeneratorTemplate  extends AbstractFixture | ||||
|             [ | ||||
|                 'name' => ['fr' => 'FORMULAIRE AEB'], | ||||
|                 'desc' => 'stocké sur openstack comedienbe', | ||||
|                 'file' => 'FORMULAIRE_AEB.docx', | ||||
|                 'file' => 'FORMULAIRE_AEB_WITH_DATA_INJ.docx', | ||||
|                 'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext', | ||||
|                 'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction'], | ||||
|                 'entities' => [AccompanyingPeriodWorkEvaluation::class], | ||||
|             ], [ | ||||
|                 'name' => ['fr' => 'AIDE ALIMENTAIRE'], | ||||
|                 'desc' => 'stocké sur openstack comedienbe', | ||||
|                 'file' => 'AIDE_ALIMENTAIRE.docx', | ||||
|                 'context' => 'Chill\DocGeneratorBundle\Context\HouseholdMemberSelectionContext', | ||||
|                 'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction'], | ||||
|                 'entities' => ['Chill\PersonBundle\Entity\AccompanyingPeriod', 'Chill\PersonBundle\Entity\SocialWork\SocialAction', AccompanyingPeriodWorkEvaluation::class], | ||||
|             ], | ||||
|         ]; | ||||
|  | ||||
|   | ||||
| @@ -4,11 +4,11 @@ namespace Chill\DocGeneratorBundle\Entity; | ||||
|  | ||||
| use Chill\DocGeneratorBundle\Repository\DocGeneratorTemplateRepository; | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
|  | ||||
| /** | ||||
|  * @ORM\Entity | ||||
|  * @ORM\Table(name="chill_docgen_template") | ||||
|  | ||||
|  */ | ||||
| class DocGeneratorTemplate | ||||
| { | ||||
| @@ -16,11 +16,13 @@ class DocGeneratorTemplate | ||||
|      * @ORM\Id | ||||
|      * @ORM\GeneratedValue | ||||
|      * @ORM\Column(type="integer") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private int $id; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="json_array") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private array $name = []; | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
| {% block layout_wvm_content %} | ||||
|     {% embed '@ChillMain/CRUD/_index.html.twig' %} | ||||
|         {% block table_entities_thead_tr %} | ||||
|             <th></th> | ||||
|             <th>{{ 'Title'|trans }}</th> | ||||
|             <th>{{ 'File'|trans }}</th> | ||||
|         {% endblock %} | ||||
| @@ -10,6 +11,7 @@ | ||||
|         {% block table_entities_tbody %} | ||||
|             {% for entity in entities %} | ||||
|                 <tr> | ||||
|                     <td>{{ entity.id }}</td> | ||||
|                     <td>{{ entity.name | localize_translatable_string }}</td> | ||||
|                     <td>{{ entity.file }}</td> | ||||
|                 </tr> | ||||
|   | ||||
| @@ -1,18 +1,18 @@ | ||||
| <?php | ||||
| /* | ||||
|  *  | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\DocStoreBundle\Entity; | ||||
|  | ||||
| use Doctrine\ORM\Mapping as ORM; | ||||
| use ChampsLibres\AsyncUploaderBundle\Model\AsyncFileInterface; | ||||
| use ChampsLibres\AsyncUploaderBundle\Validator\Constraints\AsyncFileExists; | ||||
| use DateTimeInterface; | ||||
| use Symfony\Component\Serializer\Annotation as Serializer; | ||||
|  | ||||
| /** | ||||
|  * Represent a document stored in an object store  | ||||
|  * Represent a document stored in an object store | ||||
|  * | ||||
|  * @author Julien Fastré <julien.fastre@champs-libres.coop> | ||||
|  *  | ||||
|  * @ORM\Entity() | ||||
|  * @ORM\Table("chill_doc.stored_object") | ||||
|  * @AsyncFileExists( | ||||
| @@ -25,53 +25,51 @@ class StoredObject implements AsyncFileInterface | ||||
|      * @ORM\Id() | ||||
|      * @ORM\GeneratedValue() | ||||
|      * @ORM\Column(type="integer") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private $id; | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="text") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private $filename; | ||||
|      | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="json_array", name="key") | ||||
|      * @var array | ||||
|      */ | ||||
|     private $keyInfos = array(); | ||||
|      | ||||
|     private array $keyInfos = []; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @var int[]  | ||||
|      * @var int[] | ||||
|      * @ORM\Column(type="json_array", name="iv") | ||||
|      */ | ||||
|     private $iv = array(); | ||||
|      | ||||
|     private array $iv = []; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @var \DateTime | ||||
|      * @ORM\Column(type="datetime", name="creation_date") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private $creationDate; | ||||
|      | ||||
|     private DateTimeInterface $creationDate; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @var string  | ||||
|      * @ORM\Column(type="text", name="type") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private $type = ''; | ||||
|      | ||||
|     private string $type = ''; | ||||
|  | ||||
|     /** | ||||
|      * | ||||
|      * @var array | ||||
|      * @ORM\Column(type="json_array", name="datas") | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private $datas = []; | ||||
|      | ||||
|     private array $datas = []; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->creationDate = new \DateTime(); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public function getId() | ||||
|     { | ||||
|         return $this->id; | ||||
| @@ -100,35 +98,39 @@ class StoredObject implements AsyncFileInterface | ||||
|     public function setFilename($filename) | ||||
|     { | ||||
|         $this->filename = $filename; | ||||
|          | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setCreationDate(\DateTime $creationDate) | ||||
|     { | ||||
|         $this->creationDate = $creationDate; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setType($type) | ||||
|     { | ||||
|         $this->type = $type; | ||||
|          | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setDatas(array $datas) | ||||
|     { | ||||
|         $this->datas = $datas; | ||||
|          | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @deprecated Use method "getFilename()". | ||||
|      */ | ||||
|     public function getObjectName() | ||||
|     { | ||||
|         return $this->getFilename(); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     public function getKeyInfos() | ||||
|     { | ||||
|         return $this->keyInfos; | ||||
| @@ -142,14 +144,14 @@ class StoredObject implements AsyncFileInterface | ||||
|     public function setKeyInfos($keyInfos) | ||||
|     { | ||||
|         $this->keyInfos = $keyInfos; | ||||
|          | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function setIv($iv) | ||||
|     { | ||||
|         $this->iv = $iv; | ||||
|          | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -0,0 +1,51 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\DocStoreBundle\Repository; | ||||
|  | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Doctrine\ORM\EntityManagerInterface; | ||||
| use Doctrine\ORM\EntityRepository; | ||||
| use Doctrine\Persistence\ObjectRepository; | ||||
|  | ||||
| final class StoredObjectRepository implements ObjectRepository | ||||
| { | ||||
|     private EntityRepository $repository; | ||||
|  | ||||
|     public function __construct(EntityManagerInterface $entityManager) | ||||
|     { | ||||
|         $this->repository = $entityManager->getRepository(StoredObject::class); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array<int, StoredObject> | ||||
|      */ | ||||
|     public function findAll(): array | ||||
|     { | ||||
|         return $this->repository->findAll(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array<int, StoredObject> | ||||
|      */ | ||||
|     public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array | ||||
|     { | ||||
|         return $this->repository->findBy($criteria, $orderBy, $limit, $offset); | ||||
|     } | ||||
|  | ||||
|     public function findOneBy(array $criteria): ?StoredObject | ||||
|     { | ||||
|         return $this->repository->findOneBy($criteria); | ||||
|     } | ||||
|  | ||||
|     public function getClassName(): string | ||||
|     { | ||||
|         return StoredObject::class; | ||||
|     } | ||||
|  | ||||
|     public function find($id, $lockMode = null, $lockVersion = null): ?StoredObject | ||||
|     { | ||||
|         return $this->repository->find($id, $lockMode, $lockVersion); | ||||
|     } | ||||
| } | ||||
| @@ -2,6 +2,12 @@ parameters: | ||||
| #    cl_chill_person.example.class: Chill\PersonBundle\Example | ||||
|  | ||||
| services: | ||||
|     Chill\DocStoreBundle\Repository\: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|         resource: '../Repository/' | ||||
|         tags: ['doctrine.repository_service'] | ||||
|  | ||||
|     Chill\DocStoreBundle\Form\DocumentCategoryType: | ||||
|         class: Chill\DocStoreBundle\Form\DocumentCategoryType | ||||
|         arguments: ['%kernel.bundles%'] | ||||
|   | ||||
| @@ -93,9 +93,8 @@ const datetimeToISO = (date) => { | ||||
| }; | ||||
|  | ||||
| const intervalDaysToISO = (days) => { | ||||
|     console.log(days); | ||||
|     if (null === days) { | ||||
|         return 'PD0'; | ||||
|         return 'P0D'; | ||||
|     } | ||||
|  | ||||
|     return `P${days}D`; | ||||
|   | ||||
| @@ -24,8 +24,8 @@ class AddressNormalizer implements NormalizerAwareInterface, NormalizerInterface | ||||
|         $data['floor'] = $address->getFloor(); | ||||
|         $data['corridor'] = $address->getCorridor(); | ||||
|         $data['steps'] = $address->getSteps(); | ||||
|         $data['flat'] = $address->getBuildingName(); | ||||
|         $data['buildingName'] = $address->getFlat(); | ||||
|         $data['flat'] = $address->getFlat(); | ||||
|         $data['buildingName'] = $address->getBuildingName(); | ||||
|         $data['distribution'] = $address->getDistribution(); | ||||
|         $data['extra'] = $address->getExtra(); | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,8 @@ | ||||
| services:     | ||||
| services: | ||||
|     _defaults: | ||||
|         autowire: true | ||||
|         autoconfigure: true | ||||
|  | ||||
|     chill.main.security.authorization.helper: | ||||
|         class: Chill\MainBundle\Security\Authorization\AuthorizationHelper | ||||
|         arguments: | ||||
| @@ -6,47 +10,47 @@ services: | ||||
|             $hierarchy: "%security.role_hierarchy.roles%" | ||||
|             $em: '@Doctrine\ORM\EntityManagerInterface' | ||||
|     Chill\MainBundle\Security\Authorization\AuthorizationHelper: '@chill.main.security.authorization.helper' | ||||
|              | ||||
|  | ||||
|     chill.main.role_provider: | ||||
|         class: Chill\MainBundle\Security\RoleProvider | ||||
|          | ||||
|  | ||||
|     chill.main.user_provider: | ||||
|         class: Chill\MainBundle\Security\UserProvider\UserProvider | ||||
|         arguments: | ||||
|             $em: '@Doctrine\ORM\EntityManagerInterface' | ||||
|              | ||||
|  | ||||
|     Symfony\Component\Security\Core\User\UserProviderInterface: "@chill.main.user_provider" | ||||
|  | ||||
|     Chill\MainBundle\Security\Authorization\ChillExportVoter: | ||||
|         arguments: | ||||
|             $authorizationHelper: '@Chill\MainBundle\Security\Authorization\AuthorizationHelper' | ||||
|         tags: | ||||
|             - { name: security.voter } | ||||
|              | ||||
|     Chill\MainBundle\Security\PasswordRecover\TokenManager:  | ||||
|  | ||||
|     Chill\MainBundle\Security\PasswordRecover\TokenManager: | ||||
|         arguments: | ||||
|             $secret: '%kernel.secret%' | ||||
|             $logger: '@Psr\Log\LoggerInterface' | ||||
|      | ||||
|  | ||||
|     Chill\MainBundle\Security\PasswordRecover\RecoverPasswordHelper: | ||||
|         arguments: | ||||
|             $tokenManager: '@Chill\MainBundle\Security\PasswordRecover\TokenManager' | ||||
|             $urlGenerator: '@Symfony\Component\Routing\Generator\UrlGeneratorInterface' | ||||
|             $mailer: '@Chill\MainBundle\Notification\Mailer' | ||||
|             $routeParameters: "%chill_main.notifications%" | ||||
|          | ||||
|  | ||||
|     Chill\MainBundle\Security\PasswordRecover\PasswordRecoverEventSubscriber: | ||||
|         arguments: | ||||
|             $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' | ||||
|         tags: | ||||
|             - { name: kernel.event_subscriber } | ||||
|              | ||||
|  | ||||
|     Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker: | ||||
|         arguments: | ||||
|             $chillRedis: '@Chill\MainBundle\Redis\ChillRedis' | ||||
|             $logger: '@Psr\Log\LoggerInterface' | ||||
|              | ||||
|  | ||||
|     Chill\MainBundle\Security\PasswordRecover\PasswordRecoverVoter: | ||||
|         arguments: | ||||
|             $locker: '@Chill\MainBundle\Security\PasswordRecover\PasswordRecoverLocker' | ||||
|             $requestStack: '@Symfony\Component\HttpFoundation\RequestStack' | ||||
|         tags: | ||||
|             - { name: security.voter } | ||||
|             - { name: security.voter } | ||||
|   | ||||
| @@ -499,6 +499,17 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface | ||||
|         return $collection->count() > 0 ? $collection->first() : NULL; | ||||
|     } | ||||
|  | ||||
|     public function getOPenParticipations(): Collection | ||||
|     { | ||||
|         return $this | ||||
|             ->getParticipations() | ||||
|             ->filter( | ||||
|                 static function(AccompanyingPeriodParticipation $participation): bool { | ||||
|                     return null === $participation->getEndDate(); | ||||
|                 } | ||||
|             ); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Return true if the accompanying period contains a person. | ||||
|      * | ||||
| @@ -974,6 +985,22 @@ class AccompanyingPeriod implements TrackCreationInterface, TrackUpdateInterface | ||||
|         return $this->personLocation; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get a list of person which have an adresse available for a valid location | ||||
|      * | ||||
|      * @return Collection|Person[] | ||||
|      */ | ||||
|     public function getAvailablePersonLocation(): Collection | ||||
|     { | ||||
|         return $this->getOPenParticipations() | ||||
|             ->filter(function(AccompanyingPeriodParticipation $p) { | ||||
|                 return $p->getPerson()->hasCurrentHouseholdAddress(); | ||||
|             }) | ||||
|             ->map(function(AccompanyingPeriodParticipation $p) { | ||||
|                 return $p->getPerson(); | ||||
|             }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @Groups({"write"}) | ||||
|      */ | ||||
|   | ||||
| @@ -169,7 +169,7 @@ use Symfony\Component\Validator\Constraints as Assert; | ||||
|       *     orphanRemoval=true | ||||
|       * ) | ||||
|       * @Serializer\Groups({"read"}) | ||||
|       * @internal /!\ the serialization for read / write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` | ||||
|       * @internal /!\ the serialization for write evaluations is handled in `AccompanyingPeriodWorkDenormalizer` | ||||
|       */ | ||||
|     private Collection $accompanyingPeriodWorkEvaluations; | ||||
|  | ||||
|   | ||||
| @@ -120,12 +120,29 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre | ||||
|      * @var Collection | ||||
|      * @ORM\OneToMany( | ||||
|      *     targetEntity=AccompanyingPeriodWorkEvaluationDocument::class, | ||||
|      *     mappedBy="accompanyingPeriodWorkEvaluation" | ||||
|      *     mappedBy="accompanyingPeriodWorkEvaluation", | ||||
|      *     cascade={"remove"} | ||||
|      *     ) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private Collection $documents; | ||||
|  | ||||
|     /** | ||||
|      * This is a workaround for client, to allow them to assign arbitrary data | ||||
|      * dedicated to their job. | ||||
|      * | ||||
|      * This data is not persisted into database, but will appears on the data | ||||
|      * normalized during the same request (like PUT/PATCH request) | ||||
|      * | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      * @Serializer\Groups({"write"}) | ||||
|      * @Serializer\Groups({"accompanying_period_work_evaluation:create"}) | ||||
|      * | ||||
|      * @var mixed | ||||
|      * | ||||
|      */ | ||||
|     private $key = null; | ||||
|  | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->documents = new ArrayCollection(); | ||||
| @@ -187,7 +204,8 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre | ||||
|             ($this->evaluation instanceof Evaluation | ||||
|                 && null === $evaluation) | ||||
|         ) { | ||||
|             throw new \LogicException("once set, an ${self::class} cannot | ||||
|             $cl = AccompanyingPeriodWorkEvaluation::class; | ||||
|             throw new \LogicException("once set, an $cl  cannot | ||||
|                 change or remove the linked Evaluation::class"); | ||||
|         } | ||||
|  | ||||
| @@ -366,5 +384,42 @@ class AccompanyingPeriodWorkEvaluation implements TrackUpdateInterface, TrackCre | ||||
|         return $this->documents; | ||||
|     } | ||||
|  | ||||
|     public function addDocument(AccompanyingPeriodWorkEvaluationDocument $document): self | ||||
|     { | ||||
|         if (!$this->documents->contains($document)) { | ||||
|             $this->documents[] = $document; | ||||
|             $document->setAccompanyingPeriodWorkEvaluation($this); | ||||
|         } | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     public function removeDocument(AccompanyingPeriodWorkEvaluationDocument $document): self | ||||
|     { | ||||
|         $this->documents->removeElement($document); | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Arbitrary data, used for client | ||||
|      * | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getKey() | ||||
|     { | ||||
|         return $this->key; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Arbitrary data, used for client | ||||
|      * | ||||
|      * @param mixed $key | ||||
|      * @return AccompanyingPeriodWorkEvaluation | ||||
|      */ | ||||
|     public function setKey($key): self | ||||
|     { | ||||
|         $this->key = $key; | ||||
|  | ||||
|         return $this; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| namespace Chill\PersonBundle\Entity\AccompanyingPeriod; | ||||
|  | ||||
| use Chill\DocGeneratorBundle\Entity\DocGeneratorTemplate; | ||||
| use Chill\DocStoreBundle\Entity\StoredObject; | ||||
| use Chill\MainBundle\Doctrine\Model\TrackCreationInterface; | ||||
| use Chill\MainBundle\Doctrine\Model\TrackUpdateInterface; | ||||
| @@ -37,7 +38,7 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct | ||||
|      *     inversedBy="documents" | ||||
|      * ) | ||||
|      */ | ||||
|     private ?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation; | ||||
|     private ?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne( | ||||
| @@ -45,13 +46,13 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct | ||||
|      * ) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?User $createdBy; | ||||
|     private ?User $createdBy = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?\DateTimeImmutable $createdAt; | ||||
|     private ?\DateTimeImmutable $createdAt = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\ManyToOne( | ||||
| @@ -59,18 +60,29 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct | ||||
|      * ) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?User $updatedBy; | ||||
|     private ?User $updatedBy = null; | ||||
|  | ||||
|     /** | ||||
|      * @ORM\Column(type="date_immutable", nullable=true, options={"default": null}) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?DateTimeImmutable $updatedAt; | ||||
|     private ?\DateTimeImmutable $updatedAt = null; | ||||
|  | ||||
|     // TODO: indiquer le document généré par le module "document" | ||||
|     private ?StoredObject $storedObject; | ||||
|     /** | ||||
|     * @ORM\ManyToOne( | ||||
|     *     targetEntity=StoredObject::class | ||||
|     * ) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|     */ | ||||
|     private ?StoredObject $storedObject = null; | ||||
|  | ||||
|     // TODO: ajouter gabarit | ||||
|     /** | ||||
|      * @ORM\ManyToOne( | ||||
|      *     targetEntity=DocGeneratorTemplate::class | ||||
|      * ) | ||||
|      * @Serializer\Groups({"read"}) | ||||
|      */ | ||||
|     private ?DocGeneratorTemplate $template = null; | ||||
|  | ||||
|     /** | ||||
|      * @return AccompanyingPeriodWorkEvaluation|null | ||||
| @@ -86,6 +98,14 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct | ||||
|      */ | ||||
|     public function setAccompanyingPeriodWorkEvaluation(?AccompanyingPeriodWorkEvaluation $accompanyingPeriodWorkEvaluation): AccompanyingPeriodWorkEvaluationDocument | ||||
|     { | ||||
|         // if an evaluation is already associated, we cannot change the association (removing the association, | ||||
|         // by setting a null value, is allowed. | ||||
|         if ($this->accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation | ||||
|             && $accompanyingPeriodWorkEvaluation instanceof AccompanyingPeriodWorkEvaluation) { | ||||
|             if ($this->accompanyingPeriodWorkEvaluation !== $accompanyingPeriodWorkEvaluation) { | ||||
|                 throw new \RuntimeException("It is not allowed to change the evaluation for a document"); | ||||
|             } | ||||
|         } | ||||
|         $this->accompanyingPeriodWorkEvaluation = $accompanyingPeriodWorkEvaluation; | ||||
|         return $this; | ||||
|     } | ||||
| @@ -172,4 +192,22 @@ class AccompanyingPeriodWorkEvaluationDocument implements \Chill\MainBundle\Doct | ||||
|         return $this->updatedAt; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return DocGeneratorTemplate|null | ||||
|      */ | ||||
|     public function getTemplate(): ?DocGeneratorTemplate | ||||
|     { | ||||
|         return $this->template; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param DocGeneratorTemplate|null $template | ||||
|      * @return AccompanyingPeriodWorkEvaluationDocument | ||||
|      */ | ||||
|     public function setTemplate(?DocGeneratorTemplate $template): AccompanyingPeriodWorkEvaluationDocument | ||||
|     { | ||||
|         $this->template = $template; | ||||
|         return $this; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -103,7 +103,7 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI | ||||
|      * | ||||
|      * @ORM\Column(type="date", nullable=true) | ||||
|      */ | ||||
|     private $birthdate;  | ||||
|     private $birthdate; | ||||
|  | ||||
|     /** | ||||
|      * The person's deathdate | ||||
| @@ -736,8 +736,8 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI | ||||
|     { | ||||
|         return $this->birthdate; | ||||
|     } | ||||
|      | ||||
|     public function getAge(): ?int  | ||||
|  | ||||
|     public function getAge(): ?int | ||||
|     { | ||||
|         if ($this->birthdate instanceof \DateTimeInterface) { | ||||
|             return date_diff($this->birthdate, date_create('now'))->format("%y"); | ||||
| @@ -1439,6 +1439,11 @@ class Person implements HasCenterInterface, TrackCreationInterface, TrackUpdateI | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function hasCurrentHouseholdAddress(?\DateTimeImmutable $at = null): bool | ||||
|     { | ||||
|         return null !== $this->getCurrentHouseholdAddress($at); | ||||
|     } | ||||
|  | ||||
|     public function getGenderComment(): CommentEmbeddable | ||||
|     { | ||||
|         return $this->genderComment; | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| const onSubmit = function(e) { | ||||
|     e.preventDefault(); | ||||
|     let | ||||
|         form = e.target, | ||||
|         formData = new FormData(form), | ||||
|         url = form.action, | ||||
|         payload = { | ||||
|             type: 'accompanying_period', | ||||
|             id: Number.parseInt(formData.get('periodId'), 10), | ||||
|             personLocation: { | ||||
|                 type: 'person', | ||||
|                 id: Number.parseInt(formData.get('personLocation'), 10) | ||||
|             } | ||||
|         } | ||||
|     ; | ||||
|  | ||||
|     console.log('event', e); | ||||
|     console.log('form', form); | ||||
|     console.log('formData', formData); | ||||
|     console.log('url', url); | ||||
|     console.log('payload', payload); | ||||
|  | ||||
|     window.fetch(url, { | ||||
|         method: 'PATCH', | ||||
|         headers: { | ||||
|             'Content-Type': 'application/json' | ||||
|         }, | ||||
|         body: JSON.stringify(payload), | ||||
|     }) | ||||
|         .then(r => { | ||||
|             if (r.ok) { | ||||
|                 console.log('response ok'); | ||||
|                 window.location.reload(); | ||||
|             } else { | ||||
|                 console.err("could not patch accompanying course"); | ||||
|             } | ||||
|         }); | ||||
| } | ||||
|  | ||||
| window.addEventListener('DOMContentLoaded', function(e) { | ||||
|   let forms = document.querySelectorAll('.quickLocationForm'); | ||||
|  | ||||
|   console.log(forms); | ||||
|  | ||||
|   forms.forEach(function(form){ | ||||
|       console.log('form quickLocation found', form); | ||||
|       form.addEventListener('submit', onSubmit); | ||||
|   }) | ||||
| }); | ||||
| @@ -1,374 +0,0 @@ | ||||
| import { createStore } from 'vuex'; | ||||
| import { householdMove, fetchHouseholdSuggestionByAccompanyingPeriod, fetchAddressSuggestionByPerson} from './../api.js'; | ||||
| import { datetimeToISO } from 'ChillMainAssets/chill/js/date.js'; | ||||
|  | ||||
| const debug = process.env.NODE_ENV !== 'production'; | ||||
|  | ||||
| const concerned = window.household_members_editor_data.persons.map(p => { | ||||
|   return { | ||||
|     person: p, | ||||
|     position: null, | ||||
|     allowRemove: false, | ||||
|     holder: false, | ||||
|     comment: "", | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1); | ||||
|  | ||||
| const store = createStore({ | ||||
|   strict: debug, | ||||
|   state: { | ||||
|     concerned, | ||||
|     household: window.household_members_editor_data.household, | ||||
|     positions: window.household_members_editor_data.positions.sort((a, b) => { | ||||
|         if (a.ordering < b.ordering) { | ||||
|           return -1; | ||||
|         } | ||||
|         if (a.ordering > b.ordering) { | ||||
|           return 1; | ||||
|         } | ||||
|         return 0; | ||||
|       }), | ||||
|     startDate: new Date(), | ||||
|     allowHouseholdCreate: window.household_members_editor_data.allowHouseholdCreate, | ||||
|     allowHouseholdSearch: window.household_members_editor_data.allowHouseholdSearch, | ||||
|     allowLeaveWithoutHousehold: window.household_members_editor_data.allowLeaveWithoutHousehold, | ||||
|     forceLeaveWithoutHousehold: false, | ||||
|     householdSuggestionByAccompanyingPeriod: [], | ||||
|     showHouseholdSuggestion: window.household_members_editor_expand_suggestions === 1, | ||||
|     addressesSuggestion: [], | ||||
|     warnings: [], | ||||
|     errors: [] | ||||
|   }, | ||||
|   getters: { | ||||
|     isHouseholdNew(state) { | ||||
|       if (state.household === null) { | ||||
|         return false; | ||||
|       } | ||||
|       return !Number.isInteger(state.household.id); | ||||
|     }, | ||||
|     hasHousehold(state) { | ||||
|       return state.household !== null; | ||||
|     }, | ||||
|     hasHouseholdOrLeave(state) { | ||||
|       return state.household !== null || state.forceLeaveWithoutHousehold; | ||||
|     }, | ||||
|     hasHouseholdSuggestion(state, getters) { | ||||
|       return getters.filterHouseholdSuggestionByAccompanyingPeriod.length > 0; | ||||
|     }, | ||||
|     countHouseholdSuggestion(state, getters) { | ||||
|       return getters.filterHouseholdSuggestionByAccompanyingPeriod.length; | ||||
|     }, | ||||
|     filterHouseholdSuggestionByAccompanyingPeriod(state) { | ||||
|       if (state.household === null) { | ||||
|         return state.householdSuggestionByAccompanyingPeriod; | ||||
|       } | ||||
|  | ||||
|       return state.householdSuggestionByAccompanyingPeriod | ||||
|         .filter(h => h.id !== state.household.id) | ||||
|         ; | ||||
|     }, | ||||
|     hasPersonsWellPositionnated(state, getters) { | ||||
|       return getters.needsPositionning === false | ||||
|         || (getters.persons.length > 0 && getters.concUnpositionned.length === 0); | ||||
|     }, | ||||
|     persons(state) { | ||||
|       return state.concerned.map(conc => conc.person); | ||||
|     }, | ||||
|     concUnpositionned(state) { | ||||
|       return state.concerned | ||||
|         .filter(conc => conc.position === null) | ||||
|       ; | ||||
|     }, | ||||
|     positions(state) { | ||||
|       return state.positions; | ||||
|     }, | ||||
|     personByPosition: (state) => (position_id) => { | ||||
|       return state.concerned | ||||
|         .filter(conc => | ||||
|           conc.position !== null ? conc.position.id === position_id : false | ||||
|         ) | ||||
|         .map(conc => conc.person) | ||||
|       ; | ||||
|     }, | ||||
|     concByPosition: (state) => (position_id) => { | ||||
|       return state.concerned | ||||
|         .filter(conc => | ||||
|           conc.position !== null ? conc.position.id === position_id : false | ||||
|         ) | ||||
|       ; | ||||
|     }, | ||||
|     concByPersonId: (state) => (person_id) => { | ||||
|       return state.concerned | ||||
|         .find(conc => conc.person.id === person_id) | ||||
|       ; | ||||
|     }, | ||||
|     needsPositionning(state) { | ||||
|       return state.forceLeaveWithoutHousehold === false; | ||||
|     }, | ||||
|     buildPayload: (state) => { | ||||
|       let | ||||
|         conc, | ||||
|         payload_conc, | ||||
|         payload = { | ||||
|           concerned: [], | ||||
|           destination: null | ||||
|         } | ||||
|       ; | ||||
|  | ||||
|       if (state.forceLeaveWithoutHousehold === false) { | ||||
|         payload.destination = { | ||||
|             id: state.household.id, | ||||
|             type: state.household.type | ||||
|           }; | ||||
|       } | ||||
|  | ||||
|       for (let i in state.concerned) { | ||||
|         conc = state.concerned[i]; | ||||
|         payload_conc = { | ||||
|           person: { | ||||
|             id: conc.person.id, | ||||
|             type: conc.person.type | ||||
|           }, | ||||
|           start_date: { | ||||
|             datetime: datetimeToISO(state.startDate) | ||||
|           } | ||||
|         }; | ||||
|  | ||||
|         if (state.forceLeaveWithoutHousehold === false) { | ||||
|           payload_conc.position = { | ||||
|             id: conc.position.id, | ||||
|             type: conc.position.type | ||||
|           }; | ||||
|           payload_conc.holder = conc.holder; | ||||
|           payload_conc.comment = conc.comment; | ||||
|         } | ||||
|  | ||||
|         payload.concerned.push(payload_conc); | ||||
|       } | ||||
|  | ||||
|       return payload; | ||||
|     }, | ||||
|   }, | ||||
|   mutations: { | ||||
|     addConcerned(state, person) { | ||||
|       let persons = state.concerned.map(conc => conc.person.id); | ||||
|       if (!persons.includes(person.id)) { | ||||
|         state.concerned.push({ | ||||
|           person, | ||||
|           position: null, | ||||
|           allowRemove: true, | ||||
|           holder: false, | ||||
|           comment: "", | ||||
|         }); | ||||
|       } else { | ||||
|         console.err("person already included"); | ||||
|       } | ||||
|     }, | ||||
|     markPosition(state, { person_id, position_id}) { | ||||
|       let | ||||
|         position = state.positions.find(pos => pos.id === position_id), | ||||
|         conc = state.concerned.find(c => c.person.id === person_id); | ||||
|       conc.position = position; | ||||
|     }, | ||||
|     setComment(state, {conc, comment}) { | ||||
|       conc.comment = comment; | ||||
|     }, | ||||
|     toggleHolder(state, conc) { | ||||
|       conc.holder = !conc.holder; | ||||
|     }, | ||||
|     removePosition(state, conc) { | ||||
|       conc.holder = false; | ||||
|       conc.position = null; | ||||
|     }, | ||||
|     removeConcerned(state, conc) { | ||||
|       state.concerned = state.concerned.filter(c => | ||||
|         c.person.id !== conc.person.id | ||||
|       ) | ||||
|     }, | ||||
|     createHousehold(state) { | ||||
|       state.household = { type: 'household', members: [], current_address: null,  | ||||
|         current_members_id: [] }; | ||||
|       state.forceLeaveWithoutHousehold = false; | ||||
|     }, | ||||
|     removeHousehold(state) { | ||||
|       state.household = null; | ||||
|       state.forceLeaveWithoutHousehold = false; | ||||
|     }, | ||||
|     setHouseholdAddress(state, address) { | ||||
|       if (null === state.household) { | ||||
|         console.error("no household"); | ||||
|         throw new Error("No household"); | ||||
|       } | ||||
|  | ||||
|       state.household.current_address = address; | ||||
|       state.household.force_new_address = address; | ||||
|     }, | ||||
|     forceLeaveWithoutHousehold(state) { | ||||
|       state.household = null; | ||||
|       state.forceLeaveWithoutHousehold = true; | ||||
|     }, | ||||
|     selectHousehold(state, household) { | ||||
|       state.household = household; | ||||
|       state.forceLeaveWithoutHousehold = false; | ||||
|     }, | ||||
|     setHouseholdSuggestionByAccompanyingPeriod(state, households) { | ||||
|       let existingIds = state.householdSuggestionByAccompanyingPeriod | ||||
|         .map(h => h.id); | ||||
|       for (let i in households) { | ||||
|         if (!existingIds.includes(households[i].id)) { | ||||
|           state.householdSuggestionByAccompanyingPeriod.push(households[i]); | ||||
|         } | ||||
|       } | ||||
|     }, | ||||
|     setStartDate(state, dateI) { | ||||
|       state.startDate = dateI; | ||||
|     }, | ||||
|     toggleHouseholdSuggestion(state) { | ||||
|       state.showHouseholdSuggestion = !state.showHouseholdSuggestion; | ||||
|     }, | ||||
|     setWarnings(state, warnings) { | ||||
|       state.warnings = warnings; | ||||
|       // reset errors, which should come from servers | ||||
|       state.errors.splice(0, state.errors.length); | ||||
|     }, | ||||
|     setErrors(state, errors) { | ||||
|       state.errors = errors; | ||||
|     }, | ||||
|     addAddressesSuggestion(state, addresses) { | ||||
|       let existingIds = state.addressesSuggestion | ||||
|         .map(a => a.id); | ||||
|  | ||||
|       for (let i in addresses) { | ||||
|         if (!existingIds.includes(addresses[i].id)) { | ||||
|           state.addressesSuggestion.push(addresses[i]); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   actions: { | ||||
|     addConcerned({ commit, dispatch }, person) { | ||||
|       commit('addConcerned', person); | ||||
|       dispatch('computeWarnings'); | ||||
|       dispatch('fetchAddressSuggestions'); | ||||
|     }, | ||||
|     markPosition({ commit, state, dispatch }, { person_id, position_id }) { | ||||
|       commit('markPosition', { person_id, position_id }); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     toggleHolder({ commit, dispatch }, conc) { | ||||
|       commit('toggleHolder', conc); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     removePosition({ commit, dispatch }, conc) { | ||||
|       commit('removePosition', conc); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     removeConcerned({ commit, dispatch }, conc) { | ||||
|       commit('removeConcerned', conc); | ||||
|       dispatch('computeWarnings'); | ||||
|       dispatch('fetchAddressSuggestions'); | ||||
|     }, | ||||
|     removeHousehold({ commit, dispatch }) { | ||||
|       commit('removeHousehold'); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     createHousehold({ commit, dispatch }) { | ||||
|       commit('createHousehold'); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     forceLeaveWithoutHousehold({ commit, dispatch }) { | ||||
|       commit('forceLeaveWithoutHousehold'); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     selectHousehold({ commit }, h) { | ||||
|       commit('selectHousehold', h); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     setStartDate({ commit, dispatch }, date) { | ||||
|       commit('setStartDate', date); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     setComment({ commit }, payload) { | ||||
|       commit('setComment', payload); | ||||
|     }, | ||||
|     fetchHouseholdSuggestionForConcerned({ commit, state }, person) { | ||||
|       fetchHouseholdSuggestionByAccompanyingPeriod(person.id) | ||||
|         .then(households => { | ||||
|           commit('setHouseholdSuggestionByAccompanyingPeriod', households); | ||||
|         }); | ||||
|     }, | ||||
|     fetchAddressSuggestions({ commit, state }) { | ||||
|       for (let i in state.concerned) { | ||||
|         fetchAddressSuggestionByPerson(state.concerned[i].person.id) | ||||
|           .then(addresses => { | ||||
|             commit('addAddressesSuggestion', addresses); | ||||
|           }) | ||||
|           .catch(e => { | ||||
|             console.log(e); | ||||
|           }); | ||||
|       } | ||||
|     }, | ||||
|     computeWarnings({ commit, state, getters }) { | ||||
|       let warnings = [], | ||||
|         payload; | ||||
|  | ||||
|       if (!getters.hasHousehold && !state.forceLeaveWithoutHousehold) { | ||||
|         warnings.push({ m: 'household_members_editor.add_destination', a: {} }); | ||||
|       } | ||||
|  | ||||
|       if (state.concerned.length === 0) { | ||||
|         warnings.push({ m: 'household_members_editor.add_at_least_onePerson', a: {} }); | ||||
|       } | ||||
|  | ||||
|       if (getters.concUnpositionned.length > 0 | ||||
|         && !state.forceLeaveWithoutHousehold) { | ||||
|         warnings.push({ m: 'household_members_editor.give_a_position_to_every_person', a: {} }) | ||||
|       } | ||||
|  | ||||
|       commit('setWarnings', warnings); | ||||
|     }, | ||||
|     confirm({ getters, state, commit }) { | ||||
|       let payload = getters.buildPayload, | ||||
|         errors = [], | ||||
|         person_id, | ||||
|         household_id, | ||||
|         error | ||||
|       ; | ||||
|  | ||||
|         householdMove(payload).then(household => { | ||||
|           if (household === null) { | ||||
|             person_id = getters.persons[0].id; | ||||
|             window.location.replace(`/fr/person/${person_id}/general`); | ||||
|           } else { | ||||
|             if (household.type === 'household') { | ||||
|               household_id = household.id; | ||||
|               // nothing to do anymore here, bye-bye ! | ||||
|               window.location.replace(`/fr/person/household/${household_id}/summary`); | ||||
|             } else { | ||||
|               // we assume the answer was 422... | ||||
|               error = household; | ||||
|               for (let i in error.violations) { | ||||
|                 let e = error.violations[i]; | ||||
|                 errors.push(e.title); | ||||
|               } | ||||
|  | ||||
|               commit('setErrors', errors); | ||||
|             } | ||||
|           } | ||||
|       }); | ||||
|     }, | ||||
|   } | ||||
| }); | ||||
|  | ||||
| store.dispatch('computeWarnings'); | ||||
| store.dispatch('fetchAddressSuggestions'); | ||||
|  | ||||
| if (concerned.length > 0) { | ||||
|   concerned.forEach(c => { | ||||
|     store.dispatch('fetchHouseholdSuggestionForConcerned', c.person); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| export { store }; | ||||
| @@ -68,12 +68,15 @@ | ||||
|                      </li> | ||||
|                   </ul> | ||||
|                </div> | ||||
|                <ul class="record_actions"> | ||||
|                <ul class="record_actions" v-if="availableForCheckGoal.length > 0"> | ||||
|                   <li> | ||||
|                      <button :title="$t('add_an_objective')" class="btn btn-create" | ||||
|                              @click="toggleAddObjective"></button> | ||||
|                   </li> | ||||
|                </ul> | ||||
|                <div v-else> | ||||
|                  <span class="chill-no-data-statement">{{ $t('no_goals_available') }}</span> | ||||
|                </div> | ||||
|             </div> | ||||
|             <div><!-- empty for results --></div> | ||||
|          </div> | ||||
| @@ -102,11 +105,14 @@ | ||||
|                   </li> | ||||
|                </ul> | ||||
|             </div> | ||||
|             <ul class="record_actions"> | ||||
|             <ul class="record_actions" v-if="evaluationsForAction.length > 0"> | ||||
|                <li> | ||||
|                   <button :title="$t('add_an_evaluation')" class="btn btn-create" @click="toggleAddEvaluation"></button> | ||||
|                </li> | ||||
|             </ul> | ||||
|            <div v-else> | ||||
|              <span class="chill-no-data-statement">{{ $t('no_evaluations_available') }}</span> | ||||
|            </div> | ||||
|          </div> | ||||
|       </div> | ||||
|  | ||||
| @@ -255,6 +261,8 @@ const i18n = { | ||||
|          choose_thirdparties: "Choisir des tiers", | ||||
|          fix_these_errors: "Veuillez corriger les erreurs suivantes :", | ||||
|          available_evaluations_text: "Évaluations disponibles pour ajout :", | ||||
|          no_evaluations_available: "Aucune évaluation disponible", | ||||
|          no_goals_available: "Aucun objectif disponible", | ||||
|       } | ||||
|    } | ||||
| }; | ||||
| @@ -378,7 +386,6 @@ export default { | ||||
|          this.$store.commit('removeGoal', g); | ||||
|       }, | ||||
|       addEvaluation(e) { | ||||
|          console.log('add Evaluation', e); | ||||
|          this.$store.commit('addEvaluation', e); | ||||
|       }, | ||||
|       toggleAddEvaluation() { | ||||
|   | ||||
| @@ -19,8 +19,20 @@ | ||||
|             <dt v-if="evaluation.warningInterval">{{ $t('warningInterval') }} :</dt> | ||||
|             <dd v-if="evaluation.warningInterval">{{ evaluation.warningInterval }}</dd> | ||||
|  | ||||
|             <dt v-if="evaluation.documents && evaluation.documents.length > 0">{{ $t('documents') }} :</dt> | ||||
|             <dd v-if="evaluation.documents && evaluation.documents.length > 0">{{ evaluation.documents.length }}</dd> | ||||
|             <template v-if="evaluation.documents.length > 0"> | ||||
|               <dt>{{ $t('documents') }} :</dt> | ||||
|               <dd> | ||||
|                 <ul> | ||||
|                   <li v-for="d in evaluation.documents"> | ||||
|                     {{ d.template.name.fr }} | ||||
|                     <a :href="buildEditLink(d.storedObject)" class="btn btn-action btn-sm"> | ||||
|                       <i class="fa fa-edit"></i> | ||||
|                     </a> | ||||
|                   </li> | ||||
|                 </ul> | ||||
|               </dd> | ||||
|  | ||||
|             </template> | ||||
|  | ||||
|          </dl> | ||||
|          <dl class="item-details"> | ||||
| @@ -82,7 +94,7 @@ export default { | ||||
|    computed: { | ||||
|       pickedEvaluations() { | ||||
|          return this.$store.state.evaluationsPicked; | ||||
|       } | ||||
|       }, | ||||
|    }, | ||||
|    methods: { | ||||
|       removeEvaluation(e) { | ||||
| @@ -95,7 +107,11 @@ export default { | ||||
|       }, | ||||
|       submitForm() { | ||||
|          this.toggleEditEvaluation(); | ||||
|       } | ||||
|       }, | ||||
|       buildEditLink(storedObject) { | ||||
|         return `/fr/chill_wopi/edit/${storedObject.filename}?returnPath=` + encodeURIComponent( | ||||
|             window.location.pathname + window.location.search + window.location.hash); | ||||
|       }, | ||||
|    } | ||||
| } | ||||
| </script> | ||||
|   | ||||
| @@ -67,19 +67,15 @@ | ||||
|             <div class="input-group"> | ||||
|                <select class="form-select form-select-sm" v-model="template"> | ||||
|                   <option disabled value="">{{ $t('evaluation_choose_a_template') }}</option> | ||||
|                   <option>A</option> | ||||
|                   <template v-for="t in getTemplatesAvailaibleForEvaluation"> | ||||
|                     <option  v-bind:value="t.id">{{ t.name.fr }}</option> | ||||
|                   </template> | ||||
|                </select> | ||||
|                <button class="btn btn-update btn-sm change-icon" type="button"><i class="fa fa-fw fa-cog"></i></button> | ||||
|               <button v-if="canGenerate" class="btn btn-update btn-sm change-icon" type="button" @click="generateDocument"><i class="fa fa-fw fa-cog"></i></button> | ||||
|               <button v-else class="btn btn-update btn-sm change-icon" type="button" disabled ><i class="fa fa-fw fa-cog"></i></button> | ||||
|             </div> | ||||
|          </div> | ||||
|       </div> | ||||
|  | ||||
|       <!--div class="row mb-3"> | ||||
|          <label class="col-sm-4 col-form-label">{{ $t('evaluation_add_a_document') }}</label> | ||||
|          <div class="col-sm-8"> | ||||
|             <button class="btn btn-create btn-sm" @click="documents"></button> | ||||
|          </div> | ||||
|       </div--> | ||||
|    </div> | ||||
| </div> | ||||
| </template> | ||||
| @@ -88,6 +84,7 @@ | ||||
| import {dateToISO, ISOToDate, ISOToDatetime} from 'ChillMainAssets/chill/js/date.js'; | ||||
| import CKEditor from '@ckeditor/ckeditor5-vue'; | ||||
| import ClassicEditor from 'ChillMainAssets/module/ckeditor5/index.js'; | ||||
| import { mapGetters, mapState } from 'vuex'; | ||||
|  | ||||
| const i18n = { | ||||
|    messages: { | ||||
| @@ -119,25 +116,19 @@ export default { | ||||
|    data() { | ||||
|       return { | ||||
|          editor: ClassicEditor, | ||||
|          //evaluation: { | ||||
|          //   status: null, | ||||
|          //   startDate: null, | ||||
|          //   endDate: null, | ||||
|          //   maxDate: null, | ||||
|          //   warningInterval: null, | ||||
|          //   comment: null, | ||||
|          //   template: null, | ||||
|          //   //documents: null | ||||
|          //} | ||||
|          template: null, | ||||
|       } | ||||
|    }, | ||||
|    computed: { | ||||
|       /* | ||||
|       status: { | ||||
|          get() { return this.evaluation.status; }, | ||||
|          set(v) { this.evaluation.status = v; } | ||||
|      ...mapGetters([ | ||||
|         'getTemplatesAvailaibleForEvaluation' | ||||
|      ]), | ||||
|      ...mapState([ | ||||
|        'isPosting' | ||||
|      ]), | ||||
|       canGenerate() { | ||||
|         return !this.$store.state.isPosting && this.template !== null; | ||||
|       }, | ||||
|       */ | ||||
|       startDate: { | ||||
|         get() { | ||||
|           return dateToISO(this.evaluation.startDate); | ||||
| @@ -171,10 +162,6 @@ export default { | ||||
|          get() { return this.evaluation.comment; }, | ||||
|          set(v) { this.$store.commit('setEvaluationComment', { key: this.evaluation.key, comment: v }); } | ||||
|       }, | ||||
|       template: { | ||||
|          get() { return this.evaluation.template; }, | ||||
|          set(v) { this.evaluation.template = v; } | ||||
|       }, | ||||
|    }, | ||||
|    methods: { | ||||
|       listAllStatus() { | ||||
| @@ -189,6 +176,10 @@ export default { | ||||
|          }) | ||||
|          ; | ||||
|       }, | ||||
|       generateDocument() { | ||||
|         console.log('template picked', this.template); | ||||
|         this.$store.dispatch('generateDocument', { key: this.evaluation.key, templateId: this.template}) | ||||
|       } | ||||
|    }, | ||||
|    mounted() { | ||||
|       //this.listAllStatus(); | ||||
|   | ||||
| @@ -4,6 +4,7 @@ import { findSocialActionsBySocialIssue } from 'ChillPersonAssets/vuejs/_api/Soc | ||||
| import { create } from 'ChillPersonAssets/vuejs/_api/AccompanyingCourseWork.js'; | ||||
|  | ||||
| const debug = process.env.NODE_ENV !== 'production'; | ||||
| const evalFQDN = encodeURIComponent("Chill\\PersonBundle\\Entity\\AccompanyingPeriod\\AccompanyingPeriodWorkEvaluation"); | ||||
|  | ||||
| const store = createStore({ | ||||
|   strict: debug, | ||||
| @@ -32,6 +33,7 @@ const store = createStore({ | ||||
|       return k; | ||||
|     }), | ||||
|     evaluationsForAction: [], | ||||
|     templatesAvailableForEvaluation: [], | ||||
|     personsPicked: window.accompanyingCourseWork.persons, | ||||
|     personsReachables: window.accompanyingCourseWork.accompanyingPeriod.participations.filter(p => p.endDate == null) | ||||
|       .map(p => p.person), | ||||
| @@ -63,6 +65,9 @@ const store = createStore({ | ||||
|     hasThirdParties(state) { | ||||
|       return state.thirdParties.length > 0; | ||||
|     }, | ||||
|     getTemplatesAvailaibleForEvaluation(state) { | ||||
|       return state.templatesAvailableForEvaluation; | ||||
|     }, | ||||
|     buildPayload(state) { | ||||
|       return { | ||||
|         type: 'accompanying_period_work', | ||||
| @@ -99,6 +104,7 @@ const store = createStore({ | ||||
|         accompanyingPeriodWorkEvaluations: state.evaluationsPicked.map(e => { | ||||
|           let o = { | ||||
|             type: e.type, | ||||
|             key: e.key, | ||||
|             evaluation: { | ||||
|               id: e.evaluation.id, | ||||
|               type: e.evaluation.type | ||||
| @@ -216,6 +222,11 @@ const store = createStore({ | ||||
|       let evaluation = state.evaluationsPicked.find(e => e.key === key); | ||||
|       evaluation.editEvaluation = !evaluation.editEvaluation; | ||||
|     }, | ||||
|     setTemplatesAvailableForEvaluation(state, templates) { | ||||
|       for (let i in templates) { | ||||
|         state.templatesAvailableForEvaluation.push(templates[i]); | ||||
|       } | ||||
|     }, | ||||
|     setPersonsPickedIds(state, ids) { | ||||
|       state.personsPicked = state.personsReachables | ||||
|         .filter(p => ids.includes(p.id)) | ||||
| @@ -317,7 +328,38 @@ const store = createStore({ | ||||
|           commit('setEvaluationsForAction', data.results); | ||||
|         }); | ||||
|     }, | ||||
|     submit({ getters, state, commit }) { | ||||
|     getReachableTemplatesForEvaluation({commit}) { | ||||
|       const | ||||
|           url = `/fr/doc/gen/templates/for/${evalFQDN}` | ||||
|       ; | ||||
|       window.fetch(url).then(r => { | ||||
|         if (r.ok) { | ||||
|           return r.json(); | ||||
|         } | ||||
|         throw new Error("not possible to load templates for evaluations") | ||||
|       }).then(data => { | ||||
|         commit('setTemplatesAvailableForEvaluation', data.results); | ||||
|       }).catch(e => { | ||||
|         console.error(e); | ||||
|       }) | ||||
|     }, | ||||
|     generateDocument({ dispatch }, {key, templateId}) { | ||||
|       const callback = function(data) { | ||||
|         // get the evaluation id from the data | ||||
|         const | ||||
|             evaluationId = data.accompanyingPeriodWorkEvaluations.find(e => e.key === key).id, | ||||
|             returnPath = encodeURIComponent(window.location.pathname + window.location.search + window.location.hash), | ||||
|             url = `/fr/doc/gen/generate/from/${templateId}/for/${evalFQDN}/${evaluationId}?returnPath=${returnPath}` | ||||
|         ; | ||||
|         //http://localhost:8001/fr/doc/gen/generate/from/12/for/Chill%5CPersonBundle%5CEntity%5CAccompanyingPeriod%5CAccompanyingPeriodWorkEvaluation/41 | ||||
|  | ||||
|         console.log('I will generate your doc at', url); | ||||
|         window.location.assign(url); | ||||
|       }; | ||||
|  | ||||
|       dispatch('submit', callback); | ||||
|     }, | ||||
|     submit({ getters, state, commit }, callback) { | ||||
|       let | ||||
|         payload = getters.buildPayload, | ||||
|         url = `/api/1.0/person/accompanying-course/work/${state.work.id}.json`, | ||||
| @@ -345,6 +387,8 @@ const store = createStore({ | ||||
|           } | ||||
|           commit('setErrors', errors); | ||||
|           commit('setIsPosting', false); | ||||
|         } else if (typeof(callback) !== 'undefined') { | ||||
|           callback(data); | ||||
|         } else { | ||||
|           console.info('nothing to do here, bye bye'); | ||||
|           window.location.assign(`/fr/person/accompanying-period/${state.work.accompanyingPeriod.id}/work`); | ||||
| @@ -360,6 +404,7 @@ const store = createStore({ | ||||
|       dispatch('getReachablesResultsForAction'); | ||||
|       dispatch('getReachablesGoalsForAction'); | ||||
|       dispatch('getReachablesEvaluationsForAction'); | ||||
|       dispatch('getReachableTemplatesForEvaluation'); | ||||
|     }, | ||||
|   } | ||||
| }); | ||||
|   | ||||
| @@ -7,35 +7,43 @@ | ||||
|     </div> | ||||
|     <div v-if="isHouseholdNew && !hasHouseholdAddress"> | ||||
|  | ||||
|       <h3>À quelle adresse habite ce ménage ?</h3> | ||||
|       <h3 >À quelle adresse habite ce ménage ?</h3> | ||||
|  | ||||
|       <ul v-if="filterAddressesSuggestion.length > 0"> | ||||
|         <li v-for="a in filterAddressesSuggestion"> | ||||
|           <show-address :address="a"></show-address>  | ||||
|           <button class="btn" @click="setHouseholdAddress(a)"> | ||||
|       <div v-if="filterAddressesSuggestion.length > 0" class="flex-table householdAddressSuggestionList"> | ||||
|         <div v-for="a in filterAddressesSuggestion" class="item-bloc"> | ||||
|           <show-address :address="a"></show-address> | ||||
|           <button class="btn btn-action" @click="setHouseholdAddress(a)"> | ||||
|             Le ménage habite cette adresse | ||||
|           </button> | ||||
|         </li> | ||||
|       </ul> | ||||
|         </div> | ||||
|       </div> | ||||
|         <div v-else> | ||||
|           <span class="chill-no-data-statement">Aucune adresse à suggérer</span> | ||||
|       </div> | ||||
|  | ||||
|       <ul class="record_actions"> | ||||
|         <li > | ||||
|           <button class="btn"> | ||||
|             Créer une adresse | ||||
|           </button> | ||||
|           <add-address | ||||
|               :context="addAddress.context" | ||||
|               :key="addAddress.key" | ||||
|               :options="addAddress.options" | ||||
|               :result="addAddress.result" | ||||
|               @submitAddress="setHouseholdCreatedAddress" | ||||
|               ref="addAddress"> | ||||
|           </add-address> | ||||
|         </li> | ||||
|       </ul> | ||||
|        | ||||
|  | ||||
|     </div> | ||||
|     <div v-if="isHouseholdNew && hasHouseholdAddress"> | ||||
|       <ul class="record_actions"> | ||||
|         <li > | ||||
|           <button class="btn" @click="removeHouseholdAddress"> | ||||
|           <button class="btn btn-misc" @click="removeHouseholdAddress"> | ||||
|             Supprimer cette adresse | ||||
|           </button> | ||||
|         </li> | ||||
|       </ul> | ||||
|        | ||||
|  | ||||
|     </div> | ||||
|   </div> | ||||
|   <div v-else-if="isForceLeaveWithoutHousehold"> | ||||
| @@ -51,7 +59,7 @@ | ||||
|         class="btn btn-misc" | ||||
|         @click="toggleHouseholdSuggestion" | ||||
|       > | ||||
|         {{ $tc('household_members_editor.show_household_suggestion',  | ||||
|         {{ $tc('household_members_editor.show_household_suggestion', | ||||
|           countHouseholdSuggestion) }} | ||||
|       </button> | ||||
|     </li> | ||||
| @@ -106,11 +114,24 @@ | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
|    | ||||
|  | ||||
| </template> | ||||
|  | ||||
| <style lang="scss"> | ||||
|  | ||||
| div.householdAddressSuggestionList { | ||||
|   /* | ||||
|   display: flex; | ||||
|   list-style-type: none; | ||||
|   padding: 0; | ||||
|  | ||||
|    */ | ||||
|  | ||||
|   & > li { | ||||
|  | ||||
|   } | ||||
| } | ||||
|  | ||||
| .householdSuggestionList { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| @@ -136,12 +157,45 @@ | ||||
| import { mapGetters, mapState } from 'vuex'; | ||||
| import HouseholdViewer from 'ChillPersonAssets/vuejs/_components/Household/Household.vue'; | ||||
| import ShowAddress from 'ChillMainAssets/vuejs/Address/components/ShowAddress.vue'; | ||||
| import AddAddress from 'ChillMainAssets/vuejs/Address/components/AddAddress.vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'Household', | ||||
|   components: { | ||||
|     HouseholdViewer, | ||||
|     ShowAddress, | ||||
|     AddAddress, | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       addAddress: { | ||||
|         context: { | ||||
|           entity: { | ||||
|             type: 'household_create', | ||||
|             id: 0 | ||||
|           }, | ||||
|           edit: false, | ||||
|           addressId: null | ||||
|         }, | ||||
|         key: 'household_new', | ||||
|         options: { | ||||
|           hideDateFrom: true, | ||||
|           bindModal: { | ||||
|  | ||||
|           }, | ||||
|           button: { | ||||
|             text:  { | ||||
|               create: null, | ||||
|               edit: null, | ||||
|             } | ||||
|           }, | ||||
|           title: { | ||||
|             create: null, | ||||
|             edit: null, | ||||
|           }, | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   computed: { | ||||
|     ...mapGetters([ | ||||
| @@ -172,13 +226,13 @@ export default { | ||||
|     allowRemoveHousehold() { | ||||
|       return this.$store.getters.hasHousehold && | ||||
|         ( | ||||
|           this.allowHouseholdCreate || this.allowHouseholdSearch ||  | ||||
|           this.allowHouseholdCreate || this.allowHouseholdSearch || | ||||
|             this.allowLeaveWithoutHousehold | ||||
|         ) | ||||
|       ; | ||||
|     }, | ||||
|     allowChangeHousehold() { | ||||
|       return this.allowHouseholdCreate || this.allowHouseholdSearch ||  | ||||
|       return this.allowHouseholdCreate || this.allowHouseholdSearch || | ||||
|         this.allowLeaveWithoutHousehold; | ||||
|     }, | ||||
|     isForceLeaveWithoutHousehold() { | ||||
| @@ -202,8 +256,15 @@ export default { | ||||
|       this.$store.dispatch('removeHousehold'); | ||||
|     }, | ||||
|     setHouseholdAddress(a) { | ||||
|       let payload = this.$refs.addAddress.submitNewAddress(); | ||||
|       console.log('setHouseholdAddress', a); | ||||
|       this.$store.commit('setHouseholdAddress', a); | ||||
|     }, | ||||
|     setHouseholdCreatedAddress() { | ||||
|       let payload = this.$refs.addAddress.submitNewAddress(); | ||||
|       console.log('setHouseholdAddress', payload); | ||||
|       this.$store.dispatch('setHouseholdNewAddress', payload); | ||||
|     }, | ||||
|     removeHouseholdAddress() { | ||||
|       this.$store.commit('removeHouseholdAddress'); | ||||
|     } | ||||
|   | ||||
| @@ -14,8 +14,6 @@ const concerned = window.household_members_editor_data.persons.map(p => { | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| console.log('expand suggestions', window.household_members_editor_expand_suggestions === 1); | ||||
|  | ||||
| const store = createStore({ | ||||
|   strict: debug, | ||||
|   state: { | ||||
| @@ -211,7 +209,12 @@ const store = createStore({ | ||||
|       ) | ||||
|     }, | ||||
|     createHousehold(state) { | ||||
|       state.household = { type: 'household', members: [], current_address: null, current_members_id: [] } | ||||
|       state.household = { | ||||
|         type: 'household', | ||||
|         members: [], | ||||
|         current_address: null, | ||||
|         current_members_id: [] | ||||
|       }; | ||||
|       state.forceLeaveWithoutHousehold = false; | ||||
|     }, | ||||
|     removeHousehold(state) { | ||||
| @@ -274,7 +277,7 @@ const store = createStore({ | ||||
|           state.addressesSuggestion.push(addresses[i]); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     }, | ||||
|   }, | ||||
|   actions: { | ||||
|     addConcerned({ commit, dispatch }, person) { | ||||
| @@ -307,6 +310,19 @@ const store = createStore({ | ||||
|       commit('createHousehold'); | ||||
|       dispatch('computeWarnings'); | ||||
|     }, | ||||
|     setHouseholdNewAddress({ commit }, payload) { | ||||
|       let url = `/api/1.0/main/address/${payload.addressId}.json`; | ||||
|       window.fetch(url).then(r => { | ||||
|         if (r.ok) { | ||||
|           return r.json(); | ||||
|         } | ||||
|         throw new Error("error while fetch address"); | ||||
|       }).then(data => { | ||||
|         commit('setHouseholdAddress', data); | ||||
|       }).catch(e => { | ||||
|         console.error(e); | ||||
|       }); | ||||
|     }, | ||||
|     forceLeaveWithoutHousehold({ commit, dispatch }) { | ||||
|       commit('forceLeaveWithoutHousehold'); | ||||
|       dispatch('computeWarnings'); | ||||
| @@ -373,8 +389,14 @@ const store = createStore({ | ||||
|           } else { | ||||
|             if (household.type === 'household') { | ||||
|               household_id = household.id; | ||||
|  | ||||
|               // nothing to do anymore here, bye-bye ! | ||||
|               window.location.replace(`/fr/person/household/${household_id}/summary`); | ||||
|               let params = new URLSearchParams(window.location.search); | ||||
|               if (params.has('returnPath')) { | ||||
|                 window.location.replace(params.get('returnPath')); | ||||
|               } else { | ||||
|                 window.location.replace(`/fr/person/household/${household_id}/summary`); | ||||
|               } | ||||
|             } else { | ||||
|               // we assume the answer was 422... | ||||
|               error = household; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|       {{ $t('household_number', { number: household.id } ) }} | ||||
|     </div> | ||||
|  | ||||
|     <!-- member part -->  | ||||
|     <!-- member part --> | ||||
|     <div v-if="hasCurrentMembers" class="members"> | ||||
|       <span class="current-members">{{ $t('current_members') }}: </span> | ||||
|       <template v-for="(m, index) in currentMembers()" :key="m.id"> | ||||
| @@ -40,7 +40,7 @@ | ||||
| <style lang="scss"> | ||||
| .chill-entity__household { | ||||
|   display: grid; | ||||
|   grid-template-areas:  | ||||
|   grid-template-areas: | ||||
|     "identifier identifier where" | ||||
|     "who        who        where" | ||||
|     ; | ||||
| @@ -50,7 +50,7 @@ | ||||
|  | ||||
|   .identifier { | ||||
|     grid-area: identifier; | ||||
|      | ||||
|  | ||||
|     font-size: 1.3em; | ||||
|     font-weight: 700; | ||||
|     color: var(--chill-blue); | ||||
| @@ -110,7 +110,7 @@ export default { | ||||
|       return this.household.members.filter(m => this.household.current_members_id.includes(m.id)) | ||||
|         .sort((a, b) => { | ||||
|           if (a.position.ordering < b.position.ordering) { | ||||
|             return -1;   | ||||
|             return -1; | ||||
|           } | ||||
|           if (a.position.ordering > b.position.ordering) { | ||||
|             return 1; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ | ||||
|  | ||||
|     <div id="withoutHouseholdList" class="collapse p-3"> | ||||
|         <form method="GET" | ||||
|               action="{{ chill_path_add_return_path('chill_person_household_members_editor') }}"> | ||||
|               action="{{ path('chill_person_household_members_editor') }}"> | ||||
|  | ||||
|             <h3>{{ 'household.Select people to move'|trans }}</h3> | ||||
|             <ul> | ||||
| @@ -27,6 +27,7 @@ | ||||
|             </ul> | ||||
|  | ||||
|             <input type="hidden" name="expand_suggestions" value="true" /> | ||||
|             <input type="hidden" name="returnPath" value="{{ app.request.requestUri|escape('html_attr') }}" /> | ||||
|             <input type="hidden" name="accompanying_period_id" value="{{ accompanyingCourse.id }}" /> | ||||
|             <ul class="record_actions mb-0"> | ||||
|                 <li> | ||||
|   | ||||
| @@ -1,5 +1,61 @@ | ||||
| <div class="alert alert-danger alert-with-actions mb-0"> | ||||
|     <div class="message"> | ||||
|         {{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }} | ||||
| {%- set countPersonLocation = accompanyingCourse.availablePersonLocation|length -%} | ||||
| {%- set hasPersonLocation = countPersonLocation|length > 0 -%} | ||||
| {% macro quickLocationForm(accompanyingCourse, person, whichButton) %} | ||||
|     <form method="PATCH" action="{{ path('chill_api_single_accompanying_course__entity', {'id': accompanyingCourse.id, '_format': 'json'}) }}" class="quickLocationForm"> | ||||
|         <input type="hidden" name="personLocation" value="{{ person.id }}" /> | ||||
|         <input type="hidden" name="periodId" value="{{ accompanyingCourse.id }}" /> | ||||
|         {% if whichButton == 'string' %} | ||||
|         <button type="submit" class="btn btn-chill-pink"> | ||||
|             <span class="text-light">{{ 'Locate by'|trans }} {{ person|chill_entity_render_string }}</span> | ||||
|         </button> | ||||
|         {% elseif whichButton == 'icon' %} | ||||
|             <button type="submit" class="btn btn-sm btn-secondary"> | ||||
|                 <i class="fa fa-map-marker"></i> | ||||
|             </button> | ||||
|         {% endif %} | ||||
|     </form> | ||||
| {% endmacro %} | ||||
| <div class="border border-danger"> | ||||
|     <div class="alert alert-danger {% if hasPersonLocation %}alert-with-actions{% endif %} mb-0"> | ||||
|         <div class="message"> | ||||
|             {{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }} | ||||
|             {% if not hasPersonLocation %} | ||||
|             {{ 'Associate at least one member with an household, and set an address to this household'|trans }} | ||||
|             {% endif %} | ||||
|         </div> | ||||
|  | ||||
|         {% if 1 == countPersonLocation %} | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     {{ _self.quickLocationForm(accompanyingCourse, accompanyingCourse.availablePersonLocation.first, 'string') }} | ||||
|                 </li> | ||||
|             </ul> | ||||
|         {% elseif 1 < countPersonLocation %} | ||||
|             <ul class="record_actions"> | ||||
|                 <li> | ||||
|                     <button class="btn btn-chill-pink" data-bs-toggle="collapse" href="#locateAtPerson"> | ||||
|                         <i class="fa fa-fw fa-caret-down"></i><span class="text-light">{{ 'Choose a person to locate by'|trans }}</span> | ||||
|                     </button> | ||||
|                 </li> | ||||
|             </ul> | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     {% if 1 < countPersonLocation %} | ||||
|         <div id="locateAtPerson" class="collapse"> | ||||
|                <p>{{ 'Locate by'|trans }}:</p> | ||||
|  | ||||
|                <div class="flex-table mb-3"> | ||||
|                    {% for p in accompanyingCourse.availablePersonLocation %} | ||||
|                        <div class="item-bloc"> | ||||
|                          {{ p|chill_entity_render_box({ | ||||
|                           'render': 'bloc', 'addLink': false, 'addInfo': true, 'addAltNames': false, 'customButtons': { | ||||
|                                  'replace': _self.quickLocationForm(accompanyingCourse, p, 'icon') | ||||
|                              } | ||||
|                          }) }} | ||||
|                        </div> | ||||
|                    {% endfor %} | ||||
|                </div> | ||||
|         </div> | ||||
|     {% endif %} | ||||
| </div> | ||||
|   | ||||
| @@ -15,6 +15,11 @@ | ||||
|     {% endif %} | ||||
| {% endmacro %} | ||||
|  | ||||
| {% block js %} | ||||
| {{ parent() }} | ||||
| {{ encore_entry_script_tags('page_accompanying_course_index_person_locate') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block content %} | ||||
| <div class="accompanyingcourse-resume"> | ||||
|  | ||||
| @@ -27,11 +32,6 @@ | ||||
|                 </a> | ||||
|             </span> | ||||
|         </div> | ||||
|         {% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %} | ||||
|             <div class="alert alert-danger"> | ||||
|                 {{ 'This course is located at a temporarily address. You should locate this course to an user'|trans }} | ||||
|             </div> | ||||
|         {% endif %} | ||||
|     {% endif %} | ||||
|  | ||||
|     <h1>{{ 'Resume Accompanying Course'|trans }}</h1> | ||||
| @@ -54,12 +54,29 @@ | ||||
|             {% if withoutHousehold|length > 0 %} | ||||
|                 {% include '@ChillPerson/AccompanyingCourse/_join_household.html.twig' with {} %} | ||||
|             {% endif %} | ||||
|             {% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %} | ||||
|                 {% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %} | ||||
|             {% endif %} | ||||
|         {% endif %} | ||||
|     </div> | ||||
|  | ||||
|     <div class="location mb-5"> | ||||
|         <h2 class="mb-3">{{ 'Accompanying course location'|trans }}</h2> | ||||
|  | ||||
|         {% if accompanyingCourse.locationStatus == 'person' %} | ||||
|             <p>{{ 'This course is located by'|trans }}: {{ accompanyingCourse.personLocation|chill_entity_render_string }}</p> | ||||
|         {%  elseif accompanyingCourse.locationStatus == 'address' %} | ||||
|             <p>{{ 'This course has a temporarily location'|trans }}</p> | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if accompanyingCourse.locationStatus != 'none' %} | ||||
|             {{ accompanyingCourse.location|chill_entity_render_box }} | ||||
|         {% endif %} | ||||
|  | ||||
|         {% if accompanyingCourse.locationStatus == 'address' or accompanyingCourse.locationStatus == 'none' %} | ||||
|             {% include '@ChillPerson/AccompanyingCourse/_warning_address.html.twig' with {} %} | ||||
|         {% endif %} | ||||
|  | ||||
|  | ||||
|     </div> | ||||
|  | ||||
|     <div class="requestor mb-5"> | ||||
|         <h2 class="mb-3">{{ 'Requestor'|trans }}</h2> | ||||
|         {% if accompanyingCourse.requestorPerson is not empty %} | ||||
|   | ||||
| @@ -17,6 +17,10 @@ | ||||
|         'layout': '@ChillPerson/menu.html.twig', | ||||
|         'args' : { 'accompanyingCourse': accompanyingCourse } | ||||
|     }) }} | ||||
|      | ||||
|     {% block block_post_menu %} | ||||
|     {% endblock %} | ||||
|  | ||||
| {% endblock %} | ||||
|  | ||||
| {% block css %} | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| {% extends accompanyingCourse != null ? '@ChillPerson/AccompanyingCourse/layout.html.twig'  | ||||
| {% extends accompanyingCourse != null ? '@ChillPerson/AccompanyingCourse/layout.html.twig' | ||||
|   : '@ChillMain/layout.html.twig' %} | ||||
|  | ||||
| {% block title 'household.Edit household members'|trans  %} | ||||
| @@ -13,11 +13,12 @@ | ||||
| {% endblock %} | ||||
|  | ||||
| {% block js %} | ||||
|   <script type="text/javascript"> | ||||
|     window.household_members_editor_data = {{ data|json_encode|raw }}; | ||||
|     window.household_members_editor_expand_suggestions = {{ expandSuggestions }}; | ||||
|   </script> | ||||
|   {{ encore_entry_script_tags('vue_household_members_editor') }} | ||||
|     {{ parent() }} | ||||
|     <script type="text/javascript"> | ||||
|       window.household_members_editor_data = {{ data|json_encode|raw }}; | ||||
|       window.household_members_editor_expand_suggestions = {{ expandSuggestions }}; | ||||
|     </script> | ||||
|     {{ encore_entry_script_tags('vue_household_members_editor') }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block css %} | ||||
|   | ||||
| @@ -16,4 +16,5 @@ module.exports = function(encore, entries) | ||||
|  | ||||
|     encore.addEntry('page_household_edit_metadata', __dirname + '/Resources/public/page/household_edit_metadata/index.js'); | ||||
|     encore.addEntry('page_person', __dirname + '/Resources/public/page/person/index.js'); | ||||
|     encore.addEntry('page_accompanying_course_index_person_locate', __dirname + '/Resources/public/page/accompanying_course_index/person_locate.js'); | ||||
| }; | ||||
|   | ||||
| @@ -0,0 +1,39 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\Migrations\Person; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| /** | ||||
|  * Add a link between evaluation document, stored object, and document template | ||||
|  */ | ||||
| final class Version20210820093927 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add a link between evaluation document, stored object, and document template'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD template_id INT DEFAULT NULL'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD storedObject_id INT DEFAULT NULL'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD CONSTRAINT FK_33EC92296C99C13A FOREIGN KEY (storedObject_id) REFERENCES chill_doc.stored_object (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document ADD CONSTRAINT FK_33EC92295DA0FB8 FOREIGN KEY (template_id) REFERENCES chill_docgen_template (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); | ||||
|         $this->addSql('CREATE INDEX IDX_33EC92296C99C13A ON chill_person_accompanying_period_work_evaluation_document (storedObject_id)'); | ||||
|         $this->addSql('CREATE INDEX IDX_33EC92295DA0FB8 ON chill_person_accompanying_period_work_evaluation_document (template_id)'); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP CONSTRAINT FK_33EC92296C99C13A'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP CONSTRAINT FK_33EC92295DA0FB8'); | ||||
|         $this->addSql('DROP INDEX IDX_33EC92296C99C13A'); | ||||
|         $this->addSql('DROP INDEX IDX_33EC92295DA0FB8'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP template_id'); | ||||
|         $this->addSql('ALTER TABLE chill_person_accompanying_period_work_evaluation_document DROP storedObject_id'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,29 @@ | ||||
| <?php | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\Migrations\Person; | ||||
|  | ||||
| use Doctrine\DBAL\Schema\Schema; | ||||
| use Doctrine\Migrations\AbstractMigration; | ||||
|  | ||||
| /** | ||||
|  * Add missing sequence for evaluation documents | ||||
|  */ | ||||
| final class Version20210820100407 extends AbstractMigration | ||||
| { | ||||
|     public function getDescription(): string | ||||
|     { | ||||
|         return 'Add missing sequence for evaluation documents'; | ||||
|     } | ||||
|  | ||||
|     public function up(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('CREATE SEQUENCE chill_person_social_work_eval_doc_id_seq INCREMENT BY 1 MINVALUE 1000 START 1000'); | ||||
|     } | ||||
|  | ||||
|     public function down(Schema $schema): void | ||||
|     { | ||||
|         $this->addSql('DROP SEQUENCE chill_person_social_work_eval_doc_id_seq'); | ||||
|     } | ||||
| } | ||||
| @@ -376,6 +376,12 @@ Edit Accompanying Course: Modifier le parcours | ||||
| Create Accompanying Course: Créer un nouveau parcours | ||||
| Drop Accompanying Course: Supprimer le parcours | ||||
| This course is located at a temporarily address. You should locate this course to an user: Ce parcours est localisé à une adresse temporaire. Il devrait être localisé auprès d'un usager concerné. | ||||
| Accompanying course location: Localisation du parcours | ||||
| This course is located by: Ce parcours est localisé auprès de | ||||
| This course has a temporarily location: Ce parcours a une localisation temporaire | ||||
| Choose a person to locate by: Localiser auprès d'un usager concerné | ||||
| Associate at least one member with an household, and set an address to this household: Associez au moins un membre du parcours à un ménage, et indiquez une adresse à ce ménage. | ||||
| Locate by: Localiser auprès de | ||||
|  | ||||
| # Household | ||||
| Household: Ménage | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/Bundle/ChillWopiBundle/.editorconfig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Bundle/ChillWopiBundle/.editorconfig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| ; top-most EditorConfig file | ||||
| root = true | ||||
|  | ||||
| ; Unix-style newlines | ||||
| [*] | ||||
| charset = utf-8 | ||||
| end_of_line = LF | ||||
| insert_final_newline = true | ||||
| trim_trailing_whitespace = true | ||||
|  | ||||
| [*.{php,html,twig}] | ||||
| indent_style = space | ||||
| indent_size = 4 | ||||
|  | ||||
| [*.md] | ||||
| max_line_length = 80 | ||||
|  | ||||
| [COMMIT_EDITMSG] | ||||
| max_line_length = 0 | ||||
							
								
								
									
										11
									
								
								src/Bundle/ChillWopiBundle/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/Bundle/ChillWopiBundle/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| composer.lock | ||||
| vendor/* | ||||
| parameters.yml | ||||
| *~ | ||||
| *.DS_Store | ||||
| *.sass-cache | ||||
| Resources/node_modules/ | ||||
| Tests/Fixtures/App/app/config/parameters.yml | ||||
| /nbproject/private/ | ||||
| Resources/test/Fixtures/App/bootstrap.php.cache | ||||
|  | ||||
							
								
								
									
										661
									
								
								src/Bundle/ChillWopiBundle/LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										661
									
								
								src/Bundle/ChillWopiBundle/LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,661 @@ | ||||
|                     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/>. | ||||
							
								
								
									
										3
									
								
								src/Bundle/ChillWopiBundle/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/Bundle/ChillWopiBundle/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # ChillWopiBundle | ||||
|  | ||||
| Read documentation here : http://chill.readthedocs.org | ||||
							
								
								
									
										6
									
								
								src/Bundle/ChillWopiBundle/chill.webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Bundle/ChillWopiBundle/chill.webpack.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| // this file loads all assets from the Chill person bundle | ||||
| module.exports = function(encore, entries) | ||||
| { | ||||
|     encore.addEntry('page_wopi_editor', __dirname + '/src/Resources/public/page/editor/index.js'); | ||||
|     //home/julien/dev/département-vendee/chill/vendor/chill-project/chill-bundles/src/Bundle/ChillWopiBundle/src/Resources/public/page/editor/index.js | ||||
| }; | ||||
							
								
								
									
										29
									
								
								src/Bundle/ChillWopiBundle/composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/Bundle/ChillWopiBundle/composer.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| { | ||||
|     "name": "chill-project/chill-wopi-bundle", | ||||
|     "type": "symfony-bundle", | ||||
|     "description": "TODO", | ||||
|     "keywords": [ | ||||
|         "chill", | ||||
|         "wopi" | ||||
|     ], | ||||
|     "homepage": "http://www.champs-libres.coop", | ||||
|     "license": "AGPL-3.0", | ||||
|     "authors": [ | ||||
|         { | ||||
|             "name": "Champs-Libres", | ||||
|             "email": "info@champs-libres.coop", | ||||
|             "homepage": "http://www.champs-libres.coop" | ||||
|         } | ||||
|     ], | ||||
|     "require": { | ||||
|         "php": ">= 7.4", | ||||
|         "champs-libres/wopi-bundle": "dev-master", | ||||
|         "nyholm/psr7": "^1.4", | ||||
|         "php-opencloud/openstack": "^3.2.1" | ||||
|     }, | ||||
|     "autoload": { | ||||
|         "psr-4": { | ||||
|             "Chill\\WopiBundle\\": "src/" | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/Bundle/ChillWopiBundle/grumphp.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Bundle/ChillWopiBundle/grumphp.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| imports: | ||||
|   - { resource: vendor/drupol/php-conventions/config/php73/grumphp.yml } | ||||
|  | ||||
| parameters: | ||||
|   tasks.license.holder: Champs-Libres | ||||
|   tasks.license.date_from: 2021 | ||||
|   tasks.license.name: AGPL-3.0 | ||||
|   extra_tasks: | ||||
|     phpunit: ~ | ||||
							
								
								
									
										15
									
								
								src/Bundle/ChillWopiBundle/psalm.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/Bundle/ChillWopiBundle/psalm.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| <?xml version="1.0"?> | ||||
| <psalm | ||||
|     errorLevel="3" | ||||
|     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" | ||||
| > | ||||
|     <projectFiles> | ||||
|         <directory name="src" /> | ||||
|         <ignoreFiles> | ||||
|             <directory name="vendor" /> | ||||
|         </ignoreFiles> | ||||
|     </projectFiles> | ||||
| </psalm> | ||||
							
								
								
									
										16
									
								
								src/Bundle/ChillWopiBundle/src/ChillWopiBundle.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/Bundle/ChillWopiBundle/src/ChillWopiBundle.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle; | ||||
|  | ||||
| use Symfony\Component\HttpKernel\Bundle\Bundle; | ||||
|  | ||||
| final class ChillWopiBundle extends Bundle | ||||
| { | ||||
| } | ||||
							
								
								
									
										103
									
								
								src/Bundle/ChillWopiBundle/src/Controller/Test.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/Bundle/ChillWopiBundle/src/Controller/Test.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\Controller; | ||||
|  | ||||
| use ChampsLibres\WopiLib\Configuration\WopiConfigurationInterface; | ||||
| use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface; | ||||
| use Chill\DocStoreBundle\Repository\StoredObjectRepository; | ||||
| use Chill\WopiBundle\Service\Controller\ResponderInterface; | ||||
| use Exception; | ||||
| use loophp\psr17\Psr17Interface; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | ||||
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||||
| use Symfony\Component\Routing\RouterInterface; | ||||
| use Symfony\Component\Security\Core\Security; | ||||
|  | ||||
| final class Test | ||||
| { | ||||
|     private StoredObjectRepository $storedObjectRepository; | ||||
|  | ||||
|     private WopiDiscoveryInterface $wopiDiscovery; | ||||
|  | ||||
|     private WopiConfigurationInterface $wopiConfiguration; | ||||
|  | ||||
|     private ResponderInterface $responder; | ||||
|  | ||||
|     private Security $security; | ||||
|  | ||||
|     private Psr17Interface $psr17; | ||||
|  | ||||
|     private RouterInterface $router; | ||||
|  | ||||
|     public function __construct( | ||||
|         StoredObjectRepository $storedObjectRepository, | ||||
|         WopiConfigurationInterface $wopiConfiguration, | ||||
|         WopiDiscoveryInterface $wopiDiscovery, | ||||
|         ResponderInterface $responder, | ||||
|         Security $security, | ||||
|         Psr17Interface $psr17, | ||||
|         RouterInterface $router | ||||
|     ) { | ||||
|         $this->storedObjectRepository = $storedObjectRepository; | ||||
|         $this->wopiConfiguration = $wopiConfiguration; | ||||
|         $this->wopiDiscovery = $wopiDiscovery; | ||||
|         $this->responder = $responder; | ||||
|         $this->security = $security; | ||||
|         $this->psr17 = $psr17; | ||||
|         $this->router = $router; | ||||
|     } | ||||
|  | ||||
|     public function __invoke(string $fileId): Response | ||||
|     { | ||||
|         $configuration = $this->wopiConfiguration->jsonSerialize(); | ||||
|  | ||||
|         $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); | ||||
|  | ||||
|         if (null === $storedObject) { | ||||
|             throw new NotFoundHttpException(sprintf('Unable to find object named %s', $fileId)); | ||||
|         } | ||||
|  | ||||
|         if ([] === $discoverExtension = $this->wopiDiscovery->discoverMimeType($storedObject->getType())) { | ||||
|             throw new Exception(sprintf('Unable to find mime type %s', $storedObject->getType())); | ||||
|         } | ||||
|  | ||||
|         $configuration['favIconUrl'] = ''; | ||||
|         $configuration['access_token'] = $this->security->getUser()->getUsername(); | ||||
|  | ||||
|         $configuration['server'] = $this | ||||
|             ->psr17 | ||||
|             ->createUri($discoverExtension[0]['urlsrc']) | ||||
|             ->withQuery( | ||||
|                 http_build_query( | ||||
|                     [ | ||||
|                         'WOPISrc' => $this | ||||
|                             ->router | ||||
|                             ->generate( | ||||
|                                 'checkFileInfo', | ||||
|                                 [ | ||||
|                                     'fileId' => $storedObject->getFilename(), | ||||
|                                 ], | ||||
|                                 UrlGeneratorInterface::ABSOLUTE_URL | ||||
|                             ), | ||||
|                         'closebutton' => 1 | ||||
|                     ] | ||||
|                 ) | ||||
|             ); | ||||
|  | ||||
|         return $this | ||||
|             ->responder | ||||
|             ->render( | ||||
|                 '@ChillWopi/Editor/page.html.twig', | ||||
|                 $configuration | ||||
|             ); | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\DependencyInjection; | ||||
|  | ||||
| use Symfony\Component\Config\FileLocator; | ||||
| use Symfony\Component\DependencyInjection\ContainerBuilder; | ||||
| use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; | ||||
| use Symfony\Component\HttpKernel\DependencyInjection\Extension; | ||||
|  | ||||
| final class ChillWopiExtension extends Extension | ||||
| { | ||||
|     public function load(array $configs, ContainerBuilder $container) | ||||
|     { | ||||
|         $config = $this->processConfiguration(new Configuration(), $configs); | ||||
|  | ||||
|         $container | ||||
|             ->setParameter( | ||||
|                 'chill_wopi', | ||||
|                 $config | ||||
|             ); | ||||
|  | ||||
|         $loader = new PhpFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); | ||||
|         $loader->load('services.php'); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\DependencyInjection; | ||||
|  | ||||
| use Symfony\Component\Config\Definition\Builder\TreeBuilder; | ||||
| use Symfony\Component\Config\Definition\ConfigurationInterface; | ||||
|  | ||||
| final class Configuration implements ConfigurationInterface | ||||
| { | ||||
|     public function getConfigTreeBuilder() | ||||
|     { | ||||
|         $treeBuilder = new TreeBuilder('chill_wopi'); | ||||
|         $rootNode = $treeBuilder->getRootNode(); | ||||
|  | ||||
|         return $treeBuilder; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| use Chill\WopiBundle\Controller\Test; | ||||
| use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; | ||||
|  | ||||
| return static function (RoutingConfigurator $routes) { | ||||
|     $routes | ||||
|         ->add('chill_wopi_file_edit', '/edit/{fileId}') | ||||
|         ->controller(Test::class); | ||||
| }; | ||||
							
								
								
									
										37
									
								
								src/Bundle/ChillWopiBundle/src/Resources/config/services.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Bundle/ChillWopiBundle/src/Resources/config/services.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Symfony\Component\DependencyInjection\Loader\Configurator; | ||||
|  | ||||
| use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; | ||||
| use ChampsLibres\WopiLib\WopiInterface; | ||||
| use Chill\WopiBundle\Service\Wopi\ChillWopi; | ||||
|  | ||||
| return static function (ContainerConfigurator $container) { | ||||
|     $services = $container | ||||
|         ->services(); | ||||
|  | ||||
|     $services | ||||
|         ->defaults() | ||||
|         ->autowire() | ||||
|         ->autoconfigure(); | ||||
|  | ||||
|     $services | ||||
|         ->load('Chill\\WopiBundle\\Service\\', __DIR__ . '/../../Service'); | ||||
|  | ||||
|     $services | ||||
|         ->load('Chill\\WopiBundle\\Controller\\', __DIR__ . '/../../Controller') | ||||
|         ->tag('controller.service_arguments'); | ||||
|  | ||||
|     $services | ||||
|         ->alias(WopiInterface::class, ChillWopi::class); | ||||
|  | ||||
|     $services | ||||
|         ->alias(TempUrlGeneratorInterface::class, 'async_uploader.temp_url_generator'); | ||||
| }; | ||||
| @@ -0,0 +1,46 @@ | ||||
| require('./index.scss'); | ||||
|  | ||||
| window.addEventListener('DOMContentLoaded', function(e) { | ||||
|     let frameholder = document.getElementById('frameholder'); | ||||
|     let office_frame = document.createElement('iframe'); | ||||
|     office_frame.name = 'office_frame'; | ||||
|     office_frame.id = 'office_frame'; | ||||
|  | ||||
|     // The title should be set for accessibility | ||||
|     office_frame.title = 'Office Frame'; | ||||
|  | ||||
|     // This attribute allows true fullscreen mode in slideshow view | ||||
|     // when using PowerPoint's 'view' action. | ||||
|     office_frame.setAttribute('allowfullscreen', 'true'); | ||||
|  | ||||
|     // The sandbox attribute is needed to allow automatic redirection to the O365 sign-in page in the business user flow | ||||
|     office_frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation allow-popups-to-escape-sandbox'); | ||||
|     frameholder.appendChild(office_frame); | ||||
|  | ||||
|     document.getElementById('office_form').submit(); | ||||
|     console.log(office_frame); | ||||
|  | ||||
|     const url = new URL(editor_url); | ||||
|     const editor_domain = url.origin; | ||||
|  | ||||
|     window.addEventListener("message", function(message) { | ||||
|         if (message.origin !== editor_domain) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         let data = JSON.parse(message.data); | ||||
|  | ||||
|         if ('UI_Close' === data.MessageId) { | ||||
|             closeEditor(); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
| }); | ||||
|  | ||||
| function closeEditor() { | ||||
|    let | ||||
|        params = new URLSearchParams(window.location.search), | ||||
|        returnPath = params.get('returnPath'); | ||||
|  | ||||
|    window.location.assign(returnPath); | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
|   overflow: hidden; | ||||
|   -ms-content-zooming: none; | ||||
| } | ||||
|  | ||||
| #office_frame { | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
|   margin: 0; | ||||
|   border: none; | ||||
|   display: block; | ||||
| } | ||||
| @@ -0,0 +1,36 @@ | ||||
| <style type="text/css"> | ||||
|     #office_frame { | ||||
|         width: 100%; | ||||
|         height: 800px; | ||||
|         margin: 0; | ||||
|         border: none; | ||||
|         display: block; | ||||
|     } | ||||
| </style> | ||||
|  | ||||
| <form id="office_form" name="office_form" target="office_frame" action="{{ server }}" method="post"> | ||||
|     <input name="access_token" value="{{ access_token }}" type="hidden" /> | ||||
|     <input name="access_token_ttl" value="{{ access_token_ttl }}" type="hidden" /> | ||||
| </form> | ||||
|  | ||||
| <span id="frameholder"></span> | ||||
|  | ||||
| <script type="text/javascript"> | ||||
|     var frameholder = document.getElementById('frameholder'); | ||||
|     var office_frame = document.createElement('iframe'); | ||||
|     office_frame.name = 'office_frame'; | ||||
|     office_frame.id = 'office_frame'; | ||||
|  | ||||
|     // The title should be set for accessibility | ||||
|     office_frame.title = 'Office Frame'; | ||||
|  | ||||
|     // This attribute allows true fullscreen mode in slideshow view | ||||
|     // when using PowerPoint's 'view' action. | ||||
|     office_frame.setAttribute('allowfullscreen', 'true'); | ||||
|  | ||||
|     // The sandbox attribute is needed to allow automatic redirection to the O365 sign-in page in the business user flow | ||||
|     office_frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation allow-popups-to-escape-sandbox'); | ||||
|     frameholder.appendChild(office_frame); | ||||
|  | ||||
|     document.getElementById('office_form').submit(); | ||||
| </script> | ||||
| @@ -0,0 +1,33 @@ | ||||
| <!doctype html> | ||||
| <html> | ||||
|  | ||||
| <head> | ||||
|     <meta charset="utf-8"> | ||||
|  | ||||
|     <!-- Enable IE Standards mode --> | ||||
|     <meta http-equiv="x-ua-compatible" content="ie=edge"> | ||||
|  | ||||
|     <title></title> | ||||
|     <meta name="description" content=""> | ||||
|     <meta name="viewport" | ||||
|         content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"> | ||||
|  | ||||
|     <link rel="shortcut icon" href="{{ favIconUrl }}" /> | ||||
|     <script type="text/javascript"> | ||||
|         const editor_url = "{{ server|escape('js') }}"; | ||||
|     </script> | ||||
|     {{ encore_entry_link_tags('page_wopi_editor') }} | ||||
|     {{ encore_entry_script_tags('page_wopi_editor') }} | ||||
| </head> | ||||
|  | ||||
| <body> | ||||
|  | ||||
|     <form id="office_form" name="office_form" target="office_frame" action="{{ server }}" method="post"> | ||||
|         <input name="access_token" value="{{ access_token }}" type="hidden" /> | ||||
|         <input name="access_token_ttl" value="{{ access_token_ttl }}" type="hidden" /> | ||||
|     </form> | ||||
|  | ||||
|     <span id="frameholder"></span> | ||||
|  | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										101
									
								
								src/Bundle/ChillWopiBundle/src/Service/Controller/Responder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/Bundle/ChillWopiBundle/src/Service/Controller/Responder.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\Service\Controller; | ||||
|  | ||||
| use Symfony\Component\HttpFoundation\BinaryFileResponse; | ||||
| use Symfony\Component\HttpFoundation\JsonResponse; | ||||
| use Symfony\Component\HttpFoundation\RedirectResponse; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpFoundation\ResponseHeaderBag; | ||||
| use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||||
| use Symfony\Component\Serializer\SerializerInterface; | ||||
| use Twig\Environment; | ||||
|  | ||||
| final class Responder implements ResponderInterface | ||||
| { | ||||
|     private SerializerInterface $serializer; | ||||
|  | ||||
|     private Environment $twig; | ||||
|  | ||||
|     private UrlGeneratorInterface $urlGenerator; | ||||
|  | ||||
|     public function __construct( | ||||
|         Environment $twig, | ||||
|         UrlGeneratorInterface $urlGenerator, | ||||
|         SerializerInterface $serializer | ||||
|     ) { | ||||
|         $this->twig = $twig; | ||||
|         $this->urlGenerator = $urlGenerator; | ||||
|         $this->serializer = $serializer; | ||||
|     } | ||||
|  | ||||
|     public function file( | ||||
|         $file, | ||||
|         ?string $filename = null, | ||||
|         string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT | ||||
|     ): BinaryFileResponse { | ||||
|         $response = new BinaryFileResponse($file); | ||||
|  | ||||
|         $filename ??= $response->getFile()->getFilename(); | ||||
|         $response->setContentDisposition($disposition, $filename); | ||||
|  | ||||
|         return $response; | ||||
|     } | ||||
|  | ||||
|     public function json( | ||||
|         $data, | ||||
|         int $status = 200, | ||||
|         array $headers = [], | ||||
|         array $context = [] | ||||
|     ): JsonResponse { | ||||
|         return new JsonResponse( | ||||
|             $this | ||||
|                 ->serializer | ||||
|                 ->serialize( | ||||
|                     $data, | ||||
|                     'json', | ||||
|                     array_merge( | ||||
|                         [ | ||||
|                             'json_encode_options' => JsonResponse::DEFAULT_ENCODING_OPTIONS, | ||||
|                         ], | ||||
|                         $context | ||||
|                     ) | ||||
|                 ), | ||||
|             $status, | ||||
|             $headers, | ||||
|             true | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     public function redirect(string $url, int $status = 302, array $headers = []): RedirectResponse | ||||
|     { | ||||
|         return new RedirectResponse($url, $status, $headers); | ||||
|     } | ||||
|  | ||||
|     public function redirectToRoute( | ||||
|         string $route, | ||||
|         array $parameters = [], | ||||
|         int $status = 302, | ||||
|         array $headers = [] | ||||
|     ): RedirectResponse { | ||||
|         return $this->redirect($this->urlGenerator->generate($route, $parameters), $status, $headers); | ||||
|     } | ||||
|  | ||||
|     public function render(string $template, array $context = [], int $status = 200, array $headers = []): Response | ||||
|     { | ||||
|         $response = new Response($this->twig->render($template, $context), $status, $headers); | ||||
|  | ||||
|         if (!$response->headers->has('Content-Type')) { | ||||
|             $response->headers->set('Content-Type', 'text/html; charset=UTF-8'); | ||||
|         } | ||||
|  | ||||
|         return $response; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,74 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\Service\Controller; | ||||
|  | ||||
| use SplFileInfo; | ||||
| use Symfony\Component\HttpFoundation\BinaryFileResponse; | ||||
| use Symfony\Component\HttpFoundation\JsonResponse; | ||||
| use Symfony\Component\HttpFoundation\RedirectResponse; | ||||
| use Symfony\Component\HttpFoundation\Response; | ||||
| use Symfony\Component\HttpFoundation\ResponseHeaderBag; | ||||
| use Twig\Error\Error as TwigError; | ||||
|  | ||||
| interface ResponderInterface | ||||
| { | ||||
|     /** | ||||
|      * Returns a BinaryFileResponse object with original or customized file name and disposition header. | ||||
|      * | ||||
|      * @param SplFileInfo|string $file | ||||
|      */ | ||||
|     public function file( | ||||
|         $file, | ||||
|         ?string $filename = null, | ||||
|         string $disposition = ResponseHeaderBag::DISPOSITION_ATTACHMENT | ||||
|     ): BinaryFileResponse; | ||||
|  | ||||
|     /** | ||||
|      * Returns a JsonResponse that uses the serializer component if enabled, or json_encode. | ||||
|      * | ||||
|      * @param array<string, list<string>|string> $headers | ||||
|      * @param array<string, mixed> $context | ||||
|      */ | ||||
|     public function json( | ||||
|         mixed $data, | ||||
|         int $status = 200, | ||||
|         array $headers = [], | ||||
|         array $context = [] | ||||
|     ): JsonResponse; | ||||
|  | ||||
|     /** | ||||
|      * Returns a RedirectResponse to the given URL. | ||||
|      * | ||||
|      * @param array<string, list<string>|string> $headers | ||||
|      */ | ||||
|     public function redirect(string $url, int $status = 302, array $headers = []): RedirectResponse; | ||||
|  | ||||
|     /** | ||||
|      * Returns a RedirectResponse to the given route with the given parameters. | ||||
|      * | ||||
|      * @param array<array-key, scalar> $parameters | ||||
|      * @param array<string, list<string>> $headers | ||||
|      */ | ||||
|     public function redirectToRoute( | ||||
|         string $route, | ||||
|         array $parameters = [], | ||||
|         int $status = 302, | ||||
|         array $headers = [] | ||||
|     ): RedirectResponse; | ||||
|  | ||||
|     /** | ||||
|      * Render the given twig template and return an HTML response. | ||||
|      * | ||||
|      * @param array<string, list<string>|string> $headers | ||||
|      * | ||||
|      * @throws TwigError | ||||
|      */ | ||||
|     public function render(string $template, array $context = [], int $status = 200, array $headers = []): Response; | ||||
| } | ||||
							
								
								
									
										305
									
								
								src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								src/Bundle/ChillWopiBundle/src/Service/Wopi/ChillWopi.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,305 @@ | ||||
| <?php | ||||
|  | ||||
| /** | ||||
|  * For the full copyright and license information, please view | ||||
|  * the LICENSE file that was distributed with this source code. | ||||
|  */ | ||||
|  | ||||
| declare(strict_types=1); | ||||
|  | ||||
| namespace Chill\WopiBundle\Service\Wopi; | ||||
|  | ||||
| use ChampsLibres\AsyncUploaderBundle\TempUrl\TempUrlGeneratorInterface; | ||||
| use ChampsLibres\WopiLib\Discovery\WopiDiscoveryInterface; | ||||
| use ChampsLibres\WopiLib\WopiInterface; | ||||
| use Chill\DocStoreBundle\Repository\StoredObjectRepository; | ||||
| use Exception; | ||||
| use loophp\psr17\Psr17Interface; | ||||
| use Psr\Http\Client\ClientInterface; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; | ||||
| use Symfony\Component\Security\Core\User\UserProviderInterface; | ||||
|  | ||||
| final class ChillWopi implements WopiInterface | ||||
| { | ||||
|     private Psr17Interface $psr17; | ||||
|  | ||||
|     private WopiDiscoveryInterface $wopiDiscovery; | ||||
|  | ||||
|     private StoredObjectRepository $storedObjectRepository; | ||||
|  | ||||
|     private ClientInterface $httpClient; | ||||
|  | ||||
|     private TempUrlGeneratorInterface $tempUrlGenerator; | ||||
|  | ||||
|     private UserProviderInterface $userProvider; | ||||
|  | ||||
|     public function __construct( | ||||
|         Psr17Interface $psr17, | ||||
|         WopiDiscoveryInterface $wopiDiscovery, | ||||
|         StoredObjectRepository $storedObjectRepository, | ||||
|         ClientInterface $httpClient, | ||||
|         TempUrlGeneratorInterface $tempUrlGenerator, | ||||
|         UserProviderInterface $userProvider | ||||
|     ) { | ||||
|         $this->psr17 = $psr17; | ||||
|         $this->wopiDiscovery = $wopiDiscovery; | ||||
|         $this->storedObjectRepository = $storedObjectRepository; | ||||
|         $this->httpClient = $httpClient; | ||||
|         $this->tempUrlGenerator = $tempUrlGenerator; | ||||
|         $this->userProvider = $userProvider; | ||||
|     } | ||||
|  | ||||
|     public function checkFileInfo( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         try { | ||||
|             $user = $this->userProvider->loadUserByUsername($accessToken); | ||||
|         } catch (UsernameNotFoundException $e) { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(401); | ||||
|         } | ||||
|  | ||||
|         $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); | ||||
|  | ||||
|         if (null === $storedObject) { | ||||
|             throw new Exception(sprintf('Unable to find object named %s', $fileId)); | ||||
|         } | ||||
|  | ||||
|         $mimeType = $storedObject->getType(); | ||||
|  | ||||
|         if ([] === $this->wopiDiscovery->discoverMimeType($mimeType)) { | ||||
|             throw new Exception(sprintf('Unable to find mime type %s', $mimeType)); | ||||
|         } | ||||
|  | ||||
|         return $this | ||||
|             ->psr17 | ||||
|             ->createResponse() | ||||
|             ->withHeader('Content-Type', 'application/json') | ||||
|             ->withBody($this->psr17->createStream((string) json_encode( | ||||
|                 [ | ||||
|                     'BaseFileName' => $storedObject->getFilename(), | ||||
|                     'OwnerId' => uniqid(), | ||||
|                     'Size' => 0, | ||||
|                     'UserId' => uniqid(), | ||||
| //                    'Version' => 'v' . uniqid(), | ||||
|                     'ReadOnly' => false, | ||||
|                     'UserCanWrite' => true, | ||||
|                     'UserCanNotWriteRelative' => true, | ||||
|                     'SupportsLocks' => false, | ||||
|                     'UserFriendlyName' => sprintf('User %s', $user->getUsername()), | ||||
|                     'UserExtraInfo' => [], | ||||
|                     'LastModifiedTime' => date('Y-m-d\TH:i:s.u\Z', $storedObject->getCreationDate()->getTimestamp()), | ||||
|                     'CloseButtonClosesWindow' => true, | ||||
|                     'EnableInsertRemoteImage' => true, | ||||
|                     'EnableShare' => false, | ||||
|                     'SupportsUpdate' => true, | ||||
|                     'SupportsRename' => false, | ||||
|                     'DisablePrint' => false, | ||||
|                     'DisableExport' => false, | ||||
|                     'DisableCopy' => false, | ||||
|                 ] | ||||
|             ))); | ||||
|     } | ||||
|  | ||||
|     public function deleteFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function enumerateAncestors( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function getFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         try { | ||||
|             $user = $this->userProvider->loadUserByUsername($accessToken); | ||||
|         } catch (UsernameNotFoundException $e) { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(401); | ||||
|         } | ||||
|  | ||||
|         $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); | ||||
|  | ||||
|         if (null === $storedObject) { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(404); | ||||
|         } | ||||
|  | ||||
|         // TODO: Add strict typing in champs-libres/async-uploader-bundle | ||||
|         /** @var StdClass $object */ | ||||
|         $object = $this->tempUrlGenerator->generate('GET', $storedObject->getFilename()); | ||||
|  | ||||
|         $response = $this->httpClient->sendRequest($this->psr17->createRequest('GET', $object->url)); | ||||
|  | ||||
|         if (200 !== $response->getStatusCode()) | ||||
|         { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(500); | ||||
|         } | ||||
|  | ||||
|         return $this | ||||
|             ->psr17 | ||||
|             ->createResponse() | ||||
|             ->withHeader( | ||||
|                 'Content-Type', | ||||
|                 'application/octet-stream', | ||||
|             ) | ||||
|             ->withHeader( | ||||
|                 'Content-Disposition', | ||||
|                 sprintf('attachment; filename=%s', $storedObject->getFilename()) | ||||
|             ) | ||||
|             ->withBody($response->getBody()); | ||||
|     } | ||||
|  | ||||
|     public function getLock(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function getShareUrl( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function lock( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function putFile( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         string $xWopiEditors, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         try { | ||||
|             $user = $this->userProvider->loadUserByUsername($accessToken); | ||||
|         } catch (UsernameNotFoundException $e) { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(401); | ||||
|         } | ||||
|  | ||||
|         $storedObject = $this->storedObjectRepository->findOneBy(['filename' => $fileId]); | ||||
|  | ||||
|         if (null === $storedObject) { | ||||
|             throw new Exception(sprintf('Unable to find object named %s', $fileId)); | ||||
|         } | ||||
|  | ||||
|         // TODO: Add strict typing in champs-libres/async-uploader-bundle | ||||
|         /** @var StdClass $object */ | ||||
|         $object = $this->tempUrlGenerator->generate('PUT', $storedObject->getFilename()); | ||||
|  | ||||
|         $response = $this->httpClient->sendRequest($this->psr17->createRequest('PUT', $object->url)->withBody($request->getBody())); | ||||
|  | ||||
|         if (201 !== $response->getStatusCode()) | ||||
|         { | ||||
|             return $this | ||||
|                 ->psr17 | ||||
|                 ->createResponse(500); | ||||
|         } | ||||
|  | ||||
|         return $this | ||||
|             ->psr17 | ||||
|             ->createResponse() | ||||
|             ->withHeader('Content-Type', 'application/json') | ||||
|             ->withAddedHeader('X-WOPI-Lock', $xWopiLock) | ||||
|             ->withBody($this->psr17->createStream((string) json_encode([]))); | ||||
|     } | ||||
|  | ||||
|     public function putRelativeFile(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function putUserInfo(string $fileId, ?string $accessToken, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function refreshLock( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function renameFile( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         string $xWopiRequestedName, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function unlock( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     public function unlockAndRelock( | ||||
|         string $fileId, | ||||
|         ?string $accessToken, | ||||
|         string $xWopiLock, | ||||
|         string $xWopiOldLock, | ||||
|         RequestInterface $request | ||||
|     ): ResponseInterface { | ||||
|         return $this->getDebugResponse(__FUNCTION__, $request); | ||||
|     } | ||||
|  | ||||
|     private function getDebugResponse(string $method, RequestInterface $request): ResponseInterface | ||||
|     { | ||||
|         $params = []; | ||||
|         parse_str($request->getUri()->getQuery(), $params); | ||||
|  | ||||
|         $data = (string) json_encode(array_merge( | ||||
|             ['method' => $method], | ||||
|             $params, | ||||
|             $request->getHeaders() | ||||
|         )); | ||||
|  | ||||
|         return $this | ||||
|             ->psr17 | ||||
|             ->createResponse() | ||||
|             ->withHeader('content', 'application/json') | ||||
|             ->withBody($this->psr17->createStream($data)); | ||||
|     } | ||||
|  | ||||
|     private function getLockFilepath(string $fileId): string | ||||
|     { | ||||
|         return sprintf( | ||||
|             '%s/%s.lock', | ||||
|             $this->filesRepository, | ||||
|             $fileId | ||||
|         ); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user