Completed
Push — master ( 54db03...00e7b3 )
by Andreas
03:31
created

mgdobject::__get()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 5
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 4
rs 10
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 133
    public function __construct($id = null)
35
    {
36 133
        if ($id !== null) {
37 50
            if (is_int($id)) {
38 41
                $this->get_by_id($id);
39 14
            } elseif (is_string($id)) {
40 14
                $this->get_by_guid($id);
41
            }
42
        }
43 131
    }
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 110
    public function __set($field, $value)
71
    {
72 110
        if ($field == 'guid') {
73 6
            return;
74
        }
75 110
        parent::__set($field, $value);
76 110
    }
77
78 126
    public function __get($field)
79
    {
80 126
        if (   $field === 'metadata'
81 126
            && $this->metadata === null
82 126
            && $this instanceof metadata_interface) {
83 102
            $this->metadata = new metadata($this);
84
        }
85
86 126
        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 ($this->$candidate !== null) {
101
                //Proxies become stale if the object itself is detached, so we have to re-fetch
102 4
                if (   $this->$candidate instanceof Proxy
103 4
                    && $this->$candidate->__isInitialized()) {
104
                    try {
105 1
                        $this->$candidate->get_by_id($this->$candidate->id);
106
                    } catch (exception $e) {
107
                        connection::log()->error('Failed to refresh parent from proxy: ' . $e->getMessage());
108
                        return null;
109
                    }
110
                }
111 4
                return $this->$candidate;
112
            }
113
        }
114 1
        return null;
115
    }
116
117 43
    public function get_by_id(int $id) : bool
118
    {
119 43
        $entity = connection::get_em()->find(get_class($this), $id);
120
121 43
        if ($entity === null) {
122 3
            throw exception::not_exists();
123
        }
124
        // According to Doctrine documentation, proxies should be transparent, but in practice,
125
        // there will be problems if we don't force-load
126 42
        if (   $entity instanceof Proxy
127 42
            && !$entity->__isInitialized()) {
128
            try {
129 7
                $entity->__load();
130 1
            } catch (\Doctrine\ORM\EntityNotFoundException $e) {
131 1
                throw exception::object_purged();
132
            }
133
        }
134 42
        if ($entity instanceof metadata_interface && $entity->{metadata_interface::DELETED_FIELD}) {
135
            // This can happen when the "deleted" entity is still in EM's identity map
136
            throw exception::object_deleted();
137
        }
138 42
        if (empty($entity->guid)) {
139
            // This can happen when a reference proxy to a purged entity is still in EM's identity map
140
            throw exception::object_purged();
141
        }
142
143 42
        $this->populate_from_entity($entity);
144
145 42
        connection::get_em()->detach($entity);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::detach() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

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

145
        /** @scrutinizer ignore-deprecated */ connection::get_em()->detach($entity);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
146 42
        midgard_connection::get_instance()->set_error(MGD_ERR_OK);
147 42
        return true;
148
    }
149
150 16
    public function get_by_guid(string $guid) : bool
151
    {
152 16
        if (!mgd_is_guid($guid)) {
153 1
            throw new \InvalidArgumentException("'$guid' is not a valid guid");
154
        }
155 15
        $entity = connection::get_em()->getRepository(get_class($this))->findOneBy(['guid' => $guid]);
156 15
        if ($entity === null) {
157
            throw exception::not_exists();
158
        }
159 15
        $this->populate_from_entity($entity);
160
161 15
        connection::get_em()->detach($entity);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::detach() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

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

161
        /** @scrutinizer ignore-deprecated */ connection::get_em()->detach($entity);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

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

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