Passed
Push — master ( d76f23...1b6466 )
by Andreas
26:38 queued 05:10
created

midcom_helper_reflector_copy::copy_tree()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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