Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

modules/Reminders/Reminder.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/*********************************************************************************
3
 * SugarCRM Community Edition is a customer relationship management program developed by
4
 * SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.
5
 *
6
 * SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
7
 * Copyright (C) 2011 - 2016 Salesagility Ltd.
8
 *
9
 * This program is free software; you can redistribute it and/or modify it under
10
 * the terms of the GNU Affero General Public License version 3 as published by the
11
 * Free Software Foundation with the addition of the following permission added
12
 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
13
 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
14
 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License along with
22
 * this program; if not, see http://www.gnu.org/licenses or write to the Free
23
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
24
 * 02110-1301 USA.
25
 *
26
 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
27
 * SW2-130, Cupertino, CA 95014, USA. or at email address [email protected].
28
 *
29
 * The interactive user interfaces in modified source and object code versions
30
 * of this program must display Appropriate Legal Notices, as required under
31
 * Section 5 of the GNU Affero General Public License version 3.
32
 *
33
 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
34
 * these Appropriate Legal Notices must retain the display of the "Powered by
35
 * SugarCRM" logo and "Supercharged by SuiteCRM" logo. If the display of the logos is not
36
 * reasonably feasible for  technical reasons, the Appropriate Legal Notices must
37
 * display the words  "Powered by SugarCRM" and "Supercharged by SuiteCRM".
38
 ********************************************************************************/
39
40
/**
41
 * Reminder class
42
 *
43
 */
