Browse Source

CL-561 migration du backend profil utilisateur

raphael 4 years ago
parent
commit
3c02bf3d8f
38 changed files with 665 additions and 2124 deletions
  1. 7
    0
      assets/exclusive/js/backOffice/profil_form.js
  2. 0
    1581
      assets/exclusive/js/component/croppie.js
  3. 0
    0
      assets/exclusive/scss/component/croppie.scss
  4. 0
    1
      assets/shared/build/global/build_global-js.js
  5. 2
    0
      assets/shared/build/global/build_global-scss.js
  6. 0
    471
      assets/shared/build/js/backOffice/dashboard.js
  7. 2
    5
      config/doctrine/User.User.orm.xml
  8. 12
    4
      config/packages/vich_uploader.yaml
  9. 1
    1
      config/routes.yaml
  10. 9
    9
      config/services.yaml
  11. 6
    0
      config/vich_uploader/Entity.User.User.xml
  12. 2
    1
      package.json
  13. 6
    3
      public/build/manifest.json
  14. 4
    2
      src/CaptainLearning/Command/MigrateCommand.php
  15. 7
    1
      src/CaptainLearning/Controller/AppController.php
  16. 16
    4
      src/CaptainLearning/Controller/BackOffice/Individual/IndividualController.php
  17. 130
    11
      src/CaptainLearning/Controller/BackOffice/Individual/UserController.php
  18. 24
    6
      src/CaptainLearning/Controller/Common/LoginController.php
  19. 44
    0
      src/CaptainLearning/Entity/Common/AbstractUploadableEntity.php
  20. 7
    0
      src/CaptainLearning/Entity/Interfaces/Uploadable.php
  21. 3
    3
      src/CaptainLearning/Entity/Type/EnumUserCivilityType.php
  22. 89
    9
      src/CaptainLearning/Entity/User/User.php
  23. 47
    0
      src/CaptainLearning/Form/Profil/UserType.php
  24. 46
    0
      src/CaptainLearning/Service/FileUploader.php
  25. 6
    1
      src/CaptainLearning/Twig/BackOffice/WidgetExtension.php
  26. 1
    1
      templates/backOffice/common/layout/menu.base.html.twig
  27. 2
    2
      templates/backOffice/common/widget/layout.html.twig
  28. 21
    0
      templates/backOffice/individual/user/edit/edit_profil.html.twig
  29. 24
    0
      templates/backOffice/individual/user/edit/widget-change-password.html.twig
  30. 93
    0
      templates/backOffice/individual/user/edit/widget-edit-profil.html.twig
  31. 0
    5
      templates/backOffice/individual/user/edit_profil.html.twig
  32. 0
    0
      templates/backOffice/individual/user/profil/profil.html.twig
  33. 28
    0
      templates/backOffice/individual/user/profil/widget-welcome.html.twig
  34. 1
    1
      templates/common/layout/header/header.base.html.twig
  35. 1
    1
      templates/common/layout/header/individual.part.html.twig
  36. 4
    1
      templates/common/layout/layout.base.html.twig
  37. 19
    0
      translations/messages.fr.xlf
  38. 1
    0
      webpack.config.js

+ 7
- 0
assets/exclusive/js/backOffice/profil_form.js View File

@@ -0,0 +1,7 @@
1
+require('croppie');
2
+
3
+$(document).ready(function()
4
+{
5
+    $("input[data-croppie]").cptInitCroppie();
6
+
7
+});

+ 0
- 1581
assets/exclusive/js/component/croppie.js
File diff suppressed because it is too large
View File


assets/exclusive/scss/component/croppie.css → assets/exclusive/scss/component/croppie.scss View File


+ 0
- 1
assets/shared/build/global/build_global-js.js View File

@@ -31,7 +31,6 @@ require('../js/backOffice/menu.js');
31 31
 require('../js/backOffice/menu-back-office.js');
32 32
 require('../js/common/ressources.js');
33 33
 require('../js/common/barchart.js');
34
-require('../js/backOffice/dashboard.js');
35 34
 require('../js/backOffice/select_autocomplete.js');
36 35
 
37 36
 require('../js/common/cpt-plugins.js');

+ 2
- 0
assets/shared/build/global/build_global-scss.js View File

@@ -1,3 +1,5 @@
1 1
 // REQUIRE CSS
2 2
 require('../scss/core.scss');
3 3
 require('../scss/temp.scss');
4
+
5
+require('../../../exclusive/scss/component/croppie.scss');

+ 0
- 471
assets/shared/build/js/backOffice/dashboard.js View File

