Passed
Push — master ( 0d88a1...fded30 )
by Andreas
14:17
created

mgdobject::is_unique()   B

Complexity

Conditions 10
Paths 55

Size

Total Lines 55
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 10.0186

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 10
eloc 35
c 2
b 0
f 0
nc 55
nop 0
dl 0
loc 55
ccs 33
cts 35
cp 0.9429
crap 10.0186
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
4
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
6
 */
7
namespace midgard\portable\api;
8
9
use midgard\portable\storage\connection;
10
use midgard\portable\storage\objectmanager;
11
use midgard\portable\storage\collection;
12
use midgard\portable\storage\interfaces\metadata as metadata_interface;
13
use midgard\portable\mgdschema\translator;
14
use midgard\portable\api\error\exception;
15
use Doctrine\ORM\Query;
16
use midgard_connection;
17
use Doctrine\ORM\Proxy\Proxy;
18
use Doctrine\ORM\QueryBuilder;
19
use Doctrine\ORM\EntityNotFoundException;
20
21
/**
22
 * @property metadata $metadata
23
 */
24
abstract class mgdobject extends dbobject
25
{
26
    protected $metadata; // compat with mgd behavior: If the schema has no metadata, the property is present anyway
27
28
    public $action = ''; // <== does this need to do anything?
29
30
    private $collections = [];
31
32
    /**
33
     * @param mixed $id ID or GUID
34
     */
35 132
    public function __construct($id = null)
36
    {
37 132
        if ($id !== null) {
38 49
            if (is_int($id)) {
39 40
                $this->get_by_id($id);
40 14
            } elseif (is_string($id)) {
41 14
                $this->get_by_guid($id);
42
            }
43
        }
44
    }
45
46 16
    private function get_collection(string $classname) : collection
47
    {
48 16
        if (!isset($this->collections[$classname])) {
49 16
            $this->collections[$classname] = new collection($classname);
50
        }
51 16
        return $this->collections[$classname];
52
    }
53
54 1
    public function __debugInfo()
55
    {
56 1
        $ret = parent::__debugInfo();
57 1
        if (property_exists($this, 'metadata')) {
58 1
            $metadata = new \stdClass;
59 1
            foreach ($this->cm->getFieldNames() as $name) {
60 1
                if (strpos($name, 'metadata_') !== false) {
61 1
                    $fieldname = str_replace('metadata_', '', $name);
62 1
                    $metadata->$fieldname = $this->__get($name);
63
                }
64
            }
65 1
            $ret['metadata'] = $metadata;
66
        }
67
68 1
        return $ret;
69
    }
70
71 109
    public function __set($field, $value)
72
    {
73 109
        if ($field == 'guid') {
74 6
            return;
75
        }
76 109
        parent::__set($field, $value);
77
    }
78
79 125
    public function __get($field)
80
    {
81 125
        if (   $field === 'metadata'
82 125
            && $this->metadata === null
83 125
            && $this instanceof metadata_interface) {
84 101
            $this->metadata = new metadata($this);
85
        }
86
87 125
        return parent::__get($field);
88
    }
89
90 1
    public function __call($method, $args)
91
    {
92 1
        if ($method === 'list') {
93 1
            return $this->_list();
94
        }
95
        throw new \BadMethodCallException("Unknown method " . $method . " on " . get_class($this));
96
    }
97
98 4
    protected function load_parent(array $candidates) : ?dbobject
99
    {
100 4
        foreach ($candidates as $candidate) {
101 4
            if (   is_string($this->$candidate)
102 4
                && mgd_is_guid($this->$candidate)) {
103
                return \midgard_object_class::get_object_by_guid($this->$candidate);
104
            }
105 4
            if ($this->$candidate !== null) {
106
                //Proxies become stale if the object itself is detached, so we have to re-fetch
107 4
                if (   $this->$candidate instanceof Proxy
108 4
                    && $this->$candidate->__isInitialized()) {
109
                    try {
110 1
                        $this->$candidate->get_by_id($this->$candidate->id);
111
                    } catch (exception $e) {
112
                        connection::log()->error('Failed to refresh parent from proxy: ' . $e->getMessage());
113
                        return null;
114
                    }
115
                }
116 4
                return $this->$candidate;
117
            }
118
        }
119 1
        return null;
120
    }
121
122 42
    public function get_by_id(int $id) : bool
123
    {
124 42
        $entity = connection::get_em()->find(get_class($this), $id);
125
126 42
        if ($entity === null) {
127 2
            throw exception::not_exists();
128
        }
129
        // Normally, the proxy should be automatically loaded when accessing e.g. $entity->guid,
130
        // because that triggers the proxy's magic __get method, which initializes. But since mgdobject
131
        // is the entity's (grand)parent class, PHP rules dictate that we have access to
132
        // its parents' protected variables. Hence, we need to load explicitly.
133 41
        if ($entity instanceof Proxy && !$entity->__isInitialized()) {
134
            try {
135 6
                $entity->__load();
136
            } catch (EntityNotFoundException $e) {
137
                throw exception::object_purged();
138
            }
139
        }
140 41
        if ($entity instanceof metadata_interface && $entity->{metadata_interface::DELETED_FIELD}) {
141
            // This can happen when the "deleted" entity is still in EM's identity map
142
            throw exception::object_deleted();
143
        }
144
145 41
        $this->populate_from_entity($entity);
146
147 41
        connection::get_em()->detach($entity);
148 41
        exception::ok();
149 41
        return true;
150
    }
151
152 16
    public function get_by_guid(string $guid) : bool
153
    {
154 16
        if (!mgd_is_guid($guid)) {
155 1
            throw new \InvalidArgumentException("'$guid' is not a valid guid");
156
        }
157 15
        $entity = connection::get_em()->getRepository(get_class($this))->findOneBy(['guid' => $guid]);
158 15
        if ($entity === null) {
159
            throw exception::not_exists();
160
        }
161 15
        $this->populate_from_entity($entity);
162
163 15
        connection::get_em()->detach($entity);
164 15
        exception::ok();
165 15
        return true;
166
    }
167
168 107
    public function create() : bool
169
    {
170 107
        if (!empty($this->id)) {
171 2
            exception::duplicate();
172 2
            return false;
173
        }
174 107
        if (   !$this->is_unique()
175 107
            || !$this->check_parent()) {
176 2
            return false;
177
        }
178 107
        if (!$this->check_fields()) {
179 1
            return false;
180
        }
181
        try {
182 106
            $om = new objectmanager(connection::get_em());
183 106
            $om->create($this);
184
        } catch (\Exception $e) {
185
            exception::internal($e);
186
            return false;
187
        }
188
189 106
        return $this->id != 0;
190
    }
191
192 16
    public function update() : bool
193
    {
194 16
        if (empty($this->id)) {
195 1
            midgard_connection::get_instance()->set_error(MGD_ERR_INTERNAL);
196 1
            return false;
197
        }
198 15
        if (!$this->check_fields()) {
199 2
            return false;
200
        }
201
        try {
202 13
            $om = new objectmanager(connection::get_em());
203 13
            $om->update($this);
204
        } catch (\Exception $e) {
205
            exception::internal($e);
206
            return false;
207
        }
208
209 13
        return true;
210
    }
211
212
    /**
213
     * @todo: Tests indicate that $check_dependencies is ignored in the mgd2 extension,
214
     * so we might consider ignoring it, too
215
     */
216 28
    public function delete(bool $check_dependencies = true) : bool
217
    {
218 28
        if (empty($this->id)) {
219 1
            exception::invalid_property_value();
220 1
            return false;
221
        }
222 27
        if (   $check_dependencies
223 27
            && $this->has_dependents()) {
224 4
            exception::has_dependants();
225 4
            return false;
226
        }
227 27
        if (!($this instanceof metadata_interface)) {
228 1
            exception::invalid_property_value();
229 1
            return false;
230
        }
231 26
        if ($this->{metadata_interface::DELETED_FIELD}) {
232 1
            return true;
233
        }
234
235
        try {
236 26
            $om = new objectmanager(connection::get_em());
237 26
            $om->delete($this);
238
        } catch (\Exception $e) {
239
            exception::internal($e);
240
            return false;
241
        }
242
243 26
        return true;
244
    }
245
246 107
    private function is_unique() : bool
247
    {
248 107
        $this->initialize();
249
250 107
        if (empty($this->cm->midgard['unique_fields'])) {
251 101
            return true;
252
        }
253
254 8
        $qb = connection::get_em()->createQueryBuilder();
255 8
        $qb->from(get_class($this), 'c');
256 8
        $conditions = $qb->expr()->andX();
257 8
        $parameters = [];
258 8
        if ($this->id) {
259
            $parameters['id'] = $this->id;
260
            $conditions->add($qb->expr()->neq('c.id', ':id'));
261
        }
262 8
        $found = false;
263 8
        foreach ($this->cm->midgard['unique_fields'] as $field) {
264 8
            if (empty($this->$field)) {
265
                //empty names automatically pass according to Midgard logic
266 2
                continue;
267
            }
268 7
            $conditions->add($qb->expr()->eq('c.' . $field, ':' . $field));
269 7
            $parameters[$field] = $this->$field;
270 7
            $found = true;
271
        }
272
273 8
        if (!$found) {
274 2
            return true;
275
        }
276
277 7
        foreach (['upfield', 'parentfield'] as $candidate) {
278 7
            if (!empty($this->cm->midgard[$candidate])) {
279
                // TODO: This needs to be changed so that value is always numeric, since this is how midgard does it
280 6
                if ($this->{$this->cm->midgard[$candidate]} === null) {
281 6
                    $conditions->add($qb->expr()->isNull('c.' . $this->cm->midgard[$candidate]));
282
                } else {
283 6
                    $conditions->add($qb->expr()->eq('c.' . $this->cm->midgard[$candidate], ':' . $this->cm->midgard[$candidate]));
284 6
                    $parameters[$this->cm->midgard[$candidate]] = $this->{$this->cm->midgard[$candidate]};
285
                }
286 6
                break;
287
            }
288
        }
289
290 7
        $qb->where($conditions)
291 7
            ->setParameters($parameters);
292
293 7
        $qb->select("count(c)");
294 7
        $count = (int) $qb->getQuery()->getSingleScalarResult();
295
296 7
        if ($count !== 0) {
297 1
            exception::object_name_exists();
298 1
            return false;
299
        }
300 7
        return true;
301
    }
302
303 107
    private function check_parent() : bool
304
    {
305 107
        $this->initialize();
306
307 107
        if (   empty($this->cm->midgard['parentfield'])
308 107
            || empty($this->cm->midgard['parent'])) {
309 107
            return true;
310
        }
311
312 8
        if (empty($this->{$this->cm->midgard['parentfield']})) {
313 1
            exception::object_no_parent();
314 1
            return false;
315
        }
316 8
        return true;
317
    }
318
319 107
    private function check_fields() : bool
320
    {
321 107
        $this->initialize();
322
323 107
        foreach ($this->cm->fieldMappings as $name => $field) {
324 107
            if (   $field['midgard:midgard_type'] == translator::TYPE_GUID
325 107
                && !empty($this->$name)
326 107
                && !mgd_is_guid($this->$name)) {
327 2
                exception::invalid_property_value("'" . $name . "' property's value is not a guid.");
328 2
                return false;
329
            }
330
        }
331 106
        return $this->check_upfield();
332
    }
333
334 106
    private function check_upfield() : bool
335
    {
336 106
        if (   !empty($this->id)
337 106
            && !empty($this->cm->midgard['upfield'])
338 106
            && $this->__get($this->cm->midgard['upfield']) === $this->id
339 106
            && $this->cm->getAssociationMapping($this->cm->midgard['upfield'])['targetEntity'] === $this->cm->getName()) {
340 1
            exception::tree_is_circular();
341 1
            return false;
342
        }
343
        // @todo this should be recursive
344 106
        return true;
345
    }
346
347
    public function is_in_parent_tree($root_id, $id) : bool
348
    {
349
        return false;
350
    }
351
352
    public function is_in_tree($root_id, $id) : bool
353
    {
354
        return false;
355
    }
356
357 34
    public function has_dependents() : bool
358
    {
359 34
        $this->initialize();
360
361 34
        $stat = false;
362
363 34
        if (!empty($this->cm->midgard['upfield'])) {
364 29
            $qb = connection::get_em()->createQueryBuilder();
365 29
            $qb->from(get_class($this), 'c')
366 29
                ->where('c.' . $this->cm->midgard['upfield'] . ' = ?0')
367 29
                ->setParameter(0, $this->id)
368 29
                ->select("COUNT(c)");
369 29
            $results = (int) $qb->getQuery()->getSingleScalarResult();
370 29
            $stat = $results > 0;
371
        }
372
373 34
        if (   !$stat
374 34
            && !empty($this->cm->midgard['childtypes'])) {
375 27
            foreach ($this->cm->midgard['childtypes'] as $typename => $parentfield) {
376 27
                $qb = connection::get_em()->createQueryBuilder();
377 27
                $qb->from(connection::get_fqcn($typename), 'c')
378 27
                    ->where('c.' . $parentfield . ' = ?0')
379 27
                    ->setParameter(0, $this->id)
380 27
                    ->select("COUNT(c)");
381
382 27
                $results = (int) $qb->getQuery()->getSingleScalarResult();
383 27
                $stat = $results > 0;
384 27
                if ($stat) {
385 3
                    break;
386
                }
387
            }
388
        }
389
390 34
        return $stat;
391
    }
392
393
    public function get_parent()
394
    {
395
        return null;
396
    }
397
398
    /**
399
     * This function is called list() in Midgard, but that doesn't work in plain PHP
400
     */
401 1
    private function _list() : array
402
    {
403 1
        $this->initialize();
404
405 1
        if (!empty($this->cm->midgard['upfield'])) {
406 1
            $qb = connection::get_em()->createQueryBuilder();
407 1
            $qb->from(get_class($this), 'c')
408 1
                ->where('c.' . $this->cm->midgard['upfield'] . ' = ?0')
409 1
                ->setParameter(0, $this->id)
410 1
                ->select("c");
411 1
            return $qb->getQuery()->getResult();
412
        }
413
414
        return [];
415
    }
416
417
    /**
418
     * This should return child objects, but only if they are of a different type
419
     * For all other input, an empty array is returned
420
     * (not implemented yet)
421
     */
422
    public function list_children(string $classname) : array
423
    {
424
        return [];
425
    }
426
427 1
    public function get_by_path(string $path) : bool
428
    {
429 1
        $parts = explode('/', trim($path, '/'));
430 1
        if (empty($parts)) {
431
            return false;
432
        }
433 1
        $this->initialize();
434
435 1
        if (count($this->cm->midgard['unique_fields']) != 1) {
436
            return false;
437
        }
438
439 1
        $field = $this->cm->midgard['unique_fields'][0];
440
441 1
        if (!empty($this->cm->midgard['parent'])) {
442 1
            $parent_cm = connection::get_em()->getClassMetadata(connection::get_fqcn($this->cm->midgard['parent']));
443 1
            $parentclass = $this->cm->fullyQualifiedClassName($this->cm->midgard['parent']);
444 1
            $parentfield = $parent_cm->midgard['upfield'];
0 ignored issues
show
Bug introduced by
Accessing midgard on the interface Doctrine\Persistence\Mapping\ClassMetadata suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
445 1
            $upfield = $this->cm->midgard['parentfield'];
446 1
        } elseif (!empty($this->cm->midgard['upfield'])) {
447 1
            $parentclass = get_class($this);
448 1
            $upfield = $this->cm->midgard['upfield'];
449 1
            $parentfield = $upfield;
450
        } else {
451
            return false;
452
        }
453
454 1
        $name = array_pop($parts);
455 1
        $up = 0;
456 1
        foreach ($parts as $part) {
457 1
            $qb = $this->get_uniquefield_query($parentclass, $field, $part, $parentfield, $up);
0 ignored issues
show
Bug introduced by
It seems like $parentclass can also be of type null; however, parameter $classname of midgard\portable\api\mgd...get_uniquefield_query() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

457
            $qb = $this->get_uniquefield_query(/** @scrutinizer ignore-type */ $parentclass, $field, $part, $parentfield, $up);
Loading history...
458 1
            $qb->select("c.id");
459 1
            $up = (int) $qb->getQuery()->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR);
460 1
            if ($up === 0) {
461 1
                exception::not_exists();
462 1
                $this->id = 0;
0 ignored issues
show
Bug Best Practice introduced by
The property id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
463 1
                $this->set_guid('');
464 1
                return false;
465
            }
466
        }
467
468 1
        $qb = $this->get_uniquefield_query(get_class($this), $field, $name, $upfield, $up);
469 1
        $qb->select("c");
470
471 1
        $entity = $qb->getQuery()->getOneOrNullResult();
472
473 1
        if ($entity === null) {
474 1
            exception::not_exists();
475 1
            $this->id = 0;
476 1
            $this->set_guid('');
477 1
            return false;
478
        }
479 1
        $this->populate_from_entity($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity can also be of type integer; however, parameter $entity of midgard\portable\api\dbo...:populate_from_entity() does only seem to accept midgard\portable\api\dbobject, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

479
        $this->populate_from_entity(/** @scrutinizer ignore-type */ $entity);
Loading history...
480
481 1
        return true;
482
    }
483
484 1
    protected function get_uniquefield_query(string $classname, string $field, string $part, string $upfield, int $up) : QueryBuilder
485
    {
486 1
        $qb = connection::get_em()->createQueryBuilder();
487 1
        $qb->from($classname, 'c');
488 1
        $conditions = $qb->expr()->andX();
489 1
        $conditions->add($qb->expr()->eq('c.' . $field, ':' . $field));
490 1
        $parameters = [
491 1
            $field => $part
492 1
        ];
493
494 1
        if (empty($up)) {
495
            // If the database was created by Midgard, it might contain 0 instead of NULL, so...
496 1
            $empty_conditions = $qb->expr()->orX()
497 1
                ->add($qb->expr()->isNull('c.' . $upfield))
498 1
                ->add($qb->expr()->eq('c.' . $upfield, '0'));
499 1
            $conditions->add($empty_conditions);
500
        } else {
501 1
            $conditions->add($qb->expr()->eq('c.' . $upfield, ':' . $upfield));
502 1
            $parameters[$upfield] = $up;
503
        }
504
505 1
        $qb->where($conditions)
506 1
            ->setParameters($parameters);
507
508 1
        return $qb;
509
    }
510
511
    /**
512
     * @return boolean
513
     */
514
    public function parent()
515
    {
516
        return false;
517
    }
518
519 1
    public function has_parameters() : bool
520
    {
521 1
        return !$this->get_collection('midgard_parameter')->is_empty($this->guid);
522
    }
523
524 4
    public function list_parameters(string $domain = null) : array
525
    {
526 4
        $constraints = [];
527 4
        if ($domain) {
528 1
            $constraints = ["domain" => $domain];
529
        }
530
531 4
        return $this->get_collection('midgard_parameter')->find($this->guid, $constraints);
532
    }
533
534 3
    public function find_parameters(array $constraints = []) : array
535
    {
536 3
        return $this->get_collection('midgard_parameter')->find($this->guid, $constraints);
537
    }
538
539 1
    public function delete_parameters(array $constraints = []) : int
540
    {
541 1
        return $this->get_collection('midgard_parameter')->delete($this->guid, $constraints);
542
    }
543
544 1
    public function purge_parameters(array $constraints = []) : int
545
    {
546 1
        return $this->get_collection('midgard_parameter')->purge($this->guid, $constraints);
547
    }
548
549 2
    public function get_parameter(string $domain, string $name)
550
    {
551 2
        if (!$this->guid) {
552 1
            return false;
553
        }
554 2
        $qb = connection::get_em()->createQueryBuilder();
555 2
        $qb
556 2
            ->select('c.value')
557 2
            ->from(connection::get_fqcn('midgard_parameter'), 'c')
558 2
            ->where('c.domain = :domain AND c.name = :name AND c.parentguid = :parentguid')
559 2
            ->setParameters(['domain' => $domain, 'name' => $name, 'parentguid' => $this->guid]);
560
561 2
        return $qb->getQuery()->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR);
562
    }
