calendar_uiforms::ajax_custom_mail()   F
last analyzed

Complexity

Conditions 32
Paths 2304

Size

Total Lines 94
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
eloc 51
nc 2304
nop 3
dl 0
loc 94
rs 0
c 0
b 0
f 0

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
 * EGroupware - Calendar's forms of the UserInterface
4
 *
5
 * @link http://www.egroupware.org
6
 * @package calendar
7
 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
8
 * @copyright (c) 2004-18 by RalfBecker-At-outdoor-training.de
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 */
11
12
use EGroupware\Api;
13
use EGroupware\Api\Link;
14
use EGroupware\Api\Framework;
15
use EGroupware\Api\Egw;
16
use EGroupware\Api\Acl;
17
use EGroupware\Api\Vfs;
18
use EGroupware\Api\Etemplate;
19
20
/**
21
 * calendar UserInterface forms: view and edit events, freetime search
22
 *
23
 * The new UI, BO and SO classes have a strikt definition, in which time-zone they operate:
24
 *  UI only operates in user-time, so there have to be no conversation at all !!!
25
 *  BO's functions take and return user-time only (!), they convert internaly everything to servertime, because
26
 *  SO operates only on server-time
27
 *
28
 * The state of the UI elements is managed in the uical class, which all UI classes extend.
29
 *
30
 * All permanent debug messages of the calendar-code should done via the debug-message method of the bocal class !!!
31
 */
