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

463
            $qb = $this->get_uniquefield_query(/** @scrutinizer ignore-type */ $parentclass, $field, $part, $parentfield, $up);
Loading history...
464 1
            $qb->select("c.id");
465 1
            $up = (int) $qb->getQuery()->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR);
466 1
            if ($up === 0) {
467 1
                exception::not_exists();
468 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...
469 1
                $this->set_guid('');
470 1
                return false;
471
            }
472
        }
473
474 1
        $qb = $this->get_uniquefield_query(get_class($this), $field, $name, $upfield, $up);
475 1
        $qb->select("c");
476
477 1
        $entity = $qb->getQuery()->getOneOrNullResult();
478
479 1
        if ($entity === null) {
480 1
            exception::not_exists();
481 1
            $this->id = 0;
482 1
            $this->set_guid('');
483 1
            return false;
484
        }
485 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

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