Messenger.php 16 KB
Newer Older
1
2
3
<?php
namespace UnicaenApp\View\Helper;

4
use Exception;
5
6
use UnicaenApp\Traits\MessageAwareInterface;
use UnicaenApp\Traits\MessageAwareTrait;
7
8
use Zend\EventManager\EventManagerAwareInterface;
use Zend\EventManager\EventManagerAwareTrait;
9
use Zend\Mvc\Plugin\FlashMessenger\FlashMessenger as FlashMessengerPlugin;
10
11
12
use Zend\View\Helper\AbstractHelper;

/**
13
14
15
 * Aide de vue permettant de stocker une liste de messages d'information de différentes sévérités
 * et de générer le code HTML pour les afficher (affublés d'un icône correspondant à leur sévérité).
 *
16
 * Possibilité d'importer les messages du FlashMessenger pour les mettre en forme de la même manière
17
 * (avec support du namespace, ex: 'these/info' ou 'these/*').
18
19
20
 *
 * @author Bertrand GAUTHIER <bertrand.gauthier@unicaen.fr>
 */
21
class Messenger extends AbstractHelper implements MessageAwareInterface, EventManagerAwareInterface
22
{
23
24
25
    const EVENT_RENDER = 'unicaen-app-messenger-render';

    use EventManagerAwareTrait;
26
    use MessageAwareTrait;
27

28
29
30
31
32
33
34
35
    const NAMESPACED_SEVERITY_SEPARATOR = '/';

    protected $uiClasses = [
        // severity   => [ alert class, icon class ]
        self::INFO    => ['info', 'info-sign'],
        self::SUCCESS => ['success', 'ok-sign'],
        self::WARNING => ['warning', 'warning-sign'],
        self::ERROR   => ['danger', 'exclamation-sign'],
36
37
    ];

38
39
40
41
42
43
    protected $severityOrder = [
        // severity   => order
        self::SUCCESS => 1,
        self::ERROR   => 2,
        self::WARNING => 3,
        self::INFO    => 4,
44
45
    ];

46
47
    /**
     * Activation ou non de l'affichage de l'icône
48
     *
49
50
51
     * @var bool
     */
    protected $withIcon = true;
52

53
54
55
56
    /**
     * @var string
     */
    protected $containerInnerTemplate = '%s';
57

58
59
60
61
62
    /**
     * @var array
     */
    protected $containerClassesToAdd = [];

63
    /**
64
     * @var FlashMessengerPlugin
65
66
     */
    protected $pluginFlashMessenger;
67

68
    /**
69
     * Helper entry point.
70
     *
71
     * @return static
72
     */
73
    public function __invoke()
74
75
76
    {
        return $this;
    }
77

78
79
    /**
     * Retourne le code HTML généré par cette aide de vue.
80
     *
81
82
83
84
     * @return string
     */
    public function __toString()
    {
85
86
        try {
            return $this->render();
87
        } catch (Exception $exc) {
88
89
            var_dump($exc->getMessage(), \UnicaenApp\Util::formatTraceString($exc->getTraceAsString()));
            die;
90
        }
91
    }
92
93

    /**
94
     * Génère le code HTML.
95
     *
96
97
     * @return string
     */
98
    protected function render()
99
100
101
102
103
104
    {
        if (!$this->hasMessages()) {
            return '';
        }

        $out = '';
105

106
107
        foreach ($this->getSortedMessages() as $severity => $array) {
            foreach ($array as $priority => $message) {
108
109
110
111
                if ($em = $this->getEventManager()){
                    $em->trigger(self::EVENT_RENDER, null, compact('severity','message'));
                }

112
                $out .= sprintf(
113
114
                    $this->getTemplate(is_string($severity) ? $severity : (is_string($priority) ? $priority : 'info')),
                    implode('<br />', (array)$message)
115
                );;
116
            }
117
118
119
120
121
        }

        return $out;
    }

122
123
    /**
     * Génère le code HTML d'un seul message. Pour usage ponctuel.
124
125
     *
     * @param string $message  Message à afficher
126
     * @param string $severity Ex: MessageAwareInterface::INFO
127
     *
128
129
130
131
     * @return string
     */
    public function renderMessage($message, $severity = null)
    {
132
        if (!$message) {
133
134
135
136
137
138
            return '';
        }

        return sprintf($this->getTemplate($severity ?: 'info'), $message);
    }

139
140
141
    /**
     * @return array
     */
142
    protected function getSortedMessages()
143
    {
144
        $messages = (array)$this->getMessages();
145
        $order    = $this->severityOrder;
146
147

        uksort($messages, function ($s1, $s2) use ($order) {
148
149
150
151
152
153
            if ($order[$s1] < $order[$s2]) {
                return -1;
            }
            if ($order[$s1] > $order[$s2]) {
                return 1;
            }
154

155
156
            return 0;
        });
157

158
159
        return $messages;
    }
160

161
    /**
162
163
     * Importe les messages n-1 du FlashMessenger.
     *
164
     * @param string $namespace
165
     * @return static
166
     */
167
    protected function importFlashMessages($namespace = null)
168
    {
169
        return $this->_importFromFlashMessages($currentMessages = false, $namespace);
170
    }
171
172
173
174

    /**
     * Importe les messages courants du FlashMessenger.
     *
175
     * @param string $namespace
176
     * @return static
177
     */
178
    protected function importCurrentFlashMessages($namespace = null)
179
    {
180
181
182
183
        return $this->_importFromFlashMessages($currentMessages = true, $namespace);
    }

    /**
184
185
     * @param bool $currentMessages
     * @param string|null $namespace Facultatif, ex: 'info', 'these/danger', 'these/*', 'these' (i.e. 'these/*')
186
187
     * @return static
     */
188
    private function _importFromFlashMessages(bool $currentMessages, string $namespace = null)
189
190
191
    {
        $getMethod = $currentMessages ? 'getCurrentMessagesFromNamespace' : 'getMessagesFromNamespace';

192
193
194
        $fm = $this->getPluginFlashMessenger();

        if ($namespace) {
195
196
197
198
199
            // collecte des messages dans les namespaces de la forme "namespace/sévérité"
            $array = $this->extractSeveritiesAndNamespaces($namespace);
            foreach ($array as $sev => $ns) {
                foreach ($fm->$getMethod($ns) as $message) {
                    $this->addMessage($message, $sev);
200
201
202
                }
                if ($currentMessages) {
                    /* Si on importe alors on nettoie pour éviter un deuxième affichage */
203
                    $fm->clearCurrentMessagesFromNamespace($ns);
204
205
                }
            }
206
207
208
209
210
211
212
213
214

            // collecte des messages dans le namespace tel quel (fonctionne aussi si le namespace est une sévérité)
            foreach ((array)$fm->$getMethod($namespace) as $message) {
                $this->addMessage($message, $namespace);
            }
            if ($currentMessages) {
                /* Si on importe alors on nettoie pour éviter un deuxième affichage */
                $fm->clearCurrentMessagesFromNamespace($namespace);
            }
215
        }
216
        else {
217
            foreach ((array)$fm->$getMethod('error') as $message) {
218
219
                $this->addMessage($message, self::ERROR);
            }
220
            foreach ((array)$fm->$getMethod('success') as $message) {
221
222
                $this->addMessage($message, self::SUCCESS);
            }
223
            foreach ((array)$fm->$getMethod('info') as $message) {
224
225
                $this->addMessage($message, self::INFO);
            }
226
            foreach ((array)$fm->$getMethod('warning') as $message) {
227
228
229
                $this->addMessage($message, self::WARNING);
            }

230
231
232
            if ($currentMessages) {
                $fm->clearCurrentMessagesFromContainer();
            }
233
234
235
236
237
        }

        return $this;
    }

238
    /**
239
240
     * @param string $namespace Ex: 'these/danger' ou 'these/*'
     * @return array 'severity' => 'namespace'
241
     */
242
    private function extractSeveritiesAndNamespaces($namespace)
243
    {
244
245
246
247
248
        // Normalisation : $namespace doit être de la forme 'namespace/severity' ou 'namespace/*'.
        // Ex: 'these/danger' ou 'these/*'
        $separatorFound = strrpos($namespace, $sep = self::NAMESPACED_SEVERITY_SEPARATOR) !== false;
        if (! $separatorFound) {
            $namespace = $namespace . '/*';
249
        }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

        $parts = explode($sep, $namespace);
        $severity = array_pop($parts);

        // Si $namespace est de la forme 'namespace/*', cela revient à importer les namespaces
        // 'namespace/danger', 'namespace/success', 'namespace/info' et 'namespace/warning'.
        if ($severity === '*') {
            $namespacePrefix = implode(self::NAMESPACED_SEVERITY_SEPARATOR, $parts) . '/';
            $array = [
                $s = self::ERROR   => $namespacePrefix . $s,
                $s = self::SUCCESS => $namespacePrefix . $s,
                $s = self::INFO    => $namespacePrefix . $s,
                $s = self::WARNING => $namespacePrefix . $s,
            ];
        } else {
265
            $array = [
266
                $severity => $namespace,
267
            ];
268
        }
269

270
        return $array;
271
    }
272

273
274
    /**
     * Spécifie l'unique message courant au format Exception.
275
     *
276
277
     * @param Exception $exception Exception
     * @param string    $severity  Ex: Messenger::INFO
278
     *
279
     * @return static
280
     */
281
    public function setException(Exception $exception, $severity = self::ERROR)
282
283
284
285
286
    {
        $message = sprintf("<p><strong>%s</strong></p>", $exception->getMessage());
        if (($previous = $exception->getPrevious())) {
            $message .= sprintf("<p>Cause :<br />%s</p>", $previous->getMessage());
        }
287
288

        return $this->setMessages([$severity => $message]);
289
    }
290

291
    /**
292
     * @param string|null $namespace
293
     * @return static
294
295
     * @deprecated Utilisez plutôt setMessagesFromFlashMessengerWithNamespace($namespace)
     *                          ou setMessagesFromFlashMessengerWithNoNamespace()
296
     */
297
    public function setMessagesFromFlashMessenger($namespace = null)
298
    {
299
        $this->messages = [];
300
        $this->importFlashMessages($namespace);
301
302
303
304
305

        return $this;
    }

    /**
306
     * Remplace les messages existants par les messages *courants* du FlashMessenger stoqués dans le namespace spécifié.
307
     *
308
309
     * @param string $namespace
     * @return static
310
     */
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
    public function setMessagesFromFlashMessengerWithNamespace($namespace)
    {
        $this->messages = [];
        $this->importFlashMessages($namespace);

        return $this;
    }

    /**
     * Remplace les messages existants par les messages *courants* du FlashMessenger n'étant pas stoqués dans un namespace particulier
     * (ou pour être exact qui sont stoqués dans le namespace 'error', 'success', 'info' ou 'warning').
     *
     * @return static
     */
    public function setMessagesFromFlashMessengerWithNoNamespace()
    {
        $this->messages = [];
        $this->importFlashMessages();

        return $this;
    }

    /**
     * @param string|null $namespace
     * @return static
     * @deprecated Utilisez plutôt setCurrentMessagesFromFlashMessengerWithNamespace($namespace)
     *                          ou setCurrentMessagesFromFlashMessengerWithNoNamespace()
     */
339
    public function setCurrentMessagesFromFlashMessenger($namespace = null)
340
341
    {
        $this->messages = [];
342
        $this->importCurrentFlashMessages($namespace);
343

344
345
        return $this;
    }
346

347
    /**
348
     * Remplace les messages existants par les messages *en session* du FlashMessenger stoqués dans le namespace spécifié.
349
     *
350
351
     * @param string $namespace
     * @return static
352
     */
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
    public function setCurrentMessagesFromFlashMessengerWithNamespace($namespace)
    {
        $this->messages = [];
        $this->importCurrentFlashMessages($namespace);

        return $this;
    }

    /**
     * Remplace les messages existants par les messages *en session* du FlashMessenger n'étant pas stoqués dans un namespace particulier
     * (ou pour être exact qui sont stoqués dans le namespace 'error', 'success', 'info' ou 'warning').
     *
     *
     * @return static
     */
    public function setCurrentMessagesFromFlashMessengerWithNoNamespace()
    {
        $this->messages = [];
        $this->importCurrentFlashMessages();

        return $this;
    }

    /**
     * @param string|null $namespace
     * @return static
     * @deprecated Utilisez plutôt addMessagesFromFlashMessengerWithNamespace($namespace)
     *                          ou addMessagesFromFlashMessengerWithNoNamespace()
     */
382
    public function addMessagesFromFlashMessenger($namespace = null)
383
    {
384
        $this->importFlashMessages($namespace);
385

386
387
        return $this;
    }
388
389

    /**
390
     * Ajoute aux messages à afficher les messages *courants* du FlashMessenger stoqués dans le namespace spécifié.
391
     *
392
393
     * @param string $namespace
     * @return static
394
     */
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    public function addMessagesFromFlashMessengerWithNamespace($namespace)
    {
        $this->importFlashMessages($namespace);

        return $this;
    }

    /**
     * Ajoute aux messages à afficher les messages *courants* du FlashMessenger n'étant pas stoqués dans un namespace particulier
     * (ou pour être exact qui sont stoqués dans le namespace 'error', 'success', 'info' ou 'warning').
     *
     * @return static
     */
    public function addMessagesFromFlashMessengerWithNoNamespace()
    {
        $this->importFlashMessages();

        return $this;
    }

    /**
     * @param string $namespace
     * @return static
     * @deprecated Utilisez plutôt addCurrentMessagesFromFlashMessengerWithNamespace($namespace)
     *                          ou addCurrentMessagesFromFlashMessengerWithNoNamespace()
     */
421
    public function addCurrentMessagesFromFlashMessenger($namespace = null)
422
    {
423
        $this->importCurrentFlashMessages($namespace);
424
425
426
427

        return $this;
    }

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
    /**
     * Ajoute aux messages à afficher les messages *en session* du FlashMessenger stoqués dans le namespace spécifié.
     *
     * @param string $namespace
     * @return static
     */
    public function addCurrentMessagesFromFlashMessengerWithNamespace($namespace)
    {
        $this->importCurrentFlashMessages($namespace);

        return $this;
    }

    /**
     * Ajoute aux messages à afficher les messages *en session* du FlashMessenger n'étant pas stoqués dans un namespace particulier
     * (ou pour être exact qui sont stoqués dans le namespace 'error', 'success', 'info' ou 'warning').
     *
     * @return static
     */
    public function addCurrentMessagesFromFlashMessengerWithNoNamespace()
    {
        $this->importCurrentFlashMessages();

        return $this;
    }

454
455
    /**
     * Active ou non l'affichage de l'icône.
456
     *
457
     * @param bool $withIcon <tt>true</tt> pour activer l'affichage de l'icône.
458
     *
459
     * @return static
460
461
462
     */
    public function withIcon($withIcon = true)
    {
463
464
        $this->withIcon = (bool)$withIcon;

465
466
        return $this;
    }
467
468

    /**
469
     * Retourne le motif utilisé pour générer le conteneur de chaque message à afficher.
470
     *
471
     * @param string $severity    Ex: Messenger::INFO
472
     * @param string $containerId Dom id éventuel à utiliser pour la div englobante
473
474
     *
     * @return string
475
     */
476
    public function getTemplate($severity, $containerId = null)
477
    {
478
        $severity = array_key_exists($severity, $this->uiClasses) ? $severity : self::INFO;
479

480
481
482
        $alertClass = $this->uiClasses[$severity][0];
        $iconClass  = $this->uiClasses[$severity][1];

483
        $innerTemplate = $this->containerInnerTemplate ?: '%s';
484
        $iconMarkup    = $this->withIcon ? "<span class=\"glyphicon glyphicon-$iconClass\"></span>" : null;
485
        $containerId   = $containerId ? sprintf('id="%s"', $containerId) : '';
486

487
        $classesToAdd = $this->containerClassesToAdd ? implode(' ', $this->containerClassesToAdd) : '';
488

489
        $template = <<<EOT
490
<div class="messenger alert alert-$alertClass $classesToAdd" $containerId>
491
    <button type="button" class="close" title="Fermer cette alerte" data-dismiss="alert">&times;</button>
492
    $iconMarkup
493
    $innerTemplate
494
</div>
495
EOT;
496

497
498
        return $template . PHP_EOL;
    }
499

500
501
502
503
504
505
506
507
508
509
510
    /**
     * @param array|string $containerClassesToAdd Ex: ['alert-md']
     * @return static
     */
    public function setContainerClassesToAdd($containerClassesToAdd)
    {
        $this->containerClassesToAdd = (array) $containerClassesToAdd;

        return $this;
    }

511
    /**
512
513
514
     *
     * @param string $containerInnerTemplate
     *
515
     * @return static
516
517
518
519
     */
    public function setContainerInnerTemplate($containerInnerTemplate = '%s')
    {
        $this->containerInnerTemplate = $containerInnerTemplate;
520

521
522
        return $this;
    }
523
524

    /**
525
     * @param FlashMessengerPlugin $pluginFlashMessenger
526
     */
527
    public function setPluginFlashMessenger(FlashMessengerPlugin $pluginFlashMessenger)
528
    {
529
530
        $this->pluginFlashMessenger = $pluginFlashMessenger;
    }
531

532
533
534
535
536
    /**
     * @return FlashMessengerPlugin
     */
    protected function getPluginFlashMessenger()
    {
537
538
        return $this->pluginFlashMessenger;
    }
539
}