Passed
Push — master ( cfd6c6...83cee0 )
by Andreas
18:32
created

org_openpsa_calendar_event_dba   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 393
Duplicated Lines 0 %

Test Coverage

Coverage 61.14%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 167
c 1
b 0
f 0
dl 0
loc 393
ccs 107
cts 175
cp 0.6114
rs 6.4799
wmc 54

17 Methods

Rating   Name   Duplication   Size   Complexity  
A get_label() 0 7 2
A details_text() 0 11 1
A _on_updating() 0 4 1
A _get_resources() 0 5 1
A _suspect_defaults() 0 8 1
A implode_members() 0 8 2
A _on_creating() 0 3 1
A _on_created() 0 8 2
A _get_em() 0 12 2
A get_suspected_sales_links() 0 20 4
B _prepare_save() 0 41 9
A get_suspected_task_links() 0 27 5
A _check_timerange() 0 23 4
A _on_loaded() 0 25 5
A _get_participants() 0 5 1
B _on_updated() 0 42 8
A _on_deleting() 0 24 5

How to fix   Complexity   

Complex Class

Complex classes like org_openpsa_calendar_event_dba often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use org_openpsa_calendar_event_dba, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @package org.openpsa.calendar
4
 * @author Nemein Oy, http://www.nemein.com/
5
 * @copyright Nemein Oy, http://www.nemein.com/
6
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License
7
 */
8
9
/**
10
 * MidCOM wrapper for org_openpsa_event with various helper functions
11
 * refactored from OpenPSA 1.x calendar
12
 *
13
 * @todo Figure out a good way to always use UTC for internal time storage
14
 * @property integer $start
15
 * @property integer $end
16
 * @property string $title
17
 * @property string $description
18
 * @property integer $type
19
 * @property string $extra
20
 * @property boolean $busy
21
 * @property integer $up
22
 * @property string $location
23
 * @property boolean $tentative
24
 * @property string $externalGuid
25
 * @property string $vCalSerialized
26
 * @property integer $orgOpenpsaAccesstype
27
 * @property string $orgOpenpsaOwnerWg
28
 * @package org.openpsa.calendar
29
 */