@@ -1,471 +0,0 @@
1
-$( document ).ready(function() {
2
-  initDashboard();
3
-
4
-  $('.searchdatepicker').datepicker({
5
-	  minDate:new Date($("input[name='start']").val()),
6
-	 maxDate:new Date($("input[name='end']").val()),
7
-  });
8
-});
9
-
10
-function initDashboard()
11
-{
12
-  var actionSaveWidget = $(".edit-widget").attr("data-action-save");
13
-  var actionGetListWidgetClass = $(".edit-widget").attr("data-action-widget-list");
14
-  var actionGetWidgetContent = $(".edit-widget").attr("data-action-widget-get");
15
-  var actionGetWidgetDatas = $(".edit-widget").attr("data-action-widget-get-datas");
16
-  var idFormation = $(".edit-widget").attr("data-formation-id");
17
-
18
-
19
-  var options = {
20
-      cellHeight: 50,
21
-      verticalMargin: 10,
22
-      disableDrag: true,
23
-      disableResize: true
24
-  };
25
-  $('.grid-stack').gridstack(options);
26
-  var grid = $('.grid-stack').data('gridstack');
27
-  initWidget($(".grid-stack-item"));
28
-
29
-  // Variable qui contiendra la liste des class de widget
30
-  var selectWidgetClass = [];
31
-
32
-  // Lancer la récupérationn des class de widget
33
-  getListWidgetClass(function(result)
34
-  {
35
-    // Création d'un select à partir des données remontées
36
-    selectWidgetClass = $('<select>', {class:"widgets_class form-control"});
37
-    // Ajouter une option pour chaque class de widget
38
-    $(result).each(function() {
39
-      selectWidgetClass.append($("<option>").attr('value',this.class).text(this.name));
40
-    });
41
-
42
-    // Ajouter le select au widget
43
-    initSelectWidget($(".grid-stack-item"), 'd-none');
44
-  });
45
-
46
-  // Click sur le bouton édition
47
-  $(".edit-widget").click(function(){
48
-    var icon = $(this).children(".ion");
49
-    // Si le bouton est celui d'édition passé en mode édition et changer le bouton en sauvegarde
50
-    if (icon.hasClass("ion-md-create"))
51
-    {
52
-      // Passer en mode édition
53
-      editMode();
54
-    }
55
-    else if (icon.hasClass("ion-md-save"))
56
-    {
57
-      // Bonton de sauvegarde passé en bouton d'édition
58
-      waitMode();
59
-
60
-      // Savegarde des données
61
-      saveDashboard(function(result){
62
-        if (result) {
63
-          // Si la sauvegarde c'est bien passé on sort de l'édition
64
-          viewMode();
65
-        }
66
-        else {
67
-          // Si ça c'est mal passé on retroune à l'édition
68
-          editMode();
69
-        }
70
-      });
71
-    }
72
-
73
-    // Modifier le titre du bouton d'édition
74
-    var savTitle = $(this).attr("data-original-title");
75
-    $(this).attr("data-original-title", $(this).attr("data-title-active"));
76
-    $(this).attr("data-title-active", savTitle);
77
-  });
78
-
79
-  /** Action d'ajout d'un widget **/
80
-  $(".add-widget").click(function(){
81
-    addWidget();
82
-  });
83
-
84
-  function addWidget()
85
-  {
86
-    // On récupère le template de widget et on le clone
87
-    var template = $("#widget-content-template");
88
-    var templateClone = template.clone(true);
89
-    var content = $(templateClone.html());
90
-    grid.addWidget(content, 0, 0, 6, 4, true, 3, 12, 2);
91
-    grid.movable(content, true);
92
-    grid.resizable(content, true);
93
-
94
-    // Initialisations
95
-    initWidget(content);
96
-    initSelectWidget(content);
97
-  }
98
-
99
-  /**
100
-   * Passe le tableau de bord en mode édition
101
-   */
102
-  function editMode()
103
-  {
104
-    var icon = $(".edit-widget .ion");
105
-    // Passé en mode édition et changer le bouton en sauvegarde
106
-    icon.removeClass("ion-md-create ion-md-hourglass").addClass("ion-md-save");
107
-    grid.movable('.grid-stack-item', true);
108
-    grid.resizable('.grid-stack-item', true);
109
-    // Masquer les éléments d'édition
110
-    $(".add-widget, .delete-widget, .widgets_class").removeClass("d-none");
111
-
112
-    // Titre éditable
113
-    var title = $("h2.title-widget");
114
-    title.each(function(){
115
-      var titleVal = $(this).text();
116
-      $(this).replaceWith($("<input>", {type:"text", class:"title-widget h3 w-100 p-1 px-2", value:titleVal}));
117
-    });
118
-  }
119
-
120
-  /**
121
-   * Passe le tableau de bord en mode lecture
122
-   */
123
-  function viewMode()
124
-  {
125
-    var icon = $(".edit-widget .ion");
126
-
127
-    // Bonton de sauvegarde passé en bouton d'édition
128
-    icon.removeClass("ion-md-hourglass ion-md-save").addClass("ion-md-create");
129
-    grid.movable('.grid-stack-item', false);
130
-    grid.resizable('.grid-stack-item', false);
131
-    // Afficher les éléments d'édition
132
-    $(".add-widget, .delete-widget, .widgets_class").addClass("d-none");
133
-
134
-    // Titre non éditable
135
-    var title = $("input.title-widget");
136
-    title.each(function(){
137
-      var titleVal = $(this).val();
138
-      $(this).replaceWith($("<h2>", {class:"title-widget", text:titleVal}));
139
-    });
140
-  }
141
-
142
-  /**
143
-   * Passe le tableau de bord en mode attente
144
-   */
145
-  function waitMode()
146
-  {
147
-    var icon = $(".edit-widget .ion");
148
-
149
-    // Bonton de sauvegarde passé en bouton d'édition
150
-    icon.removeClass("ion-md-save ion-md-create").addClass("ion-md-hourglass");
151
-    grid.movable('.grid-stack-item', false);
152
-    grid.resizable('.grid-stack-item', false);
153
-    // Masquer les éléments d'édition
154
-    $(".add-widget, .delete-widget, .widgets_class").addClass("d-none");
155
-
156
-    // Titre non éditable
157
-    var title = $("input.title-widget");
158
-    title.each(function(){
159
-      var titleVal = $(this).val();
160
-      $(this).replaceWith($("<h2>", {class:"title-widget", text:titleVal}));
161
-    });
162
-  }
163
-
164
-
165
-  /**
166
-   * Initialise les événements des widgets
167
-   * @param object[] widgets Le widget ou un enssemble de widgets
168
-   */
169
-  function initWidget(widgets)
170
-  {
171
-    widgets.each(function( index ) {
172
-      var widget = $(this);
173
-
174
-      widget.saveWidget = function(callback){
175
-        callback = callback || function(){};
176
-        saveDashboard(callback, false);
177
-      };
178
-
179
-      // Event
180
-      widget.find(".delete-widget").click(function(){
181
-        $(this).tooltip('hide')
182
-        grid.removeWidget(widget);
183
-      }).tooltip();
184
-
185
-      // Contenu
186
-      udpateWidgetContent(widget);
187
-    });
188
-  }
189
-
190
-  /**
191
-   * Initialise les selects pour chaque widget renseigné
192
-   * @param dom[] widgets Les widgets auquels ajouter le select
193
-   * @param string classSelect La ou les class css à ajouter au select
194
-   */
195
-  function initSelectWidget(widgets, classSelect)
196
-  {
197
-    classSelect = classSelect || "";
198
-
199
-    // Pour chaque widget renseigné en param
200
-    widgets.each(function( index )
201
-    {
202
-      var widget = $(this);
203
-      var selectClone = selectWidgetClass.clone().addClass(classSelect);
204
-      widget.find(".title-widget").after(selectClone);
205
-      var value = widget.attr("data-widget-class");
206
-      if (typeof value != "undefined") {
207
-        selectClone.val(value);
208
-      }
209
-
210
-      // Au changement du select, meetre à jour le contenu du widget
211
-      selectClone.change(function(){
212
-        widget.attr("data-widget-class", selectClone.val());
213
-        udpateWidgetContent(widget);
214
-      });
215
-    });
216
-  }
217
-
218
-
219
-  /**
220
-   * Met à jour le contenu d'un widget
221
-   * @param dom widget Le widget à mettre à jour
222
-   */
223
-  function udpateWidgetContent(widget)
224
-  {
225
-    // On récupère la class du widget
226
-    var newValue = widget.attr("data-widget-class");
227
-
228
-    widget.getWidgetData = function(callback) {
229
-      getWidgetDatas(newValue, callback);
230
-    };
231
-
232
-    // On récupère le contenu de la class
233
-    getWidgetContent(newValue, function(result)
234
-    {
235
-      // Zone du contenu du widget
236
-      var contentWidget = widget.find(".content-widget");
237
-      // Bouton du menu du widget
238
-      var menuButtonWidget = widget.find(".menu-button-widget");
239
-      // Zone du menu du widget
240
-      var menuContenWidget = widget.find(".menu-content-widget");
241
-
242
-      // On met le resultat dans une div pour selectionner les différent élément à l'aide de find
243
-      resultHtml = $("<div>"+result.html+"</div>");
244
-
245
-      // On récupère le contenu
246
-      var content = resultHtml.find("content");
247
-      // On récupère le menu
248
-      var menu = resultHtml.find("menu");
249
-
250
-      // Mise en place du contenu
251
-      if (content.length) {
252
-        contentWidget.html(content.html());
253
-      }
254
-      // Mise en place du menu
255
-      if (menu.length) {
256
-        menuContenWidget.html(menu.html());
257
-      }
258
-
259
-      // Si on n'a pas de menu, masquer le bouton du menu, sinon l'afficher
260
-      if (!menu.length || $.trim(menu.html()) == "") {
261
-        menuButtonWidget.hide();
262
-      }
263
-      else {
264
-        menuButtonWidget.show();
265
-      }
266
-
267
-      // Initialiser les options
268
-      if (widget.attr("data-widget-options"))
269
-      {
270
-        var optionsDecode = $.parseJSON(widget.attr("data-widget-options"));
271
-        $.each(optionsDecode, function(index, value){
272
-          var input = widget.find("[data-option-name='"+index+"']");
273
-          if (input.length) {
274
-            if (input.is(':checkbox')) {
275
-                input.prop( "checked", value );
276
-            }
277
-            else {
278
-                input.val( value );
279
-            }
280
-          }
281
-        });
282
-      }
283
-
284
-      // Initialise les éléments devant prendre la place libre du parent
285
-      widget.find(".heightFreeSpace").each(function(index){
286
-        // L'élément
287
-        var elem = $(this);
288
-        initHeightFreeSpace(elem);
289
-      });
290
-
291
-      // Si on a une fonction d'initialisation renseigner l'executer
292
-      if (result.initJs != "") {
293
-        eval(result.initJs+"(widget)");
294
-      }
295
-
296
-      widget.find(".delete-widget-show").click(function(){
297
-        grid.removeWidget(widget);
298
-        saveDashboard();
299
-      });
300
-    });
301
-  }
302
-
303
-  /**
304
-   * Récupère la liste des class de widget
305
-   * @param function callback Fonction appelé à la fin de la récupération avec le resultat en paramètre
306
-   */
307
-  function getListWidgetClass(callback)
308
-  {
309
-    callback = callback || function(){};
310
-    var result = [];
311
-    $.ajax({
312
-      type: "GET",
313
-      url: actionGetListWidgetClass,
314
-      success: function(data, dataType)
315
-      {
316
-        if (typeof data.widgets != "undefined") {
317
-          result = data.widgets;
318
-        }
319
-        callback(result);
320
-      },
321
-      error: function(XMLHttpRequest, textStatus, errorThrown)
322
-      {
323
-        callback(result);
324
-      }
325
-    });
326
-  }
327
-
328
-  /**
329
-   * Récupère le contenue du widget en fonction de sa class
330
-   * @param string widgetClass Class du widget à charger
331
-   * @param function callback Fonction appelé à la fin de la récupération avec le resultat en paramètre
332
-   */
333
-  function getWidgetContent(widgetClass, callback)
334
-  {
335
-    callback = callback || function(){};
336
-    var result = "";
337
-    $.ajax({
338
-      type: "GET",
339
-      url: actionGetWidgetContent+"/"+widgetClass,
340
-      success: function(data, dataType)
341
-      {
342
-        if (typeof data != "undefined") {
343
-          result = data;
344
-        }
345
-        callback(result);
346
-      },
347
-      error: function(XMLHttpRequest, textStatus, errorThrown)
348
-      {
349
-        callback(result);
350
-      }
351
-    });
352
-  }
353
-
354
-  /**
355
-   * Récupère les données du widget en fonction de sa class
356
-   * @param string widgetClass Class du widget à charger
357
-   * @param function callback Fonction appelé à la fin de la récupération avec le resultat en paramètre
358
-   */
359
-  function getWidgetDatas(widgetClass, callback)
360
-  {
361
-    callback = callback || function(){};
362
-
363
-    let start = $('input[name="start"].hasDatepicker').datepicker("getDate");
364
-    let end = $('input[name="end"].hasDatepicker').datepicker('getDate');
365
-    var datas = {
366
-      idFormation: idFormation,
367
-      object: $('select[name="object"]').val(),
368
-      actor: $('select[name="actor"]').val(),
369
-      start: $.datepicker.formatDate('yy-m-d',start),
370
-      end:$.datepicker.formatDate('yy-m-d',end)
371
-    };
372
-
373
-    var result = "";
374
-    $.ajax({
375
-      type: "GET",
376
-      data: datas,
377
-      url: actionGetWidgetDatas+"/"+widgetClass,
378
-      success: function(data, dataType)
379
-      {
380
-        result = data;
381
-        callback(result);
382
-      },
383
-      error: function(XMLHttpRequest, textStatus, errorThrown)
384
-      {
385
-        callback(result);
386
-      }
387
-    });
388
-  }
389
-
390
-  /**
391
-   * Retourne la structure des widgets au format json
392
-   * @return string
393
-   */
394
-  function getGridDataSerialized()
395
-  {
396
-    this.serializedData = _.map($('.grid-stack > .grid-stack-item:visible'), function (el) {
397
-        el = $(el);
398
-        var node = el.data('_gridstack_node');
399
-
400
-        // Récupérer les options du widget
401
-        var options = {};
402
-        var optionsObjects = node.el.find('.option-widget');
403
-        optionsObjects.each(function(index, optionDom)
404
-        {
405
-          var optionObject = $(optionDom);
406
-
407
-          if (optionObject.is(':checkbox')) {
408
-            options[optionObject.attr('data-option-name')] = optionObject.is(':checked');
409
-          }
410
-          else {
411
-            options[optionObject.attr('data-option-name')] = optionObject.val();
412
-          }
413
-        });
414
-
415
-        return {
416
-            title: node.el.find("h2").html(),
417
-            x: node.x,
418
-            y: node.y,
419
-            width: node.width,
420
-            height: node.height,
421
-            widgetClass: node.el.attr("data-widget-class"),
422
-            options: options
423
-        };
424
-    }, this);
425
-    return JSON.stringify(this.serializedData, null, '');
426
-  };
427
-
428
-  /**
429
-   * Sauvegarde les données du dashboardId et affiche le retour sous forme d'alerte
430
-   * @param string action Url de la requête
431
-   * @param function callback Fonction appelé lors d'un retour avec comme valeur un booléen pour informé si tout c'est bien passé ou non.
432
-   **/
433
-  function saveDashboard(callback, showAlert)
434
-  {
435
-    callback = callback || function(){};
436
-    if (typeof showAlert == 'undefined') {
437
-      showAlert = true;
438
-    }
439
-
440
-    // Savegarde des données
441
-    var datas = {
442
-      structure: getGridDataSerialized(),
443
-      title: $("#dashboard-title").text()
444
-    };
445
-
446
-    $.ajax({
447
-      type: "POST",
448
-      url: actionSaveWidget,
449
-      data: datas,
450
-      success: function(data, dataType)
451
-      {
452
-        if (typeof data.success != "undefined") {
453
-          if (showAlert) {
454
-            addAlert("alert-success", data.success, true, 5000);
455
-          }
456
-          callback(true);
457
-        }
458
-        else if (typeof data.error != "undefined") {
459
-          if (showAlert) {
460
-            addAlert("alert-danger", data.error, true);
461
-          }
462
-          callback(false);
463
-        }
464
-      },
465
-      error: function(XMLHttpRequest, textStatus, errorThrown)
466
-      {
467
-        addAlert("alert-danger", errorThrown);
468
-      }
469
-    });
470
-  }
471
-}

