Completed
Push — master ( a999b2...131e79 )
by Andreas
18:19
created

midcom_baseclasses_core_dbobject::refresh()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 10
ccs 1
cts 1
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.baseclasses
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use midcom\events\dbaevent;
10
use midgard\portable\api\mgdobject;
11
12
/**
13
 * This class only contains static functions which are there to hook into
14
 * the classes you derive from the midcom_core_dbaobject.
15
 *
16
 * The static members will invoke a number of callback methods so that you should
17
 * normally never have to override the base midgard methods like update or the like.
18
 *
19
 * @package midcom.baseclasses
20
 */
21
class midcom_baseclasses_core_dbobject
22
{
23
    /**
24
     * "Pre-flight" checks for update method
25
     *
26
     * Separated so that dbfactory->import() can reuse the code
27
     *
28
     * @param midcom_core_dbaobject $object The DBA object we're working on
29
     */
30 115
    public static function update_pre_checks(midcom_core_dbaobject $object) : bool
31
    {
32 115
        if (!$object->can_do('midgard:update')) {
33 2
            debug_add("Failed to load object, update privilege on the " . get_class($object) . " {$object->id} not granted for the current user.",
34 2
                MIDCOM_LOG_ERROR);
35 2
            midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
36 2
            return false;
37
        }
38 114
        if (!$object->_on_updating()) {
39
            debug_add("The _on_updating event handler returned false.");
40
            return false;
41
        }
42
        // Still check name uniqueness
43 114
        return self::_pre_check_name($object);
44
    }
45
46
    /**
47
     * Execute a DB update of the object passed. This will call the corresponding
48
     * event handlers. Calling sequence with method signatures:
49
     *
50
     * 1. Validate privileges using can_do. The user needs midgard:update privilege on the content object.
51
     * 2. bool $object->_on_updating() is executed. If it returns false, update is aborted.
52
     * 3. bool $object->__object->update() is executed to do the actual DB update. This has to execute parent::update()
53
     *    and return its value, nothing else.
54
     * 4. void $object->_on_updated() is executed to notify the class from a successful DB update.
55
     *
56
     * @param midcom_core_dbaobject $object The DBA object we're working on
57
     * @return bool Indicating success.
58
     */
59 115
    public static function update(midcom_core_dbaobject $object) : bool
60
    {
61 115
        if (!self::update_pre_checks($object)) {
62 2
            debug_add('Pre-flight check returned false', MIDCOM_LOG_ERROR);
63 2
            return false;
64
        }
65
66 114
        if (!$object->__object->update()) {
67
            debug_add("Failed to update the record, last Midgard error: " . midcom_connection::get_error_string());
68
            return false;
69
        }
70
71 114
        self::update_post_ops($object);
72
73 114
        return true;
74
    }
75
76
    /**
77
     * Post object creation operations for create
78
     *
79
     * Separated so that dbfactory->import() can reuse the code
80
     *
81
     * @param midcom_core_dbaobject $object The DBA object we're working on
82
     */
83 114
    public static function update_post_ops(midcom_core_dbaobject $object)
84
    {
85 114
        if ($object->_use_rcs) {
86 51
            midcom::get()->rcs->update($object, $object->get_rcs_message());
87
        }
88
89 114
        $object->_on_updated();
90
91 114
        midcom::get()->dispatcher->dispatch(new dbaevent($object), dbaevent::UPDATE);
92 114
    }
93
94
    /**
95
     * Add full privileges to the owner of the object.
96
     * This is essentially sets the midgard:owner privilege for the current user.
97
     *
98
     * @param midcom_core_dbaobject $object The DBA object we're working on
99
     */
100 283
    private static function _set_owner_privileges(midcom_core_dbaobject $object)
101
    {
102 283
        if (!midcom::get()->auth->user) {
103 164
            debug_add("Could not retrieve the midcom_core_user instance for the creator of " . get_class($object) . " {$object->guid}, skipping owner privilege assignment.",
104 164
                MIDCOM_LOG_INFO);
105 164
            return;
106
        }
107
108
        // Circumvent the main privilege class as we need full access here regardless of
109
        // the actual circumstances.
110 144
        $privilege = new midcom_core_privilege_db();
111 144
        $privilege->assignee = midcom::get()->auth->user->id;
112 144
        $privilege->privilegename = 'midgard:owner';
113 144
        $privilege->objectguid = $object->guid;
114 144
        $privilege->value = MIDCOM_PRIVILEGE_ALLOW;
115
116 144
        if (!$privilege->create()) {
117
            debug_add("Could not set the owner privilege {$privilege->privilegename} for {$object->guid}, see debug level log for details. Last Midgard Error: " . midcom_connection::get_error_string(),
118
                MIDCOM_LOG_WARN);
119
        }
120 144
    }
121
122
    /**
123
     * "Pre-flight" checks for create method
124
     *
125
     * Separated so that dbfactory->import() can reuse the code
126
     *
127
     * @param midcom_core_dbaobject $object The DBA object we're working on
128
     */
129 283
    public static function create_pre_checks(midcom_core_dbaobject $object) : bool
130
    {
131 283
        $parent = $object->get_parent();
132
133 283
        if ($parent !== null) {
134
            // Attachments are a special case
135 168
            if ($object instanceof midcom_db_attachment) {
136 12
                if (   !$parent->can_do('midgard:attachments')
137 12
                    || !$parent->can_do('midgard:update')) {
138
                    debug_add("Failed to create attachment, update or attachments privilege on the parent " . get_class($parent) . " {$parent->guid} not granted for the current user.",
139
                        MIDCOM_LOG_ERROR);
140
                    midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
141 12
                    return false;
142
                }
143 157
            } elseif (   !$parent->can_do('midgard:create')
144 157
                      && !midcom::get()->auth->can_user_do('midgard:create', null, get_class($object))) {
145
                debug_add("Failed to create object, create privilege on the parent " . get_class($parent) . " {$parent->guid} or the actual object class not granted for the current user.",
146
                    MIDCOM_LOG_ERROR);
147
                midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
148 168
                return false;
149
            }
150 206
        } elseif (!midcom::get()->auth->can_user_do('midgard:create', null, get_class($object))) {
151
            debug_add("Failed to create object, general create privilege not granted for the current user.", MIDCOM_LOG_ERROR);
152
            midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
153
            return false;
154
        }
155
156 283
        if (!$object->_on_creating()) {
157 3
            debug_add("The _on_creating event handler returned false.");
158 3
            return false;
159
        }
160
161
        // Still check name uniqueness
162 283
        return self::_pre_check_name($object);
163
    }
164
165
    /**
166
     * Helper method to call in the _xxx_pre_checks, handles the API
167
     * level checks and automatic operations as specified in ticket #809
168
     *
169
     * @see http://trac.midgard-project.org/ticket/809
170
     * Quoting the ticket API-level section:
171
     * <pre>
172
     *      1. Checks will be done in the pre-flight check phase (ie just after _on_creating/_on_updating)
173
     *      2. If name is not unique false is returned for pre-flight check, preventing create/update
174
     *          2.2 UNLESS a property in the object ('allow_name_catenate') is set to true in which case unique one is generated by catenating an incrementing number to the name.
175
     *      3. if name is empty unique name is generated from title property (unless title is empty too)
176
     *      4. if name is not URL-safe false is returned
177
     * </pre>
178
     *
179
     * @param midcom_core_dbaobject $object The DBA object we're working on
180
     * @return boolean indicating whether from our point of view everything is ok
181
     *
182
     * @see midcom_helper_reflector_nameresolver::name_is_safe()
183
     * @see midcom_helper_reflector_nameresolver::name_is_unique()
184
     * @see midcom_helper_reflector_nameresolver::generate_unique_name()
185
     */
186 290
    private static function _pre_check_name(midcom_core_dbaobject $object) : bool
187
    {
188
        // Make sure name is empty of unique if the object has such property
189 290
        $name_property = midcom_helper_reflector::get_name_property($object);
190 290
        if (empty($name_property)) {
191
            // This object has no name property, return early
192 211
            return true;
193
        }
194
195 137
        $resolver = new midcom_helper_reflector_nameresolver($object);
196
197
        /**
198
         * If name is empty, try to generate new, unique one
199
         *
200
         * @see http://trac.midgard-project.org/ticket/809
201
         */
202 137
        if (empty($object->{$name_property})) {
203
            // name is empty, try to generate
204 69
            $object->{$name_property} = (string) $resolver->generate_unique_name();
205 69
            if (empty($object->{$name_property})) {
206 65
                return true;
207
            }
208
        }
209
210
        // Enforce URL-safe names
211 89
        if (!$resolver->name_is_safe()) {
212 1
            midcom_connection::set_error(MGD_ERR_INVALID_NAME);
213 1
            return false;
214
        }
215
216
        // Enforce unique (or empty) names
217 89
        if (!$resolver->name_is_unique()) {
218
            if ($object->allow_name_catenate) {
219
                // Transparent catenation allowed, let's try again.
220
                if ($new_name = $resolver->generate_unique_name()) {
221
                    $object->{$name_property} = $new_name;
222
                    return true;
223
                }
224
                debug_add('allow_name_catenate was set but midcom_helper_reflector_nameresolver::generate_unique_name() returned empty value, falling through', MIDCOM_LOG_WARN);
225
            }
226
            midcom_connection::set_error(MGD_ERR_OBJECT_NAME_EXISTS);
227
            return false;
228
        }
229
230
        // All checks ok, we're fine.
231 89
        return true;
232
    }
233
234
    /**
235
     * Execute a DB create of the object passed. This will call the corresponding
236
     * event handlers. Calling sequence with method signatures:
237
     *
238
     * 1. Validate privileges using can_do. The user needs midgard:create privilege to the parent object or in general, if there is no parent.
239
     * 2. bool $object->_on_creating() is executed. If it returns false, create is aborted.
240
     * 3. bool $object->__object->create() is executed to do the actual DB create. This has to execute parent::create()
241
     *    and return its value, nothing else.
242
     * 4. void $object->_on_created() is executed to notify the class from a successful DB creation.
243
     *
244
     * @param midcom_core_dbaobject $object The DBA object we're working on
245
     * @return bool Indicating success.
246
     */
247 283
    public static function create(midcom_core_dbaobject $object) : bool
248
    {
249 283
        if (!self::create_pre_checks($object)) {
250 4
            debug_add('Pre-flight check returned false', MIDCOM_LOG_ERROR);
251 4
            return false;
252
        }
253
254 283
        if (midcom::get()->auth->user !== null) {
255
            // Default the authors to current user
256 144
            if (empty($object->metadata->authors)) {
257 144
                $object->metadata->set('authors', "|" . midcom::get()->auth->user->guid . "|");
258
            }
259
260
            // Default the owner to first group of current user
261 144
            if (   empty($object->metadata->owner)
262 144
                && $first_group = midcom::get()->auth->user->get_first_group_guid()) {
263
                $object->metadata->set('owner', $first_group);
264
            }
265
        }
266
        // Default the publication time to current date/time
267 283
        if (empty($object->metadata->published)) {
268 283
            $object->metadata->set('published', time());
269
        }
270
271 283
        if (!$object->__object->create()) {
272 6
            debug_add("Failed to create " . get_class($object) . ", last Midgard error: " . midcom_connection::get_error_string());
273 6
            return false;
274
        }
275
276 283
        self::create_post_ops($object);
277
278 283
        return true;
279
    }
280
281
    /**
282
     * Post object creation operations for create
283
     *
284
     * Separated so that dbfactory->import() can reuse the code
285
     *
286
     * @param midcom_core_dbaobject $object The DBA object we're working on
287
     */
288 283
    public static function create_post_ops(midcom_core_dbaobject $object)
289
    {
290
        // Now assign all midgard privileges to the creator, this is necessary to get
291
        // an owner like scheme to work by default.
292
        // TODO: Check if there is a better solution like this.
293 283
        self::_set_owner_privileges($object);
294
295 283
        $object->_on_created();
296 283
        midcom::get()->dispatcher->dispatch(new dbaevent($object), dbaevent::CREATE);
297
298 283
        if ($object->_use_rcs) {
299 35
            midcom::get()->rcs->update($object, $object->get_rcs_message());
300
        }
301 283
    }
302
303
    /**
304
     * Execute a DB delete of the object passed. This will call the corresponding
305
     * event handlers. Calling sequence with method signatures:
306
     *
307
     * 1. Validate privileges using can_do. The user needs midgard:delete privilege on the content object.
308
     * 2. bool $object->_on_deleting() is executed. If it returns false, delete is aborted.
309
     * 3. All extensions of the object are deleted
310
     * 4. bool $object->__object->delete() is executed to do the actual DB delete. This has to execute parent::delete()
311
     *    and return its value, nothing else.
312
     * 5. void $object->_on_deleted() is executed to notify the class from a successful DB deletion.
313
     *
314
     * @param midcom_core_dbaobject $object The DBA object we're working on
315
     * @return bool Indicating success.
316
     */
317 185
    public static function delete(midcom_core_dbaobject $object) : bool
318
    {
319 185
        if (!self::delete_pre_checks($object)) {
320
            debug_add('Pre-flight check returned false', MIDCOM_LOG_ERROR);
321
            return false;
322
        }
323
324
        // Delete all extensions:
325
        // Attachments can't have attachments so no need to query those
326 185
        if (!$object instanceof midcom_db_attachment) {
327 179
            foreach ($object->list_attachments() as $attachment) {
328 4
                if (!$attachment->delete()) {
329
                    debug_add("Failed to delete attachment ID {$attachment->id}", MIDCOM_LOG_ERROR);
330
                    return false;
331
                }
332
            }
333
        }
334
335 185
        $query = new midgard_query_builder('midgard_parameter');
336 185
        $query->add_constraint('parentguid', '=', $object->guid);
337 185
        foreach ($query->execute() as $parameter) {
338 29
            if (!$parameter->delete()) {
339
                debug_add("Failed to delete parameter ID {$parameter->id}", MIDCOM_LOG_ERROR);
340
                return false;
341
            }
342
        }
343
344 185
        if (!self::_delete_privileges($object)) {
345
            debug_add('Failed to delete the object privileges.', MIDCOM_LOG_INFO);
346
            return false;
347
        }
348
349
        // Finally, delete the object itself
350 185
        if (!$object->__object->delete()) {
351 34
            debug_add("Failed to delete " . get_class($object) . ", last Midgard error: " . midcom_connection::get_error_string(), MIDCOM_LOG_INFO);
352 34
            return false;
353
        }
354
355
        // Explicitly set this in case someone needs to check against it
356 185
        self::delete_post_ops($object);
357
358 185
        return true;
359
    }
360
361
    /**
362
     * Unconditionally drop all privileges assigned to the given object.
363
     * Called upon successful delete
364
     *
365
     * @return bool Indicating Success.
366
     */
367 185
    private static function _delete_privileges(midcom_core_dbaobject $object) : bool
368
    {
369 185
        $qb = new midgard_query_builder('midcom_core_privilege_db');
370 185
        $qb->add_constraint('objectguid', '=', $object->guid);
371
372 185
        foreach ($qb->execute() as $dbpriv) {
373 98
            if (!$dbpriv->purge()) {
374
                return false;
375
            }
376
        }
377 185
        return true;
378
    }
379
380
    /**
381
     * Execute a DB delete of the object passed and delete its descendants. This will call the corresponding
382
     * event handlers. Calling sequence with method signatures:
383
     *
384
     * 1. Get all of the child objects
385
     * 2. Delete them recursively starting from the top, working towards the root
386
     * 3. Finally delete the root object
387
     *
388
     * @param midcom_core_dbaobject $object The DBA object we're working on
389
     * @return boolean Indicating success.
390
     */
391 1
    public static function delete_tree(midcom_core_dbaobject $object) : bool
392
    {
393 1
        $reflector = midcom_helper_reflector_tree::get($object);
394 1
        $child_classes = $reflector->get_child_classes();
395
396 1
        foreach ($child_classes as $class) {
397 1
            if ($qb = $reflector->_child_objects_type_qb($class, $object, false)) {
398 1
                $children = $qb->execute();
399
                // Delete first the descendants
400 1
                while ($child = array_pop($children)) {
401
                    //Inherit RCS status (so that f.x. large tree deletions can run faster)
402
                    $child->_use_rcs = $object->_use_rcs;
403
                    if (!self::delete_tree($child)) {
404
                        debug_print_r('Failed to delete the children of this object:', $object, MIDCOM_LOG_INFO);
405
                        return false;
406
                    }
407
                }
408
            }
409
        }
410
411 1
        if (!self::delete($object)) {
412
            debug_print_r('Failed to delete the object', $object, MIDCOM_LOG_ERROR);
413
            return false;
414
        }
415
416 1
        return true;
417
    }
418
419
    /**
420
     * Post object creation operations for delete
421
     *
422
     * Separated so that dbfactory->import() can reuse the code
423
     *
424
     * @param midcom_core_dbaobject $object The DBA object we're working on
425
     */
426 185
    public static function delete_post_ops(midcom_core_dbaobject $object)
427
    {
428 185
        $object->_on_deleted();
429 185
        midcom::get()->dispatcher->dispatch(new dbaevent($object), dbaevent::DELETE);
430 185
        if ($object->_use_rcs) {
431 14
            midcom::get()->rcs->update($object, $object->get_rcs_message());
432
        }
433 185
    }
434
435
    /**
436
     * Undelete objects
437
     *
438
     * @param array $guids
439
     * @return integer Size of undeleted objects
440
     * @todo We should only undelete parameters & attachments deleted inside some small window of the main objects delete
441
     */
442
    public static function undelete($guids) : int
443
    {
444
        $undeleted_size = 0;
445
446
        foreach ((array) $guids as $guid) {
447
            if (!mgdobject::undelete($guid)) {
448
                debug_add("Failed to undelete object with GUID {$guid} errstr: " . midcom_connection::get_error_string(), MIDCOM_LOG_ERROR);
449
                continue;
450
            }
451
            // refresh
452
            $object = midcom::get()->dbfactory->get_object_by_guid($guid);
453
            $undeleted_size += $object->metadata->size;
454
            $parent = $object->get_parent();
455
            if (!empty($parent->guid)) {
456
                // Invalidate parent from cache so content caches have chance to react
457
                midcom::get()->cache->invalidate($parent->guid);
458
            }
459
460
            // FIXME: We should only undelete parameters & attachments deleted inside some small window of the main objects delete
461
            $undeleted_size += self::undelete_parameters($guid);
462
            $undeleted_size += self::undelete_attachments($guid);
463
464
            //FIXME: are we sure we want to undelete all children here unconditionally, shouldn't it be left as UI decision ??
465
            // List all deleted children
466
            $children_types = midcom_helper_reflector_tree::get_child_objects($object, true);
0 ignored issues
show
Bug introduced by
$object of type midcom_core_dbaobject is incompatible with the type midgard\portable\api\mgdobject expected by parameter $object of midcom_helper_reflector_tree::get_child_objects(). ( Ignorable by Annotation )

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

466
            $children_types = midcom_helper_reflector_tree::get_child_objects(/** @scrutinizer ignore-type */ $object, true);
Loading history...
467
468
            if (empty($children_types)) {
469
                continue;
470
            }
471
472
            foreach ($children_types as $children) {
473
                $child_guids = array_column($children, 'guid');
474
                $undeleted_size += self::undelete($child_guids);
475
            }
476
        }
477
478
        return $undeleted_size;
479
    }
480
481
    /**
482
     * Recover the parameters related to a deleted object
483
     *
484
     * @param string $guid
485
     * @return integer Size of undeleted objects
486
     * @todo We should only undelete parameters & attachments deleted inside some small window of the main objects delete
487
     */
488
    public static function undelete_parameters(string $guid) : int
489
    {
490
        $undeleted_size = 0;
491
492
        $qb = new midgard_query_builder('midgard_parameter');
493
        $qb->include_deleted();
494
        $qb->add_constraint('parentguid', '=', $guid);
495
        $qb->add_constraint('metadata.deleted', '=', true);
496
        foreach ($qb->execute() as $param) {
497
            if ($param->undelete($param->guid)) {
498
                $undeleted_size += $param->metadata->size;
499
            }
500
        }
501
502
        return $undeleted_size;
503
    }
504
505
    /**
506
     * Recover the attachments related to a deleted object
507
     *
508
     * @param string $guid
509
     * @return integer Size of undeleted objects
510
     * @todo We should only undelete parameters & attachments deleted inside some small window of the main objects delete
511
     */
512
    public static function undelete_attachments(string $guid) : int
513
    {
514
        $undeleted_size = 0;
515
516
        $qb = new midgard_query_builder('midgard_attachment');
517
        $qb->include_deleted();
518
        $qb->add_constraint('parentguid', '=', $guid);
519
        $qb->add_constraint('metadata.deleted', '=', true);
520
        foreach ($qb->execute() as $att) {
521
            if ($att->undelete($att->guid)) {
522
                midcom::get()->uimessages->add(midcom::get()->i18n->get_string('midgard.admin.asgard', 'midgard.admin.asgard'), sprintf(midcom::get()->i18n->get_string('attachment %s undeleted', 'midgard.admin.asgard'), $att->name, midcom_connection::get_error_string()));
523
                $undeleted_size += $att->metadata->size;
524
                $undeleted_size += self::undelete_parameters($att->guid);
525
            } else {
526
                midcom::get()->uimessages->add(midcom::get()->i18n->get_string('midgard.admin.asgard', 'midgard.admin.asgard'), sprintf(midcom::get()->i18n->get_string('failed undeleting attachment %s, reason %s', 'midgard.admin.asgard'), $att->name, midcom_connection::get_error_string()), 'error');
527
            }
528
        }
529
530
        return $undeleted_size;
531
    }
532
533
    /**
534
     * Purge objects
535
     *
536
     * @param array $guids
537
     * @param string $type
538
     * @return integer Size of purged objects
539
     */
540
    public static function purge(array $guids, $type) : int
541
    {
542
        $purged_size = 0;
543 1
        $qb = new midgard_query_builder($type);
544
        $qb->add_constraint('guid', 'IN', $guids);
545 1
        $qb->include_deleted();
546 1
547 1
        foreach ($qb->execute() as $object) {
548 1
            // first kill your children
549
            $children_types = midcom_helper_reflector_tree::get_child_objects($object, true);
550 1
551
            if (is_array($children_types)) {
552
                foreach ($children_types as $child_type => $children) {
553
                    $child_guids = array_column($children, 'guid');
554
                    self::purge($child_guids, $child_type);
555
                }
556
            }
557
558
            // then shoot your dogs
559
            $purged_size += self::purge_parameters($object->guid);
560
            $purged_size += self::purge_attachments($object->guid);
561
562
            // now shoot yourself
563
            if (!$object->purge()) {
564
                debug_add("Failed to purge object " . get_class($object) . " {$object->guid}", MIDCOM_LOG_INFO);
565
            } else {
566
                $purged_size += $object->metadata->size;
567
            }
568
        }
569
570
        return $purged_size;
571
    }
572
573
    /**
574
     * Purge the parameters related to a deleted object
575
     *
576 1
     * @param string $guid
577
     * @return integer Size of purged objects
578
     */
579
    public static function purge_parameters(string $guid) : int
580
    {
581
        $purged_size = 0;
582
583
        $qb = new midgard_query_builder('midgard_parameter');
584
        $qb->include_deleted();
585
        $qb->add_constraint('parentguid', '=', $guid);
586
        foreach ($qb->execute() as $param) {
587
            if ($param->purge()) {
588
                $purged_size += $param->metadata->size;
589
            } else {
590
                midcom::get()->uimessages->add(
591
                    midcom::get()->i18n->get_string('midgard.admin.asgard', 'midgard.admin.asgard'),
592
                    sprintf(midcom::get()->i18n->get_string('failed purging parameter %s => %s, reason %s', 'midgard.admin.asgard'), $param->domain, $param->name, midcom_connection::get_error_string()),
593
                    'error'
594
                );
595
            }
596
        }
597
598
        return $purged_size;
599
    }
600
601
    /**
602
     * Purge the attachments related to a deleted object
603
     *
604
     * @param string $guid
605
     * @return integer Size of purged objects
606
     */
607
    public static function purge_attachments(string $guid) : int
608
    {
609
        $purged_size = 0;
610
611
        $qb = new midgard_query_builder('midgard_attachment');
612
        $qb->include_deleted();
613
        $qb->add_constraint('parentguid', '=', $guid);
614
        foreach ($qb->execute() as $att) {
615
            if ($att->purge()) {
616
                $purged_size += $att->metadata->size;
617
                self::purge_parameters($att->guid);
618
            } else {
619
                midcom::get()->uimessages->add(midcom::get()->i18n->get_string('midgard.admin.asgard', 'midgard.admin.asgard'), sprintf(midcom::get()->i18n->get_string('failed purging attachment %s => %s, reason %s', 'midgard.admin.asgard'), $att->guid, $att->name, midcom_connection::get_error_string()), 'error');
620
            }
621
        }
622
623
        return $purged_size;
624
    }
625
626
    /**
627
     * After we instantiated the midgard object do some post processing and ACL checks
628
     *
629
     * @param midcom_core_dbaobject $object The DBA object we're working on
630
     * @see load()
631
     */
632
    public static function post_db_load_checks(midcom_core_dbaobject $object)
633
    {
634
        if (!$object->can_do('midgard:read')) {
635
            debug_add("Failed to load object, read privilege on the " . get_class($object) . " {$object->guid} not granted for the current user.");
636
            throw new midcom_error_forbidden();
637
        }
638 386
        $object->_on_loaded();
639
640 386
        // Register the GUID as loaded in this request
641
        if (isset(midcom::get()->cache->content)) {
642
            midcom::get()->cache->content->register($object->guid);
643
        }
644 386
    }
645
646
    /**
647 386
     * This is a simple wrapper with (currently) no additional functionality
648 386
     * over get_by_id that resynchronizes the object state with the database.
649
     * Use this if you think that your current object is stale. It does full
650 386
     * access control.
651
     *
652
     * On any failure, the object is cleared.
653
     *
654
     * @param midcom_core_dbaobject $object The DBA object we're working on
655
     * @return bool Indicating Success
656
     */
657
    public static function refresh(midcom_core_dbaobject $object) : bool
658
    {
659
        /**
660
         * Use try/catch here since the object might have been deleted...
661
         * @see http://trac.midgard-project.org/ticket/927
662
         */
663 187
        try {
664
            return $object->get_by_id($object->id);
665
        } catch (Exception $e) {
666
            return false;
667
        }
668
    }
669
670 187
    /**
671 62
     * This call wraps the original get_by_id call to provide access control.
672 62
     * The calling sequence is as with the corresponding constructor.
673
     *
674
     * @param midcom_core_dbaobject $object The DBA object we're working on
675
     * @param int $id The id of the object to load from the database.
676
     * @return bool Indicating Success
677
     */
678
    public static function get_by_id(midcom_core_dbaobject $object, int $id) : bool
679
    {
680
        if (!$id) {
681
            debug_add("Failed to load " . get_class($object) . " object, incorrect ID provided.", MIDCOM_LOG_ERROR);
682
            return false;
683
        }
684 187
685
        $object->__object->get_by_id((int) $id);
686 187
687
        if ($object->id != 0) {
688
            if (!$object->can_do('midgard:read')) {
689
                debug_add("Failed to load object, read privilege on the " . get_class($object) . " {$object->guid} not granted for the current user.",
690
                    MIDCOM_LOG_ERROR);
691 187
                $object->__object = new $object->__mgdschema_class_name__;
692
                return false;
693 171
            }
694 171
695
            $object->_on_loaded();
696
            return true;
697
        }
698
        debug_add("Failed to load the record identified by {$id}, last Midgard error was:" . midcom_connection::get_error_string(), MIDCOM_LOG_INFO);
699
        return false;
700
    }
701 171
702 171
    /**
703
     * This call wraps the original get_by_guid call to provide access control.
704
     * The calling sequence is as with the corresponding constructor.
705
     *
706
     * @param midcom_core_dbaobject $object The DBA object we're working on
707
     * @param string $guid The guid of the object to load from the database.
708
     * @return bool Indicating Success
709
     */
710
    public static function get_by_guid(midcom_core_dbaobject $object, string $guid) : bool
711
    {
712
        if (   !midcom::get()->auth->admin
713
            && !midcom::get()->auth->acl->can_do_byguid('midgard:read', $guid, get_class($object), midcom::get()->auth->acl->get_user_id())) {
714
            debug_add("Failed to load object, read privilege on the " . get_class($object) . " {$guid} not granted for the current user.", MIDCOM_LOG_ERROR);
715
            return false;
716
        }
717
        $object->__object->get_by_guid($guid);
718
719
        if ($object->id != 0) {
720
            $object->_on_loaded();
721
            return true;
722
        }
723
        debug_add("Failed to load the record identified by {$guid}, last Midgard error was: " . midcom_connection::get_error_string(), MIDCOM_LOG_INFO);
724
        return false;
725
    }
726
727
    /**
728
     * This call wraps the original get_by_guid call to provide access control.
729
     * The calling sequence is as with the corresponding constructor.
730
     *
731
     * @param midcom_core_dbaobject $object The DBA object we're working on
732
     * @param string $path The path of the object to load from the database.
733
     * @return bool Indicating Success
734
     */
735
    public static function get_by_path(midcom_core_dbaobject $object, $path) : bool
736
    {
737
        $object->__object->get_by_path((string) $path);
738
739
        if ($object->id != 0) {
740
            if (!$object->can_do('midgard:read')) {
741 2
                $object->__object = new $object->__mgdschema_class_name__;
742
                return false;
743 2
            }
744
745 2
            $object->_on_loaded();
746 1
            return true;
747
        }
748
        return false;
749
    }
750
751 1
    /**
752 1
     * "Pre-flight" checks for delete method
753
     *
754 1
     * Separated so that dbfactory->import() can reuse the code
755
     *
756
     * @param midcom_core_dbaobject $object The DBA object we're working on
757
     */
758
    public static function delete_pre_checks(midcom_core_dbaobject $object) : bool
759
    {
760
        if (!$object->id) {
761
            debug_add("Failed to delete object, object " . get_class($object) . " is non-persistent (empty ID).", MIDCOM_LOG_ERROR);
762
            return false;
763
        }
764 185
765
        if (!$object->can_do('midgard:delete')) {
766 185
            debug_add("Failed to delete object, delete privilege on the " . get_class($object) . " {$object->guid} not granted for the current user.",
767
                MIDCOM_LOG_ERROR);
768
            midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
769
            return false;
770
        }
771 185
        return $object->_on_deleting();
772
    }
773
}
774