563
564 11
    public function set_parameter(string $domain, string $name, $value) : bool
565
    {
566 11
        $constraints = [
567 11
            'domain' => $domain,
568 11
            'name' => $name,
569 11
        ];
570 11
        $params = $this->get_collection('midgard_parameter')->find($this->guid, $constraints);
571
572
        // check value
573 11
        if (in_array($value, [false, null, ''], true)) {
574 2
            if (empty($params)) {
575 1
                exception::not_exists();
576 1
                return false;
577
            }
578 2
            foreach ($params as $param) {
579 2
                $stat = $param->delete();
580
            }
581 2
            return $stat;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $stat seems to be defined by a foreach iteration on line 578. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
582
        }
583
584 11
        $om = new objectmanager(connection::get_em());
585
        try {
586
            // create new
587 11
            if (empty($params)) {
588 11
                $parameter = $om->new_instance(connection::get_fqcn('midgard_parameter'));
589 11
                $parameter->parentguid = $this->guid;
0 ignored issues
show
Bug Best Practice introduced by
The property parentguid does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
590 11
                $parameter->domain = $domain;
0 ignored issues
show
Bug Best Practice introduced by
The property domain does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
591 11
                $parameter->name = $name;
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
592 11
                $parameter->value = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property value does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
593 11
                $om->create($parameter);
594
            }
595
            // use existing
596
            else {
597 1
                $parameter = $params[0];
598 1
                $parameter->value = $value;
0 ignored issues
show
Bug Best Practice introduced by
The property value does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
599 1
                $om->update($parameter);
600
            }
601 11
            return true;
602
        } catch (\Exception $e) {
603
            exception::internal($e);
604
            return false;
605
        }
606
    }