+ 2
- 5
config/doctrine/User.User.orm.xml View File

@@ -22,13 +22,10 @@
22 22
 	   	<field name="role" column="role" type="captainEnumRoleType" />
23 23
 		<field name="status" column="status" type="captainEnumUserStatusType" />
24 24
 
25
-		<field mapping="user_images"
26
-			name="imageFile"
27
-			filename_property="image"
28
-			nullable="true"
29
-		/>
30 25
 		<field name="updatedAt" column="updated_at" type="datetimetz" />
31 26
 
27
+		<field name="foto" column="foto" type="string" nullable="true"/>
28
+
32 29
 		<one-to-one field="clients" target-entity="Logipro\CaptainLearning\Entity\Client" mapped-by="user" />
33 30
 
34 31
 		<lifecycle-callbacks>

+ 12
- 4
config/packages/vich_uploader.yaml View File

@@ -1,11 +1,19 @@
1 1
 vich_uploader:
2 2
   db_driver: orm
3
+  twig: true            # set to false to disable twig integration (requires templating)                  
4
+  form: true 
5
+  metadata:
6
+    auto_detection: false
7
+    directories:
8
+      - { path: '%kernel.project_dir%/config/vich_uploader', namespace_prefix: 'Logipro\CaptainLearning' }
3 9
 
4 10
   mappings:
5
-    company_images:
6
-      uri_prefix: 'images/company_images'
7
-      upload_destination: '%kernel.project_dir%/public/images/companies'
11
+    user_images:
12
+      uri_prefix:         '%app.url.upload_public%'
13
+      upload_destination: '%app.path.upload_dir%'
8 14
 
9 15
       inject_on_load: false
10 16
       delete_on_update: true
11
-      delete_on_remove: true
17
+      delete_on_remove: true
18
+
19
+        

+ 1
- 1
config/routes.yaml View File

@@ -47,7 +47,7 @@ subscribe:
47 47
   controller: \Logipro\CaptainLearning\Controller\Common\LoginController::validateSubscribe
48 48
 subscribeConfirm:
49 49
   path: /subscribe/confirm
50
-  controller: \Logipro\CaptainLearning\Controller\Common\LoginController::confirmSubscribe
50
+  controller: \Logipro\CaptainLearning\Controller\Common\LoginController::confirmUserEmail
51 51
 
52 52
 
53 53
 redirectToSpace:

+ 9
- 9
config/services.yaml View File

@@ -1,15 +1,9 @@
1 1
 parameters:
2 2
   # Config pour envoyer des emails
3 3
   app.notifications.email_sender: captain@logipro.com
4
-  app.path.user_images: /uploads/images/users
5
-
6
-# config/packages/vich_uploader.yaml
7
-vich_uploader:
8
-    # ...
9
-    mappings:
10
-        user_images:
11
-            uri_prefix:         '%app.path.user_images%'
12
-            upload_destination: '%kernel.root_dir%/../web%app.path.user_images%'
4
+  app.path.upload_dir: '%kernel.project_dir%/public/uploads'
5
+  app.path.upload_temp_dir: '%kernel.project_dir%/public/uploads/tmp'
6
+  app.url.upload_public: '%env(APP_URL_PREFIX)%/uploads'
13 7
 
14 8
 services:
15 9
   # default configuration for services in *this* file
@@ -59,3 +53,9 @@ services:
59 53
   Logipro\CaptainLearning\Event\EventSubscriber\UserSuscribeNotifyUser:
60 54
         # le nom de la variable que l'on utilisera dans le service
61 55
         $sender: '%app.notifications.email_sender%'
56
+
57
+#===============================================================================
58
+# Services de gestion des uploads.
59
+#===============================================================================
60
+  Logipro\CaptainLearning\Service\FileUploader:
61
+    arguments: ['@service_container']

+ 6
- 0
config/vich_uploader/Entity.User.User.xml View File

@@ -0,0 +1,6 @@
1
+<vich_uploader class="Logipro\CaptainLearning\Entity\User\User">
2
+	<field mapping="user_images"
3
+		name="fotoFile"
4
+		filename_property="foto"
5
+		/>
6
+</vich_uploader>

+ 2
- 1
package.json View File

@@ -9,7 +9,8 @@
9 9
     "uglifyjs-webpack-plugin": "^1.2.7",
10 10
     "yarn": "^1.9.4",
11 11
     "gridstack": "^0.4.0",
12
-    "chart.js": "^2.7.2"
12
+    "chart.js": "^2.7.2",
13
+    "croppie": "^2.6.1"
13 14
   },