30
class org_openpsa_calendar_event_dba extends midcom_core_dbaobject
31
{
32
    public $__midcom_class_name__ = __CLASS__;
33
    public $__mgdschema_class_name__ = 'org_openpsa_event';
34
35
    /**
36
     * list of participants
37
     *
38
     * (stored as eventmembers, referenced here for easier access)
39
     *
40
     * @var array
41
     */
42
    public $participants = [];
43
44
    /**
45
     * like $participants but for resources.
46
     *
47
     * @var array
48
     */
49
    public $resources = [];
50
51
    /**
52
     * vCalendar (or similar external source) GUID for this event
53
     *
54
     * (for vCalendar imports)
55
     *
56
     * @var string
57
     */
58
    private $old_externalGuid = '';
59
60
    /**
61
     * Send notifications to participants of the event
62
     *
63
     * @var boolean
64
     */
65
    var $send_notify = true;
66
67
    /**
68
     * Send notification also to current user
69
     *
70
     * @var boolean
71
     */
72
    public $send_notify_me = false;
73
74
    /**
75
     * Used to work around DM creation features to get correct notification type out
76
     *
77
     * @var boolean
78
     */
79
    var $notify_force_add = false;
80
81
    public $search_relatedtos = true;
82
    public $ignorebusy_em = false;
83
    public $rob_tentative = false;
84
85 2
    public function get_label() : string
86
    {
87 2
        if ($this->start == 0) {
88
            return $this->title;
89
        }
90 2
        $formatter = midcom::get()->i18n->get_l10n()->get_formatter();
91 2
        return $formatter->date($this->start) . " {$this->title}";
92
    }
93
94 14
    public function _on_loaded()
95
    {
96 14
        $l10n = midcom::get()->i18n->get_l10n('org.openpsa.calendar');
97
98
        // Preserve vCal GUIDs once set
99 14
        if (isset($this->externalGuid)) {
100 14
            $this->old_externalGuid = $this->externalGuid;
101
        }
102
103
        // Hide details if we're not allowed to see them
104 14
        if (!$this->can_do('org.openpsa.calendar:read')) {
105
            $keep = ['metadata', 'id', 'guid', 'start', 'end', 'orgOpenpsaAccesstype'];
106
            $hide = array_diff($this->get_properties(), $keep);
107
            foreach ($hide as $key) {
108
                $this->$key = null;
109
            }
110
            $this->title = $l10n->get('private event');
111
        }
112
        // Check for empty title
113 14
        if (!$this->title) {
114 7
            $this->title = $l10n->get('untitled');
115
        }
116
117
        // Populate resources and participants list
118 14
        $this->_get_em();
119 14
    }
120
121
    /**
122
     * Preparations related to all save operations (=create/update)
123
     */
124 9
    private function _prepare_save() : bool
125
    {
126
        // Make sure we have accessType
127 9
        if (!$this->orgOpenpsaAccesstype) {
128 9
            $this->orgOpenpsaAccesstype = org_openpsa_core_acl::ACCESS_PUBLIC;
129
        }
130
131
        // Make sure we can actually reserve the resources we need
132 9
        $resources = array_keys(array_filter($this->resources));
133 9
        $checker = new org_openpsa_calendar_event_resource_dba;
134 9
        foreach ($resources as $id) {
135
            $checker->resource = $id;
136
            if (!$checker->verify_can_reserve()) {
137
                debug_add("Cannot reserve resource #{$id}, returning false", MIDCOM_LOG_ERROR);
138
                midcom_connection::set_error(MGD_ERR_ACCESS_DENIED);
139
                return false;
140
            }
141
        }
142
143
        //Check up
144 9
        if (   !$this->up
145 9
            && $this->title != '__org_openpsa_calendar') {
146 7
            $root_event = org_openpsa_calendar_interface::find_root_event();
147 7
            $this->up = $root_event->id;
148
        }
149
150
        //check for busy participants/resources
151 9
        if (!$this->ignorebusy_em) {
152 9
            $conflictmanager = new org_openpsa_calendar_conflictmanager($this);
153 9
            if (!$conflictmanager->run($this->rob_tentative)) {
154
                debug_add("Unresolved resource conflicts, aborting", MIDCOM_LOG_WARN);
155
                return false;
156
            }
157
        }
158
159
        //Preserve vCal GUIDs once set
160 9
        if (isset($this->old_externalGuid)) {
161 9
            $this->externalGuid = $this->old_externalGuid;
162
        }
163
164 9
        return $this->_check_timerange();
165
    }
166
167 9
    private function _check_timerange() : bool
168
    {
169 9
        if (   !$this->start
170 9
            || !$this->end) {
171 1
            debug_add('Event must have start and end timestamps');
172 1
            midcom_connection::set_error(MGD_ERR_RANGE);
173 1
            return false;
174
        }
175
176
        /*
177
         * Force start and end seconds to 1 and 0 respectively
178
         * (to avoid stupid one second overlaps)
179
         */
180 9
        $this->start = floor($this->start / 60) * 60 + 1;
181 9
        $this->end = (floor($this->end / 60) * 60);
182
183 9
        if ($this->end < $this->start) {
184
            debug_add('Event cannot end before it starts, aborting');
185
            midcom_connection::set_error(MGD_ERR_RANGE);
186
            return false;
187
        }
188
189 9
        return true;
190
    }
191
192 9
    public function _on_creating()
193
    {
194 9
        return $this->_prepare_save();
195
    }
196
197 9
    public function _on_created()
198
    {
199
        //TODO: handle the repeats somehow (if set)
200
201 9
        if ($this->search_relatedtos) {
202
            //TODO: add check for failed additions
203 9
            $this->get_suspected_task_links();
204 9
            $this->get_suspected_sales_links();
205
        }
206 9
    }
207
208
    /**
209
     * Returns a defaults template for relatedto objects
210
     */
211 9
    private function _suspect_defaults() : org_openpsa_relatedto_dba
212
    {
213 9
        $link_def = new org_openpsa_relatedto_dba();
214 9
        $link_def->fromComponent = 'org.openpsa.calendar';
215 9
        $link_def->fromGuid = $this->guid;
216 9
        $link_def->fromClass = get_class($this);
217 9
        $link_def->status = org_openpsa_relatedto_dba::SUSPECTED;
218 9
        return $link_def;
219
    }
220
221
    /**
222
     * Queries org.openpsa.projects for suspected task links and saves them
223
     */
224 9
    private function get_suspected_task_links()
225
    {
226
        // Do not seek if we have only one participant (gives a ton of results, most of them useless)
227 9
        if (count($this->participants) < 2) {
228 9
            debug_add("we have less than two participants, skipping seek");
229 9
            return;
230
        }
231
232
        // Do no seek if we already have confirmed links
233
        $mc = new org_openpsa_relatedto_collector($this->guid, org_openpsa_projects_task_dba::class, 'outgoing');
234
        $mc->add_constraint('status', '=', org_openpsa_relatedto_dba::CONFIRMED);
235
236
        $links = $mc->get_related_guids();
237
        if (!empty($links)) {
238
            $cnt = count($links);
239
            debug_add("Found {$cnt} confirmed links already, skipping seek");
240
            return;
241
        }
242
243
        $link_def = $this->_suspect_defaults();
244
        $projects_suspect_links = org_openpsa_relatedto_suspect::find_links_object_component($this, 'org.openpsa.projects', $link_def);
245
246
        foreach ($projects_suspect_links as $linkdata) {
247
            if ($linkdata['link']->create()) {
248
                debug_add("saved link to task #{$linkdata['other_obj']->id} (link id #{$linkdata['link']->id})", MIDCOM_LOG_INFO);
249
            } else {
250
                debug_add("could not save link to task #{$linkdata['other_obj']->id}, errstr" . midcom_connection::get_error_string(), MIDCOM_LOG_WARN);
251
            }
252
        }
253
    }
254
255
    /**
256
     * Queries org.openpsa.sales for suspected task links and saves them
257
     */
258 9
    private function get_suspected_sales_links()
259
    {
260
        // Do no seek if we already have confirmed links
261 9
        $mc = new org_openpsa_relatedto_collector($this->guid, [org_openpsa_sales_salesproject_dba::class, org_openpsa_sales_salesproject_deliverable_dba::class]);
262 9
        $mc->add_constraint('status', '=', org_openpsa_relatedto_dba::CONFIRMED);
263
264 9
        $links = $mc->get_related_guids();
265 9
        if (!empty($links)) {
266
            $cnt = count($links);
267
            debug_add("Found {$cnt} confirmed links already, skipping seek");
268
            return;
269
        }
270
271 9
        $link_def = $this->_suspect_defaults();
272 9
        $sales_suspect_links = org_openpsa_relatedto_suspect::find_links_object_component($this, 'org.openpsa.sales', $link_def);
273 9
        foreach ($sales_suspect_links as $linkdata) {
274
            if ($linkdata['link']->create()) {
275
                debug_add("saved sales link to {$linkdata['other_obj']->guid} (link id #{$linkdata['link']->id})", MIDCOM_LOG_INFO);
276
            } else {
277
                debug_add("could not save sales link to {$linkdata['other_obj']->guid}, errstr" . midcom_connection::get_error_string(), MIDCOM_LOG_WARN);
278
            }
279
        }
280 9
    }
281
282 2
    public function _on_updating()
283
    {
284
        //TODO: Handle repeats
285 2
        return $this->_prepare_save();
286
    }
287
288 2
    public function _on_updated()
289
    {
290 2
        $this->_get_em();
291 2
        if ($this->send_notify) {
292 2
            $message_type = 'update';
293 2
            if ($this->notify_force_add) {
294
                $message_type = 'add';
295
            }
296
297 2
            foreach ($this->_get_participants() as $res_object) {
298
                debug_add("Notifying participant #{$res_object->id}");
299
                $res_object->notify($message_type, $this);
300
            }
301
302 2
            foreach ($this->_get_resources() as $res_object) {
303
                debug_add("Notifying resource #{$res_object->id}");
304
                $res_object->notify($message_type, $this);
305
            }
306
        }
307
308
        // Handle ACL accordingly
309 2
        foreach (array_keys($this->participants) as $person_id) {
310
            $user = midcom::get()->auth->get_user($person_id);
311
312
            // All participants can read and update
313
            $this->set_privilege('org.openpsa.calendar:read', $user->id, MIDCOM_PRIVILEGE_ALLOW);
314
            $this->set_privilege('midgard:read', $user->id, MIDCOM_PRIVILEGE_ALLOW);
315
            $this->set_privilege('midgard:update', $user->id, MIDCOM_PRIVILEGE_ALLOW);
316
            $this->set_privilege('midgard:delete', $user->id, MIDCOM_PRIVILEGE_ALLOW);
317
            $this->set_privilege('midgard:create', $user->id, MIDCOM_PRIVILEGE_ALLOW);
318
            $this->set_privilege('midgard:privileges', $user->id, MIDCOM_PRIVILEGE_ALLOW);
319
        }
320
321 2
        if ($this->orgOpenpsaAccesstype == org_openpsa_core_acl::ACCESS_PRIVATE) {
322
            $this->set_privilege('org.openpsa.calendar:read', 'EVERYONE', MIDCOM_PRIVILEGE_DENY);
323
        } else {
324 2
            $this->set_privilege('org.openpsa.calendar:read', 'EVERYONE', MIDCOM_PRIVILEGE_ALLOW);
325
        }
326
327 2
        if ($this->search_relatedtos) {
328 2
            $this->get_suspected_task_links();
329 2
            $this->get_suspected_sales_links();
330
        }
331 2
    }
332
333
    /**
334
     * @return org_openpsa_calendar_event_member_dba[]
335
     */
336 9
    private function _get_participants() : array
337
    {
338 9
        $qb = org_openpsa_calendar_event_member_dba::new_query_builder();
339 9
        $qb->add_constraint('eid', '=', $this->id);
340 9
        return $qb->execute_unchecked();
341
    }
342
343
    /**
344
     * @return org_openpsa_calendar_event_resource_dba[]
345
     */
346 9
    private function _get_resources() : array
347
    {
348 9
        $qb = org_openpsa_calendar_event_resource_dba::new_query_builder();
349 9
        $qb->add_constraint('event', '=', $this->id);
350 9
        return $qb->execute_unchecked();
351
    }
352
353 9
    public function _on_deleting()
354
    {
355
        //Remove participants
356 9
        midcom::get()->auth->request_sudo('org.openpsa.calendar');
357 9
        foreach ($this->_get_participants() as $obj) {
358 3
            if ($this->send_notify) {
359 3
                $obj->notify('cancel', $this);
360
            }
361 3
            $obj->notify_person = false;
362 3
            $obj->delete();
363
        }
364
365
        //Remove resources
366 9
        foreach ($this->_get_resources() as $obj) {
367
            if ($this->send_notify) {
368
                $obj->notify('cancel', $this);
369
            }
370
            $obj->delete();
371
        }
372
373
        //Remove event parameters
374 9
        midcom::get()->auth->drop_sudo();
375
376 9
        return parent::_on_deleting();
377
    }
378
379
    /**
380
     * Fills $this->participants and $this->resources
381
     */
382 14
    private function _get_em()
383
    {
384 14
        if (!$this->id) {
385
            return;
386
        }
387
388
        // Participants
389 14
        $mc = org_openpsa_calendar_event_member_dba::new_collector('eid', $this->id);
390 14
        $this->participants = array_fill_keys($mc->get_values('uid'), true);
391
        // Resources
392 14
        $mc2 = org_openpsa_calendar_event_resource_dba::new_collector('event', $this->id);
393 14
        $this->resources = array_fill_keys($mc2->get_values('resource'), true);
394 14
    }
395
396
    /**
397
     * Returns a string describing the event and its participants
398
     */
399
    public function details_text(string $nl) : string
400
    {
401
        $l10n = midcom::get()->i18n->get_l10n('org.openpsa.calendar');
402
        $str = '';
403
        $str .= sprintf($l10n->get('location: %s') . $nl, $this->location);
404
        $str .= sprintf($l10n->get('time: %s') . $nl, $l10n->get_formatter()->timeframe($this->start, $this->end));
405
        $str .= sprintf($l10n->get('participants: %s') . $nl, $this->implode_members($this->participants));
406
        $str .= sprintf($l10n->get('resources: %s') . $nl, $this->implode_members($this->resources));
407
        //TODO: Tentative, overlaps, public
408
        $str .= sprintf($l10n->get('description: %s') . $nl, $this->description);
409
        return $str;
410
    }
411
412
    /**
413
     * Returns a comma separated list of persons from array
414
     */
415
    private function implode_members(array $array) : string
416
    {
417
        $output = [];
418
        foreach (array_keys($array) as $pid) {
419
            $person = org_openpsa_contacts_person_dba::get_cached($pid);
420
            $output[] = $person->name;
421
        }
422
        return implode(', ', $output);
423
    }
424
}
425