607
608
    /**
609
     * The signature is a little different from original, because Doctrine doesn't support func_get_args() in proxies
610
     */
611 2
    public function parameter(string $domain, string $name, $value = '__UNINITIALIZED__')
612
    {
613 2
        if ($value === '__UNINITIALIZED__') {
614 1
            return $this->get_parameter($domain, $name);
615
        }
616 2
        return $this->set_parameter($domain, $name, $value);
617
    }
618
619 1
    public function has_attachments() : bool
620
    {
621 1
        return !$this->get_collection('midgard_attachment')->is_empty($this->guid);
622
    }
623
624 2
    public function list_attachments() : array
625
    {
626 2
        return $this->get_collection('midgard_attachment')->find($this->guid, []);
627
    }
628
629
    public function find_attachments(array $constraints = []) : array
630
    {
631
        return $this->get_collection('midgard_attachment')->find($this->guid, $constraints);
632
    }
633
634
    public function delete_attachments(array $constraints = []) : int
635
    {
636
        return $this->get_collection('midgard_attachment')->delete($this->guid, $constraints);
637
    }
638
639
    /**
640
     * @return boolean False if one or more attachments couldn't be deleted
641
     * @todo Implement delete_blob & return value
642
     */
643
    public function purge_attachments(array $constraints = [], bool $delete_blob = true)