14 15
   "devDependencies": {
15 16
     "@symfony/webpack-encore": "^0.20",

+ 6
- 3
public/build/manifest.json View File

@@ -1,11 +1,14 @@
1 1
 {
2 2
   "build/shared.js": "http://localhost:3000/CaptainLearning/build/shared.js",
3 3
   "build/shared.css": "http://localhost:3000/CaptainLearning/build/shared.css",
4
+  "build/profil_form.js": "http://localhost:3000/CaptainLearning/build/profil_form.js",
4 5
   "build/rocket-chat.js": "http://localhost:3000/CaptainLearning/build/rocket-chat.js",
5 6
   "build/list.js": "http://localhost:3000/CaptainLearning/build/list.js",
6 7
   "build/captcha.js": "http://localhost:3000/CaptainLearning/build/captcha.js",
8
+  "build/training_organization_form.js": "http://localhost:3000/CaptainLearning/build/training_organization_form.js",
9
+  "build/company_form.js": "http://localhost:3000/CaptainLearning/build/company_form.js",
7 10
   "build/manifest.js": "http://localhost:3000/CaptainLearning/build/manifest.js",
8
-  "build/images/billets.svg": "http://localhost:3000/CaptainLearning/build/images/billets.847328cb.svg",
11
+  "build/images/close.svg": "http://localhost:3000/CaptainLearning/build/images/close.14776e9e.svg",
9 12
   "build/images/apple-touch-icon.png": "http://localhost:3000/CaptainLearning/build/images/apple-touch-icon.b4c8c50b.png",
10 13
   "build/images/captain-sign-in-organisme-de-formation.jpg": "http://localhost:3000/CaptainLearning/build/images/captain-sign-in-organisme-de-formation.9fb0bb2d.jpg",
11 14
   "build/images/formations-achat-transport-logistique-internationale.jpg": "http://localhost:3000/CaptainLearning/build/images/formations-achat-transport-logistique-internationale.3668aafa.jpg",
@@ -52,9 +55,9 @@
52 55
   "build/images/add_hover.svg": "http://localhost:3000/CaptainLearning/build/images/add_hover.3acf793e.svg",
53 56
   "build/images/add_souhait.svg": "http://localhost:3000/CaptainLearning/build/images/add_souhait.935a729a.svg",
54 57
   "build/images/add_souhait_maincolor.svg": "http://localhost:3000/CaptainLearning/build/images/add_souhait_maincolor.2ebb95d1.svg",
55
-  "build/images/avatar.png": "http://localhost:3000/CaptainLearning/build/images/avatar.b6a1e70d.png",
58
+  "build/images/billets.svg": "http://localhost:3000/CaptainLearning/build/images/billets.847328cb.svg",
56 59
   "build/images/billetsNoir.svg": "http://localhost:3000/CaptainLearning/build/images/billetsNoir.514fdca3.svg",
57
-  "build/images/close.svg": "http://localhost:3000/CaptainLearning/build/images/close.14776e9e.svg",
60
+  "build/images/avatar.png": "http://localhost:3000/CaptainLearning/build/images/avatar.b6a1e70d.png",
58 61
   "build/images/close_hover.svg": "http://localhost:3000/CaptainLearning/build/images/close_hover.a2e05323.svg",
59 62
   "build/images/cpf.svg": "http://localhost:3000/CaptainLearning/build/images/cpf.b6dfda1d.svg",
60 63
   "build/images/datadock.svg": "http://localhost:3000/CaptainLearning/build/images/datadock.1f113e4a.svg",

+ 4
- 2
src/CaptainLearning/Command/MigrateCommand.php View File

@@ -683,7 +683,8 @@ class MigrateCommand extends ContainerAwareCommand
683 683
 			"Mlle" => EnumUserCivilityType::CIVILITY_MS
684 684
 		);
685 685
 
686
-		$query = 'SELECT * FROM ' . $oldTableName;
686
+		$query = 'SELECT * FROM ' . $oldTableName .
687
+		'left outer join vue on mod_utilisateur.ref_vue=vue.id_vue';
687 688
 		$result = $this->oldconnection->query($query);
688 689
 		while ($row = $result->fetch())
689 690
 		{
@@ -696,7 +697,8 @@ class MigrateCommand extends ContainerAwareCommand
696 697
 				"first_name" => $row['prenom'],
697 698
 				"role" => EnumRoleType::ROLE_USER,
698 699
 				"status" => EnumUserStatusType::STATUS_ENABLED,
699
-				"updated_at" => date("Y-m-d H:i:s")
700
+				"updated_at" => date("Y-m-d H:i:s"),
701
+				'foto' => $row['vue_file']
700 702
 			);
701 703
 
702 704
 			$this->insertData($newTableName,$newUser);

+ 7
- 1
src/CaptainLearning/Controller/AppController.php View File

@@ -71,6 +71,11 @@ class AppController extends Controller
71 71
 		}
72 72
 
73 73
 		// Variable des paramettres globaux au controler
74
+		$fotoUrl = $currentUser->getFoto() . "";
75
+		if ($fotoUrl != "")
76
+		{
77
+			$fotoUrl = $this->getParameter('app.url.upload_public') .  '/' .$fotoUrl;
78
+		}
74 79
 		$golbalParameters = array
75 80
 		(
76 81
 			// @FIMXE à corriger
@@ -82,7 +87,8 @@ class AppController extends Controller
82 87
 			"user" => $currentUser,
83 88
 			"company" => $this->getEnvCompany(),
84 89
 			"companies" => $companies,
85
-			'password_policy' => PasswordValidator::renderError($this->get('translator'),$this->get('captainSettings'),"")
90
+			'password_policy' => PasswordValidator::renderError($this->get('translator'),$this->get('captainSettings'),""),
91
+			'user_foto_url' => $fotoUrl,
86 92
 		);
87 93
 
88 94
 

+ 16
- 4
src/CaptainLearning/Controller/BackOffice/Individual/IndividualController.php View File

@@ -6,15 +6,27 @@ use Symfony\Component\HttpFoundation\Response;
6 6
 
7 7
 class IndividualController extends BackOfficeController