32
class calendar_uiforms extends calendar_ui
33
{
34
	var $public_functions = array(
35
		'freetimesearch'  => True,
36
		'ajax_add' => true,
37
		'ajax_conflicts' => true,
38
		'edit' => true,
39
		'process_edit' => true,
40
		'export' => true,
41
		'import' => true,
42
		'cat_acl' => true,
43
		'meeting' => true,
44
		'mail_import' => true,
45
	);
46
47
	/**
48
	 * Standard durations used in edit and freetime search
49
	 *
50
	 * @var array
51
	 */
52
	var $durations = array();
53
54
	/**
55
	 * default locking time for entries, that are opened by another user
56
	 *
57
	 * @var locktime in seconds
0 ignored issues
show
Bug introduced by
The type locktime was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
58
	 */
59
	var $locktime_default=1;
60
61
	/**
62
	 * Constructor
63
	 */
64
	function __construct()
65
	{
66
		parent::__construct(true);	// call the parent's constructor
67
68
		for ($n=15; $n <= 16*60; $n+=($n < 60 ? 15 : ($n < 240 ? 30 : ($n < 600 ? 60 : 120))))
69
		{
70
			$this->durations[$n*60] = sprintf('%d:%02d',$n/60,$n%60);
71
		}
72
	}
73
74
	/**
75
	 * Create a default event (adding a new event) by evaluating certain _GET vars
76
	 *
77
	 * @return array event-array
78
	 */
79
	function &default_add_event()
80
	{
81
		$extra_participants = $_GET['participants'] ?
82
			(!is_array($_GET['participants']) ? explode(',',$_GET['participants']) : $_GET['participants']) :
83
			array();
84
85
		// if participant is a contact, add its link title as title
86
		foreach($extra_participants as $uid)
87
		{
88
			if ($uid[0] == 'c')
89
			{
90
				$title = Link::title('addressbook', substr($uid, 1));
91
				break;
92
			}
93
		}
94
95
		if($_GET['title'])
96
		{
97
			$title = $_GET['title'];
98
		}
99
		if (isset($_GET['owner']))
100
		{
101
			$owner = $_GET['owner'];
102
			if(!is_array($owner) && strpos($owner, ','))
103
			{
104
				$owner = explode(',',$owner);
105
			}
106
			if(is_array($owner))
107
			{
108
				// old behavior "selected" should also be used for not set preference, therefore we need to test for !== '0'
109
				if($this->cal_prefs['default_participant'] !== '0' || count($extra_participants) === 0 && count($owner) === 1)
110
				{
111
					$extra_participants += $owner;
112
				}
113
				$owner = count($owner) > 1 ? $this->user : $owner[0];
114
			}
115
			else if ($owner)
116
			{
117
				$extra_participants[] = $owner;
118
			}
119
		}
120
		else
121
		{
122
			// old behavior "selected" should also be used for not set preference, therefore we need to test for === '0'
123
			$owner = $this->cal_prefs['default_participant'] === '0' ? $this->user : $this->owner;
124
		}
125
126
		if (!$owner || !is_numeric($owner) || $GLOBALS['egw']->accounts->get_type($owner) != 'u' ||
127
			!$this->bo->check_perms(Acl::ADD,0,$owner))
128
		{
129
			if ($owner)	// make an owner who is no user or we have no add-rights a participant
130
			{
131
				if(!is_array($owner))
132
				{
133
					$owner = explode(',',$owner);
134
				}
135
				// if we come from ressources we don't need any users selected in calendar
136
				if (!isset($_GET['participants']) || $_GET['participants'][0] != 'r')
137
				{
138
					foreach($owner as $uid)
139
					{
140
						$extra_participants[] = $uid;
141
					}
142
				}
143
			}
144
			$owner = $this->user;
145
		}
146
		//error_log("this->owner=$this->owner, _GET[owner]=$_GET[owner], user=$this->user => owner=$owner, extra_participants=".implode(',',$extra_participants).")");
147
148
		if(isset($_GET['start']))
149
		{
150
			$start = Api\DateTime::to($_GET['start'], 'ts');
151
		}
152
		else
153
		{
154
			$ts = new Api\DateTime();
155
			$ts->setUser();
156
			$start = $this->bo->date2ts(array(
157
				'full' => isset($_GET['date']) && (int) $_GET['date'] ? (int) $_GET['date'] : $this->date,
158
				'hour' => (int) (isset($_GET['hour']) ? $_GET['hour'] : ($ts->format('H')+1)),
159
				'minute' => (int) $_GET['minute'],
160
			));
161
		}
162
		//echo "<p>_GET[date]=$_GET[date], _GET[hour]=$_GET[hour], _GET[minute]=$_GET[minute], this->date=$this->date ==> start=$start=".date('Y-m-d H:i',$start)."</p>\n";
163
164
		$participant_types['u'] = $participant_types = $participants = array();
165
		foreach($extra_participants as $uid)
166
		{
167
			if (isset($participants[$uid])) continue;	// already included
168
169
			if (!$this->bo->check_acl_invite($uid)) continue;	// no right to invite --> ignored
170
171
			if (is_numeric($uid))
172
			{
173
				$participants[$uid] = $participant_types['u'][$uid] =
174
					calendar_so::combine_status($uid == $this->user ? 'A' : 'U',1,
175
					($uid == $this->user || ($uid == $owner && $this->bo->check_perms(Acl::ADD,0,$owner))) ? 'CHAIR' : 'REQ-PARTICIPANT');
176
			}
177
			elseif (is_array($this->bo->resources[$uid[0]]))
178
			{
179
				// Expand mailing lists
180
				if($uid[0] == 'l')
181
				{
182
					foreach($this->bo->enum_mailing_list($uid) as $contact)
183
					{
184
						$participants[$contact] = $participant_types['c'][substr($contact,1)] =
185
							calendar_so::combine_status('U',1,'REQ-PARTICIPANT');
186
					}
187
					continue;
188
				}
189
				// if contact is a user, use the user instead (as the GUI)
190
				if ($uid[0] == 'c' && ($account_id = $GLOBALS['egw']->accounts->name2id(substr($uid,1),'person_id')))
191
				{
192
					$uid = $account_id;
193
					$participants[$uid] = $participant_types['u'][$uid] =
194
						calendar_so::combine_status($uid == $this->user ? 'A' : 'U',1,
195
						($uid == $this->user || ($uid == $owner && $this->bo->check_perms(Acl::ADD,0,$owner))) ? 'CHAIR' : 'REQ-PARTICIPANT');
196
					continue;
197
				}
198
				$res_data = $this->bo->resources[$uid[0]];
199
				list($id,$quantity) = explode(':',substr($uid,1));
200
				if (($status = $res_data['new_status'] ? ExecMethod($res_data['new_status'],$id) : 'U'))
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

200
				if (($status = $res_data['new_status'] ? /** @scrutinizer ignore-deprecated */ ExecMethod($res_data['new_status'],$id) : 'U'))

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
201
				{
202
					$participants[$uid] = $participant_types[$uid[0]][$id] =
203
						calendar_so::combine_status($status,$quantity,'REQ-PARTICIPANT');
204
				}
205
			}
206
		}
207
		if (!$participants)	// if all participants got removed, include current user
208
		{
209
			$participants[$this->user] = $participant_types['u'][$this->user] = calendar_so::combine_status('A',1,'CHAIR');
210
		}
211
		if(isset($_GET['cat_id']))
212
		{
213
			$cat_id = explode(',',$_GET['cat_id']);
214
			foreach($cat_id as &$cat)
215
			{
216
				$cat = (int)$cat;
217
			}
218
		}
219
		else
220
		{
221
			$cat_id = $this->cal_prefs['default_category'];
222
		}
223
		$duration = isset($_GET['duration']) ? (int)$_GET['duration'] : (int) $this->bo->cal_prefs['defaultlength']*60;
224
		if(isset($_GET['end']))
225
		{
226
			$end = Api\DateTime::to($_GET['end'], 'ts');
227
			$duration = $end - $start;
228
		}
229
		else
230
		{
231
			$end = $start + $duration;
232
		}
233
		$whole_day = ($duration + 60 == DAY_s);
234
235
		$alarms = array();
236
		$alarm_pref = $whole_day ? 'default-alarm-wholeday' : 'default-alarm';
237
		// if default alarm set in prefs --> add it
238
		// we assume here that user does NOT have a whole-day but no regular default-alarm, no whole-day!
239
		if ((string)$this->cal_prefs[$alarm_pref] !== '')
240
		{
241
			$offset = 60 * $this->cal_prefs[$alarm_pref];
242
			$alarms[1] =  array(
243
				'default' => 1,
244
				'offset' => $offset ,
245
				'time'   => $start - $offset,
246
				'all'    => false,
247
				'owner'  => $owner,
248
				'id'	=> 1,
249
			);
250
		}
251
252
		return array(
253
			'participant_types' => $participant_types,
254
			'participants' => $participants,
255
			'owner' => $owner,
256
			'start' => $start,
257
			'end'   => $end,
258
			'tzid'  => $this->bo->common_prefs['tz'],
259
			'priority' => 2,	// normal
260
			'public'=> $this->cal_prefs['default_private'] ? 0 : 1,
261
			'alarm' => $alarms,
262
			'recur_exception' => array(),
263
			'title' => $title ? $title : '',
264
			'category' => $cat_id,
265
		);
266
	}
267
268
	/**
269
	 * Process the edited event and evtl. call edit to redisplay it
270
	 *
271
	 * @param array $content posted eTemplate content
272
	 * @ToDo add conflict check / available quantity of resources when adding participants
273
	 */
274
	function process_edit($content)
275
	{
276
		if (!is_array($content))	// redirect from etemplate, if POST empty
0 ignored issues
show
introduced by
The condition is_array($content) is always true.
Loading history...
277
		{
278
			return $this->edit(null,null,strip_tags($_GET['msg']));
279
		}
280
		// clear notification errors
281
		notifications::errors(true);
282
		$messages = null;
283
		$msg_permission_denied_added = false;
284
285
		// We'd like to just refresh the data as that's the fastest, but some changes
286
		// affect more than just one event widget, so require a full refresh.
287
		// $update_type is one of the update types
288
		// (add, edit, update, delete)
289
		$update_type = $content['id'] ? ($content['recur_type'] == MCAL_RECUR_NONE ? 'update' : 'edit') : 'add';
290
291
		$button = @key($content['button']);
292
		if (!$button && $content['action']) $button = $content['action'];	// action selectbox
293
		unset($content['button']); unset($content['action']);
294
295
		$view = $content['view'];
296
		if ($button == 'ical')
297
		{
298
			$msg = $this->export($content['id'],true);
299
		}
300
		// delete a recur-exception
301
		if ($content['recur_exception']['delete_exception'])
302
		{
303
			$date = key($content['recur_exception']['delete_exception']);
304
			// eT2 converts time to
305
			if (!is_numeric($date)) $date = Api\DateTime::to (str_replace('Z','', $date), 'ts');
306
			unset($content['recur_exception']['delete_exception']);
307
			if (($key = array_search($date,$content['recur_exception'])) !== false)
308
			{
309
				// propagate the exception to a single event
310
				$recur_exceptions = $this->bo->so->get_related($content['uid']);
311
				foreach ($recur_exceptions as $id)
312
				{
313
					if (!($exception = $this->bo->read($id)) ||
314
							$exception['recurrence'] != $content['recur_exception'][$key]) continue;
315
					$exception['uid'] = Api\CalDAV::generate_uid('calendar', $id);
316
					$exception['reference'] = $exception['recurrence'] = 0;
317
					$this->bo->update($exception, true, true,false,true,$messages,$content['no_notifications']);
318
					break;
319
				}
320
				unset($content['recur_exception'][$key]);
321
				$content['recur_exception'] = array_values($content['recur_exception']);
322
			}
323
			$update_type = 'edit';
324
		}
325
		// delete an alarm
326
		if ($content['alarm']['delete_alarm'])
327
		{
328
			$id = key($content['alarm']['delete_alarm']);
329
			//echo "delete alarm $id"; _debug_array($content['alarm']['delete_alarm']);
330
331
			if ($content['id'])
332
			{
333
				if ($this->bo->delete_alarm($id))
334
				{
335
					$msg = lang('Alarm deleted');
336
					unset($content['alarm'][$id]);
337
				}
338
				else
339
				{
340
					$msg = lang('Permission denied');
341
				}
342
			}
343
			else
344
			{
345
				unset($content['alarm'][$id]);
346
			}
347
		}
348
		if ($content['duration'])
349
		{
350
			$content['end'] = $content['start'] + $content['duration'];
351
		}
352
		// fix default alarm for a new (whole day) event, to be according to default-alarm(-wholeday) pref
353
		if ($content['alarm'][1]['default'])
354
		{
355
			$def_alarm = $this->cal_prefs['default-alarm'.($content['whole_day'] ? '-wholeday' : '')];
356
			if ((string)$def_alarm === '')
357
			{
358
				unset($content['alarm'][1]);	// '' = no alarm on whole day --> delete it
359
			}
360
			else
361
			{
362
				$content['alarm'][1]['offset'] = $offset = 60 * $def_alarm;
363
				$content['start'] = $this->bo->date2array($content['start']);
364
				$content['start'][1]['time'] = $this->bo->date2ts($content['start']) - $offset;
365
				$content['start'] = $this->bo->date2ts($content['start']);
366
			}
367
		}
368
		else if ($content['cal_id'] && count($content['alarm']) > 0 && current($content['alarm'])['default'] &&
369
			// Existing event, check for change from/to whole day
370
			($old = $this->bo->read($content['cal_id'])) && $old['whole_day'] !== $content['whole_day'] &&
371
			($def_alarm = $this->cal_prefs['default-alarm'.($content['whole_day'] ? '-wholeday' : '')])
372
		)
373
		{
374
			// Reset default alarm
375
			$old_default = array_shift($content['alarm']);
376
			$this->bo->delete_alarm($old_default['id']);
377
			$offset = 60 * $def_alarm;
378
			array_unshift($content['alarm'], array(
379
				'default' => 1,
380
				'offset' => $offset ,
381
				'time'   => $content['start'] - $offset,
382
				'all'    => false,
383
				'owner'  => 0,
384
				'id'	=> 1
385
			));
386
		}
387
388
		$event = $content;
389
		unset($event['new_alarm']);
390
		unset($event['alarm']['delete_alarm']);
391
		unset($event['duration']);
392
393
		if (in_array($button,array('ignore','freetime','reedit','confirm_edit_series')))
394
		{
395
			// no conversation necessary, event is already in the right format
396
		}
397
		else
398
		{
399
			// convert content => event
400
			if ($content['whole_day'])
401
			{
402
				$event['start'] = $this->bo->date2array($event['start']);
403
				$event['start']['hour'] = $event['start']['minute'] = 0; unset($event['start']['raw']);
404
				$event['start'] = $this->bo->date2ts($event['start']);
405
				$event['end'] = $this->bo->date2array($event['end']);
406
				$event['end']['hour'] = 23; $event['end']['minute'] = $event['end']['second'] = 59; unset($event['end']['raw']);
407
				$event['end'] = $this->bo->date2ts($event['end']);
408
			}
409
			// some checks for recurrences, if you give a date, make it a weekly repeating event and visa versa
410
			if ($event['recur_type'] == MCAL_RECUR_NONE && $event['recur_data']) $event['recur_type'] = MCAL_RECUR_WEEKLY;
411
			if ($event['recur_type'] == MCAL_RECUR_WEEKLY && !$event['recur_data'])
412
			{
413
				$event['recur_data'] = 1 << (int)date('w',$event['start']);
414
			}
415
			if ($event['recur_type'] != MCAL_RECUR_NONE && !isset($event['recur_enddate']))
416
			{
417
				// No recur end date, make sure it's set to something or it won't be changed
418
				$event['recur_enddate'] = 0;
419
			}
420
			if (isset($content['participants']))
421
			{
422
423
				$event['participants'] = $event['participant_types'] = array();
424
425
				foreach($content['participants'] as $key => $data)
426
				{
427
					switch($key)
428
					{
429
						case 'delete':		// handled in default
430
						case 'quantity':	// handled in new_resource
431
						case 'role':		// handled in add, account or resource
432
						case 'cal_resources':
433
						case 'status_date':
434
							break;
435
						case 'participant':
436
							foreach($data as $participant)
437
							{
438
								if (is_null($participant))
439
								{
440
									continue;
441
								}
442
443
								// email or rfc822 addresse (eg. "Ralf Becker <[email protected]>")
444
								$email = array();
445
								if(preg_match('/^(.*<)?([a-z0-9_.-]+@[a-z0-9_.-]{5,})>?$/i',$participant,$email))
446
								{
447
									$status = calendar_so::combine_status('U',$content['participants']['quantity'],$content['participants']['role']);
448
									if (($data = $GLOBALS['egw']->accounts->name2id($email[2],'account_email')) && $this->bo->check_acl_invite($data))
449
									{
450
										$event['participants'][$data] = $event['participant_types']['u'][$data] = $status;
451
									}
452
									elseif ((list($data) = ExecMethod2('addressbook.addressbook_bo.search',array(
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod2() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

452
									elseif ((list($data) = /** @scrutinizer ignore-deprecated */ ExecMethod2('addressbook.addressbook_bo.search',array(

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
453
										'email' => $email[2],
454
										'email_home' => $email[2],
455
									),true,'','','',false,'OR')))
456
									{
457
										$event['participants']['c'.$data['id']] = $event['participant_types']['c'][$data['id']] = $status;
458
									}
459
									else
460
									{
461
										$event['participants']['e'.$participant] = $event['participant_types']['e'][$participant] = $status;
462
									}
463
								}
464
								else
465
								{
466
									if(is_numeric($participant))
467
									{
468
										$uid = $participant;
469
										$id = $participant;
470
										$resource = $this->bo->resources[''];
471
									}
472
									else
473
									{
474
										$uid = $participant;
475
										$id = substr($participant,1);
476
										$resource = $this->bo->resources[$participant[0]];
477
									}
478
									if(!$this->bo->check_acl_invite($uid))
479
									{
480
										if(!$msg_permission_denied_added)
481
										{
482
											$msg .= lang('Permission denied!');
483
											$msg_permission_denied_added = true;
484
										}
485
										continue;
486
									}
487
488
									$type = $resource['type'];
489
									$status = isset($this->bo->resources[$type]['new_status']) ?
490
										ExecMethod($this->bo->resources[$type]['new_status'],$id) :
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

490
										/** @scrutinizer ignore-deprecated */ ExecMethod($this->bo->resources[$type]['new_status'],$id) :

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
491
										($uid == $this->bo->user ? 'A' : 'U');
492
493
									// Expand mailing lists
494
									if($type == 'l')
495
									{
496
										// Ignore ACL here, allow inviting anyone in the list
497
										foreach($this->bo->enum_mailing_list($participant, true) as $contact)
498
										{
499
											// Mailing lists can contain users, so allow for that possibility
500
											$_type = is_numeric($contact) ? '' : $contact[0];
501
											$_uid = is_numeric($contact) ? $contact : substr($contact,1);
502
											$event['participants'][$contact] = $event['participant_types'][$_type][$_uid] =
503
												calendar_so::combine_status($status,$content['participants']['quantity'],$content['participants']['role']);
504
										}
505
										continue;
506
									}
507
									if ($status)
508
									{
509
										$res_info = $this->bo->resource_info($uid);
510
										// todo check real availability = maximum - already booked quantity
511
										if (isset($res_info['useable']) && $content['participants']['quantity'] > $res_info['useable'])
512
										{
513
											$msg .= lang('Maximum available quantity of %1 exceeded!',$res_info['useable']);
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with $res_info['useable']. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

513
											$msg .= /** @scrutinizer ignore-call */ lang('Maximum available quantity of %1 exceeded!',$res_info['useable']);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
514
											foreach(array('quantity','resource','role') as $n)
515
											{
516
												$event['participants'][$n] = $content['participants'][$n];
517
											}
518
											continue;
519
										}
520
										else
521
										{
522
											$event['participants'][$uid] = $event['participant_types'][$type][$id] =
523
												calendar_so::combine_status($status,$content['participants']['quantity'],$content['participants']['role']);
524
										}
525
									}
526
								}
527
							}
528
							break;
529
						case 'add':
530
							if (!$content['participants']['participant'])
531
							{
532
								$msg = lang('You need to select an account, contact or resource first!');
533
							}
534
							break;
535
536
						default:		// existing participant row
537
							if (!is_array($data)) continue;	// widgets in participant tab, above participant list
538
							$quantity = $status = $role = null;
539
							foreach(array('uid','status','quantity','role') as $name)
540
							{
541
								$$name = $data[$name];
542
							}
543
							if ($content['participants']['delete'][$uid] || $content['participants']['delete'][md5($uid)])
544
							{
545
								$uid = false;	// entry has been deleted
546
							}
547
							elseif ($uid)
548
							{
549
								if (is_numeric($uid))
550
								{
551
									$id = $uid;
552
									$type = 'u';
553
								}
554
								else
555
								{
556
									$id = substr($uid,1);
557
									$type = $uid[0];
558
								}
559
								if ($data['old_status'] != $status && !(!$data['old_status'] && $status == 'G'))
560
								{
561
									//echo "<p>$uid: status changed '$data[old_status]' --> '$status<'/p>\n";
562
									$new_status = calendar_so::combine_status($status, $quantity, $role);
563
									if ($this->bo->set_status($event['id'],$uid,$new_status,isset($content['edit_single']) ? $content['participants']['status_date'] : 0, false, true, $content['no_notifications']))
564
									{
565
										// Update main window
566
										$d = new Api\DateTime($content['edit_single'], Api\DateTime::$user_timezone);
567
										$client_updated = $this->update_client($event['id'], $d);
568
569
										// refreshing the calendar-view with the changed participant-status
570
										if($event['recur_type'] != MCAL_RECUR_NONE)
571
										{
572
											$msg = lang('Status for all future scheduled days changed');
573
										}
574
										else
575
										{
576
											if(isset($content['edit_single']))
577
											{
578
												$msg = lang('Status for this particular day changed');
579
												// prevent accidentally creating a real exception afterwards
580
												$view = true;
581
												$hide_delete = true;
582
											}
583
											else
584
											{
585
												$msg = lang('Status changed');
586
												//Refresh the event in the main window after changing status
587
												Framework::refresh_opener($msg, 'calendar', $event['id'], $client_updated ? 'update' : 'delete');
588
											}
589
										}
590
										if (!$content['no_popup'])
591
										{
592
											//we are handling refreshing for status changes on client side
593
										}
594
										if ($status == 'R' && $event['alarm'])
595
										{
596
											// remove from bo->set_status deleted alarms of rejected users from UI too
597
											foreach($event['alarm'] as $alarm_id => $alarm)
598
											{
599
												if ((string)$alarm['owner'] === (string)$uid)
600
												{
601
													unset($event['alarm'][$alarm_id]);
602
												}
603
											}
604
										}
605
									}
606
								}
607
								if ($uid && $status != 'G')
608
								{
609
									$event['participants'][$uid] = $event['participant_types'][$type][$id] =
610
										calendar_so::combine_status($status,$quantity,$role);
611
								}
612
							}
613
							break;
614
					}
615
				}
616
			}
617
		}
618
		$preserv = array(
619
			'view'			=> $view,
620
			'hide_delete'	=> $hide_delete,
621
			'edit_single'	=> $content['edit_single'],
622
			'reference'		=> $content['reference'],
623
			'recurrence'	=> $content['recurrence'],
624
			'actual_date'	=> $content['actual_date'],
625
			'no_popup'		=> $content['no_popup'],
626
			'tabs'			=> $content['tabs'],
627
			'template'      => $content['template'],
628
		);
629
		$noerror=true;
630
631
		//error_log(__METHOD__.$button.'#'.array2string($content['edit_single']).'#');
632
633
		$ignore_conflicts = $status_reset_to_unknown = false;
634
635
		switch((string)$button)
636
		{
637
			case 'ignore':
638
				$ignore_conflicts = true;
639
				$button = $event['button_was'];	// save or apply
640
				unset($event['button_was']);
641
				break;
642
643
		}
644
645
		switch((string)$button)
646
		{
647
		case 'exception':	// create an exception in a recuring event
648
			$msg = $this->_create_exception($event,$preserv);
649
			break;
650
		case 'edit':
651
			// Going from add dialog to full edit dialog
652
			unset($preserv['template']);
653
			unset($event['template']);
654
			break;
655
656
		case 'copy':	// create new event with copied content, some content need to be unset to make a "new" event
657
			unset($event['id']);
658
			unset($event['uid']);
659
			unset($event['reference']);
660
			unset($preserv['reference']);
661
			unset($event['recurrence']);
662
			unset($preserv['recurrence']);
663
			unset($event['recur_exception']);
664
			unset($event['edit_single']);	// in case it has been set
665
			unset($event['modified']);
666
			unset($event['modifier']);
667
			unset($event['caldav_name']);
668
			$event['owner'] = !(int)$event['owner'] || !$this->bo->check_perms(Acl::ADD,0,$event['owner']) ? $this->user : $event['owner'];
669
670
			// Clear participant stati
671
			foreach($event['participant_types'] as $type => &$participants)
672
			{
673
				foreach($participants as $id => &$p_response)
674
				{
675
					if($type == 'u' && $id == $event['owner']) continue;
676
					calendar_so::split_status($p_response, $quantity, $role);
677
					// if resource defines callback for status of new status (eg. Resources app acknowledges direct booking acl), call it
678
					$status = isset($this->bo->resources[$type]['new_status']) ? ExecMethod($this->bo->resources[$type]['new_status'],$id) : 'U';
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated: use autoloadable class-names, instanciate and call method or use static methods ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

678
					$status = isset($this->bo->resources[$type]['new_status']) ? /** @scrutinizer ignore-deprecated */ ExecMethod($this->bo->resources[$type]['new_status'],$id) : 'U';

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
679
					$p_response = calendar_so::combine_status($status,$quantity,$role);
680
				}
681
			}
682
683
			// Copy alarms
684
			if (is_array($event['alarm']))
685
			{
686
				foreach($event['alarm'] as $n => &$alarm)
687
				{
688
					unset($alarm['id']);
689
					unset($alarm['cal_id']);
690
				}
691
			}
692
693
			// Get links to be copied
694
			// With no ID, $content['link_to']['to_id'] is used
695
			$content['link_to'] = array('to_app' => 'calendar', 'to_id' => 0);
696
			foreach(Link::get_links('calendar', $content['id']) as $link)
697
			{
698
				if ($link['app'] != Link::VFS_APPNAME)
699
				{
700
					Link::link('calendar', $content['link_to']['to_id'], $link['app'], $link['id'], $link['remark']);
701
				}
702
				elseif ($link['app'] == Link::VFS_APPNAME)
703
				{
704
					Link::link('calendar', $content['link_to']['to_id'], Link::VFS_APPNAME, array(
705
						'tmp_name' => Link::vfs_path($link['app2'], $link['id2']).'/'.$link['id'],
706
						'name' => $link['id'],
707
					), $link['remark']);
708
				}
709
			}
710
			unset($link);
711
			$preserv['view'] = $preserv['edit_single'] = false;
712
			$msg = lang('%1 copied - the copy can now be edited', lang(Link::get_registry('calendar','entry')));
713
			$event['title'] = lang('Copy of:').' '.$event['title'];
714
			break;
715
716
		case 'mail':
717
		case 'sendrequest':
718
		case 'save':
719
		case 'print':
720
		case 'apply':
721
		case 'infolog':
722
			if ($event['id'] && !$this->bo->check_perms(Acl::EDIT,$event))
723
			{
724
				$msg = lang('Permission denied');
725
				$button = '';
726
				break;
727
			}
728
			if ($event['start'] > $event['end'])
729
			{
730
				$msg = lang('Error: Starttime has to be before the endtime !!!');
731
				$button = '';
732
				break;
733
			}
734
			if ($event['recur_type'] != MCAL_RECUR_NONE && $event['recur_enddate'] && $event['start'] > $event['recur_enddate'])
735
			{
736
				$msg = lang('repetition').': '.lang('Error: Starttime has to be before the endtime !!!');
737
				$button = '';
738
				break;
739
			}
740
			if ($event['recur_type'] != MCAL_RECUR_NONE && $event['end']-$event['start'] > calendar_rrule::recurrence_interval($event['recur_type'], $event['recur_interval']))
741
			{
742
				$msg = lang('Error: Duration of event longer then recurrence interval!');
743
				$button = '';
744
				break;
745
			}
746
			if (!$event['participants'])
747
			{
748
				$msg = lang('Error: no participants selected !!!');
749
				$button = '';
750
				break;
751
			}
752
			// if private event with ressource reservation is forbidden
753
			if (!$event['public'] && $GLOBALS['egw_info']['server']['no_ressources_private'])
754
			{
755
				foreach (array_keys($event['participants']) as $uid)
756
				{
757
					if ($uid[0] == 'r') //ressource detection
758
					{
759
						$msg = lang('Error: ressources reservation in private events is not allowed!!!');
760
						$button = '';
761
						break 2; //break foreach and case
762
					}
763
				}
764
			}
765
			if ($content['edit_single'])	// we edited a single event from a series
766
			{
767
				$event['reference'] = $event['id'];
768
				$event['recurrence'] = $content['edit_single'];
769
				unset($event['id']);
770
				$conflicts = $this->bo->update($event,$ignore_conflicts,true,false,true,$messages,$content['no_notifications']);
771
				if (!is_array($conflicts) && $conflicts)
772
				{
773
					// now we need to add the original start as recur-execption to the series
774
					$recur_event = $this->bo->read($event['reference']);
775
					$recur_event['recur_exception'][] = $content['edit_single'];
776
					// check if we need to move the alarms, because they are next on that exception
777
					foreach($recur_event['alarm'] as $id => $alarm)
778
					{
779
						if ($alarm['time'] == $content['edit_single'] - $alarm['offset'])
780
						{
781
							$rrule = calendar_rrule::event2rrule($recur_event, true);
782
							foreach ($rrule as $time)
783
							{
784
								if ($content['edit_single'] < $time->format('ts'))
785
								{
786
									$alarm['time'] = $time->format('ts') - $alarm['offset'];
787
									$this->bo->save_alarm($event['reference'], $alarm);
788
									break;
789
								}
790
							}
791
						}
792
					}
793
					unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
794
					unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
795
					$this->bo->update($recur_event,true);	// no conflict check here
796
797
					// Save links
798
					if($content['links'])
799
					{
800
						Link::link('calendar', $event['id'], $content['links']['to_id']);
801
					}
802
803
					if(Api\Json\Response::isJSONResponse())
804
					{
805
						// Sending null will trigger a removal of the original
806
						// for that date
807
						Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$content['reference'].':'.$content['actual_date'], 'data' => null));
808
					}
809
810
					unset($recur_event);
811
					unset($event['edit_single']);			// if we further edit it, it's just a single event
812
					unset($preserv['edit_single']);
813
				}
814
				else	// conflict or error, we need to reset everything to the state befor we tried to save it
815
				{
816
					$event['id'] = $event['reference'];
817
					$event['reference'] = $event['recurrence'] = 0;
818
					$event['uid'] = $content['uid'];
819
				}
820
				$update_type = 'edit';
821
			}
822
			else	// we edited a non-reccuring event or the whole series
823
			{
824
				if (($old_event = $this->bo->read($event['id'])))
825
				{
826
					if ($event['recur_type'] != MCAL_RECUR_NONE)
827
					{
828
						$update_type = 'edit';
829
830
						// we edit a existing series event
831
						if ($event['start'] != $old_event['start'] ||
832
							$event['whole_day'] != $old_event['whole_day'] ||
833
							$event['end'] != $old_event['end'])
834
						{
835
							// calculate offset against old series start or clicked recurrance,
836
							// depending on which is smaller
837
							$offset = $event['start'] - $old_event['start'];
838
							if (abs($offset) > abs($off2 = $event['start'] - $event['actual_date']))
839
							{
840
								$offset = $off2;
841
							}
842
							$msg = $this->_break_recurring($event, $old_event, $event['actual_date'] + $offset,$content['no_notifications']);
843
							if($msg)
0 ignored issues
show
introduced by
The condition $msg is always false.
Loading history...
844
							{
845
								$noerror = false;
846
							}
847
						}
848
					}
849
					else
850
					{
851
						if ($old_event['start'] != $event['start'] ||
852
							$old_event['end'] != $event['end'] ||
853
							$event['whole_day'] != $old_event['whole_day'])
854
						{
855
							// check if we need to move the alarms, because they are relative
856
							$this->bo->check_move_alarms($event, $old_event);
857
						}
858
					}
859
				}
860
				// Update alarm (default alarm or set alarm before change start date)
861
				// for new event.
862
				elseif (is_array($event['alarm']) && ($event['alarm'][1]['time'] + $event['alarm'][1]['offset'] != $event['start']))
863
				{
864
					$this->bo->check_move_alarms($event);
865
				}
866
				// Adding participants needs to be done as an edit, in case we
867
				// have participants visible in seperate calendars
868
				if(is_array($old_event['participants']) && count(array_diff_key($event['participants'], $old_event['participants'])))
869
				{
870
					$update_type = 'edit';
871
				}
872
				// Changing category may affect event filtering
873
				if($this->cal_prefs['saved_states']['cat_id'] && $old_event['category'] != $event['category'])
874
				{
875
					$update_type = 'edit';
876
				}
877
				$conflicts = $this->bo->update($event,$ignore_conflicts,true,false,true,$messages,$content['no_notifications']);
878
				unset($event['ignore']);
879
			}
880
			if (is_array($conflicts))
881
			{
882
				$event['button_was'] = $button;	// remember for ignore
883
				return $this->conflicts($event,$conflicts,$preserv);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->conflicts($event, $conflicts, $preserv) targeting calendar_uiforms::conflicts() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
884
			}
885
886
			// Event spans multiple days, need an edit to make sure they all get updated
887
			// We could check old date, as removing from days could still be an update
888
			if(date('Ymd', $event['start']) != date('Ymd', $event['end']))
889
			{
890
				$update_type = 'edit';
891
			}
892
			// check if there are messages from update, eg. removed participants or Api\Categories because of missing rights
893
			if ($messages)
894
			{
895
				$msg  .= ($msg ? ', ' : '').implode(', ',$messages);
896
			}
897
			if ($conflicts === 0)
898
			{
899
				$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
900
							lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
901
								htmlspecialchars(Egw::link('/index.php',array(
902
								'menuaction' => 'calendar.calendar_uiforms.edit',
903
								'cal_id'    => $content['id'],
904
							))).'">','</a>');
905
				$noerror = false;
906
			}
907
			elseif ($conflicts > 0)
908
			{
909
				// series moved by splitting in two --> move alarms and exceptions
910
				if ($old_event && $old_event['id'] != $event['id'])
911
				{
912
					$update_type = 'edit';
913
					foreach ((array)$old_event['alarms'] as $alarm)
914
					{
915
						// check if alarms still needed in old event, if not delete it
916
						$event_time = $alarm['time'] + $alarm['offset'];
917
						if ($event_time >= $this->bo->now_su)
918
						{
919
							$this->bo->delete_alarm($alarm['id']);
920
						}
921
						$alarm['time'] += $offset;
922
						unset($alarm['id']);
923
						// if alarm would be in the past (eg. event moved back) --> move to next possible recurrence
924
						if ($alarm['time'] < $this->bo->now_su)
925
						{
926
							if (($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su+$alarm['offset'], true)))
927
							{
928
								$alarm['time'] =  $next_occurrence['start'] - $alarm['offset'];
929
							}
930
							else
931
							{
932
								$alarm = false;	// no (further) recurence found --> ignore alarm
933
							}
934
						}
935
						// alarm is currently on a previous recurrence --> set for first recurrence of new series
936
						elseif ($event_time < $event['start'])
937
						{
938
							$alarm['time'] =  $event['start'] - $alarm['offset'];
939
						}
940
						if ($alarm)
941
						{
942
							$alarm['id'] = $this->bo->save_alarm($event['id'], $alarm);
943
							$event['alarm'][$alarm['id']] = $alarm;
944
						}
945
					}
946
					// attach all future exceptions to the new series
947
					$events =& $this->bo->search(array(
948
						'query' => array('cal_uid' => $old_event['uid']),
949
						'filter' => 'owner',  // return all possible entries
950
						'daywise' => false,
951
						'date_format' => 'ts',
952
					));
953
					foreach ((array)$events as $exception)
954
					{
955
						if ($exception['recurrence'] > $this->bo->now_su)
956
						{
957
							$exception['recurrence'] += $offset;
958
							$exception['reference'] = $event['id'];
959
							$exception['uid'] = $event['uid'];
960
							$this->bo->update($exception, true, true, true, true, $msg=null, $content['no_notifications']);
0 ignored issues
show
Bug introduced by
$msg = null cannot be passed to calendar_boupdate::update() as the parameter $messages expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

960
							$this->bo->update($exception, true, true, true, true, /** @scrutinizer ignore-type */ $msg=null, $content['no_notifications']);
Loading history...
961
						}
962
					}
963
				}
964
965
				$message = lang('Event saved');
966
				if ($status_reset_to_unknown)
0 ignored issues
show
introduced by
The condition $status_reset_to_unknown is always false.
Loading history...
967
				{
968
					foreach((array)$event['participants'] as $uid => $status)
969
					{
970
						if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user)
971
						{
972
							calendar_so::split_status($status,$q,$r);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $q seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $r seems to be never defined.
Loading history...
973
							$status = calendar_so::combine_status('U',$q,$r);
974
							$this->bo->set_status($event['id'], $uid, $status, 0, true);
975
						}
976
					}
977
					$message .= lang(', stati of participants reset');
978
				}
979
980
				$response = Api\Json\Response::get();
981
				if($response && $update_type != 'delete')
982
				{
983
					$client_updated = $this->update_client($event['id']);
984
				}
985
986
				$msg = $message . ($msg ? ', ' . $msg : '');
987
				Framework::refresh_opener($msg, 'calendar', $event['id'], $client_updated ? ($event['recur_type'] ? 'edit' : $update_type) : 'delete');
988
				// writing links for new entry, existing ones are handled by the widget itself
989
				if (!$content['id'] && is_array($content['link_to']['to_id']))
990
				{
991
					Link::link('calendar',$event['id'],$content['link_to']['to_id']);
992
				}
993
			}
994
			else
995
			{
996
				$msg = lang('Error: saving the event !!!');
997
			}
998
			break;
999
1000
		case 'cancel':
1001
			if($content['cancel_needs_refresh'])
1002
			{
1003
				Framework::refresh_opener($msg, 'calendar');
1004
			}
1005
			break;
1006
1007
		case 'delete':					// delete of event (regular or series)
1008
			$exceptions_kept = null;
1009
			if ($this->bo->delete($event['id'], (int)$content['edit_single'], false, $event['no_notifications'],
1010
				$content['delete_exceptions'] == 'true', $exceptions_kept))
1011
			{
1012
				if ($event['recur_type'] != MCAL_RECUR_NONE && $content['reference'] == 0 && !$content['edit_single'])
1013
				{
1014
					$msg = lang('Series deleted');
1015
					if ($exceptions_kept) $msg .= lang(', exceptions preserved');
1016
				}
1017
				else
1018
				{
1019
					$msg = lang('Event deleted');
1020
				}
1021
1022
			}
1023
			break;
1024
1025
		case 'freetime':
1026
			// the "click" has to be in onload, to make sure the button is already created
1027
			$event['button_was'] = $button;
1028
			break;
1029
1030
		case 'add_alarm':
1031
			$time = $content['start'];
1032
			$offset = $time - $content['new_alarm']['date'];
1033
			if ($event['recur_type'] != MCAL_RECUR_NONE &&
1034
				($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + $offset, true)) &&
1035
				$time < $next_occurrence['start'])
1036
			{
1037
				$content['new_alarm']['date'] = $next_occurrence['start'] - $offset;
1038
			}
1039
			if ($this->bo->check_perms(Acl::EDIT,!$content['new_alarm']['owner'] ? $event : 0,$content['new_alarm']['owner']))
1040
			{
1041
				$alarm = array(
1042
					'offset' => $offset,
1043
					'time'   => $content['new_alarm']['date'],
1044
					'all'    => !$content['new_alarm']['owner'],
1045
					'owner'  => $content['new_alarm']['owner'] ? $content['new_alarm']['owner'] : $this->user,
1046
				);
1047
				if ($alarm['time'] < $this->bo->now_su)
1048
				{
1049
					$msg = lang("Can't add alarms in the past !!!");
1050
				}
1051
				elseif ($event['id'])	// save the alarm immediatly
1052
				{
1053
					if (($alarm_id = $this->bo->save_alarm($event['id'],$alarm)))
1054
					{
1055
						$alarm['id'] = $alarm_id;
1056
						$event['alarm'][$alarm_id] = $alarm;
1057
1058
						$msg = lang('Alarm added');
1059
						Framework::refresh_opener($msg,'calendar', $event['id'], 'update');
1060
					}
1061
					else
1062
					{
1063
						$msg = lang('Error adding the alarm');
1064
					}
1065
				}
1066
				else
1067
				{
1068
					for($alarm['id']=1; isset($event['alarm'][$alarm['id']]); $alarm['id']++) {}	// get a temporary non-conflicting, numeric id
1069
					$event['alarm'][$alarm['id']] = $alarm;
1070
				}
1071
			}
1072
			else
1073
			{
1074
				$msg = lang('Permission denied');
1075
			}
1076
			break;
1077
		}