644
    {
645
        return $this->get_collection('midgard_attachment')->purge($this->guid, $constraints);
646
    }
647
648 3
    public function create_attachment(string $name, string $title = '', string $mimetype = '') : ?attachment
649
    {
650 3
        $existing = $this->get_collection('midgard_attachment')->find($this->guid, ['name' => $name]);
651 3
        if (!empty($existing)) {
652 1
            exception::object_name_exists();
653 1
            return null;
654
        }
655 3
        $om = new objectmanager(connection::get_em());
656 3
        $att = $om->new_instance(connection::get_fqcn('midgard_attachment'));
657
658 3
        $att->parentguid = $this->guid;
0 ignored issues
show
Bug Best Practice introduced by
The property parentguid does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
659 3
        $att->title = $title;
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
660 3
        $att->name = $name;
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
661 3
        $att->mimetype = $mimetype;
0 ignored issues
show
Bug Best Practice introduced by
The property mimetype does not exist on midgard\portable\api\dbobject. Since you implemented __set, consider adding a @property annotation.
Loading history...
662
        try {
663 3
            $om->create($att);
664 3
            return $att;
665
        } catch (\Exception $e) {
666
            exception::internal($e);
667
            return null;
668
        }
669
    }
670
671
    /**
672
     * @todo: Tests indicate that $check_dependencies is ignored in the mgd2 extension,
673
     * so we might consider ignoring it, too
674
     */