8 8
 {
9
-	protected function addRenderWidget(&$renderVars,$title,$content)
9
+	protected function addRenderWidget(
10
+		&$renderVars,
11
+		$title,
12
+		$path,
13
+		$class = "",
14
+		$parameters = array()
15
+	)
10 16
 	{
11
-		$widgetData = array(
17
+		$parameters['widget_title'] = $title;
18
+		$parameters['widget_class'] = $class;
19
+		
20
+		$content = $this->renderView($path,$parameters);
21
+
22
+
23
+		/*$widgetData = array(
12 24
 			'widget_title' => $title,
13 25
 			'widget_content' => $content,
14 26
 			'widget_class' => ''
15
-		);
27
+		);*/
16 28
 
17
-		$renderVars['widgets'][] = $widgetData;
29
+		$renderVars['widgets'][] = $content;
18 30
 	}
19 31
 }
20 32
 ?>

+ 130
- 11
src/CaptainLearning/Controller/BackOffice/Individual/UserController.php View File

@@ -3,9 +3,12 @@ namespace Logipro\CaptainLearning\Controller\BackOffice\Individual;
3 3
 
4 4
 use Symfony\Component\HttpFoundation\Request;
5 5
 use Symfony\Component\HttpFoundation\Response;
6
+use Logipro\CaptainLearning\Form\Profil\UserType;
7
+use Logipro\CaptainLearning\Service\FileUploader;
6 8
 
7 9
 class UserController extends IndividualController
8 10
 {
11
+	private $viewPath = 'backOffice/individual/user/';
9 12
 	public function drawProfil(Request $request)
10 13
 	{
11 14
 		// test l'accès utilisateur
@@ -14,26 +17,142 @@ class UserController extends IndividualController
14 17
 			return $response;
15 18
 		}
16 19
 
17
-		// Widgets
18
-		//$widgetWelcome = $this->addWidget(_CPT_BO_USER_BORD_WELCOME_WIDGET_TITLE, "/view/backOffice/particulier/utilisateur/widget-bienvenue.tpl.php", "col-12 cpt-widget-col-xxl-9 mx-auto");
19
-
20
+	// rendu de la vue
20 21
 		$renderVars = array();
21 22
 
22
-		$this->addRenderWidget($renderVars,'Mes informations','');
23
+		// widget de l'accueil
24
+		$this->addRenderWidget
25
+		(
26
+			$renderVars,
27
+			'Mes informations',
28
+			$this->viewPath . '/profil/widget-welcome.html.twig',
29
+			'col-12 cpt-widget-col-xxl-9 mx-auto'
30
+		);
23 31
 
24
-		return $this->render('backOffice/individual/user/profil.html.twig',$renderVars);
32
+		return $this->render($this->viewPath . 'profil/profil.html.twig',$renderVars);
25 33
 	}
26 34
 
27
-	public function editProfil()
35
+
36
+	/**
37
+	 * fonction d'upload de croppie
38
+	 */
39
+	protected function uploadCroppie(Request $request,FileUploader $fileUploader,$entity,$croppieName,$uploadName)
28 40
 	{
29
-		// Widgets
30
-		//$widgetWelcome = $this->addWidget(_CPT_BO_USER_BORD_WELCOME_WIDGET_TITLE, "/view/backOffice/particulier/utilisateur/widget-bienvenue.tpl.php", "col-12 cpt-widget-col-xxl-9 mx-auto");
41
+		// test si on veut supprimer
42
+		if ($request->request->get($croppieName . '_delete') == 1)
43
+		{
44
+			return null;
45
+		}
31 46
 
32
-		$renderVars = array();
47
+		// traitement du fichier base64 s'il est présent
48
+		$img = $request->request->get('base64_' . $croppieName);
49
+		if ($img)
50
+		{
51
+			$file = $request->files->get($uploadName);
52
+			if (!$file)
53
+			{
54
+				$fileName = $entity->getFoto();
55
+			}
56
+			else
57
+			{
58
+				$fileName = uniqid(true) . "." . $file->guessClientExtension();
59
+			}
60
+
61
+			// place le fichier dans un dossier temporaire
62
+			$img = explode('base64,', $img);
63
+			$img = str_replace(' ', '+', $img[1]);
64
+			$data = base64_decode($img);
65
+			$tmpPath = $fileUploader->getUploadTempPath() . '/' . $fileName;
66
+			file_put_contents($tmpPath,$data);
67
+
68
+			// test la taille (bytes)
69
+			if ($entity->getUploadMaxFileSize() < filesize($tmpPath))
70
+			{
71
+				// erreur
72
+				throw new \Exception();
73
+			}
74
+
75
+			$finfo = finfo_open(FILEINFO_MIME_TYPE);
76
+			$mimeType = finfo_file($finfo, $tmpPath);
77
+			
78
+			if (!in_array($mimeType,$entity->getUploadMimeTypes()))
79
+			{
80
+				// erreur
81
+				throw new \Exception();
82
+			}
83
+			file_put_contents($fileUploader->getUploadPath() . '/' . $fileName,$data);
84
+
85
+			return $fileName;
86
+		}
87
+		return null;
88
+	}
89
+
90
+	public function editProfil(Request $request, FileUploader $fileUploader)
91
+	{
92
+		// test l'accès utilisateur
93
+		if ($response = $this->denyAccessUnless($request))
94
+		{
95
+			return $response;
96
+		}
97
+
98
+		// création du formulaire		
99
+		$user = $this->getUser();
100
+		$form = $this->createForm(UserType::class, $user);
101
+
102
+		// Validation du formulaire
103
+		if ($request->isMethod('POST'))
104
+		{
105
+			$form->handleRequest($request);
106
+
107
+			if ($form->isSubmitted() && $form->isValid())
108
+			{
109
+				try{
110
+					$result = $this->uploadCroppie(
111
+						$request,
112
+						$fileUploader,
113
+						$user,
114
+						'foto_file',
115
+						'foto_upload'
116
+					);
117
+
118
+					$user->setFoto($result);
119
+
120
+					$manager = $this->getDoctrine()->getManager();
121
+					$manager->flush();
33 122
 
34
-		$this->addRenderWidget($renderVars,'Mes informations','');
123
+					// Notification de succes
124
+					$this->addFlash(
125
+						'success',
126
+						$this->get('translator')->trans('label_success_of_update')
127
+					);
128
+				}
129
+				catch (\Exception $exp)
130
+				{
131
+					$this->addFlash(
132
+						'danger',
133
+						$this->get('translator')->trans($exp->getMessage())
134
+					);
135
+				}
136
+			}	
137
+		}
138
+
139
+	// rendu de la vue
140
+		// widget du profil
141
+		$renderVars = array();
35 142
 
36
-		return $this->render('backOffice/individual/user/edit_profil.html.twig',$renderVars);
143
+		$this->addRenderWidget
144
+		(
145
+			$renderVars,
146
+			'Informations générales',
147
+			$this->viewPath . '/edit/widget-edit-profil.html.twig',
148
+			'col-12',
149
+			$parameters = array(
150
+				'form' => $form->createView(),
151
+				'upload_max_file_size' => $user->getUploadMaxFileSize()
152
+			)
153
+		);
154
+		
155
+		return $this->render($this->viewPath . 'edit/edit_profil.html.twig',$renderVars);
37 156
 	}
38 157
 }
39 158
 ?>

+ 24
- 6
src/CaptainLearning/Controller/Common/LoginController.php View File

@@ -44,8 +44,9 @@ class LoginController extends AppController
44 44
 		
45 45
 		$user = $userRepository->findOneBy(array("email" => $_username));
46 46
 
47
-        // Check if the user exists !
48
-        if(!$user){
47
+        // test l'existance de l'utilisateur
48
+		if(!$user)
49
+		{
49 50
             return new JsonResponse(
50 51
 				array("erreur-msg" => $this->get("translator")->trans('label_login_user_error'),"result" => false)
51 52
             );
@@ -68,6 +69,12 @@ class LoginController extends AppController
68 69
 		}
69 70
 	}
70 71
 
72
+	/**
73
+	 * logue un utilisateur
74
+	 * 
75
+	 * @param User $user
76
+	 * @param Request $request
77
+	 */
71 78
 	public function loggedUser(User $user,Request $request)
72 79
 	{
73 80
 		$name = 'main';
@@ -84,6 +91,9 @@ class LoginController extends AppController
84 91
 		$this->get("event_dispatcher")->dispatch("security.interactive_login", $event);
85 92
 	}
86 93
 
94
+	/**
95
+	 * valide l'inscription et retourne la réponse sour forme de tableau json
96
+	 */
87 97
 	public function validateSubscribe(Request $request,EventDispatcherInterface $eventDispatcher)
88 98
 	{
89 99
 		try
@@ -152,6 +162,9 @@ class LoginController extends AppController
152 162
 		return new JsonResponse($result);
153 163
 	}
154 164
 
165
+	/**
166
+	 * valide le formulaire de redéfinition de mot de passe
167
+	 */
155 168
 	public function sendRecoverPassword(Request $request,EventDispatcherInterface $eventDispatcher)
156 169
 	{
157 170
 		try
@@ -190,6 +203,9 @@ class LoginController extends AppController
190 203
 		return new JsonResponse($result);
191 204
 	}
192 205
 
206
+	/**
207
+	 * affiche le formulaire quand on vient du mail pour redéfinir son mot de passe
208
+	 */
193 209
 	public function drawRecoverPassword(Request $request)
194 210
 	{
195 211
 		try
@@ -256,11 +272,13 @@ class LoginController extends AppController
256 272
 		}
257 273
 	}
258 274
 
