Completed
Push — master ( 01c28b...0d4f0a )
by Andreas
17:26
created

midcom_helper_metadata::_retrieve_value()   B

Complexity

Conditions 10
Paths 9

Size

Total Lines 40
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 10.0064

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 10
eloc 25
c 1
b 1
f 0
nc 9
nop 1
dl 0
loc 40
ccs 24
cts 25
cp 0.96
crap 10.0064
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package midcom.helper
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\datamanager\schemadb;
10
use midcom\datamanager\datamanager;
11
12
/**
13
 * This class is an interface to the metadata of MidCOM objects.
14
 *
15
 * It will use an internal mechanism to cache repeated accesses to the same
16
 * metadata key during its lifetime. (Invalidating this cache will be possible
17
 * though.)
18
 *
19
 * <b>Metadata Key Reference</b>
20
 *
21
 * See the schema in /midcom/config/metadata_default.inc
22
 *
23
 * <b>Example Usage, Metadata Retrieval</b>
24
 *
25
 * <code>
26
 * <?php
27
 * $nap = new midcom_helper_nav();
28
 * $node = $nap->get_node($nap->get_current_node());
29
 *
30
 * $meta = $node[MIDCOM_NAV_OBJECT]->metadata;
31
 * echo "Visible : " . $meta->is_visible() . "</br>";
32
 * echo "Approved : " . $meta->is_approved() . "</br>";
33
 * echo "Keywords: " . $meta->get('keywords') . "</br>";
34
 * </code>
35
 *
36
 * <b>Example Usage, Approval</b>
37
 *
38
 * <code>
39
 * <?php
40
 * $article = new midcom_db_article($my_article_created_id);
41
 *
42
 * $article->metadata->approve();
43
 * </code>
44
 *
45
 * @property integer $schedulestart The time upon which the object should be made visible. 0 for no restriction.
46
 * @property integer $scheduleend The time upon which the object should be made invisible. 0 for no restriction.
47
 * @property boolean $navnoentry Set this to true if you do not want this object to appear in the navigation without it being completely hidden.
48
 * @property boolean $hidden Set this to true to hide the object on-site, overriding scheduling.
49
 * @property integer $published The publication time of the object.
50
 * @property string $publisher The person that published the object (i.e. author), read-only except on articles and pages.
51
 * @property string $authors The persons that worked on the object, pipe-separated list of GUIDs
52
 * @property string $owner The group that owns the object.
53
 * @property-read integer $created The creation time of the object.
54
 * @property-read string $creator The person that created the object.
55
 * @property-read integer $revised The last-modified time of the object.
56
 * @property-read string $revisor The person that modified the object.
57
 * @property-read integer $revision The object's revision.
58
 * @property-read integer $locked The lock time of the object.
59
 * @property-read string $locker The person that locked the object.
60
 * @property-read integer $size The object's size in bytes.
61
 * @property-read boolean $deleted Is the object deleted.
62
 * @property integer $approved The time of approval of the object, or 0 if not approved. Set automatically through approve/unapprove.
63
 * @property string $approver The person that approved/unapproved the object. Set automatically through approve/unapprove.
64
 * @property integer $score The object's score for sorting.
65
 * @property midcom_core_dbaobject $object Object to which we are attached.
66
 * @package midcom.helper
67
 */