1078
		// add notification-errors, if we have some
1079
		if (($notification_errors = notifications::errors(true)))
1080
		{
1081
			$msg .= ($msg ? "\n" : '').implode("\n", $notification_errors);
1082
		}
1083
		// New event, send data before updating so it's there
1084
		$response = Api\Json\Response::get();
1085
		if($response && !$content['id'] && $event['id'])
1086
		{
1087
			$client_updated = $this->update_client($event['id']);
1088
		}
1089
		if (in_array($button,array('cancel','save','delete','delete_exceptions','delete_keep_exceptions')) && $noerror)
1090
		{
1091
			if ($content['lock_token'])	// remove an existing lock
1092
			{
1093
				Vfs::unlock(Vfs::app_entry_lock_path('calendar',$content['id']),$content['lock_token'],false);
1094
			}
1095
			if ($content['no_popup'])
1096
			{
1097
				Egw::redirect_link('/index.php',array(
1098
					'menuaction' => 'calendar.calendar_uiviews.index',
1099
					'msg'        => $msg,
1100
					'ajax'       => 'true'
1101
				));
1102
			}
1103
			if (in_array($button,array('delete_exceptions','delete_keep_exceptions')) || $content['recur_type'] && $button == 'delete')
1104
			{
1105
				Framework::refresh_opener($msg,'calendar');
1106
			}
1107
			else
1108
			{
1109
				Framework::refresh_opener($msg, 'calendar',
1110
					$event['id'] . ($content['edit_single'] ? ':' . (int)$content['edit_single'] : '' ),
1111
					$button == 'save' && $client_updated ? ($content['id'] ? $update_type : 'add') : 'delete'
1112
				);
1113
			}
1114
			Framework::window_close();
1115
			exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1116
		}
1117
		unset($event['no_notifications']);
1118
		return $this->edit($event,$preserv,$msg,$event['id'] ? $event['id'] : $content['link_to']['to_id']);
1119
	}
1120
1121
	/**
1122
	 * Create an exception from the clicked event
1123
	 *
1124
	 * It's not stored to the DB unless the user saves it!
1125
	 *
1126
	 * @param array &$event
1127
	 * @param array &$preserv
1128
	 * @return string message that exception was created
1129
	 */
1130
	function _create_exception(&$event,&$preserv)
1131
	{
1132
		// In some cases where the user makes the first day an exception, actual_date may be missing
1133
		$preserv['actual_date'] = $preserv['actual_date'] ? $preserv['actual_date'] : $event['start'];
1134
1135
		$event['end'] += $preserv['actual_date'] - $event['start'];
1136
		$event['reference'] = $preserv['reference'] = $event['id'];
1137
		$event['recurrence'] = $preserv['recurrence'] = $preserv['actual_date'];
1138
		$event['start'] = $preserv['edit_single'] = $preserv['actual_date'];
1139
		$event['recur_type'] = MCAL_RECUR_NONE;
1140
		foreach(array('recur_enddate','recur_interval','recur_exception','recur_data') as $name)
1141
		{
1142
			unset($event[$name]);
1143
		}
1144
		// add all alarms as new alarms to execption
1145
		$event['alarm'] = array_values((array)$event['alarm']);
1146
		foreach($event['alarm'] as &$alarm)
1147
		{
1148
			unset($alarm['uid'], $alarm['id'], $alarm['time']);
1149
		}
1150
1151
		// Copy links
1152
		if(!is_array($event['link_to'])) $event['link_to'] = array();
1153
		$event['link_to']['to_app'] = 'calendar';
1154
		$event['link_to']['to_id'] = 0;
1155
1156
		foreach(Link::get_links($event['link_to']['to_app'], $event['id']) as $link)
1157
		{
1158
			if(!$link['id']) continue;
1159
			if ($link['app'] != Link::VFS_APPNAME)
1160
			{
1161
				Link::link('calendar', $event['link_to']['to_id'], $link['app'], $link['id'], $link['remark']);
1162
			}
1163
			elseif ($link['app'] == Link::VFS_APPNAME)
1164
			{
1165
				Link::link('calendar', $event['link_to']['to_id'], Link::VFS_APPNAME, array(
1166
					'tmp_name' => Link::vfs_path($link['app2'], $link['id2']).'/'.$link['id'],
1167
					'name' => $link['id'],
1168
				), $link['remark']);
1169
			}
1170
		}
1171
1172
		$event['links'] = $event['link_to'];
1173
1174
		if($this->bo->check_perms(Acl::EDIT,$event))
1175
		{
1176
			return lang('Save event as exception - Delete single occurrence - Edit status or alarms for this particular day');
1177
		}
1178
		return lang('Edit status or alarms for this particular day');
1179
	}
1180
1181
	/**
1182
	 * Since we cannot change recurrences in the past, break a recurring
1183
	 * event (that starts in the past), and create a new event.
1184
	 *
1185
	 * $old_event will be ended (if needed) and $event will be modified with the
1186
	 * new start date and time.  It is not allowed to edit events in the past,
1187
	 * so if $as_of_date is in the past, it will be adjusted to today.
1188
	 *
1189
	 * @param array &$event Event to be modified
1190
	 * @param array $old_event Unmodified (original) event, as read from the database
1191
	 * @param date $as_of_date If provided, the break will be done as of this
1192
	 *	date instead of today
1193
	 * @param boolean $no_notifications Toggle notifications to participants
1194
	 *
1195
	 * @return false or error message
1196
	 */
1197
	function _break_recurring(&$event, $old_event, $as_of_date = null, $no_notifications = true)
1198
	{
1199
		$msg = false;
1200
1201
		if(!$as_of_date )
1202
		{
1203
			$as_of_date = time();
1204
		}
1205
1206
		//error_log(__METHOD__ . Api\DateTime::to($old_event['start']) . ' -> '. Api\DateTime::to($event['start']) . ' as of ' . Api\DateTime::to($as_of_date));
1207
1208
		if(!($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + 1, true)))
0 ignored issues
show
Unused Code introduced by
The assignment to $next_occurrence is dead and can be removed.
Loading history...
1209
		{
1210
			$msg = lang("Error: You can't shift a series from the past!");
1211
			return $msg;
1212
		}
1213
1214
		// Hold on to this in case something goes wrong
1215
		$orig_event = $event;
1216
1217
		$offset = $event['start'] - $old_event['start'];
1218
		$duration = $event['duration'] ? $event['duration'] : $event['end'] - $event['start'];
1219
1220
		// base start-date of new series on actual / clicked date
1221
		$event['start'] = $as_of_date ;
1222
1223
		if (Api\DateTime::to($old_event['start'],'Ymd') < Api\DateTime::to($as_of_date,'Ymd') ||
1224
			// Adjust for requested date in the past
1225
			Api\DateTime::to($as_of_date,'ts') < time()
1226
		)
1227
		{
1228
1229
			unset($orig_event);
1230
			// copy event by unsetting the id(s)
1231
			unset($event['id']);
1232
			unset($event['uid']);
1233
			unset($event['caldav_name']);
1234
			$event['alarm'] = array();
1235
1236
			// set enddate of existing event
1237
			$rriter = calendar_rrule::event2rrule($old_event, true);
1238
			$rriter->rewind();
1239
			$last = $rriter->current();
1240
			do
1241
			{
1242
				$rriter->next_no_exception();
1243
				$occurrence = $rriter->current();
1244
			}
1245
			while ($rriter->valid()  && (
1246
				Api\DateTime::to($occurrence, 'ts') <= time() ||
1247
				Api\DateTime::to($occurrence, 'Ymd') < Api\DateTime::to($as_of_date,'Ymd')
1248
			) && ($last = $occurrence));
1249
1250
1251
			// Make sure as_of_date is still valid, may have to move forward
1252
			if(Api\DateTime::to($as_of_date,'ts') < Api\DateTime::to($last,'ts') ||
1253
				Api\DateTime::to($as_of_date, 'Ymd') == Api\DateTime::to($last, 'Ymd'))
1254
			{
1255
				$event['start'] = Api\DateTime::to($rriter->current(),'ts') + $offset;
1256
			}
1257
1258
			//error_log(__METHOD__ ." Series should end at " . Api\DateTime::to($last) . " New series starts at " . Api\DateTime::to($event['start']));
1259
			if ($duration)
1260
			{
1261
				$event['end'] = $event['start'] + $duration;
1262
			}
1263
			elseif($event['end'] < $event['start'])
1264
			{
1265
				$event['end'] = $old_event['end'] - $old_event['start'] + $event['start'];
1266
			}
1267
			//error_log(__LINE__.": event[start]=$event[start]=".Api\DateTime::to($event['start']).", duration={$duration}, event[end]=$event[end]=".Api\DateTime::to($event['end']).", offset=$offset\n");
1268
1269
			$event['participants'] = $old_event['participants'];
1270
			foreach ($old_event['recur_exception'] as $key => $exdate)
1271
			{
1272
				if ($exdate > Api\DateTime::to($last,'ts'))
1273
				{
1274
					//error_log("Moved exception on " . Api\DateTime::to($exdate));
1275
					unset($old_event['recur_exception'][$key]);
1276
					$event['recur_exception'][$key] += $offset;
1277
				}
1278
				else
1279
				{
1280
					//error_log("Kept exception on ". Api\DateTime::to($exdate));
1281
					unset($event['recur_exception'][$key]);
1282
				}
1283
			}
1284
			$last->setTime(0, 0, 0);
1285
			$old_event['recur_enddate'] = Api\DateTime::to($last, 'ts');
1286
			if (!$this->bo->update($old_event,true,true,false,true,$dummy=null,$no_notifications))
0 ignored issues
show
Bug introduced by
$dummy = null cannot be passed to calendar_boupdate::update() as the parameter $messages expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1286
			if (!$this->bo->update($old_event,true,true,false,true,/** @scrutinizer ignore-type */ $dummy=null,$no_notifications))
Loading history...
1287
			{
1288
				$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
0 ignored issues
show
introduced by
The condition $msg is always false.
Loading history...
1289
					lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with '<a href="' . htmlspecia... $event['id']))) . '">'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1289
					/** @scrutinizer ignore-call */ 
1290
     lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1290
						htmlspecialchars(Egw::link('/index.php',array(
1291
							'menuaction' => 'calendar.calendar_uiforms.edit',
1292
							'cal_id'    => $event['id'],
1293
						))).'">','</a>');
1294
				$event = $orig_event;
1295
			}
1296
		}
1297
		$event['start'] = Api\DateTime::to($event['start'],'ts');
1298
		return $msg;
1299
	}
1300
1301
	/**
1302
	 * return javascript to open mail compose window with preset content to mail all participants
1303
	 *
1304
	 * @param array $event
1305
	 * @param boolean $added
1306
	 * @return string javascript window.open command
1307
	 */
1308
	function ajax_custom_mail($event,$added,$asrequest=false)
