Completed
Push — master ( 9c6f05...775967 )
by Andreas
17:56
created

midcom_helper_reflector_copy::resolve_object()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3.0261

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 1
dl 0
loc 14
ccs 6
cts 7
cp 0.8571
crap 3.0261
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package midcom.helper.reflector
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
/**
10
 * The Grand Unified Reflector, copying helper class
11
 *
12
 * @package midcom.helper.reflector
13
 */
14
class midcom_helper_reflector_copy extends midcom_baseclasses_components_purecode
15
{
16
    /**
17
     * Target
18
     *
19
     * @var midcom_core_dbaobject
20
     */
21
    public $target;
22
23
    /**
24
     * Exclusion list
25
     *
26
     * @var array        List of GUIDs of objects that shall not be copied
27
     */
28
    public $exclude = [];
29
30
    /**
31
     * Override properties of the new root object. This feature is
32
     * directed for overriding e.g. parent information.
33
     *
34
     * @var array        Property-value pairs
35
     */
36
    public $root_object_values = [];
37
38
    /**
39
     * Switch for attachments
40
     *
41
     * @var boolean
42
     */
43
    public $attachments = true;
44
45
    /**
46
     * Switch for parameters
47
     *
48
     * @var boolean
49
     */
50
    public $parameters = true;
51
52
    /**
53
     * Switch for privileges
54
     *
55
     * @var boolean
56
     */
57
    public $privileges = true;
58
59
    /**
60
     * Switch for metadata
61
     *
62
     * @var boolean
63
     */
64
    public $metadata = true;
65
66
    /**
67
     * Copy the whole tree
68
     *
69
     * @var boolean
70
     */
71
    public $recursive = true;
72
73
    /**
74
     * Metadata fields that shall be copied
75
     */
76
    public $copy_metadata_fields = [
77
        'owner',
78
        'authors',
79
        'schedulestart',
80
        'scheduleend',
81
        'navnoentry',
82
        'hidden',
83
        'score',
84
    ];
85
86
    /**
87
     * Switch for halt on error. If this is set to false, errors will be
88
     * reported, but will not stop executing
89
     *
90
     * @var boolean        Set to false to continue on errors
91
     */
92
    public $halt_on_errors = true;
93
94
    /**
95
     * Encountered errors
96
     *
97
     * @var array
98
     */
99
    public $errors = [];
100
101
    /**
102
     * Get the parent property for overriding it
103
     */
104 1
    public static function get_parent_property(midcom_core_dbaobject $object) : string
105
    {
106 1
        $parent = midgard_object_class::get_property_parent($object->__mgdschema_class_name__);
107 1
        if (!$parent) {
108 1
            $parent = midgard_object_class::get_property_up($object->__mgdschema_class_name__);
109
110 1
            if (!$parent) {
111
                throw new midcom_error('Failed to get the parent property for copying');
112
            }
113
        }
114
115 1
        return $parent;
116
    }
117
118
    /**
119
     * Copy an object tree. Both source and parent may be liberally filled. Source can be either
120
     * MgdSchema or MidCOM db object of the object and parent can be
121
     *
122
     * - MgdSchema object
123
     * - MidCOM db object
124
     * - left empty to copy as a parentless object
125
     *
126
     * This method is self-aware and will refuse to perform any infinite loops (e.g. to copy
127
     * itself to its descendant, copying itself again and again and again).
128
     *
129
     * Eventually this method will return the first root object that was created, i.e. the root
130
     * of the new tree.
131
     */
132
    public function copy_tree(midcom_core_dbaobject $source, midcom_core_dbaobject $parent) : ?midcom_core_dbaobject
133
    {
134
        // Copy the root object
135
        $root = $this->copy_object($source, $parent);
136
137
        if (!$root) {
138
            $this->errors[] = sprintf($this->_l10n->get('failed to copy object %s'), $source->guid);
139
            return null;
140
        }
141
142
        // Add the newly copied object to the exclusion list to prevent infinite loops
143
        $this->exclude[] = $source->guid;
144
145
        // Get the children
146
        $children = midcom_helper_reflector_tree::get_child_objects($source);
0 ignored issues
show
Bug introduced by
$source 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

146
        $children = midcom_helper_reflector_tree::get_child_objects(/** @scrutinizer ignore-type */ $source);
Loading history...
147
148
        if (empty($children)) {
149
            return $root;
150
        }
151
152
        // Loop through the children and copy them to their corresponding parents
153
        foreach ($children as $subchildren) {
154
            // Get the children of each type
155
            foreach ($subchildren as $child) {
156
                // Skip the excluded child
157
                if (in_array($child->guid, $this->exclude)) {
158
                    continue;
159
                }
160
161
                $this->copy_tree($child, $root);
162
            }
163
        }
164
165
        // Return the newly created root object
166
        return $root;
167
    }
168
169
    /**
170
     * Copy an object
171
     */
172 1
    public function copy_object(midcom_core_dbaobject $source, ?midcom_core_dbaobject $parent, array $defaults = []) : ?midcom_core_dbaobject
173
    {
174
        // Duplicate the object
175 1
        $class_name = get_class($source);
176 1
        $target = new $class_name();
177
178 1
        $properties = midcom_helper_reflector::get_object_fieldnames($source);
179
180
        // Copy the object properties
181 1
        foreach ($properties as $property) {
182
            // Skip certain fields
183 1
            if (preg_match('/^(_|metadata|guid|id)/', $property)) {
184 1
                continue;
185
            }
186
187 1
            $target->$property = $source->$property;
188
        }
189
190
        // Override requested root object properties
191 1
        if (   !empty($this->target->guid)
192 1
            && $target->guid === $this->target->guid) {
193
            foreach ($this->root_object_values as $name => $value) {
194
                $target->$name = $value;
195
            }
196
        }
197
198
        // Override with defaults
199 1
        foreach ($defaults as $name => $value) {
200
            $target->$name = $value;
201
        }
202
203 1
        if ($this->recursive) {
204
            $parent_property = self::get_parent_property($source);
205
206
            // Copy the link to parent
207
            if (!empty($parent->guid)) {
208
                // @TODO: Is there a sure way to determine if the parent is
209
                // GUID or is it ID? If so, please change it here.
210
                $parent_key = (is_string($source->$parent_property)) ? 'guid' : 'id';
211
                $target->$parent_property = $parent->$parent_key;
212
            } else {
213
                $target->$parent_property = (is_string($source->$parent_property)) ? '' : 0;
214
            }
215
        }
216 1
        if ($name_property = midcom_helper_reflector::get_name_property($target)) {
217 1
            $resolver = new midcom_helper_reflector_nameresolver($target);
218 1
            $target->$name_property = $resolver->generate_unique_name();
219
        }
220
221
        // This needs to be here, otherwise it will be overridden
222 1
        $target->allow_name_catenate = true;
223 1
        if (!$target->create()) {
224
            $this->errors[] = $this->_l10n->get('failed to create object: ' . midcom_connection::get_error_string());
225
            return null;
226
        }
227
228 1
        foreach (['parameters', 'metadata', 'attachments', 'privileges'] as $type) {
229 1
            if (!$this->_copy_data($type, $source, $target)) {
230
                return null;
231
            }
232
        }
233
234 1
        return $target;
235
    }
236
237
    /**
238
     * Copy object data
239
     */
240 1
    private function _copy_data(string $type, midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
241
    {
242 1
        if ($this->$type) {
243 1
            $method = 'copy_' . $type;
244 1
            if (   !$this->$method($source, $target)
245 1
                && $this->halt_on_errors) {
246
                $this->errors[] = $this->_l10n->get('failed to copy ' . $type);
247
                return false;
248
            }
249
        }
250
251 1
        return true;
252
    }
253
254
    /**
255
     * Copy parameters for the object
256
     */
257 1
    public function copy_parameters(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
258
    {
259
        // Loop through the parameters
260 1
        foreach ($source->list_parameters() as $parameter) {
261
            if (!$target->set_parameter($parameter->domain, $parameter->name, $parameter->value)) {
262
                $this->errors[] = sprintf($this->_l10n->get('failed to copy parameters from %s to %s'), $source->guid, $target->guid);
263
264
                if ($this->halt_on_errors) {
265
                    return false;
266
                }
267
            }
268
        }
269
270 1
        return true;
271
    }
272
273
    /**
274
     * Copy metadata for the object
275
     */
276 1
    public function copy_metadata(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
277
    {
278 1
        foreach ($this->copy_metadata_fields as $property) {
279 1
            $target->metadata->$property = $source->metadata->$property;
280
        }
281
282 1
        if ($target->update()) {
283 1
            return true;
284
        }
285
286
        $this->errors[] = sprintf($this->_l10n->get('failed to copy metadata from %s to %s'), $source->guid, $target->guid);
287
        return false;
288
    }
289
290
    /**
291
     * Copy attachments
292
     */
293 1
    public function copy_attachments(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
294
    {
295
        $defaults = [
296 1
            'parentguid' => $target->guid,
297
        ];
298
299 1
        foreach ($source->list_attachments() as $attachment) {
300
            $this->copy_object($attachment, $target, $defaults);
301
        }
302
303 1
        return true;
304
    }
305
306
    /**
307
     * Copy privileges
308
     */
309 1
    public function copy_privileges(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
310
    {
311 1
        $qb = midcom_core_privilege_db::new_query_builder();
312 1
        $qb->add_constraint('objectguid', '=', $source->guid);
313
314 1
        foreach ($qb->execute() as $privilege) {
315
            $new = new midcom_core_privilege_db();
316
            $new->objectguid = $target->guid;
317
318
            $new->classname = $privilege->classname;
319
            $new->privilegename = $privilege->privilegename;
320
            $new->value = $privilege->value;
321
            $new->assignee = $privilege->assignee;
322
323
            if (!$new->create()) {
324
                $this->errors[] = 'privilege creation failed';
325
                return false;
326
            }
327
        }
328
329 1
        return true;
330
    }
331
332
    /**
333
     * Dispatches the copy command according to the attributes set
334
     */
335 1
    public function execute(midcom_core_dbaobject $source) : ?midcom_core_dbaobject
336
    {
337 1
        if ($this->recursive) {
338
            // Disable execution timeout and memory limit, this can be very intensive
339
            midcom::get()->disable_limits();
340
341
            $new_root_object = $this->copy_tree($source, $this->target);
342
        } else {
343 1
            $new_root_object = $this->copy_object($source, $this->target);
344
        }
345
346 1
        if (empty($new_root_object->guid)) {
347
            $this->errors[] = $this->_l10n->get('failed to get the new root object');
348
            return null;
349
        }
350
351 1
        return $new_root_object;
352
    }
353
}
354