259
-	public function confirmSubscribe(Request $request)
275
+	/**
276
+	 * confirme l'adresse email d'un utilisateur
277
+	 */
278
+	public function confirmUserEmail(Request $request)
260 279
 	{
261 280
 		try
262 281
 		{
263
-			// si le formulaire a été envoyé
264 282
 			$token = $request->get('token');
265 283
 			if (!$token)
266 284
 			{
@@ -286,10 +304,10 @@ class LoginController extends AppController
286 304
 			$user->setStatus(EnumUserStatusType::STATUS_ENABLED);
287 305
 			$manager->flush();
288 306
 
289
-			// login
307
+			// logue l'utilisateur
290 308
 			$this->loggedUser($user,$request);
291 309
 
292
-			// profil
310
+			// redirige sur le profil
293 311
 			return $this->redirectToRoute('drawProfil');
294 312
 		}
295 313
 		catch (\Exception $exp)

+ 44
- 0
src/CaptainLearning/Entity/Common/AbstractUploadableEntity.php View File

@@ -0,0 +1,44 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Entity\Common;
3
+
4
+abstract class AbstractUploadableEntity extends AbstractEntity
5
+implements \Serializable
6
+{
7
+	protected static function convertToByte($size)
8
+	{
9
+		$size = trim($size);
10
+		$letter = strtolower(substr($size, -1));
11
+		$size = (int)$size;
12
+		switch ($letter)
13
+		{
14
+			case 'g': $size *= 1024;
15
+			case 'm': $size *= 1024;
16
+			case 'k': $size *= 1024;
17
+		}
18
+		return $size;
19
+	}
20
+
21
+	public static function getUploadMaxFileSize($property = "")
22
+	{
23
+		$maxFileSize = self::convertToByte(ini_get('upload_max_filesize'));
24
+		$maxPost = self::convertToByte(ini_get('post_max_size'));
25
+		$maxMemory = self::convertToByte(ini_get('memory_limit'));
26
+		
27
+		return min($maxFileSize, $maxPost, $maxMemory);
28
+	}
29
+
30
+	public static function getUploadMimeTypes($attribut = "")
31
+	{
32
+		return array();
33
+	}
34
+
35
+	protected static function getFileConstraints($property = "")
36
+	{
37
+		return array(
38
+			'mimeTypes' => self::getUploadMimeTypes($property),
39
+			'maxSize' => self::getUploadMaxFileSize($property)
40
+
41
+			//@FIXME surcharger les messages d'erreur
42
+		);
43
+	}
44
+}

+ 7
- 0
src/CaptainLearning/Entity/Interfaces/Uploadable.php View File

@@ -0,0 +1,7 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Entity\Interfaces;
3
+
4
+interface Uploadable
5
+{
6
+}
7
+?>

+ 3
- 3
src/CaptainLearning/Entity/Type/EnumUserCivilityType.php View File

@@ -38,9 +38,9 @@ class EnumUserCivilityType extends StringType
38 38
 	public static function getFormChoices()
39 39
 	{
40 40
 		return array(
41
-			self::CIVILITY_NA => '',
42
-			self::CIVILITY_M => '',
43
-			self::CIVILITY_MS => ''
41
+			'enum_user_civility_na' => self::CIVILITY_NA,
42
+			'enum_user_civility_m' => self::CIVILITY_M,
43
+			'enum_user_civility_ms' => self::CIVILITY_MS
44 44
 		);
45 45
 	}
46 46
 }

+ 89
- 9
src/CaptainLearning/Entity/User/User.php View File

@@ -1,22 +1,22 @@
1 1
 <?php
2 2
 namespace Logipro\CaptainLearning\Entity\User;
3 3
 
4
-use Logipro\CaptainLearning\Entity\Type\EnumRoleType;
4
+use Symfony\Component\HttpFoundation\File\File;
5 5
 
6
-use Symfony\Component\Validator\Mapping\ClassMetadata;
7
-use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
6
+use Logipro\CaptainLearning\Entity\Type\EnumRoleType;
8 7
 use Symfony\Component\Validator\Constraints as Assert;
8
+use Symfony\Component\Validator\Mapping\ClassMetadata;
9 9
 
10 10
 use Symfony\Component\Security\Core\User\UserInterface;
11 11
 
12
-use Logipro\CaptainLearning\Entity\Common\AbstractEntity;
13
-
12
+use Logipro\CaptainLearning\Entity\Common\AbstractUploadableEntity;
13
+use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14 14
 use Logipro\CaptainLearning\Entity\User\Constraints\Password\Password;
15 15
 
16 16
 /**
17 17
  * Représente un utilisateur du logiciel.
18 18
  */
19
-class User extends AbstractEntity
19
+class User extends AbstractUploadableEntity
20 20
 implements UserInterface
21 21
 {
22 22
 	/**
@@ -80,14 +80,35 @@ implements UserInterface
80 80
 	 */
81 81
 	protected $locale = 'fr';
82 82
 
83
-    protected $image;
84
-	protected $imageFile;
83
+	// attribut reel
84
+	protected $foto;
85
+	// attribut mappé
86
+	protected $fotoFile;
87
+
85 88
 	protected $updatedAt;
86 89
 
87 90
 	protected $clients;
88 91
 
89 92
 	protected $role;
90 93
 
94
+	
95
+	public static function getUploadMaxFileSize($property = "")
96
+	{
97
+		$captainMaxSize = self::convertToByte('1M');
98
+
99
+		return min(parent::getUploadMaxFileSize(),$captainMaxSize);
100
+	}
101
+
102
+	public static function getUploadMimeTypes($attribut = "")
103
+	{
104
+		return array(
105
+			'image/png',
106
+			'image/jpeg',
107
+			'image/jpg',
108
+			'image/gif'
109
+		);
110
+	}
111
+
91 112
 	/**
92 113
 	 * Retourne le mot de passe haché.
93 114
 	 *
@@ -244,13 +265,14 @@ implements UserInterface
244 265
 		$metadata->addPropertyConstraint('name', new Assert\NotBlank(array(
245 266
 			'message' => 'label_name_not_be_empty',
246 267
 		)));
247
-		$metadata->addPropertyConstraint('first_name', new Assert\NotBlank(array(
268
+		$metadata->addPropertyConstraint('firstName', new Assert\NotBlank(array(
248 269
 			'message' => 'label_first_name_not_be_empty',
249 270
 		)));
250 271
 		$metadata->addPropertyConstraint('email', new Assert\NotBlank(array(
251 272
 			'message' => 'label_email_not_be_empty',
252 273
 		)));
253 274
 		$metadata->addPropertyConstraint('email', new Assert\Email());
275
+		$metadata->addPropertyConstraint('fotoFile', new Assert\File(self::getFileConstraints()));
254 276
 		
255 277
 	// -- test les doublons
256 278
 		$metadata->addConstraint(new UniqueEntity(array(
@@ -303,5 +325,63 @@ implements UserInterface
303 325
 
304 326
 		return $this;
305 327
 	}
328
+
329
+
330
+
331
+	public function setFotoFile(?File $image = null): void
332
+    {
333
+        $this->fotoFile = $image;
334
+
335
+        if (null !== $image) {
336
+            // It is required that at least one field changes if you are using doctrine
337
+            // otherwise the event listeners won't be called and the file is lost
338
+            $this->updatedAt = new \DateTimeImmutable();
339
+        }
340
+    }
341
+
342
+    public function getFotoFile()
343
+    {
344
+        return $this->fotoFile;
345
+    }
346
+
347
+	public function serialize()
348
+	{
349
+		return serialize(array(
350
+			$this->userId,
351
+			$this->email,
352
+			$this->password,
353
+			$this->updatedAt,
354
+		));
355
+	}
356
+
357
+	public function unserialize($serialized)
358
+	{
359
+		list(
360
+			$this->userId,
361
+			$this->email,
362
+			$this->password,
363
+			$this->updatedAt
364
+			) = unserialize($serialized);
365
+	}
366
+
367
+	/**
368
+	 * Get the value of foto
369
+	 */ 
370
+	public function getFoto()
371
+	{
372
+		return $this->foto;
373
+	}
374
+
375
+	/**
376
+	 * Set the value of foto
377
+	 *
378
+	 * @return  self
379
+	 */ 
380
+	public function setFoto($foto)
381
+	{
382
+		$this->foto = $foto;
383
+
384
+		return $this;
385
+	}
306 386
 }
307 387
 ?>

+ 47
- 0
src/CaptainLearning/Form/Profil/UserType.php View File

@@ -0,0 +1,47 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Form\Profil;
3
+
4
+use Symfony\Component\Form\AbstractType;
5
+use Logipro\CaptainLearning\Entity\User\User;
6
+use Symfony\Component\Form\FormBuilderInterface;
7
+use Vich\UploaderBundle\Form\Type\VichImageType;
8
+use Symfony\Component\OptionsResolver\OptionsResolver;
9
+use Symfony\Component\Form\Extension\Core\Type\FileType;
10
+use Symfony\Component\Form\Extension\Core\Type\TextType;
11
+use Symfony\Component\Form\Extension\Core\Type\EmailType;
12
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
13
+use Logipro\CaptainLearning\Entity\Type\EnumUserCivilityType;
14
+
15
+class UserType extends AbstractType
16
+{
17
+	public function buildForm(FormBuilderInterface $builder, array $options)
18
+	{
19
+        $builder
20
+        ->add('civility', ChoiceType::class, array(
21
+            'choices' => EnumUserCivilityType::getFormChoices(),
22
+            'expanded'=>true,
23
+			'multiple'=>false,
24
+			'required' => true
25
+		))
26
+        ->add('name', TextType::class, array(
27
+			'required' => true,
28
+        ))
29
+        ->add('first_name', TextType::class, array(
30
+			'required' => true,
31
+        ))
32
+        ->add('email', EmailType::class, array(
33
+			'required' => true,
34
+        ));
35
+       /* ->add('fotoFile', VichImageType::class,array(
36
+            'data_class' => null
37
+        ));*/
38
+	}
39
+
40
+    public function configureOptions(OptionsResolver $resolver)
41
+    {
42
+        $resolver->setDefaults(array(
43
+            'data_class' => User::class,
44
+            'label_format' => 'form_%id%'
45
+        ));
46
+    }
47
+}

+ 46
- 0
src/CaptainLearning/Service/FileUploader.php View File

@@ -0,0 +1,46 @@
1
+<?php
2
+namespace Logipro\CaptainLearning\Service;
3
+
4
+use Symfony\Component\HttpFoundation\File\Exception\FileException;
5
+use Symfony\Component\HttpFoundation\File\UploadedFile;
6
+
7
+use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8
+use Symfony\Component\DependencyInjection\ContainerAwareTrait;
9
+use Symfony\Component\DependencyInjection\ContainerInterface;
10
+
11
+class FileUploader
12
+implements ContainerAwareInterface
13
+{
14
+    use ContainerAwareTrait;
15
+    public function __construct(ContainerInterface $container)
16
+	{
17
+		$this->setContainer($container);
18
+	}
19
+
20
+    public function upload(UploadedFile $file)
21
+    {
22
+        $fileName = md5(uniqid()).'.'.$file->guessExtension();
23
+
24
+        try {
25
+            $file->move($this->getUploadPath(), $fileName);
26
+        } catch (FileException $e) {
27
+            // ... handle exception if something happens during file upload
28
+        }
29
+
30
+        return $fileName;
31
+    }
32
+
33
+    public function getUploadPath()
34
+    {
35
+        return $this->container->getParameter('app.path.upload_dir');
36
+    }
37
+    public function getUploadTempPath()
38
+    {
39
+        return $this->container->getParameter('app.path.upload_temp_dir');
40
+    }
41
+
42
+    public function getUploadPublicRootPath()
43
+	{
44
+		return $this->container->getParameter('app.url.upload_public');
45
+	}
46
+}

+ 6
- 1
src/CaptainLearning/Twig/BackOffice/WidgetExtension.php View File

@@ -28,10 +28,15 @@ class WidgetExtension extends AbstractExtension
28 28
 
29 29
 	public function drawWidgets($widgets = array())
30 30
 	{
31
+		if (!is_array($widgets))
32
+		{
33
+			return;
34
+		}
35
+		
31 36
 		$results = array();
32 37
         foreach ($widgets as $widget)
33 38
         {
34
-            $results[] = $this->renderView('backOffice/common/widget/layout.html.twig',$widget);
39
+            $results[] = $widget;//$this->renderView('backOffice/common/widget/layout.html.twig',$widget);
35 40
 		}
36 41
 		return implode("",$results);
37 42
     }

+ 1
- 1
templates/backOffice/common/layout/menu.base.html.twig View File

@@ -10,7 +10,7 @@
10 10
 			<a data-toggle="tooltip" title="{{ 'label_bo_buyer_menu_view_dashboard'| trans }}" href="{{ welcome_path }}">
11 11
 				<div class="nav-user flex-md-column d-flex align-items-center">
12 12
 					<div class="nav-avatar contact-coralie text-center mx-auto">
13
-						<img class="w-100" src="{{ company.picture }}" alt="avatar"/>
13
+						<img class="w-100" src="{{ user_foto_url }}" alt="avatar"/>
14 14
 					</div>
15 15
 					<div class="nav-name">
16 16
 						{{ company.name }}

+ 2
- 2
templates/backOffice/common/widget/layout.html.twig View File

@@ -6,12 +6,12 @@
6 6
 		{% if icon_url != '' %}
7 7
 			<img src="{{ icon_url }}" alt="icone" class="rounded-circle d-inline ml-0 mr-2 bg-white">
8 8
 		{% endif %}
9
-			<h2 class="m-0 d-inline align-middle">{{ widget_title }}</h2>
9
+			<h2 class="m-0 d-inline align-middle">{{ widget_title|trans }}</h2>
10 10
 		</div>
11 11
 		{% endif %}
12 12
 
13 13
 		<div class="cpt-widget-content py-3 px-3">
14
-			{{ widget_content }}
14
+			{% block body %}{% endblock %}
15 15
 		</div>
16 16
 	</div>
17 17
 </div>

+ 21
- 0
templates/backOffice/individual/user/edit/edit_profil.html.twig View File

@@ -0,0 +1,21 @@
1
+{% extends "backOffice/individual/layout/layout.base.html.twig" %}
2
+{% block body_javascripts %}
3
+	{{ parent() }}
4
+	<script src="{{ asset('build/profil_form.js') }}"></script>
5
+{% endblock %}
6
+{% block head_css %}
7
+	{{ parent() }}
8
+	<link href="{{ asset('build/croppie.css') }}" rel="stylesheet"/>
9
+{% endblock %}
10
+
11
+{% block body %}
12
+    <div class="my-3 w-100">
13
+        <div class="">
14
+            <div class="message-display"></div>
15
+            <h1 class="m-0 mx-1 mx-md-3 font-weight-bold text-dark align-self-center">{{ 'label_profil_title'|trans }}</h1>
16
+            <div class="row mx-0">
17
+                {{ cpt_drawwidgets(widgets)|raw }}
18
+            </div>
19
+        </div>
20
+    </div>
21
+{% endblock %}

+ 24
- 0
templates/backOffice/individual/user/edit/widget-change-password.html.twig View File

@@ -0,0 +1,24 @@
1
+<div class="my-2">
2
+	<p class="font-weight-bold">
3
+        {{ 'label_profil_welcome'|trans }}
4
+		<span class="text-space">{{ user.getFirstName()}} {{ user.getName() }}</span>
5
+	</p>
6
+	<p>
7
+		 {{ 'label_profil_welcome_widget_content'|trans }}
8
+	</p>
9
+
10
+	<div class="d-flex flex-wrap justify-content-center font-weight-bold text-center mt-4">
11
+		<div class="d-inline mx-3 mx-md-5">
12
+			<a href="{{ path('editProfil') }}">
13
+				<img src="{{ asset('build/images/userNoir.svg') }}" style="height:80px;" alt="icone" class="d-block mx-auto"/>
14
+				{{ 'label_profil_welcome_to_profil_action'|trans }}
15
+			</a>
16
+		</div>
17
+		<div class="d-inline mx-3 mx-md-5">
18
+			<a href="">
19
+				<img src="{{ asset('build/images/entrepriseNoir.svg') }}" style="height:80px;" alt="icone" class="d-block mx-auto"/>
20
+				{{ 'label_profil_welcome_to_companies_action'|trans }}
21
+			</a>
22
+		</div>
23
+	</div>
24
+</div>

+ 93
- 0
templates/backOffice/individual/user/edit/widget-edit-profil.html.twig View File

@@ -0,0 +1,93 @@
1
+{% extends "backOffice/common/widget/layout.html.twig" %}
2
+{% block body %}
3
+{{ form_start(form, {attr: {class: 'profil-info needs-validation pb-0','enctype':'multipart/form-data'} }) }}
4
+    <div class="message-display" data-reference="profil"></div>
5
+    <div class="col-12">
6
+        {{ form_errors(form) }}
7
+    </div>
8
+
9
+    <div class="row">
10
+        <div class="col-12 cpt-widget-col-xxl-6">
11
+
12
+            <div class="form-group">
13
+				{{ form_label(form.civility) }}
14
+		    	<div class="cpt-switch">
15
+                    {{ form_widget(form.civility, { 'attr': {'class': ''} }) }}
16
+			</div>
17
+
18
+            <div class="form-row">
19
+                <div class="form-group col-md pr-md-0">
20
+                    {{ form_label(form.name) }}
21
+                    <div class="input-group">
22
+                        <div class="input-group-prepend">
23
+                            <span class="input-group-text p-1"><ion-icon class="ion-ios-person text-dark lead" style="width: 25px;"></ion-icon></span>
24
+                        </div>
25
+                        {{ form_widget(form.name, { 'attr': {'class': 'form-control','placeholder' : 'Nom'} }) }}
26
+                        <div class="invalid-feedback">
27
+                            {{ 'label_user_name_error'|trans }}
28
+                        </div>
29
+                    </div>
30
+                </div>
31
+                <div class="form-group col-md pl-md-0">
32
+                    {{ form_label(form.first_name) }}
33
+                    {{ form_widget(form.first_name, { 'attr': {'class': 'form-control','placeholder' : 'Prénom'} }) }}
34
+                    <div class="invalid-feedback">
35
+                        {{ 'label_user_first_name_error'|trans }}
36
+                    </div>
37
+                </div>
38
+            </div>
39
+
40
+            <div class="form-group">
41
+				{{ form_label(form.email) }}
42
+				<div class="input-group">
43
+					<div class="input-group-prepend">
44
+						<span class="input-group-text p-1"><ion-icon class="text-dark lead" style="width: 25px;">@</ion-icon></span>
45
+					</div>
46
+                    {{ form_widget(form.email, { 'attr': {'class': 'form-control','placeholder' : 'Adresse e-mail'} }) }}
47
+
48
+					<div class="invalid-feedback">
49
+                        {{ 'label_user_email_error'|trans }}
50
+                    </div>
51
+				</div>
52
+			</div>
53
+        </div>
54
+    </div>
55
+    <div class="col-12 cpt-widget-col-xxl-6">
56
+			<!-- Logo -->
57
+			<div class="form-row form-group">
58
+				<input type="hidden" name="foto_file" data-size="{{ upload_max_file_size }}" data-extensions="jpeg,jpg,png" value="{{ user.getFoto() }}" data-croppie="" 
59
+                data-url-default="{{ absolute_url(asset('build/images/ico-m.png')) }}" data-url="{% if user_foto_url != "" %}{{ absolute_url(user_foto_url) }}{% endif %}" data-input="#foto_upload">
60
+
61
+				<div class="col-md-6">
62
+					<div class="row m-0">
63
+						<div class="col-2">
64
+							<label>choisir</label>
65
+						</div>
66
+						<div class="col text-right">
67
+							<a class="js-delete-image text-right" href="#">Supprimer l'image</a>
68
+						</div>
69
+					</div>
70
+					<div class="text-center">
71
+						<a class="preview" id="upload-result" href="#">Prévisualiser</a>
72
+					</div>
73
+				</div>
74
+
75
+				<div class="col-md-6 mb-3">
76
+					<label>{{ 'label_profil_foto_choice'|trans }}</label>
77
+					<div class="position-relative">
78
+                        <input type="file" name="foto_upload" id="foto_upload" class="custom-file-input" accept="image/*">
79
+						<label class="custom-file-label" for="foto_upload" data-default="Déposer votre fichier">Déposer votre fichier</label>
80
+                        <small class="text-muted">
81
+                            {{uploadInfo}}
82
+						</small>
83
+					</div>
84
+				</div>
85
+			</div>
86
+		</div>
87
+	</div>
88
+
89
+     <div class="text-center form-lama-validate">
90
+        <button type="submit" class="btn btn-primary">{{ 'label_button_validate_edit'|trans }}</button>
91
+    </div>
92
+{{ form_end(form) }}
93
+{% endblock %}

+ 0
- 5
templates/backOffice/individual/user/edit_profil.html.twig View File

@@ -1,5 +0,0 @@
1
-{% extends "backOffice/individual/layout/layout.base.html.twig" %}
2
-
3
-{% block body %}
4
-body
5
-{% endblock %}

templates/backOffice/individual/user/profil.html.twig → templates/backOffice/individual/user/profil/profil.html.twig View File


+ 28
- 0
templates/backOffice/individual/user/profil/widget-welcome.html.twig View File

@@ -0,0 +1,28 @@
1
+{% extends "backOffice/common/widget/layout.html.twig" %}
2
+
3
+{% block body %}
4
+<div class="my-2">
5
+	<p class="font-weight-bold">
6
+        {{ 'label_profil_welcome'|trans }}
7
+		<span class="text-space">{{ user.getFirstName()}} {{ user.getName() }}</span>
8
+	</p>
9
+	<p>
10
+		 {{ 'label_profil_welcome_widget_content'|trans }}
11
+	</p>
12
+
13
+	<div class="d-flex flex-wrap justify-content-center font-weight-bold text-center mt-4">
14
+		<div class="d-inline mx-3 mx-md-5">
15
+			<a href="{{ path('editProfil') }}">
16
+				<img src="{{ asset('build/images/userNoir.svg') }}" style="height:80px;" alt="icone" class="d-block mx-auto"/>
17
+				{{ 'label_profil_welcome_to_profil_action'|trans }}
18
+			</a>
19
+		</div>
20
+		<div class="d-inline mx-3 mx-md-5">
21
+			<a href="">
22
+				<img src="{{ asset('build/images/entrepriseNoir.svg') }}" style="height:80px;" alt="icone" class="d-block mx-auto"/>
23
+				{{ 'label_profil_welcome_to_companies_action'|trans }}
24
+			</a>
25
+		</div>
26
+	</div>
27
+</div>
28
+{% endblock %}

+ 1
- 1
templates/common/layout/header/header.base.html.twig View File

@@ -36,7 +36,7 @@
36 36
                     <span class="d-none d-md-inline">
37 37
                     {% if not company %}
38 38
                         {{ user.getFirstName() }} {{ user.getName() }}
39
-                        {% set urlPhoto = user.getFotoUrl() %}
39
+                        {% set urlPhoto = user_foto_url %}
40 40
                         {% if urlPhoto == "" %}
41 41
                             {% if user.getCivility() == constant('Logipro\\CaptainLearning\\Entity\\Type\\EnumUserCivilityType::CIVILITY_MS') %}
42 42
                                 {% set urlPhoto = asset('build/images/ico-mme.png') %}

+ 1
- 1
templates/common/layout/header/individual.part.html.twig View File

@@ -1,7 +1,7 @@
1 1
 <?php $urlPhoto = CPTViewUtility::getVariable("url_photo"); $entreprise = CPTViewUtility::getVariable("entreprise"); ?>
2 2
 <div class="row border-bottom m-0 p-2 pr-3{% if not company %} active{% endif %}" data-id-entreprise="0"{% if company %} data-toggle="tooltip" data-placement="top" title="Prendre l'identité de {{ user.getFirstName}} {{ user.getName() }}"{% endif %}>
3 3
 	<div class="col col-4 col-sm-5 d-flex align-items-center justify-content-center px-1 pr-sm-3 pl-sm-0">
4
-		{% set urlPhoto = user.getFotoUrl() %}
4
+		{% set urlPhoto = user_foto_url %}
5 5
         {% if urlPhoto == "" %}
6 6
             {% if user.getCivility() == constant('Logipro\\CaptainLearning\\Entity\\Type\\EnumUserCivilityType::CIVILITY_MS') %}
7 7
                 {% set urlPhoto = asset('build/images/ico-mme.png') %}

+ 4
- 1
templates/common/layout/layout.base.html.twig View File

@@ -12,7 +12,10 @@
12 12
 		{% endblock %}
13 13
 		
14 14
 		<!-- stylesheets -->
15
-		<link href="{{ asset('build/shared.css') }}" rel="stylesheet"/>
15
+		{% block head_css %}
16
+			<link href="{{ asset('build/shared.css') }}" rel="stylesheet"/>
17
+		{% endblock %}
18
+
16 19
 	</head>
17 20
 
18 21
 	<body class="bg-light">

+ 19
- 0
translations/messages.fr.xlf View File

@@ -746,6 +746,25 @@
746 746
 				<source>label_login_error</source>
747 747
 				<target>Votre identifiant et/ou votre mot de passe sont incorrecte</target>
748 748
 			</trans-unit>
749
+			<!-- mot de passe oublié -->
750
+
751
+			<!-- Utilisateur -->
752
+			<trans-unit id="enum_user_civility_na">
753
+				<source>enum_user_civility_na</source>
754
+				<target>NA</target>
755
+			</trans-unit>
756
+			<trans-unit id="enum_user_civility_m">
757
+				<source>enum_user_civility_m</source>
758
+				<target>Monsieur</target>
759
+			</trans-unit>
760
+			<trans-unit id="enum_user_civility_ms">
761
+				<source>enum_user_civility_ms</source>
762
+				<target>Madame</target>
763
+			</trans-unit>
764
+			<!-- Utilisateur -->
765
+
766
+
767
+			
749 768
 		</body>
750 769
 	</file>
751 770
 </xliff>

+ 1
- 0
webpack.config.js View File

@@ -50,6 +50,7 @@ Encore
50 50
 	.addEntry('captcha', './assets/exclusive/js/component/captcha.js')
51 51
 	.addEntry('training_organization_form', './assets/exclusive/js/backOffice/training_organization_form.js')
52 52
 	.addEntry('company_form', './assets/exclusive/js/backOffice/company_form.js')
53
+	.addEntry('profil_form', './assets/exclusive/js/backOffice/profil_form.js')
53 54
 	// first, install any presets you want to use (e.g. yarn add babel-preset-es2017)
54 55
 
55 56
 // auto provide variables

Loading…
Cancel
Save