Passed
Push — master ( 4b377c...534a05 )
by Andreas
03:09
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
            && $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
        midgard_connection::get_instance()->set_error(MGD_ERR_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
        midgard_connection::get_instance()->set_error(MGD_ERR_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
        midgard_connection::get_instance()->set_error(MGD_ERR_OK);
190
191 106
        return $this->id != 0;
192
    }
193
194 16
    public function update() : bool
195
    {
196 16
        if (empty($this->id)) {
197 1
            midgard_connection::get_instance()->set_error(MGD_ERR_INTERNAL);
198 1
            return false;
199
        }
200 15
        if (!$this->check_fields()) {
201 2
            return false;
202
        }
203
        try {
204 13
            $om = new objectmanager(connection::get_em());
205 13
            $om->update($this);
206
        } catch (\Exception $e) {
207
            exception::internal($e);
208
            return false;
209
        }
210 13
        midgard_connection::get_instance()->set_error(MGD_ERR_OK);
211
212 13
        return true;
213
    }
214
215
    /**
216
     * @todo: Tests indicate that $check_dependencies is ignored in the mgd2 extension,
217
     * so we might consider ignoring it, too
218
     */
219 28
    public function delete(bool $check_dependencies = true) : bool
220
    {
221 28
        if (empty($this->id)) {
222 1
            midgard_connection::get_instance()->set_error(MGD_ERR_INVALID_PROPERTY_VALUE);
223 1
            return false;
224
        }
225 27
        if (   $check_dependencies
226 27
            && $this->has_dependents()) {
227 4
            exception::has_dependants();
228 4
            return false;
229
        }
230 27
        if (!($this instanceof metadata_interface)) {
231 1
            exception::invalid_property_value();
232 1
            return false;
233
        }
234 26
        if ($this->{metadata_interface::DELETED_FIELD}) {
235 1
            return true;
236
        }
237
238
        try {
239 26
            $om = new objectmanager(connection::get_em());
240 26
            $om->delete($this);
241
        } catch (\Exception $e) {
242
            exception::internal($e);
243
            return false;
244
        }
245
246 26
        midgard_connection::get_instance()->set_error(MGD_ERR_OK);
247 26
        return true;
248
    }
249
250 107
    private function is_unique() : bool
251
    {
252 107
        $this->initialize();
253
254 107
        if (empty($this->cm->midgard['unique_fields'])) {
255 101
            return true;
256
        }
257
258 8
        $qb = connection::get_em()->createQueryBuilder();
259 8
        $qb->from(get_class($this), 'c');
260 8
        $conditions = $qb->expr()->andX();
261 8
        $parameters = [];
262 8
        if ($this->id) {
263
            $parameters['id'] = $this->id;
264
            $conditions->add($qb->expr()->neq('c.id', ':id'));
265
        }
266 8
        $found = false;
267 8
        foreach ($this->cm->midgard['unique_fields'] as $field) {
268 8
            if (empty($this->$field)) {
269
                //empty names automatically pass according to Midgard logic
270 2
                continue;
271
            }
272 7
            $conditions->add($qb->expr()->eq('c.' . $field, ':' . $field));
273 7
            $parameters[$field] = $this->$field;
274 7
            $found = true;
275
        }
276
277 8
        if (!$found) {
278 2
            return true;
279
        }
280
281 7
        foreach (['upfield', 'parentfield'] as $candidate) {
282 7
            if (!empty($this->cm->midgard[$candidate])) {
283
                // TODO: This needs to be changed so that value is always numeric, since this is how midgard does it
284 6
                if ($this->{$this->cm->midgard[$candidate]} === null) {
285 6
                    $conditions->add($qb->expr()->isNull('c.' . $this->cm->midgard[$candidate]));
286
                } else {
287 6
                    $conditions->add($qb->expr()->eq('c.' . $this->cm->midgard[$candidate], ':' . $this->cm->midgard[$candidate]));
288 6
                    $parameters[$this->cm->midgard[$candidate]] = $this->{$this->cm->midgard[$candidate]};
289
                }
290 6
                break;
291
            }
292
        }
293
294 7
        $qb->where($conditions)
295 7
            ->setParameters($parameters);
296
297 7
        $qb->select("count(c)");
298 7
        $count = (int) $qb->getQuery()->getSingleScalarResult();
299
300 7
        if ($count !== 0) {
301 1
            exception::object_name_exists();
302 1
            return false;
303
        }
304 7
        return true;
305
    }
306
307 107
    private function check_parent() : bool
308
    {
309 107
        $this->initialize();
310
311 107
        if (   empty($this->cm->midgard['parentfield'])
312 107
            || empty($this->cm->midgard['parent'])) {
313 107
            return true;
314
        }
315
316 8
        if (empty($this->{$this->cm->midgard['parentfield']})) {
317 1
            exception::object_no_parent();
318 1
            return false;
319
        }
320 8
        return true;
321
    }
322
323 107
    private function check_fields() : bool
324
    {
325 107
        $this->initialize();
326
327 107
        foreach ($this->cm->fieldMappings as $name => $field) {
328 107
            if (   $field['midgard:midgard_type'] == translator::TYPE_GUID
329 107
                && !empty($this->$name)
330 107
                && !mgd_is_guid($this->$name)) {
331 2
                exception::invalid_property_value("'" . $name . "' property's value is not a guid.");
332 2
                return false;
333
            }
334
        }
335 106
        return $this->check_upfield();
336
    }
337
338 106
    private function check_upfield() : bool
339
    {
340 106
        if (   !empty($this->id)
341 106
            && !empty($this->cm->midgard['upfield'])
342 106
            && $this->__get($this->cm->midgard['upfield']) === $this->id
343 106
            && $this->cm->getAssociationMapping($this->cm->midgard['upfield'])['targetEntity'] === $this->cm->getName()) {
344 1
            exception::tree_is_circular();
345 1
            return false;
346
        }
347
        // @todo this should be recursive
348 106
        return true;
349
    }
350
351
    public function is_in_parent_tree($root_id, $id) : bool
352
    {
353
        return false;
354
    }
355
356
    public function is_in_tree($root_id, $id) : bool
357
    {
358
        return false;
359
    }
360
361 34
    public function has_dependents() : bool
362
    {
363 34
        $this->initialize();
364
365 34
        $stat = false;
366
367 34
        if (!empty($this->cm->midgard['upfield'])) {
368 29
            $qb = connection::get_em()->createQueryBuilder();
369 29
            $qb->from(get_class($this), 'c')
370 29
                ->where('c.' . $this->cm->midgard['upfield'] . ' = ?0')
371 29
                ->setParameter(0, $this->id)
372 29
                ->select("COUNT(c)");
373 29
            $results = (int) $qb->getQuery()->getSingleScalarResult();
374 29
            $stat = $results > 0;
375
        }
376
377 34
        if (   !$stat
378 34
            && !empty($this->cm->midgard['childtypes'])) {
379 27
            foreach ($this->cm->midgard['childtypes'] as $typename => $parentfield) {
380 27
                $qb = connection::get_em()->createQueryBuilder();
381 27
                $qb->from(connection::get_fqcn($typename), 'c')
382 27
                    ->where('c.' . $parentfield . ' = ?0')
383 27
                    ->setParameter(0, $this->id)
384 27
                    ->select("COUNT(c)");
385
386 27
                $results = (int) $qb->getQuery()->getSingleScalarResult();
387 27
                $stat = $results > 0;
388 27
                if ($stat) {
389 3
                    break;
390
                }
391
            }
392
        }
393
394 34
        return $stat;
395
    }
396
397
    public function get_parent()
398
    {
399
        return null;
400
    }
401
402
    /**
403
     * This function is called list() in Midgard, but that doesn't work in plain PHP
404
     */
405 1
    private function _list() : array
406
    {
407 1
        $this->initialize();
408
409 1
        if (!empty($this->cm->midgard['upfield'])) {
410 1
            $qb = connection::get_em()->createQueryBuilder();
411 1
            $qb->from(get_class($this), 'c')
412 1
                ->where('c.' . $this->cm->midgard['upfield'] . ' = ?0')
413 1
                ->setParameter(0, $this->id)
414 1
                ->select("c");
415 1
            return $qb->getQuery()->getResult();
416
        }
417
418
        return [];
419
    }
420
421
    /**
422
     * This should return child objects, but only if they are of a different type
423
     * For all other input, an empty array is returned
424
     * (not implemented yet)
425
     */
426
    public function list_children(string $classname) : array
427
    {
428
        return [];
429
    }
430
431 1
    public function get_by_path(string $path) : bool
432
    {
433 1
        $parts = explode('/', trim($path, '/'));
434 1
        if (empty($parts)) {
435
            return false;
436
        }
437 1
        $this->initialize();
438
439 1
        if (count($this->cm->midgard['unique_fields']) != 1) {
440
            return false;
441
        }
442
443 1
        $field = $this->cm->midgard['unique_fields'][0];
444
445 1
        if (!empty($this->cm->midgard['parent'])) {
446 1
            $parent_cm = connection::get_em()->getClassMetadata(connection::get_fqcn($this->cm->midgard['parent']));
447 1
            $parentclass = $this->cm->fullyQualifiedClassName($this->cm->midgard['parent']);
448 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...
449 1
            $upfield = $this->cm->midgard['parentfield'];
450 1
        } elseif (!empty($this->cm->midgard['upfield'])) {
451 1
            $parentclass = get_class($this);
452 1
            $upfield = $this->cm->midgard['upfield'];
453 1
            $parentfield = $upfield;
454
        } else {
455
            return false;
456
        }
457
458 1
        $name = array_pop($parts);
459 1
        $up = 0;
460 1
        foreach ($parts as $part) {
461 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

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

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