68
class midcom_helper_metadata
69
{
70
    /**
71
     * @var midcom_core_dbaobject
72
     */
73
    private $__object;
74
75
    /**
76
     * Metadata object of the current object
77
     *
78
     * @var midgard\portable\api\metadata
79
     */
80
    private $__metadata;
81
82
    /**
83
     * Holds the values already read from the database.
84
     *
85
     * @var Array
86
     */
87
    private $_cache = [];
88
89
    private $field_config = [
90
        'readonly' => ['creator', 'created', 'revisor', 'revised', 'locker', 'locked', 'revision', 'size', 'deleted', 'exported', 'imported'],
91
        'timebased' => ['created', 'revised', 'published', 'locked', 'approved', 'schedulestart', 'scheduleend', 'exported', 'imported'],
92
        'person' => ['creator', 'revisor', 'locker', 'approver'],
93
        'other' => ['authors', 'owner', 'hidden', 'navnoentry', 'score', 'revision', 'size', 'deleted']
94
    ];
95
96
    /**
97
     * This will construct a new metadata object for an existing content object.
98
     *
99
     * @param midcom_core_dbaobject $object The object to attach to.
100
     */
101 393
    public function __construct(midcom_core_dbaobject $object)
102
    {
103 393
        $this->__metadata = $object->__object->metadata;
104 393
        $this->__object = $object;
105 393
    }
106
107
    /* ------- BASIC METADATA INTERFACE --------- */
108
109
    /**
110
     * Return a single metadata key from the object. The return
111
     * type depends on the metadata key that is requested (see the class introduction).
112
     *
113
     * You will not get the data from the datamanager using this calls, but the only
114
     * slightly post-processed metadata values. See _retrieve_value for post processing.
115
     *
116
     * @see midcom_helper_metadata::_retrieve_value()
117
     * @param string $key The key to retrieve
118
     * @return mixed The key's value.
119
     */
120 404
    public function get($key)
121
    {
122 404
        if (!isset($this->_cache[$key])) {
123 285
            $this->_cache[$key] = $this->_retrieve_value($key);
124
        }
125
126 404
        return $this->_cache[$key];
127
    }
128
129 363
    public function __get($key)
130
    {
131 363
        if ($key == 'object') {
132 75
            return $this->__object;
133
        }
134 363
        return $this->get($key);
135
    }
136
137 312
    public function __isset($key)
138
    {
139 312
        if (!isset($this->_cache[$key])) {
140 312
            $this->_cache[$key] = $this->_retrieve_value($key);
141
        }
142
143 312
        return isset($this->_cache[$key]);
144
    }
145
146
    /**
147
     * Return a Datamanager instance for the current object.
148
     *
149
     * Also, whenever the containing datamanager stores its data, you
150
     * <b>must</b> call the on_update() method of this class. This is
151
     * very important or backwards compatibility will be broken.
152
     *
153
     * @return datamanager A initialized Datamanager instance for the selected object.
154
     * @see midcom_helper_metadata::on_update()
155
     */
156 95
    public function get_datamanager() : datamanager
157
    {
158 95
        $schemadb = schemadb::from_path(midcom::get()->config->get('metadata_schema'));
159
160
        // Check if we have metadata schema defined in the schemadb specific for the object's schema or component
161 95
        $object_schema = $this->__object->get_parameter('midcom.helper.datamanager2', 'schema_name');
162 95
        if ($object_schema == '' || !$schemadb->has($object_schema)) {
163 95
            $component_schema = str_replace('.', '_', midcom_core_context::get()->get_key(MIDCOM_CONTEXT_COMPONENT));
164 95
            if ($schemadb->has($component_schema)) {
165
                // No specific metadata schema for object, fall back to component-specific metadata schema
166
                $object_schema = $component_schema;
167
            } else {
168
                // No metadata schema for component, fall back to default
169 95
                $object_schema = 'metadata';
170
            }
171
        }
172 95
        $dm = new datamanager($schemadb);
173 95
        return $dm->set_storage($this->__object, $object_schema);
174
    }
175
176
    /**
177
     * Frontend for setting a single metadata option
178
     *
179
     * @param string $key The key to set.
180
     * @param mixed $value The value to set.
181
     */
182 298
    public function set($key, $value) : bool
183
    {
184
        // Store the RCS mode
185 298
        $rcs_mode = $this->__object->_use_rcs;
186
187 298
        if ($return = $this->_set_property($key, $value)) {
188 297
            if ($this->__object->guid) {
189 8
                $return = $this->__object->update();
190
            }
191
192
            // Update the corresponding cache variable
193 297
            $this->on_update($key);
194
        }
195
        // Return the original RCS mode
196 298
        $this->__object->_use_rcs = $rcs_mode;
197 298
        return $return;
198
    }
199
200 13
    public function __set($key, $value)
201
    {
202 13
        $this->set($key, $value);
203 13
    }
204
205
    /**
206
     * Directly set a metadata option.
207
     *
208
     * The passed value will be stored using the follow transformations:
209
     *
210
     * - Storing into the approver field will automatically recognize Person Objects and simple
211
     *   IDs and transform them into a GUID.
212
     * - created can only be set with articles.
213
     * - creator, editor and edited cannot be set.
214
     *
215
     * Any error will trigger midcom_error.
216
     *
217
     * @param string $key The key to set.
218
     * @param mixed $value The value to set.
219
     */
220 298
    private function _set_property($key, $value) : bool
221
    {
222 298
        if (is_object($value)) {
223
            $classname = get_class($value);
224
            debug_add("Can not set metadata '{$key}' property with '{$classname}' object as value", MIDCOM_LOG_WARN);
225
            return false;
226
        }
227
228 298
        if (in_array($key, $this->field_config['readonly'])) {
229
            midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
230
            return false;
231
        }
232
233 298
        if (in_array($key, ['approver', 'approved'])) {
234
            // Prevent lock changes from creating new revisions
235
            $this->__object->_use_rcs = false;
236
        }
237
238 298
        if (in_array($key, $this->field_config['timebased'])) {
239 296
            if (!is_numeric($value) || $value == 0) {
240 2
                $value = null;
241
            } else {
242 296
                $value = new midgard_datetime(gmstrftime('%Y-%m-%d %T', $value));
243
            }
244 159
        } elseif (!in_array($key, $this->field_config['other']) && $key !== 'approver') {
245
            // Fall-back for non-core properties
246 4
            return $this->__object->set_parameter('midcom.helper.metadata', $key, $value);
247
        }
248
249 297
        $this->__metadata->$key = $value;
250 297
        return true;
251
    }
252
253
    /**
254
     * This is the update event handler for the Metadata system. It must be called
255
     * whenever metadata changes to synchronize the various backwards-compatibility
256
     * values in place throughout the system.
257
     *
258
     * @param string $key The key that was updated. Leave empty for a complete update by the Datamanager.
259
     */
260 297
    private function on_update($key = null)
261
    {
262 297
        if ($key) {
263 297
            unset($this->_cache[$key]);
264
        } else {
265
            $this->_cache = [];
266
        }
267
268 297
        if (!empty($this->__object->guid)) {
269 8
            midcom::get()->cache->invalidate($this->__object->guid);
270
        }
271 297
    }
272
273
    /* ------- METADATA I/O INTERFACE -------- */
274
275
    /**
276
     * Retrieves a given metadata key, postprocesses it where necessary
277
     * and stores it into the local cache.
278
     *
279
     * - Person references (both guid and id) get resolved into the corresponding
280
     *   Person object.
281
     * - created, creator, edited and editor are taken from the corresponding
282
     *   MidgardObject fields.
283
     * - Parameters are accessed using $object->get_parameter directly
284
     *
285
     * Note, that we hide any errors from not existent properties explicitly,
286
     * as a few of the MidCOM objects do not support all of the predefined meta
287
     * data fields, PHP will default to "0" in these cases. For Person IDs, this
288
     * "0" is rewritten to "1" to use the MidgardAdministrator account instead.
289
     *
290
     * @param string $key The key to retrieve.
291
     */
292 404
    private function _retrieve_value($key)
293
    {
294 404
        if (in_array($key, $this->field_config['timebased'])) {
295
            // This is ugly, but seems the only possible way...
296 375
            if (   isset($this->__metadata->$key)
297 375
                && (string) $this->__metadata->$key !== "0001-01-01T00:00:00+00:00") {
298 168
                return (int) $this->__metadata->$key->format('U');
299
            }
300 330
            return 0;
301
        }
302 284
        if (in_array($key, $this->field_config['person'])) {
303 105
            if (!$this->__metadata->$key) {
304
                // Fall back to "Midgard root user" if person is not found
305 77
                static $root_user_guid = null;
306 77
                if (!$root_user_guid) {
307 1
                    $mc = new midgard_collector('midgard_person', 'id', 1);
308 1
                    $mc->set_key_property('guid');
309 1
                    $mc->execute();
310 1
                    $guids = $mc->list_keys();
311 1
                    if (empty($guids)) {
312
                        $root_user_guid = 'f6b665f1984503790ed91f39b11b5392';
313
                    } else {
314 1
                        $root_user_guid = key($guids);
315
                    }
316
                }
317
318 77
                return $root_user_guid;
319
            }
320 63
            return $this->__metadata->$key;
321
        }
322 273
        if (!in_array($key, $this->field_config['other'])) {
323
            // Fall-back for non-core properties
324 94
            $dm = $this->get_datamanager();
325 94
            if (!$dm->get_schema()->has_field($key)) {
326
                // Fall back to the parameter reader for non-core MidCOM metadata params
327 92
                return $this->__object->get_parameter('midcom.helper.metadata', $key);
328
            }
329 2
            return $dm->get_content_csv()[$key];
330
        }
331 257
        return $this->__metadata->$key;
332
    }
333
334
    /* ------- CONVENIENCE METADATA INTERFACE --------- */
335
336
    /**
337
     * Checks whether the object has been approved since its last editing.
338
     *
339
     * @return boolean Indicating approval state.
340
     */
341
    public function is_approved() : bool
342
    {
343
        return $this->__object->is_approved();
344
    }
345
346
    /**
347
     * Checks the object's visibility regarding scheduling and the hide flag.
348
     *
349
     * This does not check approval, use is_approved for that.
350
     *
351
     * @see midcom_helper_metadata::is_approved()
352
     * @return boolean Indicating visibility state.
353
     */
354
    public function is_visible() : bool
355
    {
356
        if ($this->get('hidden')) {
357
            return false;
358
        }
359
360
        $now = time();
361
        if (   $this->get('schedulestart')
362
            && $this->get('schedulestart') > $now) {
363
            return false;
364
        }
365
        if (   $this->get('scheduleend')
366
            && $this->get('scheduleend') < $now) {
367
            return false;
368
        }
369
        return true;
370
    }
371
372
    /**
373
     * This is a helper function which indicates whether a given object may be shown onsite
374
     * taking approval, scheduling and visibility settings into account. The important point
375
     * here is that it also checks the global configuration defaults, so that this is
376
     * basically the same base on which NAP decides whether to show an item or not.
377
     *
378
     * @return boolean Indicating visibility.
379
     */
380 20
    public function is_object_visible_onsite()
381
    {
382
        return
383 20
        (   (   midcom::get()->config->get('show_hidden_objects')
384 20
             || $this->is_visible())
385 20
         && (   midcom::get()->config->get('show_unapproved_objects')
386 20
             || $this->is_approved())
387
        );
388
    }
389
390
    /**
391
     * Approves the object.
392
     *
393
     * This sets the approved timestamp to the current time and the
394
     * approver person GUID to the GUID of the person currently
395
     * authenticated.
396
     */
397 1
    public function approve() : bool
398
    {
399 1
        midcom::get()->auth->require_do('midcom:approve', $this->__object);
400 1
        midcom::get()->auth->require_do('midgard:update', $this->__object);
401
402 1
        if (!is_object($this->__object)) {
403
            return false;
404
        }
405
406 1
        return $this->__object->approve();
407
    }
408
409
    /**
410
     * Unapproves the object.
411
     *
412
     * This resets the approved timestamp and sets the
413
     * approver person GUID to the GUID of the person currently
414
     * authenticated.
415
     */
416 1
    public function unapprove() : bool
417
    {
418 1
        midcom::get()->auth->require_do('midcom:approve', $this->__object);
419 1
        midcom::get()->auth->require_do('midgard:update', $this->__object);
420
421 1
        if (!is_object($this->__object)) {
422
            return false;
423
        }
424
425 1
        return $this->__object->unapprove();
426
    }
427
428
    /* ------- CLASS MEMBER FUNCTIONS ------- */
429
430
    /**
431
     * Returns a metadata object for a given content object.
432
     *
433
     * You may bass any one of the following arguments to the function:
434
     *
435
     * - Any class derived from MidgardObject, you must only ensure, that the parameter
436
     *   and guid member functions stays available.
437
     * - Any valid GUID
438
     *
439
     * @param mixed $source The object to attach to, this may be either a MidgardObject or a GUID.
440
     * @return midcom_helper_metadata The created metadata object.
441
     */
442 51
    public static function retrieve($source)
443
    {
444 51
        $object = null;
445
446 51
        if (is_object($source)) {
447 51
            $object = $source;
448 51
            $guid = $source->guid;
449
        } else {
450
            $guid = $source;
451
        }
452
453 51
        if (   $object === null
454 51
            && mgd_is_guid($guid)) {
455
            try {
456
                $object = midcom::get()->dbfactory->get_object_by_guid($guid);
457
            } catch (midcom_error $e) {
458
                debug_add("Failed to create a metadata instance for the GUID {$guid}: " . $e->getMessage(), MIDCOM_LOG_WARN);
459
                debug_print_r("Source was:", $source);
460
461
                return false;
462
            }
463
        }
464
465
        // $object is now populated, too
466 51
        return new self($object);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type null; however, parameter $object of midcom_helper_metadata::__construct() does only seem to accept midcom_core_dbaobject, 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

466
        return new self(/** @scrutinizer ignore-type */ $object);
Loading history...
467
    }
468
469
    /**
470
     * Check if the requested object is locked
471
     *
472
     * @return boolean          True if the object is locked, false if it isn't
473
     */
474 90
    public function is_locked() : bool
475
    {
476
        // Object hasn't been marked to be edited
477 90
        if ($this->get('locked') == 0) {
478 59
            return false;
479
        }
480
481 31
        if (($this->get('locked') + (midcom::get()->config->get('metadata_lock_timeout') * 60)) < time()) {
482
            // lock expired, explicitly clear lock
483
            $this->unlock();
484
            return false;
485
        }
486
487
        // Lock was created by the user, return "not locked"
488 31
        if (   !empty(midcom::get()->auth->user->guid)
489 31
            && $this->get('locker') === midcom::get()->auth->user->guid) {
490 30
            return false;
491
        }
492
493
        // Unlocked states checked and none matched, consider locked
494 1
        return $this->__object->is_locked();
495
    }
496
497
    /**
498
     * Set the object lock
499
     *
500
     * @return boolean       Indicating success
501
     */
502 44
    public function lock() : bool
503
    {
504 44
        midcom::get()->auth->require_do('midgard:update', $this->__object);
505
506 44
        if (   is_object($this->__object)
507 44
            && $this->__object->lock()) {
508 32
            $this->_cache = [];
509 32
            return true;
510
        }
511
512 12
        return false;
513
    }
514
515
    /**
516
     * Check whether current user can unlock the object
517
     *
518
     * @return boolean indicating privileges
519
     * @todo enable specifying user ?
520
     */
521 13
    public function can_unlock() : bool
522
    {
523 13
        return (   $this->__object->can_do('midcom:unlock')
524 13
                || midcom::get()->auth->can_user_do('midcom:unlock', null, midcom_services_auth::class));
525
    }
526
527
    /**
528
     * Unlock the object
529
     *
530
     * @return boolean    Indicating success
531
     */
532 12
    public function unlock() : bool
533
    {
534 12
        if (   $this->can_unlock()
535 12
            && is_object($this->__object)
536 12
            && $this->__object->unlock()) {
537 12
            $this->_cache = [];
538 12
            return true;
539
        }
540
541
        return false;
542
    }
543
}
544