1309
	{
1310
		$to = array();
1311
1312
		foreach($event['participants'] as $uid => $status)
1313
		{
1314
			//error_log(__METHOD__.__LINE__.' '.$uid.':'.array2string($status));
1315
			if (empty($status)) continue;
1316
			if(!is_array($status))
1317
			{
1318
				$quantity = $role = null;
1319
				calendar_so::split_status($status,$quantity,$role);
1320
				$status = array(
1321
					'status' => $status,
1322
					'uid' => $uid,
1323
				);
1324
			}
1325
			$toadd = '';
1326
			if ((isset($status['status']) && $status['status'] == 'R') || (isset($status['uid']) && $status['uid'] == $this->user)) continue;
1327
1328
			if (isset($status['uid']) && is_numeric($status['uid']) && $GLOBALS['egw']->accounts->get_type($status['uid']) == 'u')
1329
			{
1330
				if (!($email = $GLOBALS['egw']->accounts->id2name($status['uid'],'account_email'))) continue;
1331
1332
				$toadd = $GLOBALS['egw']->accounts->id2name($status['uid'], 'account_firstname').' '.
1333
					$GLOBALS['egw']->accounts->id2name($status['uid'], 'account_lastname').' <'.$email.'>';
1334
1335
				if (!in_array($toadd,$to)) $to[] = $toadd;
1336
			}
1337
			elseif ($uid < 0)
1338
			{
1339
				foreach($GLOBALS['egw']->accounts->members($uid,true) as $uid)
0 ignored issues
show
Comprehensibility Bug introduced by
$uid is overwriting a variable from outer foreach loop.
Loading history...
1340
				{
1341
					if (!($email = $GLOBALS['egw']->accounts->id2name($uid,'account_email'))) continue;
1342
1343
					$toadd = $GLOBALS['egw']->accounts->id2name($uid, 'account_firstname').' '.
1344
						$GLOBALS['egw']->accounts->id2name($uid, 'account_lastname').' <'.$email.'>';
1345
1346
					// dont add groupmembers if they already rejected the event, or are the current user
1347
					if (!in_array($toadd,$to) && ($event['participants'][$uid] !== 'R' && $uid != $this->user)) $to[] = $toadd;
1348
				}
1349
			}
1350
			elseif(!empty($status['uid'])&& !is_numeric(substr($status['uid'],0,1)) && ($info = $this->bo->resource_info($status['uid'])))
1351
			{
1352
				$to[] = $info['email'];
1353
				//error_log(__METHOD__.__LINE__.array2string($to));
1354
			}
1355
			elseif(!is_numeric(substr($uid,0,1)) && ($info = $this->bo->resource_info($uid)))
1356
			{
1357
				$to[] = $info['email'];
1358
				//error_log(__METHOD__.__LINE__.array2string($to));
1359
			}
1360
		}
1361
		// prefer event description over standard notification text
1362
		if (empty($event['description']))
1363
		{
1364
			list(,$body) = $this->bo->get_update_message($event,$added ? MSG_ADDED : MSG_MODIFIED);	// update-message is in TZ of the user
1365
		}
1366
		else
1367
		{
1368
			$body = $event['description'];
1369
		}
1370
		// respect user preference about html mail
1371
		if ($GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text')
1372
		{
1373
			$body = '<pre>'.$body.'</pre>';
1374
		}
1375
		//error_log(__METHOD__.print_r($event,true));
1376
		$boical = new calendar_ical();
1377
		// we need to pass $event[id] so iCal class reads event again,
1378
		// as event is in user TZ, but iCal class expects server TZ!
1379
		$ics = $boical->exportVCal(array($event['id']),'2.0','REQUEST',false);
1380
1381
		$ics_file = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'ics');
1382
		if(($f = fopen($ics_file,'w')))
1383
		{
1384
			fwrite($f,$ics);
1385
			fclose($f);
1386
		}
1387
		//error_log(__METHOD__.__LINE__.array2string($to));
1388
		$vars = array(
1389
			'menuaction'      => 'mail.mail_compose.compose',
1390
			'mimeType'		  => $GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text' ? 'html' : 'plain',
1391
			'preset[subject]' => $event['title'],
1392
			'preset[body]'    => $body,
1393
			'preset[name]'    => 'event.ics',
1394
			'preset[file]'    => $ics_file,
1395
			'preset[type]'    => 'text/calendar'.($asrequest?'; method=REQUEST':''),
1396
			'preset[size]'    => filesize($ics_file),
1397
		);
1398
		$vars[$asrequest?'preset[to]': 'preset[bcc]'] = $to;
1399
		if ($asrequest) $vars['preset[msg]'] = lang('You attempt to mail a meetingrequest to the recipients above. Depending on the client this mail is opened with, the recipient may or may not see the mailbody below, but only see the meeting request attached.');
1400
		$response = Api\Json\Response::get();
1401
		$response->call('app.calendar.custom_mail', $vars);
1402
	}
1403
1404
	/**
1405
	 * Get title of a uid / calendar participant
1406
	 *
1407
	 * @param int|string $uid
1408
	 * @return string
1409
	 */
1410
	public function get_title($uid)
1411
	{
1412
		if (is_numeric($uid))
1413
		{
1414
			return Api\Accounts::username($uid);
1415
		}
1416
		elseif (($info = $this->bo->resource_info($uid)))
1417
		{
1418
			if ($uid[0] == 'e' && $info['name'] && $info['name'] != $info['email'])
1419
			{
1420
				return $info['name'].' <'.$info['email'].'>';
1421
			}
1422
			return $info['name'] ? $info['name'] : $info['email'];
1423
		}
1424
		return '#'.$uid;
1425
	}
1426
1427
	/**
1428
	 * Compare two uid by there title
1429
	 *
1430
	 * @param int|string $uid1
1431
	 * @param int|string $uid2
1432
	 * @return int see strnatcasecmp
1433
	 */
1434
	public function uid_title_cmp($uid1, $uid2)
1435
	{
1436
		return strnatcasecmp($this->get_title($uid1), $this->get_title($uid2));
1437
	}
1438
1439
	public function ajax_add()
1440
	{
1441
		// This tells etemplate to send as JSON response, not full
1442
		// This avoids errors from trying to send header again
1443
		if(Api\Json\Request::isJSONRequest())
1444
		{
1445
			$GLOBALS['egw']->framework->response = Api\Json\Response::get();
1446
		}
1447
1448
		$this->edit();
1449
	}
1450
1451
	/**
1452
	 * Get conflict dialog via ajax.  Used by quick add.
1453
	 *
1454
	 */
1455
	public function ajax_conflicts()
1456
	{
1457
		$participants = json_decode($_GET['participants'],true);
0 ignored issues
show
Unused Code introduced by
The assignment to $participants is dead and can be removed.
Loading history...
1458
		unset($_GET['participants']);
1459
1460
		$content = $this->default_add_event();
1461
1462
		// Process edit wants to see input values
1463
		$participants = array(1=> false);
1464
		$participants['cal_resources'] = '';
1465
		foreach($content['participants'] as $id => $status)
1466
		{
1467
			$quantity = $role = '';
1468
			calendar_so::split_status($status,$quantity,$role);
1469
			$participants[] = array(
1470
				'uid' => $id,
1471
				'status' => $status,
1472
				'quantity' => $quantity,
1473
				'role' => $role
1474
			);
1475
		}
1476
		$content['participants'] = $participants;
1477
		$content['button'] = array('save' => true);
1478
		return $this->process_edit($content);
1479
	}
1480
1481
	/**
1482
	 * Edit a calendar event
1483
	 *
1484
	 * @param array $event Event to edit, if not $_GET['cal_id'] contains the event-id
1485
	 * @param array $preserv following keys:
1486
	 *	view boolean view-mode, if no edit-access we automatic fallback to view-mode
1487
	 *	hide_delete boolean hide delete button
1488
	 *	no_popup boolean use a popup or not
1489
	 *	edit_single int timestamp of single event edited, unset/null otherwise
1490
	 * @param string $msg ='' msg to display
1491
	 * @param mixed $link_to_id ='' from or for the link-widget
1492
	 * @param string $msg_type =null default automatic detect, if it contains "error"
1493
	 */
1494
	function edit($event=null,$preserv=null,$msg='',$link_to_id='',$msg_type=null)
