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

midcom_helper_reflector_copy::copy_object()   F

Complexity

Conditions 15
Paths 480

Size

Total Lines 59
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 27.278

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 15
eloc 29
c 1
b 0
f 0
nc 480
nop 3
dl 0
loc 59
ccs 18
cts 29
cp 0.6207
crap 27.278
rs 2.4722

How to fix   Long Method    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.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