Issues (3627)

app/bundles/CoreBundle/Model/FormModel.php (2 issues)

1
<?php
2
3
/*
4
 * @copyright   2014 Mautic Contributors. All rights reserved
5
 * @author      Mautic
6
 *
7
 * @link        http://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\CoreBundle\Model;
13
14
use Mautic\CoreBundle\Helper\InputHelper;
15
use Mautic\UserBundle\Entity\User;
16
use Symfony\Component\EventDispatcher\Event;
17
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
18
19
class FormModel extends AbstractCommonModel
20
{
21
    /**
22
     * Lock an entity to prevent multiple people from editing.
23
     *
24
     * @param object $entity
25
     */
26
    public function lockEntity($entity)
27
    {
28
        //lock the row if applicable
29
        if (method_exists($entity, 'setCheckedOut') && method_exists($entity, 'getId') && $entity->getId()) {
30
            if ($this->userHelper->getUser()->getId()) {
31
                $entity->setCheckedOut(new \DateTime());
32
                $entity->setCheckedOutBy($this->userHelper->getUser());
33
                $this->em->persist($entity);
34
                $this->em->flush();
35
            }
36
        }
37
    }
38
39
    /**
40
     * Check to see if the entity is locked.
41
     *
42
     * @param object $entity
43
     *
44
     * @return bool
45
     */
46
    public function isLocked($entity)
47
    {
48
        if (method_exists($entity, 'getCheckedOut')) {
49
            $checkedOut = $entity->getCheckedOut();
50
            if (!empty($checkedOut) && $checkedOut instanceof \DateTime) {
51
                $checkedOutBy = $entity->getCheckedOutBy();
52
                $maxLockTime  = $this->coreParametersHelper->get('max_entity_lock_time', 0);
53
54
                if (0 != $maxLockTime && is_numeric($maxLockTime)) {
55
                    $lockValidityDate = clone $checkedOut;
56
                    $lockValidityDate->add(new \DateInterval('PT'.$maxLockTime.'S'));
57
                } else {
58
                    $lockValidityDate = false;
59
                }
60
61
                //is lock expired ?
62
                if (false !== $lockValidityDate && (new \DateTime()) > $lockValidityDate) {
63
                    return false;
64
                }
65
66
                //is it checked out by the current user?
67
                if (!empty($checkedOutBy) && ($checkedOutBy !== $this->userHelper->getUser()->getId())) {
68
                    return true;
69
                }
70
            }
71
        }
72
73
        return false;
74
    }
75
76
    /**
77
     * Unlock an entity that prevents multiple people from editing.
78
     *
79
     * @param object $entity
80
     * @param        $extra  Can be used by model to determine what to unlock
81
     */
82
    public function unlockEntity($entity, $extra = null)
83
    {
84
        //unlock the row if applicable
85
        if (method_exists($entity, 'setCheckedOut') && method_exists($entity, 'getId') && $entity->getId()) {
86
            //flush any potential changes
87
            $this->em->refresh($entity);
88
89
            $entity->setCheckedOut(null);
90
            $entity->setCheckedOutBy(null);
91
92
            $this->em->persist($entity);
93
            $this->em->flush();
94
        }
95
    }
96
97
    /**
98
     * Create/edit entity.
99
     *
100
     * @param object $entity
101
     * @param bool   $unlock
102
     */
103
    public function saveEntity($entity, $unlock = true)
104
    {
105
        $isNew = $this->isNewEntity($entity);
106
107
        //set some defaults
108
        $this->setTimestamps($entity, $isNew, $unlock);
109
110
        $event = $this->dispatchEvent('pre_save', $entity, $isNew);
111
        $this->getRepository()->saveEntity($entity);
112
        $this->dispatchEvent('post_save', $entity, $isNew, $event);
113
    }
114
115
    /**
116
     * Create/edit entity then detach to preserve RAM.
117
     *
118
     * @param      $entity
119
     * @param bool $unlock
120
     */
121
    public function saveAndDetachEntity($entity, $unlock = true)
122
    {
123
        $this->saveEntity($entity, $unlock);
124
125
        $this->em->detach($entity);
126
    }
127
128
    /**
129
     * Save an array of entities.
130
     *
131
     * @param array $entities
132
     * @param bool  $unlock
133
     *
134
     * @return array
135
     */
136
    public function saveEntities($entities, $unlock = true)
137
    {
138
        //iterate over the results so the events are dispatched on each delete
139
        $batchSize = 20;
140
        $i         = 0;
141
        foreach ($entities as $entity) {
142
            $isNew = $this->isNewEntity($entity);
143
144
            //set some defaults
145
            $this->setTimestamps($entity, $isNew, $unlock);
146
147
            $event = $this->dispatchEvent('pre_save', $entity, $isNew);
148
            $this->getRepository()->saveEntity($entity, false);
149
            if (0 === ++$i % $batchSize) {
150
                $this->em->flush();
151
            }
152
        }
153
154
        $this->em->flush();
155
156
        // Dispatch post events after everything has been flushed
157
        foreach ($entities as $entity) {
158
            $this->dispatchEvent('post_save', $entity, $isNew, $event);
159
        }
160
    }