1495
	{
1496
		$sel_options = array(
1497
			'recur_type' => &$this->bo->recur_types,
1498
			'status'     => $this->bo->verbose_status,
1499
			'duration'   => $this->durations,
1500
			'role'       => $this->bo->roles,
1501
			'new_alarm[options]' => $this->bo->alarms + array(0 => lang('Custom')),
1502
			'action'     => array(
1503
				'copy' => array('label' => 'Copy', 'title' => 'Copy this event'),
1504
				'ical' => array('label' => 'Export', 'title' => 'Download this event as iCal'),
1505
				'print' => array('label' => 'Print', 'title' => 'Print this event'),
1506
				'infolog' => array('label' => 'InfoLog', 'title' => 'Create an InfoLog from this event'),
1507
				'mail' => array('label' => 'Mail all participants', 'title' => 'Compose a mail to all participants after the event is saved'),
1508
				'sendrequest' => array('label' => 'Meetingrequest to all participants', 'title' => 'Send meetingrequest to all participants after the event is saved'),
1509
			),
1510
		);
1511
		unset($sel_options['status']['G']);
1512
		if (!is_array($event))
1513
		{
1514
			$preserv = array(
1515
				'no_popup' => isset($_GET['no_popup']),
1516
				'template' => isset($_GET['template']) ? $_GET['template'] : (isset($_REQUEST['print']) ? 'calendar.print' : 'calendar.edit'),
1517
			);
1518
			if(!isset($_REQUEST['print']) && !empty($preserv['template']) && $this->cal_prefs['new_event_dialog'] == 'edit')
1519
			{
1520
				// User wants full thing
1521
				unset($preserv['template']);
1522
			}
1523
			$cal_id = (int) $_GET['cal_id'];
1524
			if($_GET['action'])
1525
			{
1526
				$event = $this->bo->read($cal_id);
1527
				$event['action'] = $_GET['action'];
1528
				unset($event['participants']);
1529
				return $this->process_edit($event);
1530
			}
1531
			// vfs url
1532
			if (!empty($_GET['ical_url']) && parse_url($_GET['ical_url'], PHP_URL_SCHEME) == 'vfs')
1533
			{
1534
				$_GET['ical_vfs'] = parse_url($_GET['ical_url'], PHP_URL_PATH);
1535
			}
1536
			// vfs path
1537
			if (!empty($_GET['ical_vfs']) &&
1538
				(!Vfs::file_exists($_GET['ical_vfs']) || !($_GET['ical'] = file_get_contents(Vfs::PREFIX.$_GET['ical_vfs']))))
1539
			{
1540
				//error_log(__METHOD__."() Error: importing the iCal: vfs file not found '$_GET[ical_vfs]'!");
1541
				$msg = lang('Error: importing the iCal').': '.lang('VFS file not found').': '.$_GET['ical_vfs'];
1542
				$event =& $this->default_add_event();
1543
			}
1544
			if (!empty($_GET['ical_data']) &&
1545
				!($_GET['ical'] = Link::get_data($_GET['ical_data'])))
1546
			{
1547
				//error_log(__METHOD__."() Error: importing the iCal: data not found '$_GET[ical_data]'!");
1548
				$msg = lang('Error: importing the iCal').': '.lang('Data not found').': '.$_GET['ical_data'];
1549
				$event =& $this->default_add_event();
1550
			}
1551
			if (!empty($_GET['ical']))
1552
			{
1553
				$ical = new calendar_ical();
1554
				if (!($events = $ical->icaltoegw($_GET['ical'], '', 'utf-8')))
1555
				{
1556
					error_log(__METHOD__."('$_GET[ical]') error parsing iCal!");
1557
					$msg = lang('Error: importing the iCal');
1558
					$event =& $this->default_add_event();
1559
				}
1560
				else
1561
				{
1562
					if (count($events) > 1)
1563
					{
1564
						$msg = lang('%1 events in iCal file, only first one imported and displayed!', count($events));
0 ignored issues
show
Unused Code introduced by
The call to lang() has too many arguments starting with count($events). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1564
						$msg = /** @scrutinizer ignore-call */ lang('%1 events in iCal file, only first one imported and displayed!', count($events));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1565
						$msg_type = 'notice';	// no not hide automatic
1566
					}
1567
					// as icaltoegw returns timestamps in server-time, we have to convert them here to user-time
1568
					$this->bo->db2data($events, 'ts');
1569
1570
					$event = array_shift($events);
1571
					if (($existing_event = $this->bo->read($event['uid'])))
1572
					{
1573
						$event = $existing_event;
1574
					}
1575
					else
1576
					{
1577
						$event['participant_types'] = array();
1578
						foreach($event['participants'] as $uid => $status)
1579
						{
1580
							$user_type = $user_id = null;
1581
							calendar_so::split_user($uid, $user_type, $user_id);
1582
							$event['participant_types'][$user_type][$user_id] = $status;
1583
						}
1584
					}
1585
					//error_log(__METHOD__."(...) parsed as ".array2string($event));
1586
				}
1587
				unset($ical);
1588
			}
1589
			elseif (!$cal_id || $cal_id && !($event = $this->bo->read($cal_id)))
1590
			{
1591
				if ($cal_id)
1592
				{
1593
					if (!$preserv['no_popup'])
1594
					{
1595
						Framework::window_close(lang('Permission denied'));
1596
					}
1597
					else
1598
					{
1599
						$GLOBALS['egw']->framework->render('<p class="message" align="center">'.lang('Permission denied')."</p>\n",null,true);
1600
						exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1601
					}
1602
				}
1603
				$event =& $this->default_add_event();
1604
			}
1605
			else
1606
			{
1607
				$preserv['actual_date'] = $event['start'];		// remember the date clicked
1608
				if ($event['recur_type'] != MCAL_RECUR_NONE)
1609
				{
1610
					if (empty($event['whole_day']))
1611
					{
1612
						$date = $_GET['date'];
1613
					}
1614
					else
1615
					{
1616
						$date = $this->bo->so->startOfDay(new Api\DateTime($_GET['date'], Api\DateTime::$user_timezone));
1617
						$date->setUser();
1618
					}
1619
					$event = $this->bo->read($cal_id, $date, true);
1620
					$preserv['actual_date'] = $event['start'];		// remember the date clicked
1621
					if ($_GET['exception'])
1622
					{
1623
						$msg = $this->_create_exception($event,$preserv);
1624
					}
1625
					else
1626
					{
1627
						$event = $this->bo->read($cal_id, null, true);
1628
					}
1629
				}
1630
			}
1631
			// set new start and end if given by $_GET
1632
			if(isset($_GET['start'])) { $event['start'] = Api\DateTime::to($_GET['start'],'ts'); }
1633
			if(isset($_GET['end'])) { $event['end'] = Api\DateTime::to($_GET['end'],'ts'); }
1634
			if(isset($_GET['non_blocking'])) { $event['non_blocking'] = (bool)$_GET['non_blocking']; }
1635
			// check if the event is the whole day
1636
			$start = $this->bo->date2array($event['start']);
1637
			$end = $this->bo->date2array($event['end']);
1638
			$event['whole_day'] = !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
1639
1640
			$link_to_id = $event['id'];
1641
			if (!$event['id'] && isset($_REQUEST['link_app']) && isset($_REQUEST['link_id']))
1642
			{
1643
				$link_ids = is_array($_REQUEST['link_id']) ? $_REQUEST['link_id'] : array($_REQUEST['link_id']);
1644
				foreach(is_array($_REQUEST['link_app']) ? $_REQUEST['link_app'] : array($_REQUEST['link_app']) as $n => $link_app)
1645
				{
1646
					$link_id = $link_ids[$n];
1647
					if(!preg_match('/^[a-z_0-9-]+:[:a-z_0-9-]+$/i',$link_app.':'.$link_id))	// guard against XSS
1648
					{
1649
						continue;
1650
					}
1651
					if(!$n)
1652
					{
1653
						$event['title'] = Link::title($link_app,$link_id);
1654
						// ask first linked app via "calendar_set" hook, for further data to set, incl. links
1655
						if (($set = Api\Hooks::single($event+array('location'=>'calendar_set','entry_id'=>$link_id),$link_app)))
1656
						{
1657
							foreach((array)$set['link_app'] as $i => $l_app)
1658
							{
1659
								if (($l_id=$set['link_id'][$i])) Link::link('calendar',$event['link_to']['to_id'],$l_app,$l_id);
1660
							}
1661
							unset($set['link_app']);
1662
							unset($set['link_id']);
1663
1664
							$event = array_merge($event,$set);
1665
						}
1666
					}
1667
					Link::link('calendar',$link_to_id,$link_app,$link_id);
1668
				}
1669
			}
1670
		}
1671
1672
		$etpl = new Etemplate();
1673
		if (!$etpl->read($preserv['template']))
1674
		{
1675
			$etpl->read($preserv['template'] = 'calendar.edit');
1676
		}
1677
		$view = $preserv['view'] = $preserv['view'] || $event['id'] && !$this->bo->check_perms(Acl::EDIT,$event);
1678
		//echo "view=$view, event="; _debug_array($event);
1679
		// shared locking of entries to edit
1680
		if (!$view && ($locktime = $GLOBALS['egw_info']['server']['Lock_Time_Calender']) && $event['id'])
1681
		{
1682
			$lock_path = Vfs::app_entry_lock_path('calendar',$event['id']);
1683
			$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
1684
1685
			if (($preserv['lock_token'] = $event['lock_token']))		// already locked --> refresh the lock
1686
			{
1687
				Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',true,false);
0 ignored issues
show
Bug introduced by
$scope = 'shared' cannot be passed to EGroupware\Api\Vfs::lock() as the parameter $scope expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1687
				Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,/** @scrutinizer ignore-type */ $scope='shared',$type='write',true,false);
Loading history...
Bug introduced by
$type = 'write' cannot be passed to EGroupware\Api\Vfs::lock() as the parameter $type expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1687
				Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',/** @scrutinizer ignore-type */ $type='write',true,false);
Loading history...
1688
			}
1689
			if (($lock = Vfs::checkLock($lock_path)) && $lock['owner'] != $lock_owner)
1690
			{
1691
				$msg .= ' '.lang('This entry is currently opened by %1!',
1692
					(($lock_uid = $GLOBALS['egw']->accounts->name2id(substr($lock['owner'],7),'account_email')) ?
1693
					Api\Accounts::username($lock_uid) : $lock['owner']));
1694
			}
1695
			elseif($lock)
1696
			{
1697
				$preserv['lock_token'] = $lock['token'];
1698
			}
1699
			elseif(Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',false,false))
1700
			{
1701
				//We handle AJAX_REQUEST in client-side for unlocking the locked entry, in case of closing the entry by X button or close button
1702
			}
1703
			else
1704
			{
1705
				$msg .= ' '.lang("Can't aquire lock!");		// eg. an exclusive lock via CalDAV ...
1706
				$view = true;
1707
			}
1708
		}
1709
		$content = array_merge($event,array(
1710
			'cal_id'  => $event['id'],
1711
			'link_to' => array(
1712
				'to_id'  => $link_to_id,
1713
				'to_app' => 'calendar',
1714
			),
1715
			'edit_single' => $preserv['edit_single'],	// need to be in content too, as it is used in the template
1716
			'tabs'   => $preserv['tabs'],
1717
			'view' => $view,
1718
			'query_delete_exceptions' => (int)($event['recur_type'] && $event['recur_exception']),
1719
		));
1720
		Framework::message($msg, $msg_type);
1721
		$content['duration'] = $content['end'] - $content['start'];
1722
		if (isset($this->durations[$content['duration']])) $content['end'] = '';
1723
1724
		$row = 3;
1725
		$readonlys = $content['participants'] = $preserv['participants'] = array();
1726
		// preserve some ui elements, if set eg. under error-conditions
1727
		foreach(array('quantity','resource','role') as $n)
1728
		{
1729
			if (isset($event['participants'][$n])) $content['participants'][$n] = $event['participants'][$n];
1730
		}
1731
		foreach($event['participant_types'] as $type => $participants)
1732
		{
1733
			$name = 'accounts';
1734
			if (isset($this->bo->resources[$type]))
1735
			{
1736
				$name = $this->bo->resources[$type]['app'];
1737
			}
1738
			// sort participants (in there group/app) by title
1739
			uksort($participants, array($this, 'uid_title_cmp'));
1740
			foreach($participants as $id => $status)
1741
			{
1742
				$uid = $type == 'u' ? $id : $type.$id;
1743
				$quantity = $role = null;
1744
				calendar_so::split_status($status,$quantity,$role);
1745
				$preserv['participants'][$row] = $content['participants'][$row] = array(
1746
					'app'      => $name == 'accounts' ? ($GLOBALS['egw']->accounts->get_type($id) == 'g' ? 'Group' : 'User') : $name,
1747
					'uid'      => $uid,
1748
					'status'   => $status,
1749
					'old_status' => $status,
1750
					'quantity' => $quantity > 1 || $uid[0] == 'r' ? $quantity : '',	// only display quantity for resources or if > 1
1751
					'role'     => $role,
1752
				);
1753
				// replace iCal roles with a nicer label and remove regular REQ-PARTICIPANT
1754
				if (isset($this->bo->roles[$role]))
1755
				{
1756
					$content['participants'][$row]['role_label'] = lang($this->bo->roles[$role]);
1757
				}
1758
				// allow third party apps to use categories for roles
1759
				elseif(substr($role,0,6) == 'X-CAT-')
1760
				{
1761
					$content['participants'][$row]['role_label'] = $GLOBALS['egw']->categories->id2name(substr($role,6));
1762
				}
1763
				else
1764
				{
1765
					$content['participants'][$row]['role_label'] = lang(str_replace('X-','',$role));
1766
				}
1767
				$content['participants'][$row]['delete_id'] = strpbrk($uid,'"\'<>') !== false ? md5($uid) : $uid;
1768
				//echo "<p>$uid ($quantity): $role --> {$content['participants'][$row]['role']}</p>\n";
1769
1770
				if (($no_status = !$this->bo->check_status_perms($uid,$event)) || $view)
1771
					$readonlys['participants'][$row]['status'] = $no_status;
1772
				if ($preserv['hide_delete'] || !$this->bo->check_perms(Acl::EDIT,$event))
1773
					$readonlys['participants']['delete'][$uid] = true;
1774
				// todo: make the participants available as links with email as title
1775
				$content['participants'][$row++]['title'] = $this->get_title($uid);
1776
				// enumerate group-invitations, so people can accept/reject them
1777
				if ($name == 'accounts' && $GLOBALS['egw']->accounts->get_type($id) == 'g' &&
1778
					($members = $GLOBALS['egw']->accounts->members($id,true)))
1779
				{
1780
					$sel_options['status']['G'] = lang('Select one');
1781
					// sort members by title
1782
					usort($members, array($this, 'uid_title_cmp'));
1783
					foreach($members as $member)
1784
					{
1785
						if (!isset($participants[$member]) && $this->bo->check_perms(Acl::READ,0,$member))
1786
						{
1787
							$preserv['participants'][$row] = $content['participants'][$row] = array(
1788
								'app'      => 'Group invitation',
1789
								'uid'      => $member,
1790
								'status'   => 'G',
1791
							);
1792
							$readonlys['participants'][$row]['quantity'] = $readonlys['participants']['delete'][$member] = true;
1793
							// read access is enough to invite participants, but you need edit rights to change status
1794
							$readonlys['participants'][$row]['status'] = !$this->bo->check_perms(Acl::EDIT,0,$member);
1795
							$content['participants'][$row++]['title'] = Api\Accounts::username($member);
1796
						}
1797
					}
1798
				}
1799
			}
1800
			// resouces / apps we shedule, atm. resources and addressbook
1801
			$content['participants']['cal_resources'] = '';
1802
			foreach($this->bo->resources as $data)
1803
			{
1804
				if ($data['app'] == 'email') continue;	// make no sense, as we cant search for email
1805
				$content['participants']['cal_resources'] .= ','.$data['app'];
1806
			}
1807
		}
1808
		$content['participants']['status_date'] = $preserv['actual_date'];
1809
		$preserved = array_merge($preserv,$content);
1810
		$event['new_alarm']['options'] = $content['new_alarm']['options'];
1811
		if ($event['alarm'])
1812
		{
1813
			// makes keys of the alarm-array starting with 1
1814
			$content['alarm'] = array(false);
1815
			foreach(array_values($event['alarm']) as $id => $alarm)
1816
			{
1817
				if (!$alarm['all'] && !$this->bo->check_perms(Acl::READ,0,$alarm['owner']))
1818
				{
1819
					continue;	// no read rights to the calendar of the alarm-owner, dont show the alarm
1820
				}
1821
				$alarm['all'] = (int) $alarm['all'];
1822
				// fix alarm time in case of alread run alarms, where the time will be their keep_time / when they will be cleaned up otherwise
1823
				$alarm['time'] = $event['start'] - $alarm['offset'];
1824
				$after = false;
1825
				if($alarm['offset'] < 0)
1826
				{
1827
					$after = true;
1828
					$alarm['offset'] = -1 * $alarm['offset'];
1829
				}
1830
				$days = (int) ($alarm['offset'] / DAY_s);
1831
				$hours = (int) (($alarm['offset'] % DAY_s) / HOUR_s);
1832
				$minutes = (int) (($alarm['offset'] % HOUR_s) / 60);
1833
				$label = array();
1834
				if ($days) $label[] = $days.' '.lang('days');
1835
				if ($hours) $label[] = $hours.' '.lang('hours');
1836
				if ($minutes) $label[] = $minutes.' '.lang('Minutes');
1837
				if (!$label)
1838
				{
1839
					$alarm['offset'] = lang('at start of the event');
1840
				}
1841
				else
1842
				{
1843
					$alarm['offset'] = implode(', ',$label) . ' ' . ($after ? lang('after') : lang('before'));
1844
				}
1845
				$content['alarm'][] = $alarm;
1846
1847
				$readonlys['alarm[delete_alarm]['.$alarm['id'].']'] = !$this->bo->check_perms(Acl::EDIT,$alarm['all'] ? $event : 0,$alarm['owner']);
1848
			}
1849
			if (count($content['alarm']) == 1)
1850
			{
1851
				$content['alarm'] = false; // no alarms added to content array
1852
			}
1853
		}
1854
		else
1855
		{
1856
			$content['alarm'] = false;
1857
		}
1858
		$content['msg'] = $msg;
1859
1860
		if ($view)
1861
		{
1862
			$readonlys['__ALL__'] = true;	// making everything readonly, but widgets set explicitly to false
1863
			$readonlys['button[cancel]'] = $readonlys['action'] =
1864
				$readonlys['before_after'] = $readonlys['button[add_alarm]'] = $readonlys['new_alarm[owner]'] =
1865
				$readonlys['new_alarm[options]'] = $readonlys['new_alarm[date]'] = false;
1866
1867
			$content['participants']['no_add'] = true;
1868
1869
			if(!$event['whole_day'])
1870
			{
1871
				$etpl->setElementAttribute('whole_day', 'disabled', true);
1872
			}
1873
1874
			// respect category permissions
1875
			if(!empty($event['category']))
1876
			{
1877
				$content['category'] = $this->categories->check_list(Acl::READ, $event['category']);
1878
			}
1879
		}
1880
		else
1881
		{
1882
			$readonlys['recur_exception'] = true;
1883
1884
			if ($event['recur_type'] != MCAL_RECUR_NONE)
1885
			{
1886
				$readonlys['recur_exception'] = !count($content['recur_exception']);	// otherwise we get a delete button
1887
				//$onclick =& $etpl->get_cell_attribute('button[delete]','onclick');
1888
				//$onclick = str_replace('Delete this event','Delete this series of recuring events',$onclick);
1889
			}
1890
			elseif ($event['reference'] != 0)
1891
			{
1892
				$readonlys['recur_type'] = $readonlys['recur_enddate'] = true;
1893
				$readonlys['recur_interval'] = $readonlys['recur_data'] = true;
1894
			}
1895
		}
1896
		if($content['category'] && !is_array($content['category']))
1897
		{
1898
			$content['category'] = explode(',',$event['category']);
1899
		}
1900
		// disabling the custom fields tab, if there are none
1901
		$readonlys['tabs'] = array(
1902
			'custom' => !count($this->bo->customfields),
1903
			'participants' => $this->accountsel->account_selection == 'none',
1904
			'history' => !$event['id'],
1905
		);
1906
		if (!isset($GLOBALS['egw_info']['user']['apps']['mail']))	// no mail without mail-app
1907
		{
1908
			unset($sel_options['action']['mail']);
1909
			unset($sel_options['action']['sendmeetingrequest']);
1910
		}
1911
		if (!$event['id'])	// no ical export for new (not saved) events
1912
		{
1913
			$readonlys['action'] = true;
1914
		}
1915
		if (!($readonlys['button[exception]'] = !$this->bo->check_perms(Acl::EDIT,$event) || $event['recur_type'] == MCAL_RECUR_NONE || ($event['recur_enddate'] &&$event['start'] > $event['recur_enddate'])))
1916
		{
1917
			$content['exception_label'] = $this->bo->long_date(max($preserved['actual_date'], $event['start']));
1918
		}
1919
		$readonlys['button[delete]'] = !$event['id'] || $preserved['hide_delete'] || !$this->bo->check_perms(Acl::DELETE,$event);
1920
1921
		if (!$event['id'] || $this->bo->check_perms(Acl::EDIT,$event))	// new event or edit rights to the event ==> allow to add alarm for all users
1922
		{
1923
			$sel_options['owner'][0] = lang('All participants');
1924
		}
1925
		if (isset($event['participant_types']['u'][$this->user]))
1926
		{
1927
			$sel_options['owner'][$this->user] = $this->bo->participant_name($this->user);
1928
		}
1929
		foreach((array) $event['participant_types']['u'] as $uid => $status)
1930
		{
1931
			if ($uid != $this->user && $status != 'R' && $this->bo->check_perms(Acl::EDIT,0,$uid))
1932
			{
1933
				$sel_options['owner'][$uid] = $this->bo->participant_name($uid);
1934
			}
1935
		}
1936
		$content['no_add_alarm'] = !count($sel_options['owner']);	// no rights to set any alarm
1937
		if (!$event['id'])
1938
		{
1939
			$etpl->set_cell_attribute('button[new_alarm]','type','checkbox');
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Etemplate::set_cell_attribute() has been deprecated: use setElementAttribute($name, $attr, $val) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1939
			/** @scrutinizer ignore-deprecated */ $etpl->set_cell_attribute('button[new_alarm]','type','checkbox');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1940
		}
1941
		if ($preserved['no_popup'])
1942
		{
1943
			// If not a popup, load the normal calendar interface on cancel
1944
			$etpl->set_cell_attribute('button[cancel]','onclick','app.calendar.linkHandler(\'index.php?menuaction=calendar.calendar_uiviews.index&ajax=true\')');
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Etemplate::set_cell_attribute() has been deprecated: use setElementAttribute($name, $attr, $val) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1944
			/** @scrutinizer ignore-deprecated */ $etpl->set_cell_attribute('button[cancel]','onclick','app.calendar.linkHandler(\'index.php?menuaction=calendar.calendar_uiviews.index&ajax=true\')');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1945
		}
1946
1947
		// Allow admins to restore deleted events
1948
		if($GLOBALS['egw_info']['server']['calendar_delete_history'] && $event['deleted'] )
1949
		{
1950
			$content['deleted'] = $preserved['deleted'] = null;
1951
			$etpl->set_cell_attribute('button[save]', 'label', 'Recover');
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Etemplate::set_cell_attribute() has been deprecated: use setElementAttribute($name, $attr, $val) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1951
			/** @scrutinizer ignore-deprecated */ $etpl->set_cell_attribute('button[save]', 'label', 'Recover');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1952
			$etpl->set_cell_attribute('button[apply]', 'disabled', true);
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Etemplate::set_cell_attribute() has been deprecated: use setElementAttribute($name, $attr, $val) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1952
			/** @scrutinizer ignore-deprecated */ $etpl->set_cell_attribute('button[apply]', 'disabled', true);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1953
		}
1954
		// Allow users to prevent notifications?
1955
		$etpl->set_cell_attribute('no_notifications', 'disabled', !$GLOBALS['egw_info']['server']['calendar_allow_no_notification']);
0 ignored issues
show
Deprecated Code introduced by
The function EGroupware\Api\Etemplate::set_cell_attribute() has been deprecated: use setElementAttribute($name, $attr, $val) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1955
		/** @scrutinizer ignore-deprecated */ $etpl->set_cell_attribute('no_notifications', 'disabled', !$GLOBALS['egw_info']['server']['calendar_allow_no_notification']);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
1956
1957
		// Setup history tab
1958
		$this->setup_history($content, $sel_options);
1959
1960
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - '
1961
			. (!$event['id'] ? lang('Add')
1962
				: ($view ? ($content['edit_single'] ? lang('View exception') : ($content['recur_type'] ? lang('View series') : lang('View')))
1963
					: ($content['edit_single'] ? lang('Create exception') : ($content['recur_type'] ? lang('Edit series') : lang('Edit')))));
1964
1965
		$content['cancel_needs_refresh'] = (bool)$_GET['cancel_needs_refresh'];
1966
1967
		if (!empty($preserved['lock_token'])) $content['lock_token'] = $preserved['lock_token'];
1968
1969
		// non_interactive==true from $_GET calls immediate save action without displaying the edit form
1970
		if(isset($_GET['non_interactive']) && (bool)$_GET['non_interactive'] === true)
1971
		{
1972
			unset($_GET['non_interactive']);	// prevent process_exec <--> edit loops
1973
			$content['button']['save'] = true;
1974
			$this->process_edit(array_merge($content,$preserved));
1975
		}
1976
		else
1977
		{
1978
			$etpl->exec('calendar.calendar_uiforms.process_edit',$content,$sel_options,$readonlys,$preserved,$preserved['no_popup'] ? 0 : 2);
1979
		}
1980
	}
1981
1982
	/**
1983
	 * Remove (shared) lock via ajax, when edit popup get's closed
1984
	 *
1985
	 * @param int $id
1986
	 * @param string $token
1987
	 */
1988
	function ajax_unlock($id,$token)
1989
	{
1990
		$lock_path = Vfs::app_entry_lock_path('calendar',$id);
1991
		$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
1992
1993
		if (($lock = Vfs::checkLock($lock_path)) && $lock['owner'] == $lock_owner || $lock['token'] == $token)
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($lock = EGroupware\Api\...lock['token'] == $token, Probably Intended Meaning: $lock = EGroupware\Api\V...ock['token'] == $token)
Loading history...
1994
		{
1995
			Vfs::unlock($lock_path,$token,false);
1996
		}
1997
	}
1998
1999
	/**
2000
	 * Display iCal meeting request for EMail app and allow to accept, tentative or reject it or a reply and allow to apply it
2001
	 *
2002
	 * @todo Handle situation when user is NOT invited, but eg. can view that mail ...
2003
	 * @param array $event = null; special usage if $event is array('event'=>null,'msg'=>'','useSession'=>true) we
2004
	 * 		are called by new mail-app; and we intend to use the stuff passed on by session
2005
	 * @param string $msg = null
2006
	 */
2007
	function meeting(array $event=null, $msg=null)
2008
	{
2009
		$user = $GLOBALS['egw_info']['user']['account_id'];
2010
		$readonlys['button[apply]'] = true;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$readonlys was never initialized. Although not strictly required by PHP, it is generally a good practice to add $readonlys = array(); before regardless.
Loading history...
2011
		$_usesession=!is_array($event);
2012
		//special usage if $event is array('event'=>null,'msg'=>'','useSession'=>true) we
2013
		//are called by new mail-app; and we intend to use the stuff passed on by session
2014
		if ($event == array('event'=>null,'msg'=>'','useSession'=>true))
2015
		{
2016
			$event=null; // set to null
2017
			$_usesession=true; // trigger session read
2018
		}
2019
		if (!is_array($event))
2020
		{
2021
			$ical_charset = 'utf-8';
2022
			$ical_string = $_GET['ical'];
2023
			if ($ical_string == 'session' || $_usesession)
2024
			{
2025
				$session_data = Api\Cache::getSession('calendar', 'ical');
2026
				$ical_string = $session_data['attachment'];
2027
				$ical_charset = $session_data['charset'];
2028
				$ical_method = $session_data['method'];
2029
				unset($session_data);
2030
			}
2031
			$ical = new calendar_ical();
2032
			if (!($events = $ical->icaltoegw($ical_string, '', $ical_charset)) || count($events) != 1)
2033
			{
2034
				error_log(__METHOD__."('$_GET[ical]') error parsing iCal!");
2035
				$GLOBALS['egw']->framework->render(Api\Html::fieldset('<pre>'.htmlspecialchars($ical_string).'</pre>',
2036
					lang('Error: importing the iCal')));
2037
				return;
2038
			}
2039
			$event = array_shift($events);
2040
2041
			// convert event from servertime returned by calendar_ical to user-time
2042
			$this->bo->server2usertime($event);
2043
2044
			if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true)) && // true = read the exception
2045
				!$existing_event['deleted'])
2046
			{
2047
				switch(strtolower($ical_method))
2048
				{
2049
					case 'reply':
2050
						// first participant is the one replying (our iCal parser adds owner first!)
2051
						$parts = $event['participants'];
2052
						unset($parts[$existing_event['owner']]);
2053
						$event['ical_sender_uid'] = key($parts);
2054
						$event['ical_sender_status'] = current($parts);
2055
						$quantity = $role = null;
2056
						calendar_so::split_status($event['ical_sender_status'], $quantity, $role);
2057
2058
						if ($event['ical_sender_uid'] && $this->bo->check_status_perms($event['ical_sender_uid'], $existing_event))
2059
						{
2060
							$existing_status = $existing_event['participants'][$event['ical_sender_uid']];
2061
							calendar_so::split_status($existing_status, $quantity, $role);
2062
							if ($existing_status != $event['ical_sender_status'])
2063
							{
2064
								$readonlys['button[apply]'] = false;
2065
							}
2066
							else
2067
							{
2068
								$event['error'] = lang('Status already applied');
2069
							}
2070
						}
2071
						break;
2072
2073
					case 'request':
2074
						$status = $existing_event['participants'][$user];
2075
						calendar_so::split_status($status, $quantity, $role);
2076
						if (strtolower($ical_method) == 'response' && isset($existing_event['participants'][$user]) &&
2077
							$status != 'U' && isset($this->bo->verbose_status[$status]))
2078
						{
2079
							$event['error'] = lang('You already replied to this invitation with').': '.lang($this->bo->verbose_status[$status]);
2080
						}
2081
						else
2082
						{
2083
							$event['error'] = lang('Using already existing event on server.');
2084
						}
2085
						$user_and_memberships = $GLOBALS['egw']->accounts->memberships($user, true);
2086
						$user_and_memberships[] = $user;
2087
						if (!array_intersect(array_keys($event['participants']), $user_and_memberships))
2088
						{
2089
							$event['error'] .= ($event['error'] ? "\n" : '').lang('You are not invited to that event!');
2090
							if ($event['id'])
2091
							{
2092
								$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
2093
									$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
2094
							}
2095
						}
2096
						break;
2097
				}
2098
				$event['id'] = $existing_event['id'];
2099
			}
