midcom_helper_reflector_copy   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 274
Duplicated Lines 0 %

Test Coverage

Coverage 66.67%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
dl 0
loc 274
ccs 60
cts 90
cp 0.6667
rs 9.44
c 1
b 0
f 0
wmc 37

9 Methods

Rating   Name   Duplication   Size   Complexity  
A get_parent_property() 0 4 1
A copy_attachments() 0 11 2
A copy_metadata() 0 12 3
A copy_privileges() 0 21 3
A _copy_data() 0 11 3
A copy_parameters() 0 13 4
A execute() 0 17 3
A copy_tree() 0 26 5
C copy_object() 0 51 13
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
15
{
16
    use midcom_baseclasses_components_base;
0 ignored issues
show
introduced by
The trait midcom_baseclasses_components_base requires some properties which are not provided by midcom_helper_reflector_copy: $i18n, $head
Loading history...
17
18
    public ?midcom_core_dbaobject $target = null;
19
20
    /**
21
     * List of GUIDs of objects that shall not be copied
22
     */
23
    public array $exclude = [];
24
25
    /**
26
     * Switch for attachments
27
     */
28
    public bool $attachments = true;
29
30
    /**
31
     * Switch for parameters
32
     */
33
    public bool $parameters = true;
34
35
    /**
36
     * Switch for privileges
37
     */
38
    public bool $privileges = true;
39
40
    /**
41
     * Switch for metadata
42
     */
43
    public bool $metadata = true;
44
45
    /**
46
     * Copy the whole tree
47
     */
48
    public bool $recursive = true;
49
50
    /**
51
     * Metadata fields that shall be copied
52
     */
53
    public array $copy_metadata_fields = [
54
        'owner',
55
        'authors',
56
        'schedulestart',
57
        'scheduleend',
58
        'navnoentry',
59
        'hidden',
60
        'score',
61
    ];
62
63
    /**
64
     * Encountered errors
65
     */
66
    public array $errors = [];
67
68
    /**
69
     * Get the parent property for overriding it
70
     */
71 2
    public static function get_parent_property(midcom_core_dbaobject $object) : ?string
72
    {
73 2
        return midgard_object_class::get_property_parent($object->__mgdschema_class_name__)
74 2
            ?? midgard_object_class::get_property_up($object->__mgdschema_class_name__);
75
    }
76
77
    /**
78
     * Copy an object tree. Both source and parent may be liberally filled. Source can be either
79
     * MgdSchema or MidCOM db object of the object and parent can be
80
     *
81
     * - MgdSchema object
82
     * - MidCOM db object
83
     * - left empty to copy as a parentless object
84
     *
85
     * This method is self-aware and will refuse to perform any infinite loops (e.g. to copy
86
     * itself to its descendant, copying itself again and again and again).
87
     *
88
     * Eventually this method will return the first root object that was created, i.e. the root
89
     * of the new tree.
90
     */
91 1
    public function copy_tree(midcom_core_dbaobject $source, ?midcom_core_dbaobject $parent) : ?midcom_core_dbaobject
92
    {
93
        // Copy the root object
94 1
        $root = $this->copy_object($source, $parent);
95
96 1
        if (!$root) {
97
            $this->errors[] = sprintf($this->_l10n->get('failed to copy object %s'), $source->guid);
98
            return null;
99
        }
100
101
        // Add the newly copied object to the exclusion list to prevent infinite loops
102 1
        $this->exclude[] = $source->guid;
103
104
        // Loop through the children and copy them to their corresponding parents
105 1
        foreach (midcom_helper_reflector_tree::get_child_objects($source) as $children) {
106
            // Get the children of each type
107
            foreach ($children as $child) {
108
                // Skip the excluded child
109
                if (!in_array($child->guid, $this->exclude)) {
110
                    $this->copy_tree($child, $root);
111
                }
112
            }
113
        }
114
115
        // Return the newly created root object
116 1
        return $root;
117
    }
118
119
    /**
120
     * Copy an object
121
     */
122 1
    public function copy_object(midcom_core_dbaobject $source, ?midcom_core_dbaobject $parent, array $defaults = []) : ?midcom_core_dbaobject
123
    {
124
        // Duplicate the object
125 1
        $class_name = $source::class;
126 1
        $target = new $class_name();
127
128
        // Copy the object properties
129 1
        foreach (midcom_helper_reflector::get_object_fieldnames($source) as $property) {
130
            // Skip certain fields
131 1
            if (!preg_match('/^(_|metadata|guid|id)/', $property)) {
132 1
                $target->$property = $source->$property;
133
            }
134
        }
135
136
        // Override with defaults
137 1
        foreach ($defaults as $name => $value) {
138
            $target->$name = $value;
139
        }
140
141 1
        if (   $this->recursive
142 1
            && $parent_property = self::get_parent_property($source)) {
143
144
            // Copy the link to parent
145 1
            if (!empty($parent->guid)) {
146
                // @TODO: Is there a sure way to determine if the parent is
147
                // GUID or is it ID? If so, please change it here.
148
                $parent_key = (is_string($source->$parent_property)) ? 'guid' : 'id';
149
                $target->$parent_property = $parent->$parent_key;
150
            } else {
151 1
                $target->$parent_property = (is_string($source->$parent_property)) ? '' : 0;
152
            }
153
        }
154 1
        if ($name_property = midcom_helper_reflector::get_name_property($target)) {
155 1
            $resolver = new midcom_helper_reflector_nameresolver($target);
156 1
            $target->$name_property = $resolver->generate_unique_name();
157
        }
158
159
        // This needs to be here, otherwise it will be overridden
160 1
        $target->allow_name_catenate = true;
161 1
        if (!$target->create()) {
162
            $this->errors[] = $this->_l10n->get('failed to create object: ' . midcom_connection::get_error_string());
163
            return null;
164
        }
165
166 1
        foreach (['parameters', 'metadata', 'attachments', 'privileges'] as $type) {
167 1
            if (!$this->_copy_data($type, $source, $target)) {
168
                return null;
169
            }
170
        }
171
172 1
        return $target;
173
    }
174
175
    /**
176
     * Copy object data
177
     */
178 1
    private function _copy_data(string $type, midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
179
    {
180 1
        if ($this->$type) {
181 1
            $method = 'copy_' . $type;
182 1
            if (!$this->$method($source, $target)) {
183
                $this->errors[] = $this->_l10n->get('failed to copy ' . $type);
184
                return false;
185
            }
186
        }
187
188 1
        return true;
189
    }
190
191
    /**
192
     * Copy parameters for the object
193
     */
194 2
    public function copy_parameters(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
195
    {
196
        // Loop through the parameters
197 2
        foreach ($source->list_parameters() as $domain => $parameters) {
198 1
            foreach ($parameters as $name => $value) {
199 1
                if (!$target->set_parameter($domain, $name, $value)) {
200
                    $this->errors[] = sprintf($this->_l10n->get('failed to copy parameters from %s to %s'), $source->guid, $target->guid);
201
                    return false;
202
                }
203
            }
204
        }
205
206 2
        return true;
207
    }
208
209
    /**
210
     * Copy metadata for the object
211
     */
212 1
    public function copy_metadata(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
213
    {
214 1
        foreach ($this->copy_metadata_fields as $property) {
215 1
            $target->metadata->$property = $source->metadata->$property;
216
        }
217
218 1
        if ($target->update()) {
219 1
            return true;
220
        }
221
222
        $this->errors[] = sprintf($this->_l10n->get('failed to copy metadata from %s to %s'), $source->guid, $target->guid);
223
        return false;
224
    }
225
226
    /**
227
     * Copy attachments
228
     */
229 1
    public function copy_attachments(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
230
    {
231 1
        $defaults = [
232 1
            'parentguid' => $target->guid,
233 1
        ];
234
235 1
        foreach ($source->list_attachments() as $attachment) {
236
            $this->copy_object($attachment, $target, $defaults);
237
        }
238
239 1
        return true;
240
    }
241
242
    /**
243
     * Copy privileges
244
     */
245 1
    public function copy_privileges(midcom_core_dbaobject $source, midcom_core_dbaobject $target) : bool
246
    {
247 1
        $qb = midcom_core_privilege_db::new_query_builder();
248 1
        $qb->add_constraint('objectguid', '=', $source->guid);
249
250 1
        foreach ($qb->execute() as $privilege) {
251
            $new = new midcom_core_privilege_db();
252
            $new->objectguid = $target->guid;
253
254
            $new->classname = $privilege->classname;
255
            $new->privilegename = $privilege->privilegename;
256
            $new->value = $privilege->value;
257
            $new->assignee = $privilege->assignee;
258
259
            if (!$new->create()) {
260
                $this->errors[] = 'privilege creation failed';
261
                return false;
262
            }
263
        }
264
265 1
        return true;
266
    }
267
268
    /**
269
     * Dispatches the copy command according to the attributes set
270
     */
271 1
    public function execute(midcom_core_dbaobject $source) : ?midcom_core_dbaobject
272
    {
273 1
        if ($this->recursive) {
274
            // Disable execution timeout and memory limit, this can be very intensive
275 1
            midcom::get()->disable_limits();
276
277 1
            $new_root_object = $this->copy_tree($source, $this->target);
278
        } else {
279
            $new_root_object = $this->copy_object($source, $this->target);
280
        }
281
282 1
        if (empty($new_root_object->guid)) {
283
            $this->errors[] = $this->_l10n->get('failed to get the new root object');
284
            return null;
285
        }
286
287 1
        return $new_root_object;
288
    }
289
}
290