Passed
Push — master ( 4b377c...534a05 )
by Andreas
03:09
created

mgdobject::get_by_id()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7.4822

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 28
ccs 11
cts 14
cp 0.7856
crap 7.4822
rs 8.8333
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