2100
			else	// event not in calendar
2101
			{
2102
				$readonlys['button[cancel]'] = true;	// no way to remove a canceled event not in calendar
2103
			}
2104
			$event['participant_types'] = array();
2105
			foreach($event['participants'] as $uid => $status)
2106
			{
2107
				$user_type = $user_id = null;
2108
				calendar_so::split_user($uid, $user_type, $user_id);
2109
				$event['participants'][$uid] = $event['participant_types'][$user_type][$user_id] =
2110
					$status && $status !== 'X' ? $status : 'U';	// X --> no status given --> U = unknown
2111
			}
2112
			//error_log(__METHOD__."(...) parsed as ".array2string($event));
2113
			$event['recure'] = $this->bo->recure2string($event);
2114
			$event['all_participants'] = implode(",\n",$this->bo->participants($event, true));
2115
2116
			// EGroupware event has been deleted, dont let user resurect it by accepting again
2117
			if ($existing_event && $existing_event['deleted'] && strtolower($ical_method) !== 'cancel')
2118
			{
2119
				// check if this is an EGroupware event or has an external organizer
2120
				foreach($existing_event['participants'] as $uid => $status)
2121
				{
2122
					$quantity = $role = null;
2123
					calendar_so::split_status($status, $quantity, $role);
2124
					if (!is_numeric($uid) && $role == 'CHAIR') break;
2125
				}
2126
				if (!(!is_numeric($uid) && $role == 'CHAIR'))
2127
				{
2128
					$event['error'] = lang('Event has been deleted by organizer!');
2129
					$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
2130
						$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
2131
				}
2132
			}
2133
			// ignore events in the past (for recurring events check enddate!)
2134
			elseif ($this->bo->date2ts($event['start']) < $this->bo->now_su &&
2135
				(!$event['recur_type'] || $event['recur_enddate'] && $event['recur_enddate'] < $this->bo->now_su))
2136
			{
2137
				$event['error'] = lang('Requested meeting is in the past!');
2138
				$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
2139
					$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
2140
			}
2141
		}
2142
		else
2143
		{
2144
			//_debug_array($event);
2145
			$button = key($event['button']);
2146
			unset($event['button']);
2147
2148
			// clear notification errors
2149
			notifications::errors(true);
2150
2151
			switch($button)
2152
			{
2153
				case 'reject':
2154
					if (!$event['id'])
2155
					{
2156
						// send reply to organizer
2157
						$this->bo->send_update(MSG_REJECTED,array('e'.$event['organizer'] => 'DCHAIR'),$event);
2158
						break;	// no need to store rejected event
2159
					}
2160
					// fall-through
2161
				case 'accept':
2162
				case 'tentativ':
2163
					$status = strtoupper($button[0]);	// A, R or T
2164
					if (!$event['id'])
2165
					{
2166
						// if organizer is a EGroupware user, but we have no rights to organizers calendar
2167
						if (isset($event['owner']) && !$this->bo->check_perms(Acl::ADD,0,$event['owner']))
2168
						{
2169
							// --> make organize a participant with role chair and current user the owner
2170
							$event['participant_types']['u'] = $event['participants'][$event['owner']] =
2171
								calendar_so::combine_status('A', 1, 'CHAIR');
2172
							$event['owner'] = $this->user;
2173
						}
2174
						// store event without notifications!
2175
						if (($event['id'] = $this->bo->update($event, $ignore_conflicts=true, true, false, true, $msg, true)))
2176
						{
2177
							$msg[] = lang('Event saved');
2178
						}
2179
						else
2180
						{
2181
							$msg[] = lang('Error saving the event!');
2182
							break;
2183
						}
2184
					}
2185
					// do we need to update the event itself (user-status is reset to old in event_changed!)
2186
					elseif (self::event_changed($event, $event['old']))
0 ignored issues
show
Bug Best Practice introduced by
The method calendar_uiforms::event_changed() is not static, but was called statically. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2186
					elseif (self::/** @scrutinizer ignore-call */ event_changed($event, $event['old']))
Loading history...
2187
					{
2188
						// check if we are allowed to update the event
2189
						if($this->bo->check_perms(Acl::EDIT, $event['old']))
2190
						{
2191
							if ($event['recurrence'] && !$event['old']['reference'] && ($recur_event = $this->bo->read($event['id'])))
2192
							{
2193
								// first we need to add the exception to the recurrence master
2194
								$recur_event['recur_exception'][] = $event['recurrence'];
2195
								// check if we need to move the alarms, because they are next on that exception
2196
								$this->bo->check_move_alarms($recur_event, null, $event['recurrence']);
2197
								unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
2198
								unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
2199
								$this->bo->update($recur_event, $ignore_conflicts=true, true, false, true, $msg, true);
2200
2201
								// then we need to create the exception as new event
2202
								unset($event['id']);
2203
								$event['reference'] = $event['old']['id'];
2204
								$event['caldav_name'] = $event['old']['caldav_name'];
2205
							}
2206
							else
2207
							{
2208
								// keep all EGroupware only values of existing events plus alarms
2209
								unset($event['alarm']);
2210
								$event = array_merge($event['old'], $event);
2211
							}
2212
							unset($event['old']);
2213
2214
							if (($event['id'] = $this->bo->update($event, $ignore_conflicts=true, true, false, true, $msg, true)))
2215
							{
2216
								$msg[] = lang('Event saved');
2217
							}
2218
							else
2219
							{
2220
								$msg[] = lang('Error saving the event!');
2221
								break;
2222
							}
2223
						}
2224
						else
2225
						{
2226
							$event['id'] = $event['old']['id'];
2227
							// disable "warning" that we have no rights to store any modifications
2228
							// as that confuses our users, who only want to accept or reject
2229
							//$msg[] = lang('Not enough rights to update the event!');
2230
						}
2231
					}
2232
					else
2233
					{
2234
						$event['id'] = $event['old']['id'];
2235
					}
2236
					// set status and send notification / meeting response
2237
					if ($this->bo->set_status($event['id'], $user, $status, $event['recurrence']))
2238
					{
2239
						$msg[] = lang('Status changed');
2240
					}
2241
					break;
2242
2243
				case 'apply':
2244
					// set status and send notification / meeting response
2245
					if ($this->bo->set_status($event['id'], $event['ical_sender_uid'], $event['ical_sender_status'], $event['recurrence']))
2246
					{
2247
						$msg = lang('Status changed');
2248
					}
2249
					break;
2250
2251
				case 'cancel':
2252
					if ($event['id'] && $this->bo->set_status($event['id'], $user, 'R', $event['recurrence']))
2253
					{
2254
						$msg = lang('Status changed');
2255
					}
2256
					break;
2257
			}
2258
			// add notification-errors, if we have some
2259
			$msg = array_merge((array)$msg, notifications::errors(true));
2260
		}
2261
		Framework::message(implode("\n", (array)$msg));
2262
		$readonlys['button[edit]'] = !$event['id'];
2263
		$event['ics_method'] = $readonlys['ics_method'] = strtolower($ical_method);
2264
		switch(strtolower($ical_method))
2265
		{
2266
			case 'reply':
2267
				$event['ics_method_label'] = lang('Reply to meeting request');
2268
				break;
2269
			case 'cancel':
2270
				$event['ics_method_label'] = lang('Meeting canceled');
2271
				break;
2272
			case 'request':
2273
			default:
2274
				$event['ics_method_label'] = lang('Meeting request');
2275
				break;
2276
		}
2277
		$tpl = new Etemplate('calendar.meeting');
2278
		$tpl->exec('calendar.calendar_uiforms.meeting', $event, array(), $readonlys, $event+array(
2279
			'old' => $existing_event,
2280
		), 2);
2281
	}
2282
2283
	/**
2284
	 * Check if an event changed and need to be updated
2285
	 *
2286
	 * We are reseting (keeping) status of system users to old value, as they might have been updated!
2287
	 *
2288
	 * @param array& $_event invitation, on return user status changed to the one from old $old
2289
	 * @param array $_old existing event on server
2290
	 * @return boolean true if there are some changes, false if not
2291
	 */
2292
	function event_changed(array &$_event, array $_old)
2293
	{
2294
		static $keys_to_check = array('start', 'end', 'title', 'description', 'location', 'participants',
2295
			'recur_type', 'recur_data', 'recur_interval', 'recur_exception');
2296
2297
		// only compare certain fields, taking account unset, null or '' values
2298
		$event = array_intersect_key($_event+array('recur_exception'=>array()), array_flip($keys_to_check));
2299
		$old = array_intersect_key(array_diff($_old, array(null, '')), array_flip($keys_to_check));
2300
2301
		// keep the status of existing participants (users)
2302
		foreach($old['participants'] as $uid => $status)
2303
		{
2304
			if (is_numeric($uid) && $uid > 0)
2305
			{
2306
				$event['participants'][$uid] = $_event['participants'][$uid] = $status;
2307
			}
2308
		}
2309
2310
		$ret = $event != $old;
2311
		//error_log(__METHOD__."() returning ".array2string($ret)." diff=".array2string(array_udiff_assoc($event, $old, function($a, $b) { return (int)($a != $b); })));
2312
		return $ret;
2313
	}
2314
2315
	/**
2316
	 * displays a scheduling conflict
2317
	 *
2318
	 * @param array $event
2319
	 * @param array $conflicts array with conflicting events, the events are not garantied to be readable by the user!
2320
	 * @param array $preserv data to preserv
2321
	 */
2322
	function conflicts($event,$conflicts,$preserv)
2323
	{
2324
		$etpl = new Etemplate('calendar.conflicts');
2325
		$allConflicts = array();
2326
2327
		foreach($conflicts as $k => $conflict)
2328
		{
2329
			$is_readable = $this->bo->check_perms(Acl::READ,$conflict);
2330
2331
			$conflicts[$k] += array(
2332
				'icon_participants' => $is_readable ? (count($conflict['participants']) > 1 ? 'users' : 'single') : 'private',
2333
				'tooltip_participants' => $is_readable ? implode(', ',$this->bo->participants($conflict)) : '',
2334
				'time' => $this->bo->long_date($conflict['start'],$conflict['end'],true),
2335
				'conflicting_participants' => implode(",\n",$this->bo->participants(array(
2336
					'participants' => array_intersect_key((array)$conflict['participants'],$event['participants']),
2337
				),true,true)),	// show group invitations too
2338
				'icon_recur' => $conflict['recur_type'] != MCAL_RECUR_NONE ? 'recur' : '',
2339
				'text_recur' => $conflict['recur_type'] != MCAL_RECUR_NONE ? lang('Recurring event') : ' ',
2340
			);
2341
			$allConflicts += array_intersect_key((array)$conflict['participants'],$event['participants']);
2342
		}
2343
		$content = $event + array(
2344
			'conflicts' => array_values($conflicts),	// conflicts have id-start as key
2345
		);
2346
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('Scheduling conflict');
2347
		$resources_config = Api\Config::read('resources');
2348
		$readonlys = array();
2349
2350
		foreach (array_keys($allConflicts) as $pId)
2351
		{
2352
			if(substr($pId,0,1) == 'r' && $resources_config ) // resources Allow ignore conflicts
0 ignored issues
show
Bug Best Practice introduced by
The expression $resources_config of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2353
			{
2354
2355
				switch ($resources_config['ignoreconflicts'])
2356
				{
2357
					case 'no':
2358
						$readonlys['button[ignore]'] = true;
2359
						break;
2360
					case 'allusers':
2361
						$readonlys['button[ignore]'] = false;
2362
						break;
2363
					default:
2364
						if (!$this->bo->check_status_perms($pId, $event))
2365
						{
2366
							$readonlys['button[ignore]'] = true;
2367
							break;
2368
						}
2369
				}
2370
			}
2371
		}
2372
		$etpl->exec('calendar.calendar_uiforms.process_edit',$content,array(),$readonlys,array_merge($event,$preserv),$preserv['no_popup'] ? 0 : 2);
2373
	}
2374
2375
	/**
2376
	 * Callback for freetimesearch button in edit
2377
	 *
2378
	 * It stores the data of the submitted form in the session under 'freetimesearch_args_'.$edit_content['id'],
2379
	 * for later retrival of the freetimesearch method, called by the returned window.open() command.
2380
	 *
2381
	 * @param array $edit_content
2382
	 * @return string with xajaxResponse
2383
	 */
2384
	function ajax_freetimesearch(array $edit_content)
2385
	{
2386
		$response = Api\Json\Response::get();
2387
		//$response->addAlert(__METHOD__.'('.array2string($edit_content).')');
2388
2389
		// convert start/end date-time values to timestamps
2390
		foreach(array('start', 'end') as $name)
2391
		{
2392
			if (!empty($edit_content[$name]))
2393
			{
2394
				$date = new Api\DateTime($edit_content[$name]);
2395
				$edit_content[$name] = $date->format('ts');
2396
			}
2397
		}
2398
2399
		if ($edit_content['duration'])
2400
		{
2401
			$edit_content['end'] = $edit_content['start'] + $edit_content['duration'];
2402
		}
2403
		if ($edit_content['whole_day'])
2404
		{
2405
			$arr = $this->bo->date2array($edit_content['start']);
2406
			$arr['hour'] = $arr['minute'] = $arr['second'] = 0; unset($arr['raw']);
2407
			$edit_content['start'] = $this->bo->date2ts($arr);
2408
			$earr = $this->bo->date2array($edit_content['end']);
2409
			$earr['hour'] = 23; $earr['minute'] = $earr['second'] = 59; unset($earr['raw']);
2410
			$edit_content['end'] = $this->bo->date2ts($earr);
2411
		}
2412
		$content = array(
2413
			'start'    => $edit_content['start'],
2414
			'duration' => $edit_content['end'] - $edit_content['start'],
2415
			'end'      => $edit_content['end'],
2416
			'cal_id'   => $edit_content['id'],
2417
			'recur_type'   => $edit_content['recur_type'],
2418
			'participants' => array(),
2419
		);
2420
		foreach($edit_content['participants'] as $key => $data)
2421
		{
2422
			if (is_numeric($key) && !$edit_content['participants']['delete'][$data['uid']] &&
2423
				!$edit_content['participants']['delete'][md5($data['uid'])])
2424
			{
2425
				$content['participants'][] = $data['uid'];
2426
			}
2427
			elseif ($key == 'account' && !is_array($data) && $data)
2428
			{
2429
				$content['participants'][] = $data;
2430
			}
2431
		}
2432
		// default search parameters
2433
		$content['start_time'] = $edit_content['whole_day'] ? 0 : $this->cal_prefs['workdaystarts'];
2434
		$content['end_time'] = $this->cal_prefs['workdayends'];
2435
		if ($this->cal_prefs['workdayends']*HOUR_s < $this->cal_prefs['workdaystarts']*HOUR_s+$content['duration'])
2436
		{
2437
			$content['end_time'] = 0;	// no end-time limit, as duration would never fit
2438
		}
2439
		$content['weekdays'] = MCAL_M_WEEKDAYS;
2440
2441
		$content['search_window'] = 7 * DAY_s;
2442
2443
		// store content in session
2444
		Api\Cache::setSession('calendar','freetimesearch_args_'.(int)$edit_content['id'],$content);
2445
2446
		//menuaction=calendar.calendar_uiforms.freetimesearch&values2url('start,end,duration,participants,recur_type,whole_day'),ft_search,700,500
2447
		$link = 'calendar.calendar_uiforms.freetimesearch&cal_id='. $edit_content['id'];
2448
2449
		$response->call('app.calendar.freetime_search_popup',$link);
2450
2451
		//$response->addScriptCall('egw_openWindowCentered2',$link,'ft_search',700,500);
2452
2453
	}
2454
2455
	/**
2456
	 * Freetime search
2457
	 *
2458
	 * As the function is called in a popup via javascript, parametes get initialy transfered via the url
2459
	 * @param array $content=null array with parameters or false (default) to use the get-params
2460
	 * @param string start[str] start-date
0 ignored issues
show
Documentation Bug introduced by
The doc comment start[str] at position 1 could not be parsed: Expected ']' at position 1, but found '['.
Loading history...
2461
	 * @param string start[hour] start-hour
2462
	 * @param string start[min] start-minutes
2463
	 * @param string end[str] end-date
2464
	 * @param string end[hour] end-hour
2465
	 * @param string end[min] end-minutes
2466
	 * @param string participants ':' delimited string of user-id's
2467
	 */
2468
	function freetimesearch($content = null)
