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

mgdobject::is_approved()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.2559

Importance

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

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

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

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

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