161
162
    /**
163
     * Determines if an entity is new or not.
164
     *
165
     * @param mixed $entity
166
     *
167
     * @return bool
168
     */
169
    public function isNewEntity($entity)
170
    {
171
        if (method_exists($entity, 'isNew')) {
172
            return $entity->isNew();
173
        }
174
175
        if (method_exists($entity, 'getId')) {
176
            $isNew = ($entity->getId()) ? false : true;
177
        } else {
178
            $isNew = \Doctrine\ORM\UnitOfWork::STATE_NEW === $this->em->getUnitOfWork()->getEntityState($entity);
179
        }
180
181
        return $isNew;
182
    }
183
184
    /**
185
     * Toggles entity publish status.
186
     *
187
     * @param object $entity
188
     *
189
     * @return bool Force browser refresh
190
     */
191
    public function togglePublishStatus($entity)
192
    {
193
        if (method_exists($entity, 'setIsPublished')) {
194
            $status = $entity->getPublishStatus();
195
196
            switch ($status) {
197
                case 'unpublished':
198
                    $entity->setIsPublished(true);
199
                    break;
200
                case 'published':
201
                case 'expired':
202
                case 'pending':
203
                    $entity->setIsPublished(false);
204
                    break;
205
            }
206
207
            //set timestamp changes
208
            $this->setTimestamps($entity, false, false);
209
        } elseif (method_exists($entity, 'setIsEnabled')) {
210
            $enabled    = $entity->getIsEnabled();
211
            $newSetting = ($enabled) ? false : true;
212
            $entity->setIsEnabled($newSetting);
213
        }
214
215
        //hit up event listeners
216
        $event = $this->dispatchEvent('pre_save', $entity);
217
        $this->getRepository()->saveEntity($entity);
218
        $this->dispatchEvent('post_save', $entity, false, $event);
219
220
        return false;
221
    }
222
223
    /**
224
     * Set timestamps and user ids.
225
     *
226
     * @param object $entity
227
     * @param bool   $isNew
228
     * @param bool   $unlock
229
     */
230
    public function setTimestamps(&$entity, $isNew, $unlock = true)
231
    {
232
        if ($isNew) {
233
            if (method_exists($entity, 'setDateAdded') && !$entity->getDateAdded()) {
234
                $entity->setDateAdded(new \DateTime());
235
            }
236
237
            if ($this->userHelper->getUser() instanceof User) {
0 ignored issues
show
$this->userHelper->getUser() is always a sub-type of Mautic\UserBundle\Entity\User.
Loading history...
238
                if (method_exists($entity, 'setCreatedBy') && !$entity->getCreatedBy()) {
239
                    $entity->setCreatedBy($this->userHelper->getUser());
240
                } elseif (method_exists($entity, 'setCreatedByUser') && !$entity->getCreatedByUser()) {
241
                    $entity->setCreatedByUser($this->userHelper->getUser()->getName());
242
                }
243
            }
244
        } else {
245
            if (method_exists($entity, 'setDateModified')) {
246
                $setDateModified = true;
247
                if (method_exists($entity, 'getChanges')) {
248
                    $changes = $entity->getChanges();
249
                    if (empty($changes)) {
250
                        $setDateModified = false;
251
                    }
252
                }
253
                if ($setDateModified) {
254
                    $dateModified = (defined('MAUTIC_DATE_MODIFIED_OVERRIDE')) ? \DateTime::createFromFormat('U', MAUTIC_DATE_MODIFIED_OVERRIDE)
255
                        : new \DateTime();
256
                    $entity->setDateModified($dateModified);
257
                }
258
            }
259
260
            if ($this->userHelper->getUser() instanceof User) {
0 ignored issues
show
$this->userHelper->getUser() is always a sub-type of Mautic\UserBundle\Entity\User.
Loading history...
261
                if (method_exists($entity, 'setModifiedBy')) {
262
                    $entity->setModifiedBy($this->userHelper->getUser());
263
                } elseif (method_exists($entity, 'setModifiedByUser')) {
264
                    $entity->setModifiedByUser($this->userHelper->getUser()->getName());
265
                }
266
            }
267
        }
268
269
        //unlock the row if applicable
270
        if ($unlock && method_exists($entity, 'setCheckedOut')) {
271
            $entity->setCheckedOut(null);
272
            $entity->setCheckedOutBy(null);
273
        }
274
    }
275
276
    /**
277
     * Delete an entity.
278
     *
279
     * @param object $entity
280
     */
281
    public function deleteEntity($entity)
282
    {
283
        //take note of ID before doctrine wipes it out
284
        $id    = $entity->getId();
285
        $event = $this->dispatchEvent('pre_delete', $entity);
286
        $this->getRepository()->deleteEntity($entity);
287
288
        //set the id for use in events
289
        $entity->deletedId = $id;
290
        $this->dispatchEvent('post_delete', $entity, false, $event);
291
    }