2469
	{
2470
		$etpl = new Etemplate('calendar.freetimesearch');
2471
		$sel_options['search_window'] = array(
0 ignored issues
show
Comprehensibility Best Practice introduced by
$sel_options was never initialized. Although not strictly required by PHP, it is generally a good practice to add $sel_options = array(); before regardless.
Loading history...
2472
			7*DAY_s		=> lang('one week'),
2473
			14*DAY_s	=> lang('two weeks'),
2474
			31*DAY_s	=> lang('one month'),
2475
			92*DAY_s	=> lang('three month'),
2476
			365*DAY_s	=> lang('one year'),
2477
		);
2478
		if (!is_array($content))
2479
		{
2480
			// get content from session (and delete it immediatly)
2481
			$content = Api\Cache::getSession('calendar','freetimesearch_args_'.(int)$_GET['cal_id']);
2482
			Api\Cache::unsetSession('calendar','freetimesearch_args_'.(int)$_GET['cal_id']);
2483
			//Since the start_time and end_time from calendar_user_preferences are numbers, not timestamp, in order to show them on date-timeonly
2484
			//widget we need to convert them from numbers to timestamps, only for the first time when we have template without content
2485
			$sTime = $content['start_time'];
2486
			$eTime = $content['end_time'];
2487
			$content['start_time'] = strtotime(((strlen($content['start_time'])<2)?("0".$content['start_time']):$content['start_time']).":00");
2488
			$content['end_time'] = strtotime(((strlen($content['end_time'])<2)?("0".$content['end_time']):$content['end_time']).":00");
2489
2490
			// pick a searchwindow fitting the duration (search for a 10 day slot in a one week window never succeeds)
2491
			foreach(array_keys($sel_options['search_window']) as $window)
2492
			{
2493
				if ($window > $content['duration'])
2494
				{
2495
					$content['search_window'] = $window;
2496
					break;
2497
				}
2498
			}
2499
		}
2500
		else
2501
		{
2502
			if (!$content['duration']) $content['duration'] = $content['end'] - $content['start'];
2503
			$weekds = 0;
2504
			foreach ($content['weekdays'] as &$wdays)
2505
			{
2506
				$weekds = $weekds + $wdays;
2507
			}
2508
			//split_freetime_daywise function expects to get start_time and end_time values as string numbers, only "hour", therefore, since the date-timeonly widget returns
2509
			//always timestamp, we need to convert them to only "hour" string numbers.
2510
			$sTime = date('H', $content['start_time']);
2511
			$eTime = date('H', $content['end_time']);
2512
		}
2513
2514
		if ($content['recur_type'])
2515
		{
2516
			$content['msg'] .= lang('Only the initial date of that recurring event is checked!');
2517
		}
2518
		$content['freetime'] = $this->freetime($content['participants'],$content['start'],$content['start']+$content['search_window'],$content['duration'],$content['cal_id']);
2519
		$content['freetime'] = $this->split_freetime_daywise($content['freetime'],$content['duration'],(is_array($content['weekdays'])?$weekds:$content['weekdays']),$sTime,$eTime,$sel_options);
2520
2521
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('freetime search');
2522
2523
		$sel_options['duration'] = $this->durations;
2524
		if ($content['duration'] && isset($sel_options['duration'][$content['duration']])) $content['end'] = '';
2525
2526
		$etpl->exec('calendar.calendar_uiforms.freetimesearch',$content,$sel_options,NULL,array(
2527
				'participants'	=> $content['participants'],
2528
				'cal_id'		=> $content['cal_id'],
2529
				'recur_type'	=> $content['recur_type'],
2530
			),2);
2531
	}
2532
2533
	/**
2534
	 * calculate the freetime of given $participants in a certain time-span
2535
	 *
2536
	 * @param array $participants user-id's
2537
	 * @param int $start start-time timestamp in user-time
2538
	 * @param int $end end-time timestamp in user-time
2539
	 * @param int $duration min. duration in sec, default 1
2540
	 * @param int $cal_id own id for existing events, to exclude them from being busy-time, default 0
2541
	 * @return array of free time-slots: array with start and end values
2542
	 */
2543
	function freetime($participants,$start,$end,$duration=1,$cal_id=0)
2544
	{
2545
		if ($this->debug > 2) $this->bo->debug_message(__METHOD__.'(participants=%1, start=%2, end=%3, duration=%4, cal_id=%5)',true,$participants,$start,$end,$duration,$cal_id);
2546
2547
		$busy = $this->bo->search(array(
2548
			'start' => $start,
2549
			'end'	=> $end,
2550
			'users'	=> $participants,
2551
			'ignore_acl' => true,	// otherwise we get only events readable by the user
2552
		));
2553
		$busy[] = array(	// add end-of-search-date as event, to cope with empty search and get freetime til that date
2554
			'start'	=> $end,
2555
			'end'	=> $end,
2556
		);
2557
		$ft_start = $start;
2558
		$freetime = array();
2559
		$n = 0;
2560
		foreach($busy as $event)
2561
		{
2562
			if ((int)$cal_id && $event['id'] == (int)$cal_id) continue;	// ignore our own event
2563
2564
 			if ($event['non_blocking']) continue; // ignore non_blocking events
2565
2566
			// check if from all wanted participants at least one has a not rejected status in found event
2567
			$non_rejected_found = false;
2568
			foreach($participants as $uid)
2569
			{
2570
				$status = $event['participants'][$uid];
2571
				$quantity = $role = null;
2572
				calendar_so::split_status($status, $quantity, $role);
2573
				if ($status == 'R' || $role == 'NON-PARTICIPANT') continue;
2574
2575
				if (isset($event['participants'][$uid]) ||
2576
					$uid > 0 && array_intersect(array_keys((array)$event['participants']),
2577
						$GLOBALS['egw']->accounts->memberships($uid, true)))
2578
				{
2579
					$non_rejected_found = true;
2580
					break;
2581
				}
2582
			}
2583
			if (!$non_rejected_found) continue;
2584
2585
			if ($this->debug)
2586
			{
2587
				echo "<p>ft_start=".date('D d.m.Y H:i',$ft_start)."<br>\n";
2588
				echo "event[title]=$event[title]<br>\n";
2589
				echo "event[start]=".date('D d.m.Y H:i',$event['start'])."<br>\n";
2590
				echo "event[end]=".date('D d.m.Y H:i',$event['end'])."<br>\n";
2591
			}
2592
			// $events ends before our actual position ==> ignore it
2593
			if ($event['end'] < $ft_start)
2594
			{
2595
				//echo "==> event ends before ft_start ==> continue<br>\n";
2596
				continue;
2597
			}
2598
			// $events starts before our actual position ==> set start to it's end and go to next event
2599
			if ($event['start'] < $ft_start)
2600
			{
2601
				//echo "==> event starts before ft_start ==> set ft_start to it's end & continue<br>\n";
2602
				$ft_start = $event['end'];
2603
				continue;
2604
			}
2605
			$ft_end = $event['start'];
2606
2607
			// only show slots equal or bigger to min_length
2608
			if ($ft_end - $ft_start >= $duration)
2609
			{
2610
				$freetime[++$n] = array(
2611
					'start'	=> $ft_start,
2612
					'end'	=> $ft_end,
2613
				);
2614
				if ($this->debug > 1) echo "<p>freetime: ".date('D d.m.Y H:i',$ft_start)." - ".date('D d.m.Y H:i',$ft_end)."</p>\n";
2615
			}
2616
			$ft_start = $event['end'];
2617
		}
2618
		if ($this->debug > 0) $this->bo->debug_message('uiforms::freetime(participants=%1, start=%2, end=%3, duration=%4, cal_id=%5) freetime=%6',true,$participants,$start,$end,$duration,$cal_id,$freetime);
2619
2620
		return $freetime;
2621
	}
2622
2623
	/**
2624
	 * split the freetime in daywise slot, taking into account weekdays, start- and stop-times
2625
	 *
2626
	 * If the duration is bigger then the difference of start- and end_time, the end_time is ignored
2627
	 *
2628
	 * @param array $freetime free time-slots: array with start and end values
2629
	 * @param int $duration min. duration in sec
2630
	 * @param int $weekdays allowed weekdays, bitfield of MCAL_M_...
2631
	 * @param int $_start_time minimum start-hour 0-23
2632
	 * @param int $_end_time maximum end-hour 0-23, or 0 for none
2633
	 * @param array $sel_options on return options for start-time selectbox
2634
	 * @return array of free time-slots: array with start and end values
2635
	 */
2636
	function split_freetime_daywise($freetime, $duration, $weekdays, $_start_time, $_end_time, &$sel_options)
2637
	{
2638
		if ($this->debug > 1) $this->bo->debug_message('uiforms::split_freetime_daywise(freetime=%1, duration=%2, start_time=%3, end_time=%4)',true,$freetime,$duration,$_start_time,$_end_time);
2639
2640
		$freetime_daywise = array();
2641
		if (!is_array($sel_options)) $sel_options = array();
0 ignored issues
show
introduced by
The condition is_array($sel_options) is always true.
Loading history...
2642
		$time_format = $this->common_prefs['timeformat'] == 12 ? 'h:i a' : 'H:i';
2643
2644
		$start_time = (int) $_start_time;	// ignore leading zeros
2645
		$end_time   = (int) $_end_time;
2646
2647
		// ignore the end_time, if duration would never fit
2648
		if (($end_time - $start_time)*HOUR_s < $duration)
2649
		{
2650
			$end_time = 0;
2651
			if ($this->debug > 1) $this->bo->debug_message('uiforms::split_freetime_daywise(, duration=%2, start_time=%3,..) end_time set to 0, it never fits durationn otherwise',true,$duration,$start_time);
2652
		}
2653
		$n = 0;
2654
		foreach($freetime as $ft)
2655
		{
2656
			$adaybegin = $this->bo->date2array($ft['start']);
2657
			$adaybegin['hour'] = $adaybegin['minute'] = $adaybegin['second'] = 0;
2658
			unset($adaybegin['raw']);
2659
			$daybegin = $this->bo->date2ts($adaybegin);
2660
2661
			for($t = $daybegin; $t < $ft['end']; $t += DAY_s,$daybegin += DAY_s)
2662
			{
2663
				$dow = date('w',$daybegin+DAY_s/2);	// 0=Sun, .., 6=Sat
2664
				$mcal_dow = pow(2,$dow);
2665
				if (!($weekdays & $mcal_dow))
2666
				{
2667
					//echo "wrong day of week $dow<br>\n";
2668
					continue;	// wrong day of week
2669
				}
2670
				$start = $t < $ft['start'] ? $ft['start'] : $t;
2671
2672
				if ($start-$daybegin < $start_time*HOUR_s)	// start earlier then start_time
2673
				{
2674
					$start = $daybegin + $start_time*HOUR_s;
2675
				}
2676
				// if end_time given use it, else the original slot's end
2677
				$end = $end_time ? $daybegin + $end_time*HOUR_s : $ft['end'];
2678
				if ($end > $ft['end']) $end = $ft['end'];
2679
2680
				// slot to small for duration
2681
				if ($end - $start < $duration)
2682
				{
2683
					//echo "slot to small for duration=$duration<br>\n";
2684
					continue;
2685
				}
2686
				$freetime_daywise[++$n] = array(
2687
					'start'	=> $start,
2688
					'end'	=> $end,
2689
				);
2690
				$times = array();
2691
				for ($s = $start; $s+$duration <= $end && $s < $daybegin+DAY_s; $s += 60*$this->cal_prefs['interval'])
2692
				{
2693
					$e = $s + $duration;
2694
					$end_date = $e-$daybegin > DAY_s ? lang(date('l',$e)).' '.date($this->common_prefs['dateformat'],$e).' ' : '';
2695
					$times[$s] = date($time_format,$s).' - '.$end_date.date($time_format,$e);
2696
				}
2697
				$sel_options[$n.'start'] = $times;
2698
			}
2699
		}
2700
		return $freetime_daywise;
2701
	}
2702
2703
	/**
2704
     * Export events as vCalendar version 2.0 files (iCal)
2705
     *
2706
     * @param int|array $content numeric cal_id or submitted content from etempalte::exec
2707
     * @param boolean $return_error should an error-msg be returned or a regular page with it generated (default)
2708
     * @return string error-msg if $return_error
2709
     */
2710
    function export($content=0,$return_error=false)
2711
    {
2712
		$boical = new calendar_ical();
2713
		#error_log(__METHOD__.print_r($content,true));
2714
		if (is_numeric($cal_id = $content ? $content : $_REQUEST['cal_id']))
2715
		{
2716
			if (!($ical =& $boical->exportVCal(array($cal_id),'2.0','PUBLISH',false)))
2717
			{
2718
				$msg = lang('Permission denied');
2719
2720
				if ($return_error) return $msg;
2721
			}
2722
			else
2723
			{
2724
				Api\Header\Content::type('event.ics','text/calendar',bytes($ical));
2725
				echo $ical;
2726
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2727
			}
2728
		}
2729
		if (is_array($content))
2730
		{
2731
			$events =& $this->bo->search(array(
2732
				'start' => $content['start'],
2733
				'end'   => $content['end'],
2734
				'enum_recuring' => false,
2735
				'daywise'       => false,
2736
				'owner'         => $this->owner,
2737
				'date_format'   => 'server',    // timestamp in server time for boical class
2738
			));
2739
			if (!$events)
0 ignored issues
show
introduced by
$events is of type iterator, thus it always evaluated to true.
Loading history...
2740
			{
2741
				$msg = lang('No events found');
2742
			}
2743
			else
2744
			{
2745
				$ical =& $boical->exportVCal($events,'2.0','PUBLISH',false);
2746
				Api\Header\Content::type($content['file'] ? $content['file'] : 'event.ics','text/calendar',bytes($ical));
2747
				echo $ical;
2748
				exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2749
			}
2750
		}
2751
		if (!is_array($content))
0 ignored issues
show
introduced by
The condition is_array($content) is always false.
Loading history...
2752
		{
2753
			$content = array(
2754
				'start' => $this->bo->date2ts($_REQUEST['start'] ? $_REQUEST['start'] : $this->date),
2755
				'end'   => $this->bo->date2ts($_REQUEST['end'] ? $_REQUEST['end'] : $this->date),
2756
				'file'  => 'event.ics',
2757
				'version' => '2.0',
2758
			);
2759
		}
2760
		$content['msg'] = $msg;
2761
2762
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('iCal Export');
2763
		$etpl = new Etemplate('calendar.export');
2764
		$etpl->exec('calendar.calendar_uiforms.export',$content);
2765
    }
2766
2767
	/**
2768
	 * Edit category ACL (admin only)
2769
	 *
2770
	 * @param array $_content
2771
	 */
2772
	function cat_acl(array $_content=null)
2773
	{
2774
		if (!$GLOBALS['egw_info']['user']['apps']['admin'])
2775
		{
2776
			throw new Api\Exception\NoPermission\Admin();
2777
		}
2778
		if ($_content)
2779
		{
2780
			$button = key($_content['button']);
2781
			unset($_content['button']);
2782
			if ($button != 'cancel')	// store changed Acl
2783
			{
2784
				foreach($_content as $data)
2785
				{
2786
					if (!($cat_id = $data['cat_id'])) continue;
2787
					foreach(array_merge((array)$data['add'],(array)$data['status'],array_keys((array)$data['old'])) as $account_id)
2788
					{
2789
						$rights = 0;
2790
						if (in_array($account_id,(array)$data['add'])) $rights |= calendar_boupdate::CAT_ACL_ADD;
2791
						if (in_array($account_id,(array)$data['status'])) $rights |= calendar_boupdate::CAT_ACL_STATUS;
2792
						if ($account_id) $this->bo->set_cat_rights($cat_id,$account_id,$rights);
2793
					}
2794
				}
2795
			}
2796
			if ($button != 'apply')	// end dialog
2797
			{
2798
				Egw::redirect_link('/index.php', array(
2799
					'menuaction' => 'admin.admin_ui.index',
2800
					'ajax' => 'true'
2801
				), 'admin');
2802
			}
2803
		}
2804
		$content= $preserv = array();
2805
		$n = 1;
2806
		foreach($this->bo->get_cat_rights() as $Lcat_id => $data)
2807
		{
2808
			$cat_id = substr($Lcat_id,1);
2809
			$row = array(
2810
				'cat_id' => $cat_id,
2811
				'add' => array(),
2812
				'status' => array(),
2813
			);
2814
			foreach($data as $account_id => $rights)
2815
			{
2816
				if ($rights & calendar_boupdate::CAT_ACL_ADD) $row['add'][] = $account_id;
2817
				if ($rights & calendar_boupdate::CAT_ACL_STATUS) $row['status'][] = $account_id;
2818
			}
2819
			$content[$n] = $row;
2820
			$preserv[$n] = array(
2821
				'cat_id' => $cat_id,
2822
				'old' => $data,
2823
			);
2824
			$readonlys[$n.'[cat_id]'] = true;
2825
			++$n;
2826
		}
2827
		// add empty row for new entries
2828
		$content[] = array('cat_id' => '');
2829
2830
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Calendar').' - '.lang('Category ACL');
2831
		$tmp = new Etemplate('calendar.cat_acl');
2832
		$GLOBALS['egw_info']['flags']['nonavbar'] = 1;
2833
		$tmp->exec('calendar.calendar_uiforms.cat_acl',$content,null,$readonlys,$preserv);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $readonlys seems to be defined by a foreach iteration on line 2806. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2834
	}
2835
2836
	/**
2837
	* Set up the required fields to get the history tab
2838
	*/
2839
	public function setup_history(&$content, &$sel_options)
2840
	{
2841
		$status = 'history_status';
2842
2843
		$content['history'] = array(
2844
			'id'    =>      $content['id'],
2845
			'app'   =>      'calendar',
2846
			'status-widgets' => array(
2847
				'owner'        => 'select-account',
2848
				'creator'      => 'select-account',
2849
				'category'     => 'select-cat',
2850
				'non_blocking' => array(''=>lang('No'), 1=>lang('Yes')),
2851
				'public'       => array(''=>lang('No'), 1=>lang('Yes')),
2852
2853
				'start'		   => 'date-time',
2854
				'end'		   => 'date-time',
2855
				'deleted'      => 'date-time',
2856
				'recur_enddate'=> 'date',
2857
2858
				'tz_id'        => 'select-timezone',
2859
2860
				// Participants
2861
				'participants'	=>	array(
2862
					'select-account',
2863
					$sel_options['status'],
2864
					$sel_options['role']
2865
				),
2866
				'participants-c'	=>	array(
2867
					'link:addressbook',
2868
					$sel_options['status'],
2869
					'label',
2870
					$sel_options['role']
2871
				),
2872
				'participants-r'	=>	array(
2873
					'link:resources',
2874
					$sel_options['status'],
2875
					'label',
2876
					$sel_options['role']
2877
				),
2878
			),
2879
		);
2880
2881
2882
		// Get participants for only this one, if it's recurring.  The date is on the end of the value.
2883
		if($content['recur_type'] || $content['recurrence'])
2884
		{
2885
			$content['history']['filter'] = array(
2886
				'(history_status NOT LIKE \'participants%\' OR (history_status LIKE \'participants%\' AND (
2887
					history_new_value LIKE \'%' . Api\Storage\Tracking::ONE2N_SEPERATOR . $content['recurrence'] . '\' OR
2888
					history_old_value LIKE \'%' . Api\Storage\Tracking::ONE2N_SEPERATOR . $content['recurrence'] . '\')))'
2889
			);
2890
		}
2891
2892
		// Translate labels
2893
		$tracking = new calendar_tracking();
2894
		foreach($tracking->field2label as $field => $label)
2895
		{
2896
			$sel_options[$status][$field] = lang($label);
2897
		}
2898
		// custom fields are now "understood" directly by historylog widget
2899
	}