675 16
    public function purge(bool $check_dependencies = true) : bool
676
    {
677 16
        if (empty($this->id)) {
678
            // This usually means that the object has been purged already
679
            exception::not_exists();
680
            return false;
681
        }
682 16
        if (   $check_dependencies
683 16
            && $this->has_dependents()) {
684 2
            exception::has_dependants();
685 2
            return false;
686
        }
687
688
        try {
689 16
            $om = new objectmanager(connection::get_em());
690 16
            $om->purge($this);
691 2
        } catch (\Doctrine\ORM\EntityNotFoundException $e) {
692 2
            exception::not_exists();
693 2
            return false;
694
        } catch (\Exception $e) {
695
            exception::internal($e);
696
            return false;
697
        }
698
699 16
        return true;
700
    }
701
702 2
    public static function undelete(string $guid) : bool
703
    {
704 2
        return \midgard_object_class::undelete($guid);
705
    }
706
707 1
    public static function new_query_builder() : \midgard_query_builder
708
    {
709 1
        return new \midgard_query_builder(get_called_class());
710
    }
711
712 1
    public static function new_collector(string $field, $value) : \midgard_collector
713
    {
714 1
        return new \midgard_collector(get_called_class(), $field, $value);
715
    }
716
717
    public static function new_reflection_property() : \midgard_reflection_property