44
class Reminder extends Basic
45
{
46
47
    const UPGRADE_VERSION = '7.4.3';
48
49
    var $name;
50
51
    var $new_schema = true;
52
    var $module_dir = 'Reminders';
53
    var $object_name = 'Reminder';
54
    var $table_name = 'reminders';
55
    var $tracker_visibility = false;
56
    var $importable = false;
57
    var $disable_row_level_security = true;
58
59
    var $popup;
60
    var $email;
61
    var $email_sent = false;
62
    var $timer_popup;
63
    var $timer_email;
64
    var $related_event_module;
65
    var $related_event_module_id;
66
67
    private static $remindersData = array();
68
69 5
    public function __construct()
70
    {
71 5
        parent::__construct();
72 5
    }
73
74 5
    public function bean_implements($interface)
75
    {
76
        switch ($interface) {
77 5
            case 'ACL':
78 5
                return true;
79
        }
80
        return false;
81
    }
82
83
    // ---- save and load remainders on EditViews
84
85
    /**
86
     * Save multiple reminders data from clients Meetings/Calls EditView.
87
     * Call this static function in save action.
88
     *
89
     * @param string $eventModule Event Bean module name (e.g. Meetings, Calls)
90
     * @param string $eventModuleId Event Bean GUID
91
     * @param string $remindersDataJson Remainders data as Json string from POST data.
92
     * @throws Exception throw an Exception if json format is invalid.
93
     */
94
    public static function saveRemindersDataJson($eventModule, $eventModuleId, $remindersDataJson)
95
    {
96
        $reminderData = json_decode($remindersDataJson);
97
        if (!json_last_error()) {
98
            Reminder::saveRemindersData($eventModule, $eventModuleId, $reminderData);
99
        } else {
100
            throw new Exception(json_last_error_msg());
101
        }
102
    }
103
104
    private static function saveRemindersData($eventModule, $eventModuleId, $remindersData)
105
    {
106
        $savedReminderIds = array();
107
        foreach ($remindersData as $reminderData) {
108
            if (isset($_POST['isDuplicate']) && $_POST['isDuplicate']) $reminderData->id = '';
109
            $reminderBean = BeanFactory::getBean('Reminders', $reminderData->id);
110
            $reminderBean->popup = $reminderData->popup;
111
            $reminderBean->email = $reminderData->email;
112
            $reminderBean->timer_popup = $reminderData->timer_popup;
113
            $reminderBean->timer_email = $reminderData->timer_email;
114
            $reminderBean->related_event_module = $eventModule;
115
            $reminderBean->related_event_module_id = $eventModuleId;
116
            $reminderBean->save();
117
            $savedReminderIds[] = $reminderBean->id;
118
            $reminderId = $reminderBean->id;
119
            Reminder_Invitee::saveRemindersInviteesData($reminderId, $reminderData->invitees);
120
        }
121
        $reminders = BeanFactory::getBean('Reminders')->get_full_list("", "reminders.related_event_module = '$eventModule' AND reminders.related_event_module_id = '$eventModuleId'");
122
        if ($reminders) {
123
            foreach ($reminders as $reminder) {
124
                if (!in_array($reminder->id, $savedReminderIds)) {
125
                    Reminder_Invitee::deleteRemindersInviteesMultiple($reminder->id);
126
                    $reminder->mark_deleted($reminder->id);
127
                    $reminder->save();
128
                }
129
            }
130
        }
131
        unset(self::$remindersData[$eventModule][$eventModuleId]);
132
    }
133
134
    /**
135
     * Load multiple reminders JSON data for related Event module EditViews.
136
     * Call this function in module display function.
137
     *
138
     * @param string $eventModule Related event module name (Meetings/Calls)
139
     * @param string $eventModuleId Related event GUID
140
     * @return string JSON string contains the remainders
141
     * @throws Exception
142
     */
143
    public static function loadRemindersDataJson($eventModule, $eventModuleId, $isDuplicate = false)
144
    {
145
        $remindersData = self::loadRemindersData($eventModule, $eventModuleId, $isDuplicate);
146
        $remindersDataJson = json_encode($remindersData);
147
        if (!$remindersDataJson && json_last_error()) {
148
            throw new Exception(json_last_error_msg());
149
        }
150
        return $remindersDataJson;
151
    }
152
153
    /**
154
     * Load multiple reminders data for related Event module EditViews.
155
     * Call this function in module display function.
156
     *
157
     * @param string $eventModule Related event module name (Meetings/Calls)
158
     * @param string $eventModuleId Related event GUID
159
     * @return array contains the remainders
160
     * @throws Exception
161
     */
162
    public static function loadRemindersData($eventModule, $eventModuleId, $isDuplicate = false)
163
    {
164
        if (!isset(self::$remindersData[$eventModule][$eventModuleId]) || !$eventModuleId || $isDuplicate) {
165
            $ret = array();
166
            $reminders = BeanFactory::getBean('Reminders')->get_full_list("reminders.date_entered", "reminders.related_event_module = '$eventModule' AND reminders.related_event_module_id = '$eventModuleId'");
167
            if ($reminders) {
168
                foreach ($reminders as $reminder) {
169
                    $ret[] = array(
170
                        'id' => $isDuplicate ? null : $reminder->id,
171
                        'popup' => $reminder->popup,
172
                        'email' => $reminder->email,
173
                        'timer_popup' => $reminder->timer_popup,
174
                        'timer_email' => $reminder->timer_email,
175
                        'invitees' => Reminder_Invitee::loadRemindersInviteesData($reminder->id, $isDuplicate),
176
                    );
177
                }
178
            }
179
            self::$remindersData[$eventModule][$eventModuleId] = $ret;
180
        }
181
        return self::$remindersData[$eventModule][$eventModuleId];
182
    }
183
184
    // ---- sending email reminders
185
186
    /**
187
     * Sending multiple email reminders.
188
     * Call in EmainReminder and use original EmailRemainder class for sending.
189
     *
190
     * @param EmailReminder $emailReminder Caller EmailReminder
191
     * @param Administration $admin Administration module for EmailRemainder->sendReminders() function
192
     * @param boolean $checkDecline (optional) Send email if user accept status is not decline. Default is TRUE.
193
     */
194
    public static function sendEmailReminders(EmailReminder $emailReminder, Administration $admin, $checkDecline = true)
195
    {
196
        if ($reminders = self::getUnsentEmailReminders()) {
197
            foreach ($reminders as $reminderId => $reminder) {
198
                $recipients = self::getEmailReminderInviteesRecipients($reminderId, $checkDecline);
199
                $eventBean = BeanFactory::getBean($reminder->related_event_module, $reminder->related_event_module_id);
200
                if ($eventBean && $emailReminder->sendReminders($eventBean, $admin, $recipients)) {
201
                    $reminder->email_sent = 1;
202
                    $reminder->save();
203
                }
204
            }
205
        }
206
    }
207
208
    private static function getEmailReminderInviteesRecipients($reminderId, $checkDecline = true)
209
    {
210
        $emails = array();
211
        $reminder = BeanFactory::getBean('Reminders', $reminderId);
212
        $eventModule = $reminder->related_event_module;
213
        $eventModuleId = $reminder->related_event_module_id;
214
        $event = BeanFactory::getBean($eventModule, $eventModuleId);
215
        if ($event && (!isset($event->status) || $event->status != 'Held')) {
216
            $invitees = BeanFactory::getBean('Reminders_Invitees')->get_full_list('', "reminders_invitees.reminder_id = '$reminderId'");
217
            foreach ($invitees as $invitee) {
0 ignored issues
show
The expression $invitees of type array<integer,object>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
218
                $inviteeModule = $invitee->related_invitee_module;
219
                $inviteeModuleId = $invitee->related_invitee_module_id;
220
                $personBean = BeanFactory::getBean($inviteeModule, $inviteeModuleId);
221
                // The original email reminders check the accept_status field in related users/leads/contacts etc. and filtered these users who not decline this event.
222
                if ($checkDecline && !self::isDecline($event, $personBean)) {
223
                    if (!empty($personBean->email1)) {
224
                        $arr = array(
225
                            'type' => $inviteeModule,
226
                            'name' => $personBean->full_name,
227
                            'email' => $personBean->email1,
228
                        );
229
                        $emails[] = $arr;
230
                    }
231
                }
232
            }
233
        }
234
        return $emails;
235
    }
236
237
    private static function getUnsentEmailReminders()
238
    {
239
        global $timedate;
240
        $reminders = array();
241
        $reminderBeans = BeanFactory::getBean('Reminders')->get_full_list('', "reminders.email = 1 AND reminders.email_sent = 0");
242
        if (!empty($reminderBeans)) {
243
            foreach ($reminderBeans as $reminderBean) {
244
                $eventBean = BeanFactory::getBean($reminderBean->related_event_module, $reminderBean->related_event_module_id);
245
                if($eventBean) {
246
                    $remind_ts = $timedate->fromUser($eventBean->date_start)->modify("-{$reminderBean->timer_email} seconds")->ts;
247
                    $now_ts = $timedate->getNow()->ts;
248
                    if ($now_ts >= $remind_ts) {
249
                        $reminders[$reminderBean->id] = $reminderBean;
250
                    }
251
                } else {
252
                    $reminderBean->mark_deleted($reminderBean->id);
253
                }
254
            }
255
        }
256
        return $reminders;
257
    }
258
259
    // ---- popup and alert reminders
260
261
    /**
262
     * Show a popup and/or desktop notification alert for related users with related Event information.
263
     * Call in jsAlerts class and use original jsAlerts for show notifications.
264
     *
265
     * @global ??? $current_user
266
     * @global ??? $timedate
267
     * @global ??? $app_list_strings
268
     * @global ??? $db
269
     * @global ??? $sugar_config
270
     * @global ??? $app_strings
271
     * @param jsAlerts $alert caller jsAlerts object
272
     * @param boolean $checkDecline (optional) Send email if user accept status is not decline. Default is TRUE.
273
     * @return ???
0 ignored issues
show
The doc-type ??? could not be parsed: Unknown type name "???" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
274
     */
275
    public static function addNotifications(jsAlerts $alert, $checkDecline = true)
276
    {
277
        global $current_user, $timedate, $app_list_strings, $db, $sugar_config, $app_strings;
278
279
        if (empty($current_user->id)) {
280
            return;
281
        }
282
283
        // Create separate variable to hold timedate value
284
        // These timedates need to be in the user time zone as the
285
        // datetime returned by the Bean below is in the user time zone
286
        $alertDateTimeNow = $timedate->getNow(true)->asDb(false);
287
288
        // cn: get a boundary limiter
289
        $dateTimeMax = $timedate->getNow(true)->modify("+{$app_list_strings['reminder_max_time']} seconds")->asDb(false);
290
        $dateTimeNow = $timedate->getNow(true)->asDb(false);
291
292
        $dateTimeNow = $db->convert($db->quoted($dateTimeNow), 'datetime');
293
        $dateTimeMax = $db->convert($db->quoted($dateTimeMax), 'datetime');
294
295
        // Original jsAlert used to a meeting integration.
296
297
        ///////////////////////////////////////////////////////////////////////
298
        ////	MEETING INTEGRATION
299
        $meetingIntegration = null;
300
        if (isset($sugar_config['meeting_integration']) && !empty($sugar_config['meeting_integration'])) {
301
            if (!class_exists($sugar_config['meeting_integration'])) {
302
                require_once("modules/{$sugar_config['meeting_integration']}/{$sugar_config['meeting_integration']}.php");
303
            }
304
            $meetingIntegration = new $sugar_config['meeting_integration']();
305
        }
306
        ////	END MEETING INTEGRATION
307
        ///////////////////////////////////////////////////////////////////////
308
309
        $popupReminders = BeanFactory::getBean('Reminders')->get_full_list('', "reminders.popup = 1");
310
311
        if ($popupReminders) {
312
            foreach ($popupReminders as $popupReminder) {
313
                $relatedEvent = BeanFactory::getBean($popupReminder->related_event_module, $popupReminder->related_event_module_id);
314
                if ($relatedEvent &&
315
                    (!isset($relatedEvent->status) || $relatedEvent->status == 'Planned') &&
316
                    (!isset($relatedEvent->date_start) || (strtotime($relatedEvent->date_start) >= strtotime(self::unQuoteTime($dateTimeNow)) && strtotime($relatedEvent->date_start) <= strtotime(self::unQuoteTime($dateTimeMax)))) &&
317
                    (!$checkDecline || ($checkDecline && !self::isDecline($relatedEvent, BeanFactory::getBean('Users', $current_user->id))))
318
                ) {
319
                    // The original popup/alert reminders check the accept_status field in related users/leads/contacts etc. and filtered these users who not decline this event.
320
                    $invitees = BeanFactory::getBean('Reminders_Invitees')->get_full_list('', "reminders_invitees.reminder_id = '{$popupReminder->id}' AND reminders_invitees.related_invitee_module_id = '{$current_user->id}'");
321
                    if ($invitees) {
322
                        foreach ($invitees as $invitee) {
323
                            // need to concatenate since GMT times can bridge two local days
324
                            $timeStart = strtotime($db->fromConvert(isset($relatedEvent->date_start) ? $relatedEvent->date_start : date(TimeDate::DB_DATETIME_FORMAT), 'datetime'));
325
                            $timeRemind = $popupReminder->timer_popup;
326
                            $timeStart -= $timeRemind;
327
328
                            $url = 'index.php?action=DetailView&module=' . $popupReminder->related_event_module . '&record=' . $popupReminder->related_event_module_id;
329
                            $instructions = $app_strings['MSG_JS_ALERT_MTG_REMINDER_MEETING_MSG'];
330
331
                            if ($popupReminder->related_event_module == 'Meetings') {
332
                                ///////////////////////////////////////////////////////////////////
333
                                ////	MEETING INTEGRATION
334
                                if (!empty($meetingIntegration) && $meetingIntegration->isIntegratedMeeting($popupReminder->related_event_module_id)) {
335
                                    $url = $meetingIntegration->miUrlGetJsAlert((array)$popupReminder);
336
                                    $instructions = $meetingIntegration->miGetJsAlertInstructions();
337
                                }
338
                                ////	END MEETING INTEGRATION
339
                                ///////////////////////////////////////////////////////////////////
340
                            }
341
342
                            $meetingName = from_html(isset($relatedEvent->name) ? $relatedEvent->name : $app_strings['MSG_JS_ALERT_MTG_REMINDER_NO_EVENT_NAME']);
343
                            $desc1 = from_html(isset($relatedEvent->description) ? $relatedEvent->description : $app_strings['MSG_JS_ALERT_MTG_REMINDER_NO_DESCRIPTION']);
344
                            $location = from_html(isset($relatedEvent->location) ? $relatedEvent->location : $app_strings['MSG_JS_ALERT_MTG_REMINDER_NO_LOCATION']);
345
346
                            $relatedToMeeting = $alert->getRelatedName($popupReminder->related_event_module, $popupReminder->related_event_module_id);
347
348
                            $description = empty($desc1) ? '' : $app_strings['MSG_JS_ALERT_MTG_REMINDER_AGENDA'] . $desc1 . "\n";
349
                            $description = $description . "\n" . $app_strings['MSG_JS_ALERT_MTG_REMINDER_STATUS'] . (isset($relatedEvent->status) ? $relatedEvent->status : '') . "\n" . $app_strings['MSG_JS_ALERT_MTG_REMINDER_RELATED_TO'] . $relatedToMeeting;
350
351
352
                            if (isset($relatedEvent->date_start)) {
353
                                $time_dbFromConvert = $db->fromConvert($relatedEvent->date_start, 'datetime');
354
                                $time = $timedate->to_display_date_time($time_dbFromConvert);
355
                                if (!$time) {
356
                                    $time = $relatedEvent->date_start;
357
                                }
358
                                if (!$time) {
359
                                    $time = $app_strings['MSG_JS_ALERT_MTG_REMINDER_NO_START_DATE'];
360
                                }
361
                            } else {
362
                                $time = $app_strings['MSG_JS_ALERT_MTG_REMINDER_NO_START_DATE'];
363
                            }
364
365
                            // standard functionality
366
                            $alert->addAlert($app_strings['MSG_JS_ALERT_MTG_REMINDER_MEETING'], $meetingName,
367
                                $app_strings['MSG_JS_ALERT_MTG_REMINDER_TIME'] . $time,
368
                                $app_strings['MSG_JS_ALERT_MTG_REMINDER_LOC'] . $location .
369
                                $description .
370
                                $instructions,
371
                                $timeStart - strtotime($alertDateTimeNow),
372
                                $url
373
                            );
374
                        }
375
                    }
376
                }
377
            }
378
        }
379
    }
380
381
    private static function unQuoteTime($timestr)
382
    {
383
        $ret = '';
384
        for ($i = 0; $i < strlen($timestr); $i++) {
385
            if ($timestr[$i] != "'") $ret .= $timestr[$i];
386
        }
387
        return $ret;
388
    }
389
390
    // --- test for accept status decline is?
391
392
    private static function isDecline(SugarBean $event, SugarBean $person)
393
    {
394
        return self::testEventPersonAcceptStatus($event, $person, 'decline');
395
    }
396
397
    private static function testEventPersonAcceptStatus(SugarBean $event, SugarBean $person, $acceptStatus = 'decline')
398
    {
399
        if ($acceptStats = self::getEventPersonAcceptStatus($event, $person)) {
400
            $acceptStatusLower = strtolower($acceptStatus);
401
            foreach ((array)$acceptStats as $acceptStat) {
402
                if (strtolower($acceptStat) == $acceptStatusLower) {
403
                    return true;
404
                }
405
            }
406
        }
407
        return false;
408
    }
409
410
    private static function getEventPersonAcceptStatus(SugarBean $event, SugarBean $person)
411
    {
412
        global $db;
413
        $rel_person_table_Key = "rel_{$person->table_name}_table";
414
        $rel_person_table_Value = "{$event->table_name}_{$person->table_name}";
415
        if (isset($event->$rel_person_table_Key) && $event->$rel_person_table_Key == $rel_person_table_Value) {
416
            $query = self::getEventPersonQuery($event, $person);
417
            $re = $db->query($query);
418
            $ret = array();
419
            while ($row = $db->fetchByAssoc($re)) {
420
                if (!isset($row['accept_status'])) {
421
                    return null;
422
                }
423
                $ret[] = $row['accept_status'];
424
            }
425
            return $ret;
426
        }
427
        return null;
428
    }
429
430
    private function upgradeEventPersonQuery(SugarBean $event, $person_table)
431
    {
432
        $eventIdField = strtolower($event->object_name) . '_id';
433
        $query = "
434
			SELECT * FROM {$event->table_name}_{$person_table}
435
			WHERE
436
				{$eventIdField} = '{$event->id}' AND
437
				deleted = 0
438
		";
439
        return $query;
440
    }
441
442
    private static function getEventPersonQuery(SugarBean $event, SugarBean $person)
443
    {
444
        $eventIdField = array_search($event->table_name, $event->relationship_fields);
445
        if (!$eventIdField) {
446
            $eventIdField = strtolower($event->object_name . '_id');
447
        }
448
        $personIdField = strtolower($person->object_name) . '_id';
449
        $query = "
450
			SELECT * FROM {$event->table_name}_{$person->table_name}
451
			WHERE
452
				{$eventIdField} = '{$event->id}' AND
453
				{$personIdField} = '{$person->id}' AND
454
				deleted = 0
455
		";
456
        return $query;
457
    }
458
459
    // --- user preferences as default values in reminders
460
461
    /**
462
     * Default values for Reminders from User Preferences
463
     * @return string JSON encoded default values
464
     * @throws Exception on json_encode error
465
     */
466
    public static function loadRemindersDefaultValuesDataJson()
467
    {
468
        $ret = json_encode(self::loadRemindersDefaultValuesData());
469
        if (!$ret && json_last_error()) {
470
            throw new Exception(json_last_error_msg());
471
        }
472
        return $ret;
473
    }
474
475
    /**
476
     * Default values for Reminders from User Preferences
477
     * @return array default values
478
     */
479
    public static function loadRemindersDefaultValuesData()
480
    {
481
        global $current_user;
482
483
        $preferencePopupReminderTime = $current_user->getPreference('reminder_time');
484
        $preferenceEmailReminderTime = $current_user->getPreference('email_reminder_time');
485
        $preferencePopupReminderChecked = $current_user->getPreference('reminder_checked');
486
        $preferenceEmailReminderChecked = $current_user->getPreference('email_reminder_checked');
487
488
        return array(
489
            'popup' => $preferencePopupReminderChecked,
490
            'email' => $preferenceEmailReminderChecked,
491
            'timer_popup' => $preferencePopupReminderTime,
492
            'timer_email' => $preferenceEmailReminderTime,
493
        );
494
    }
495
496
    // --- upgrade
497
498
    /**
499
     * Reminders upgrade, old reminders migrate to multiple-reminders.
500
     * @throws Exception unknown event type or any error
501
     */
502
    public static function upgrade()
503
    {
504
        self::upgradeUserPreferences();
505
        self::upgradeEventReminders('Calls');
506
        self::upgradeEventReminders('Meetings');
507
        self::upgradeRestoreReminders();
508
    }
509
510
    private static function upgradeRestoreReminders()
511
    {
512
        if ($reminders = BeanFactory::getBean('Reminders')->get_full_list('', 'reminders.deleted = 1')) {
513
            foreach ($reminders as $reminder) {
514
                $reminder->deleted = 0;
515
                $reminder->save();
516
            }
517
        }
518
        if ($reminderInvitees = BeanFactory::getBean('Reminders_Invitees')->get_full_list('', 'reminders_invitees.deleted = 1')) {
519
            foreach ($reminderInvitees as $invitee) {
520
                $invitee->deleted = 0;
521
                $invitee->save();
522
            }
523
        }
524
        global $db;
525
        $q = "UPDATE reminders SET deleted = 0";
526
        $db->query($q);
527
        $q = "UPDATE reminders_invitees SET deleted = 0";
528
        $db->query($q);
529
    }
530
531
    private static function upgradeUserPreferences()
532
    {
533
        $users = User::getActiveUsers();
534
        foreach ($users as $user_id => $user_name) {
535
            $user = new User();
536
            $user->retrieve($user_id);
537
538
            $preferencePopupReminderTime = $user->getPreference('reminder_time');
539
            $preferenceEmailReminderTime = $user->getPreference('email_reminder_time');
540
541
            $preferencePopupReminderChecked = $preferencePopupReminderTime > -1;
542
            $preferenceEmailReminderChecked = $preferenceEmailReminderTime > -1;
543
            $user->setPreference('reminder_checked', $preferencePopupReminderChecked);
544
            $user->setPreference('email_reminder_checked', $preferenceEmailReminderChecked);
545
546
        }
547
    }
548
549
    /**
550
     * @param string $eventModule 'Calls' or 'Meetings'
551
     */
552
    private static function upgradeEventReminders($eventModule)
553
    {
554
555
        $eventBean = BeanFactory::getBean($eventModule);
556
        $events = BeanFactory::getBean($eventModule)->get_full_list('', "{$eventBean->table_name}.date_start >  '2015-11-01 00:00:00' AND ({$eventBean->table_name}.reminder_time != -1 OR ({$eventBean->table_name}.email_reminder_time != -1 AND {$eventBean->table_name}.email_reminder_sent != 1))");
557
        if ($events) {
558
            foreach ($events as $event) {
559
560
                $oldReminderPopupChecked = false;
561
                $oldReminderPopupTimer = null;
562
                if ($event->reminder_time != -1) {
563
                    $oldReminderPopupChecked = true;
564
                    $oldReminderPopupTimer = $event->reminder_time;
565
                }
566
567
                $oldReminderEmailChecked = false;
568
                $oldReminderEmailTimer = null;
569
                if ($event->email_reminder_time != -1) {
570
                    $oldReminderEmailChecked = true;
571
                    $oldReminderEmailTimer = $event->email_reminder_time;
572
                }
573
574
                $oldReminderEmailSent = $event->email_reminder_sent;
575
576
                if (($oldInvitees = self::getOldEventInvitees($event)) && ($event->reminder_time != -1 || ($event->email_reminder_time != -1 && $event->email_reminder_sent != 1))) {
577
578
                    self::migrateReminder(
579
                        $eventModule,
580
                        $event->id,
581
                        $oldReminderPopupChecked,
582
                        $oldReminderPopupTimer,
583
                        $oldReminderEmailChecked,
584
                        $oldReminderEmailTimer,
585
                        $oldReminderEmailSent,
586
                        $oldInvitees
587
                    );
588
589
                }
590
            }
591
        }
592
593
    }
594
595
596
    private static function getOldEventInvitees(SugarBean $event)
597
    {
598
        global $db;
599
        $ret = array();
600
        $persons = array('users', 'contacts', 'leads');
601
        foreach ($persons as $person) {
602
            $query = self::upgradeEventPersonQuery($event, $person);
603
            $re = $db->query($query);
604
            while ($row = $db->fetchByAssoc($re)) {
605
                $ret[] = $row;
606
            }
607
        }
608
        return $ret;
609
    }
610
611
    /**
612
     * @param string $eventModule 'Calls' or 'Meetings'
613
     * @param string $eventModuleId
614
     * @param bool $oldReminderPopupChecked
615
     * @param int $oldReminderPopupTimer
616
     * @param bool $oldReminderEmailChecked
617
     * @param int $oldReminderEmailTimer
618
     * @param array $oldInvitees
619
     */
620
    private static function migrateReminder($eventModule, $eventModuleId, $oldReminderPopupChecked, $oldReminderPopupTimer, $oldReminderEmailChecked, $oldReminderEmailTimer, $oldReminderEmailSent, $oldInvitees)
621
    {
622
623
        $reminder = BeanFactory::getBean('Reminders');
624
        $reminder->popup = $oldReminderPopupChecked;
625
        $reminder->email = $oldReminderEmailChecked;
626
        $reminder->email_sent = $oldReminderEmailSent;
627
        $reminder->timer_popup = $oldReminderPopupTimer;
628
        $reminder->timer_email = $oldReminderEmailTimer;
629
        $reminder->related_event_module = $eventModule;
630
        $reminder->related_event_module_id = $eventModuleId;
631
        $reminder->save();
632
        $reminderId = $reminder->id;
633
        self::migrateReminderInvitees($reminderId, $oldInvitees);
634
635
        self::removeOldReminder($eventModule, $eventModuleId);
636
    }
637
638
    private static function migrateReminderInvitees($reminderId, $invitees)
639
    {
640
        $ret = array();
641
        foreach ((array)$invitees as $invitee) {
642
            $newInvitee = BeanFactory::getBean('Reminders_Invitees');
643
            $newInvitee->reminder_id = $reminderId;
644
            $newInvitee->related_invitee_module = self::getRelatedInviteeModuleFromInviteeArray($invitee);
645
            $newInvitee->related_invitee_module_id = self::getRelatedInviteeModuleIdFromInviteeArray($invitee);
646
            $newInvitee->save();
647
        }
648
        return $ret;
649
    }
650
651
    private static function getRelatedInviteeModuleFromInviteeArray($invitee)
652
    {
653
        if (array_key_exists('user_id', $invitee)) {
654
            return 'Users';
655
        }
656
        if (array_key_exists('lead_id', $invitee)) {
657
            return 'Leads';
658
        }
659
        if (array_key_exists('contact_id', $invitee)) {
660
            return 'Contacts';
661
        }
662
        // TODO:!!!!
663
        throw new Exception('Unknown invitee module type');
664
        //return null;
665
    }
666
667
    private static function getRelatedInviteeModuleIdFromInviteeArray($invitee)
668
    {
669
        if (array_key_exists('user_id', $invitee)) {
670
            return $invitee['user_id'];
671
        }
672
        if (array_key_exists('lead_id', $invitee)) {
673
            return $invitee['lead_id'];
674
        }
675
        if (array_key_exists('contact_id', $invitee)) {
676
            return $invitee['contact_id'];
677
        }
678
        // TODO:!!!!
679
        throw new Exception('Unknown invitee type');
680
        //return null;
681
    }
682
683
    /**
684
     * @param string $eventModule 'Calls' or 'Meetings'
685
     * @param string $eventModuleId
686
     */
687
    private static function removeOldReminder($eventModule, $eventModuleId)
688
    {
689
        $event = BeanFactory::getBean($eventModule, $eventModuleId);
690
        $event->reminder_time = -1;
691
        $event->email_reminder_time = -1;
692
        $event->email_reminder_sent = 0;
693
        $event->save();
694
    }
695
696
    // --- reminders list on detail views
697
698
    /**
699
     * Return a list of related reminders for specified event (Calls/Meetings). Call it from DetailViews.
700
     * @param SugarBean $event a Call or Meeting Bean
701
     * @return mixed|string|void output of list (html)
702
     * @throws Exception on json error in Remainders
703
     */
704
    public static function getRemindersListView(SugarBean $event)
705
    {
706
        global $mod_strings, $app_list_strings;
707
        $tpl = new Sugar_Smarty();
708
        $tpl->assign('MOD', $mod_strings);
709
        $tpl->assign('reminder_time_options', $app_list_strings['reminder_time_options']);
710
        $tpl->assign('remindersData', Reminder::loadRemindersData($event->module_name, $event->id));
711
        $tpl->assign('remindersDataJson', Reminder::loadRemindersDataJson($event->module_name, $event->id));
712
        $tpl->assign('remindersDefaultValuesDataJson', Reminder::loadRemindersDefaultValuesDataJson());
713
        $tpl->assign('remindersDisabled', json_encode(true));
714
        return $tpl->fetch('modules/Reminders/tpls/reminders.tpl');
715
    }
716
717
    /*
718
     * @todo implenent it
719
     */
720
    public static function getRemindersListInlineEditView(SugarBean $event)
721
    {
722
        // TODO: getEditFieldHTML() function in InlineEditing.php:218 doesn't pass the Bean ID to this custom inline edit view function but we have to know which Bean are in the focus to editing.
723
        if (!$event->id) {
724
            throw new Exception("No GUID for edit.");
725
        }
726
    }
727
728
}
729
730
?>
731