2900
2901
	/**
2902
	 * moves an event to another date/time
2903
	 *
2904
	 * @param string $_eventId id of the event which has to be moved
2905
	 * @param string $calendarOwner the owner of the calendar the event is in
2906
	 * @param string $targetDateTime the datetime where the event should be moved to, format: YYYYMMDD
2907
	 * @param string|string[] $targetOwner the owner of the target calendar
2908
	 * @param string $durationT the duration to support resizable calendar event
2909
	 * @param string $seriesInstance If moving a whole series, not an exception, this is
2910
	 *	which particular instance was dragged
2911
	 * @return string XML response if no error occurs
2912
	 */
2913
	function ajax_moveEvent($_eventId,$calendarOwner,$targetDateTime,$targetOwner,$durationT=null,$seriesInstance=null)
2914
	{
2915
		list($eventId, $date) = explode(':', $_eventId,2);
2916
		$ignore_conflicts = false;
2917
2918
		// we do not allow dragging into another users calendar ATM
2919
		if($targetOwner < 0)
2920
		{
2921
			$targetOwner = array($targetOwner);
2922
		}
2923
		if($targetOwner == 0 || is_array($targetOwner) && $targetOwner[0] == 0)
2924
		{
2925
			$targetOwner = $calendarOwner;
2926
		}
2927
		// But you may be viewing multiple users, or a group calendar and
2928
		// dragging your event - dragging across calendars does not change owner
2929
		if(is_array($targetOwner) && !in_array($calendarOwner, $targetOwner))
2930
		{
2931
			$return = true;
2932
			foreach($targetOwner as $owner)
2933
			{
2934
				if($owner < 0 && in_array($calendarOwner, $GLOBALS['egw']->accounts->members($owner,true)))
2935
				{
2936
					$return = false;
2937
					break;
2938
				}
2939
				else if ($owner > 0 && $this->bo->check_perms(Acl::EDIT, $eventId,0,'ts',$date))
2940
				{
2941
					$return = false;
2942
					break;
2943
				}
2944
			}
2945
			if($return) return;
2946
		}
2947
		$old_event=$event=$this->bo->read($eventId);
2948
		if (!$durationT)
2949
		{
2950
			$duration=$event['end']-$event['start'];
2951
		}
2952
		// Drag a normal event to whole day non-blocking
2953
		else if ($durationT == 'whole_day')
2954
		{
2955
			$event['whole_day'] = true;
2956
			$event['non_blocking'] = true;
2957
			// Make duration whole days, less 1 second
2958
			$duration = round(($event['end']-$event['start'])/DAY_s) * DAY_s - 1;
2959
		}
2960
		else
2961
		{
2962
			$duration = (int)$durationT;
2963
		}
2964
2965
		// If we have a recuring event for a particular day, make an exception
2966
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
2967
		{
2968
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
2969
			if (!empty($event['whole_day']))
2970
			{
2971
				$d =& $this->bo->so->startOfDay($d);
2972
				$d->setUser();
2973
			}
2974
			$event = $this->bo->read($eventId, $d, true);
2975
2976
			// For DnD, create an exception if they gave the date
2977
			$preserv = null;
2978
			$this->_create_exception($event,$preserv);
2979
			unset($event['id']);
2980
			$links = $event['link_to']['to_id'];
2981
2982
			$messages = null;
2983
			$conflicts = $this->bo->update($event,false,true,false,true,$messages);
2984
			if (!is_array($conflicts) && $conflicts)
2985
			{
2986
				// now we need to add the original start as recur-execption to the series
2987
				$recur_event = $this->bo->read($event['reference']);
2988
				$recur_event['recur_exception'][] = $d->format('ts');
2989
				// check if we need to move the alarms, because they are next on that exception
2990
				$this->bo->check_move_alarms($recur_event, null, $d);
2991
				unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
2992
				unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
2993
				$this->bo->update($recur_event,true);	// no conflict check here
2994
2995
				// Sending null will trigger a removal of the original for that date
2996
				Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$_eventId, 'data' => null));
2997
2998
				unset($recur_event);
2999
				unset($event['edit_single']);			// if we further edit it, it's just a single event
3000
				unset($preserv['edit_single']);
3001
			}
3002
		}
3003
3004
		$d = new Api\DateTime($targetDateTime, Api\DateTime::$user_timezone);
3005
		$event['start'] = $d->format('ts');
3006
		$event['end'] = $event['start']+$duration;
3007
3008
		if ($event['recur_type'] != MCAL_RECUR_NONE && !$date && $seriesInstance)
3009
		{
3010
			// calculate offset against clicked recurrance,
3011
			// depending on which is smaller
3012
			$offset = Api\DateTime::to($targetDateTime,'ts') - Api\DateTime::to($seriesInstance,'ts');
3013
			$event['start'] = $old_event['start'] + $offset;
3014
			$event['duration'] = $duration;
3015
3016
			// We have a recurring event starting in the past -
3017
			// stop it & create a new one.
3018
			$this->_break_recurring($event, $old_event, $this->bo->date2ts($targetDateTime));
3019
3020
			// Can't handle conflict.  Just ignore it.
3021
			$ignore_conflicts = true;
3022
		}
3023
		if(!$event['recur_type'])
3024
		{
3025
			$this->bo->check_move_alarms($event, $old_event);
3026
		}
3027
3028
		// Drag a whole day to a time
3029
		if($durationT && $durationT != 'whole_day')
3030
		{
3031
			$event['whole_day'] = ($duration == DAY_s);
3032
			$event['non_blocking'] = false;
3033
			// If there's a conflict, it won't save the change and the conflict popup will be blank
3034
			// so save the change now, and then let the conflict check happen.
3035
			$message = null;
3036
			$this->bo->update($event,true, true, false, true, $message,true);
3037
3038
			// Whole day non blocking with DAY_s would add a day
3039
			if($duration==DAY_s) $duration=0;
0 ignored issues
show
Unused Code introduced by
The assignment to $duration is dead and can be removed.
Loading history...
3040
		}
3041
3042
		$status_reset_to_unknown = false;
3043
		$sameday = (date('Ymd', $old_event['start']) == date('Ymd', $event['start']));
0 ignored issues
show
Unused Code introduced by
The assignment to $sameday is dead and can be removed.
Loading history...
3044
3045
		$message = false;
3046
		$conflicts=$this->bo->update($event,$ignore_conflicts, true, false, true, $message);
3047
3048
		// Save links
3049
		if($links)
3050
		{
3051
			Link::link('calendar', $event['id'], $links);
3052
		}
3053
3054
		$this->update_client($event['id'],$d);
3055
		$response = Api\Json\Response::get();
3056
		if(!is_array($conflicts) && $conflicts)
3057
		{
3058
			if(is_int($conflicts))
3059
			{
3060
				$event['id'] = $conflicts;
3061
				$response->call('egw.refresh', '','calendar',$event['id'],'edit');
3062
			}
3063
		}
3064
		else if ($conflicts)
3065
		{
3066
			$response->call(
3067
				'egw_openWindowCentered2',
3068
				$GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit
3069
					&cal_id='.$event['id']
3070
					.'&start='.$event['start']
3071
					.'&end='.$event['end']
3072
					.'&non_interactive=true'
3073
					.'&cancel_needs_refresh=true',
3074
				'',750,410);
3075
		}
3076
		else if ($message)
3077
		{
3078
			$response->call('egw.message',  implode('<br />', $message));
3079
		}
3080
		if($event['id'] != $eventId ) $this->update_client($_eventId);
3081
		if ($status_reset_to_unknown)
0 ignored issues
show
introduced by
The condition $status_reset_to_unknown is always false.
Loading history...
3082
		{
3083
			foreach((array)$event['participants'] as $uid => $status)
3084
			{
3085
				if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user)
3086
				{
3087
					calendar_so::split_status($status,$q,$r);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $r seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $q seems to be never defined.
Loading history...
3088
					$status = calendar_so::combine_status('U',$q,$r);
3089
					$this->bo->set_status($event['id'], $uid, $status, 0, true);
3090
				}
3091
			}
3092
		}
3093
	}
3094
3095
	/**
3096
	 * Change the status via ajax
3097
	 * @param string $_eventId
3098
	 * @param integer $uid
3099
	 * @param string $status
3100
	 */
3101
	function ajax_status($_eventId, $uid, $status)
3102
	{
3103
		list($eventId, $date) = explode(':', $_eventId);
3104
		$event = $this->bo->read($eventId);
3105
		if($date)
3106
		{
3107
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
3108
		}
3109
3110
		// If we have a recuring event for a particular day, make an exception
3111
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
3112
		{
3113
			if (!empty($event['whole_day']))
3114
			{
3115
				$d =& $this->bo->so->startOfDay($date);
3116
				$d->setUser();
3117
			}
3118
			$event = $this->bo->read($eventId, $d, true);
3119
			$date = $d->format('ts');
3120
		}
3121
		if($event['participants'][$uid])
3122
		{
3123
			$q = $r = null;
3124
			calendar_so::split_status($event['participants'][$uid],$q,$r);
3125
			$event['participants'][$uid] = $status = calendar_so::combine_status($status,$q,$r);
3126
			$this->bo->set_status($event['id'],$uid,$status,$date,true);
3127
		}
3128
		else
3129
		{
3130
			// Group membership
3131
			foreach(array_keys($event['participants']) as $id)
3132
			{
3133
				if($GLOBALS['egw']->accounts->get_type($id) == 'g' && in_array($uid,$GLOBALS['egw']->accounts->members($id,true)))
3134
				{
3135
					calendar_so::split_status($event['participants'][$uid],$q,$r);
3136
					$event['participants'][$uid] = $status = calendar_so::combine_status($status,$q,$r);
3137
					$this->bo->set_status($event['id'],$uid,$status,$date,true);
3138
					break;
3139
				}
3140
			}
3141
		}
3142
3143
		// Directly update stored data.  If event is still visible, it will
3144
		// be notified & update itself.
3145
		$this->update_client($eventId,$d);
3146
	}
3147
3148
	/**
3149
	 * Deletes an event
3150
	 */
3151
	public function ajax_delete($eventId)
3152
	{
3153
		list($id, $date) = explode(':',$eventId);
3154
		$event=$this->bo->read($id);
3155
		$response = Api\Json\Response::get();
3156
3157
		if ($this->bo->delete($event['id'], (int)$date))
3158
		{
3159
			if ($event['recur_type'] != MCAL_RECUR_NONE && !$date)
3160
			{
3161
				$msg = lang('Series deleted');
3162
			}
3163
			else
3164
			{
3165
				$msg = lang('Event deleted');
3166
			}
3167
			$response->apply('egw.refresh', Array($msg,'calendar',$eventId,'delete'));
3168
		}
3169
		else
3170
		{
3171
			$response->apply('egw.message', Array(lang('Error')),'error');
0 ignored issues
show
Unused Code introduced by
The call to EGroupware\Api\Json\Msg::apply() has too many arguments starting with 'error'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

3171
			$response->/** @scrutinizer ignore-call */ 
3172
              apply('egw.message', Array(lang('Error')),'error');

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
3172
		}
3173
	}
3174
3175
	/**
3176
	 *
3177
	 * @param string $_eventId id of the event to be changed.  For recurring events
3178
	 *	it may contain the instance date
3179
	 * @param string[] $invite Resources to invite
3180
	 * @param string[] $remove Remove resource from participants
3181
	 */
3182
	public function ajax_invite($_eventId, $invite = array(), $remove = array())
3183
	{
3184
		list($eventId, $date) = explode(':', $_eventId,2);
3185
3186
		$event = $this->bo->read($eventId);
3187
		if($date)
3188
		{
3189
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
3190
		}
3191
3192
		// If we have a recuring event for a particular day, make an exception
3193
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
3194
		{
3195
			if (!empty($event['whole_day']))
3196
			{
3197
				$d =& $this->bo->so->startOfDay($date);
3198
				$d->setUser();
3199
			}
3200
			$event = $this->bo->read($eventId, $d, true);
3201
			// For DnD, create an exception if they gave the date
3202
			$preserv = null;
3203
			$this->_create_exception($event,$preserv);
3204
			unset($event['id']);
3205
3206
			$messages = null;
3207
			$conflicts = $this->bo->update($event,true,true,false,true,$messages);
3208
			if (!is_array($conflicts) && $conflicts)
0 ignored issues
show
introduced by
The condition is_array($conflicts) is always false.
Loading history...
3209
			{
3210
				// now we need to add the original start as recur-execption to the series
3211
				$recur_event = $this->bo->read($event['reference']);
3212
				$recur_event['recur_exception'][] = $d->format('ts');
3213
				// check if we need to move the alarms, because they are next on that exception
3214
				$this->bo->check_move_alarms($recur_event, null, $d);
3215
				unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
3216
				unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
3217
				$this->bo->update($recur_event,true);	// no conflict check here
3218
3219
				// Sending null will trigger a removal of the original for that date
3220
				Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$_eventId, 'data' => null));
3221
3222
				unset($recur_event);
3223
				unset($event['edit_single']);			// if we further edit it, it's just a single event
3224
				unset($preserv['edit_single']);
3225
			}
3226
		}
3227
		foreach($remove as $participant)
3228
		{
3229
			unset($event['participants'][$participant]);
3230
		}
3231
		foreach($invite as $participant)
3232
		{
3233
			$event['participants'][$participant] = 'U';
3234
		}
3235
		$message = null;
3236
		$conflicts=$this->bo->update($event,false, true, false, true, $message);
3237
3238
		$response = Api\Json\Response::get();
3239
3240
		if (is_array($conflicts) && $conflicts)
3241
		{
3242
			// Save it anyway, was done with explicit user interaction,
3243
			// and if we don't we lose the invite
3244
			$this->bo->update($event,true);	// no conflict check here
3245
			$this->update_client($event['id'],$d);
3246
			$response->call(
3247
				'egw_openWindowCentered2',
3248
				$GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit
3249
					&cal_id='.$event['id']
3250
					.'&start='.$event['start']
3251
					.'&end='.$event['end']
3252
					.'&non_interactive=true'
3253
					.'&cancel_needs_refresh=true',
3254
				'',750,410);
3255
		}
3256
		else if ($message)
3257
		{
3258
			$response->call('egw.message',  implode('<br />', $message));
3259
		}
3260
		if($conflicts)
3261
		{
3262
			$this->update_client($event['id'],$d);
3263
			if(is_int($conflicts))
3264
			{
3265
				$event['id'] = $conflicts;
3266
			}
3267
			if($event['id'])
3268
			{
3269
				$response->call('egw.refresh', '','calendar',$event['id'],'edit');
3270
			}
3271
		}
3272
	}
3273
3274
	/**
3275
	 * imports a mail as Calendar
3276
	 *
3277
	 * @param array $mailContent = null mail content
3278
	 * @return  array
3279
	 */
3280
	function mail_import(array $mailContent=null)
3281
	{
3282
		// It would get called from compose as a popup with egw_data
3283
		if (!is_array($mailContent) && ($_GET['egw_data']))
3284
		{
3285
			// get raw mail data
3286
			Link::get_data ($_GET['egw_data']);
3287
			return false;
3288
		}
3289
3290
		if (is_array($mailContent))
3291
		{
3292
			// Addressbook
3293
			$AB = new Api\Contacts();
3294
			$accounts = array(0 => $GLOBALS['egw_info']['user']['account_id']);
3295
3296
			$participants[0] = array (
0 ignored issues
show
Comprehensibility Best Practice introduced by
$participants was never initialized. Although not strictly required by PHP, it is generally a good practice to add $participants = array(); before regardless.
Loading history...
3297
				'uid' => $GLOBALS['egw_info']['user']['account_id'],
3298
				'delete_id' => $GLOBALS['egw_info']['user']['account_id'],
3299
				'status' => 'A',
3300
				'old_status' => 'A',
3301
				'app' => 'User',
3302
				'role' => 'REQ-PARTICIPANT'
3303
			);
3304
			foreach($mailContent['addresses'] as $address)
3305
			{
3306
				// Get available contacts from the email
3307
				$contacts = $AB->search(array(
3308
						'email' => $address['email'],
3309
						'email_home' => $address['email']
3310
					),'contact_id,contact_email,contact_email_home,egw_addressbook.account_id as account_id','','','',false,'OR',false,array('owner' => 0),'',false);
3311
				if (is_array($contacts))
3312
				{
3313
					foreach($contacts as $account)
3314
					{
3315
						$accounts[] = $account['account_id'];
3316
					}
3317
				}
3318
				else
3319
				{
3320
					$participants []= array (
3321
						'app' => 'email',
3322
						'uid' => 'e'.$address['email'],
3323
						'status' => 'U',
3324
						'old_status' => 'U'
3325
					);
3326
				}
3327
			}
3328
			$participants = array_merge($participants , array(
3329
				"participant" => $accounts,
3330
				"role" => "REQ-PARTICIPANT",
3331
				"add" => "pressed"
3332
			));
3333
3334
			// Prepare calendar event draft
3335
			$event = array(
3336
				'title' => $mailContent['subject'],
3337
				'description' => $mailContent['message'],
3338
				'participants' => $participants,
3339
				'link_to' => array(
3340
					'to_app' => 'calendar',
3341
					'to_id' => 0,
3342
				),
3343
				'start' => $mailContent['date'],
3344
				'duration' => 60 * $this->cal_prefs['interval'],
3345
				'owner' => $GLOBALS['egw_info']['user']['account_id']
3346
			);
3347
3348
			if (is_array($mailContent['attachments']))
3349
			{
3350
				foreach ($mailContent['attachments'] as $attachment)
3351
				{
3352
					if($attachment['egw_data'])
3353
					{
3354
						Link::link('calendar',$event['link_to']['to_id'],Link::DATA_APPNAME,  $attachment);
3355
					}
3356
					else if(is_readable($attachment['tmp_name']) ||
3357
						(Vfs::is_readable($attachment['tmp_name']) && parse_url($attachment['tmp_name'], PHP_URL_SCHEME) === 'vfs'))
3358
					{
3359
						Link::link('calendar',$event['link_to']['to_id'],'file',  $attachment);
3360
					}
3361
				}
3362
			}
3363
		}
3364
		else
3365
		{
3366
			Framework::window_close(lang('No content found to show up as calendar entry.'));
3367
		}
3368
3369
		return $this->process_edit($event);
3370
	}
3371
}
3372