718
    {
719
        return new \midgard_reflection_property(get_called_class());
720
    }
721
722 107
    public function set_guid(string $guid)
723
    {
724 107
        parent::__set('guid', $guid);
725
    }
726
727
    /**
728
     * Helper for managing the isapproved and islocked metadata properties
729
     */
730 8
    private function manage_meta_property(string $action, bool $value) : bool
731
    {
732 8
        if (!($this instanceof metadata_interface)) {
733 4
            exception::no_metadata();
734 4
            return false;
735
        }
736 4
        $user = connection::get_user();
737 4
        if ($user === null) {
738 4
            exception::access_denied();
739 4
            return false;
740
        }
741 4
        if ($action == 'lock') {
742 2
            $flag = 'islocked';
743 2
        } elseif ($action == 'approve') {
744 2
            $flag = 'isapproved';
745
        } else {
746
            throw new exception('Unsupported action ' . $action);
747
        }
748
        // same val
749 4
        if ($this->__get('metadata')->$flag === $value) {
750 3
            return false;
751
        }
752 4
        if ($value === false) {
753 2
            $action = 'un' . $action;
754
        }
755
756 4
        if ($this->id) {
757
            try {
758 4
                $om = new objectmanager(connection::get_em());
759 4
                $om->{$action}($this);
760
            } catch (\Exception $e) {
761
                exception::internal($e);
762
                return false;
763
            }
764
        }
765
766 4
        return true;
767
    }
