Passed
Push — master ( d0f685...56fb23 )
by Andreas
26:41
created

midcom_helper_reflector_copy::copy_attachments()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 11
ccs 4
cts 5
cp 0.8
crap 2.032
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 2
    public function copy_parameters(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
258
    {
259
        // Loop through the parameters
260 2
        foreach ($source->list_parameters() as $domain => $parameters) {
261 1
            foreach ($parameters as $name => $value) {
262 1
                if (!$target->set_parameter($domain, $name, $value)) {
263
                    $this->errors[] = sprintf($this->_l10n->get('failed to copy parameters from %s to %s'), $source->guid, $target->guid);
264
265
                    if ($this->halt_on_errors) {
266
                        return false;
267
                    }
268
                }
269
            }
270
        }
271
272 2
        return true;
273
    }
274
275
    /**
276
     * Copy metadata for the object
277
     */
278 1
    public function copy_metadata(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
279
    {
280 1
        foreach ($this->copy_metadata_fields as $property) {
281 1
            $target->metadata->$property = $source->metadata->$property;
282
        }
283
284 1
        if ($target->update()) {
285 1
            return true;
286
        }
287
288
        $this->errors[] = sprintf($this->_l10n->get('failed to copy metadata from %s to %s'), $source->guid, $target->guid);
289
        return false;
290
    }
291
292
    /**
293
     * Copy attachments
294
     */
295 1
    public function copy_attachments(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
296
    {
297
        $defaults = [
298 1
            'parentguid' => $target->guid,
299
        ];
300
301 1
        foreach ($source->list_attachments() as $attachment) {
302
            $this->copy_object($attachment, $target, $defaults);
303
        }
304
305 1
        return true;
306
    }
307
308
    /**
309
     * Copy privileges
310
     */
311 1
    public function copy_privileges(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
312
    {
313 1
        $qb = midcom_core_privilege_db::new_query_builder();
314 1
        $qb->add_constraint('objectguid', '=', $source->guid);
315
316 1
        foreach ($qb->execute() as $privilege) {
317
            $new = new midcom_core_privilege_db();
318
            $new->objectguid = $target->guid;
319
320
            $new->classname = $privilege->classname;
321
            $new->privilegename = $privilege->privilegename;
322
            $new->value = $privilege->value;
323
            $new->assignee = $privilege->assignee;
324
325
            if (!$new->create()) {
326
                $this->errors[] = 'privilege creation failed';
327
                return false;
328
            }
329
        }
330
331 1
        return true;
332
    }
333
334
    /**
335
     * Dispatches the copy command according to the attributes set
336
     */
337 1
    public function execute(midcom_core_dbaobject $source) : ?midcom_core_dbaobject
338
    {
339 1
        if ($this->recursive) {
340
            // Disable execution timeout and memory limit, this can be very intensive
341
            midcom::get()->disable_limits();
342
343
            $new_root_object = $this->copy_tree($source, $this->target);
344
        } else {
345 1
            $new_root_object = $this->copy_object($source, $this->target);
346
        }
347
348 1
        if (empty($new_root_object->guid)) {
349
            $this->errors[] = $this->_l10n->get('failed to get the new root object');
350
            return null;
351
        }
352
353 1
        return $new_root_object;
354
    }
355
}
356