292
293
    /**
294
     * Delete an array of entities.
295
     *
296
     * @param array $ids
297
     *
298
     * @return array
299
     */
300
    public function deleteEntities($ids)
301
    {
302
        $entities = [];
303
        //iterate over the results so the events are dispatched on each delete
304
        $batchSize = 20;
305
        foreach ($ids as $k => $id) {
306
            $entity        = $this->getEntity($id);
307
            $entities[$id] = $entity;
308
            if (null !== $entity) {
309
                $event = $this->dispatchEvent('pre_delete', $entity);
310
                $this->getRepository()->deleteEntity($entity, false);
311
                //set the id for use in events
312
                $entity->deletedId = $id;
313
                $this->dispatchEvent('post_delete', $entity, false, $event);
314
            }
315
            if (0 === (($k + 1) % $batchSize)) {
316
                $this->em->flush();
317
            }
318
        }
319
        $this->em->flush();
320
        //retrieving the entities while here so may as well return them so they can be used if needed
321
        return $entities;
322
    }
323
324
    /**
325
     * Creates the appropriate form per the model.
326
     *
327
     * @param object                              $entity
328
     * @param \Symfony\Component\Form\FormFactory $formFactory
329
     * @param string|null                         $action
330
     * @param array                               $options
331
     *
332
     * @return \Symfony\Component\Form\Form
333
     *
334
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
335
     */
336
    public function createForm($entity, $formFactory, $action = null, $options = [])
337
    {
338
        throw new NotFoundHttpException('Object does not support edits.');
339
    }
340
341
    /**
342
     * Dispatches events for child classes.
343
     *
344
     * @param       $action
345
     * @param       $entity
346
     * @param bool  $isNew
347
     * @param Event $event
348
     *
349
     * @return Event|null
350
     */
351
    protected function dispatchEvent($action, &$entity, $isNew = false, Event $event = null)
352
    {
353
        //...
354
355
        return $event;
356
    }
357
358
    /**
359
     * Set default subject for user contact form.
360
     *
361
     * @param string $subject
362
     * @param object $entity
363
     *
364
     * @return mixed
365
     */
366
    public function getUserContactSubject($subject, $entity)
367
    {
368
        switch ($subject) {
369
            case 'locked':
370
                $msg = 'mautic.user.user.contact.locked';
371
                break;
372
            default:
373
                $msg = 'mautic.user.user.contact.regarding';
374
                break;
375
        }
376
377
        $nameGetter = $this->getNameGetter();
378
379
        return $this->translator->trans($msg, [
380
            '%entityName%' => $entity->$nameGetter(),
381
            '%entityId%'   => $entity->getId(),
382
        ]);
383
    }
384
385
    /**
386
     * Returns the function used to name the entity.
387
     *
388
     * @return string
389
     */
390
    public function getNameGetter()
391
    {
392
        return 'getName';
393
    }
394
395
    /**
396
     * Cleans a string to be used as an alias. The returned string will be alphanumeric or underscore, less than 25 characters
397
     * and if it is a reserved SQL keyword, it will be prefixed with f_.
398
     *
399
     * @param string   $alias
400
     * @param string   $prefix         Used when the alias is a reserved keyword by the database platform
401
     * @param int|bool $maxLength      Maximum number of characters used; 0 to disable
402
     * @param string   $spaceCharacter Character to replace spaces with
403
     *
404
     * @return string
405
     *
406
     * @throws \Doctrine\DBAL\DBALException
407
     */
408
    public function cleanAlias($alias, $prefix = '', $maxLength = false, $spaceCharacter = '_')
409
    {
410
        // Transliterate to latin characters
411
        $alias = InputHelper::transliterate(trim($alias));
412
413
        // Some labels are quite long if a question so cut this short
414
        $alias = strtolower(InputHelper::alphanum($alias, false, $spaceCharacter));
415
416
        // Ensure we have something
417
        if (empty($alias)) {
418
            $alias = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 5);
419
        }
420
421
        // Trim if applicable
422
        if ($maxLength) {
423
            $alias = substr($alias, 0, $maxLength);
424
        }
425
426
        if ('_' == substr($alias, -1)) {
427
            $alias = substr($alias, 0, -1);
428
        }
429
430
        // Check that alias is SQL safe since it will be used for the column name
431
        $databasePlatform = $this->em->getConnection()->getDatabasePlatform();
432
        $reservedWords    = $databasePlatform->getReservedKeywordsList();
433
434
        if ($reservedWords->isKeyword($alias) || is_numeric($alias)) {
435
            $alias = $prefix.$alias;
436
        }
437
438
        return $alias;
439
    }
440
441
    /**
442
     * Catch the exception in production and log the error.
443
     * Throw the exception in the dev mode only.
444
     */
445
    protected function flushAndCatch()
446
    {
447
        try {
448
            $this->em->flush();
449
        } catch (\Exception $ex) {
450
            if (MAUTIC_ENV === 'dev') {
451
                throw $ex;
452
            }
453
454
            $this->logger->addError(
455
                $ex->getMessage(),
456
                ['exception' => $ex]
457
            );
458
        }
459
    }
460
}
461