768
769 3
    public function approve() : bool
770
    {
771 3
        return $this->manage_meta_property("approve", true);
772
    }
773
774 2
    public function is_approved() : bool
775
    {
776 2
        if (!($this instanceof metadata_interface)) {
777
            exception::no_metadata();
778
            return false;
779
        }
780 2
        return $this->metadata_isapproved;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_isapproved does not exist on midgard\portable\api\mgdobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
781
    }
782
783 2
    public function unapprove() : bool
784
    {
785 2
        return $this->manage_meta_property("approve", false);
786
    }
787
788 3
    public function lock() : bool
789
    {
790 3
        if ($this->is_locked()) {
791 1
            exception::object_is_locked();
792 1
            return false;
793
        }
794 3
        return $this->manage_meta_property("lock", true);
795
    }
796
797 3
    public function is_locked() : bool
798
    {
799 3
        if (!($this instanceof metadata_interface)) {
800 1
            exception::no_metadata();
801 1
            return false;
802
        }
803 2
        return $this->metadata_islocked;
0 ignored issues
show
Bug Best Practice introduced by
The property metadata_islocked does not exist on midgard\portable\api\mgdobject. Since you implemented __get, consider adding a @property annotation.
Loading history...
804
    }
805
806 2
    public function unlock() : bool
807
    {
808 2
        return $this->manage_meta_property("lock", false);
809
    }
810
}
811