Completed
Push — 16.1 ( 26c19f...96aac5 )
by Nathan
47:03 queued 31:43
created

calendar_uiforms   F

Complexity

Total Complexity 706

Size/Duplication

Total Lines 3151
Duplicated Lines 5.01 %

Coupling/Cohesion

Components 1
Dependencies 24

Importance

Changes 0
Metric Value
dl 158
loc 3151
rs 0.5665
c 0
b 0
f 0
wmc 706
lcom 1
cbo 24

24 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 9 5
F default_add_event() 0 167 55
A ajax_unlock() 0 10 4
F meeting() 0 203 46
C conflicts() 0 52 14
C ajax_freetimesearch() 0 70 14
C freetimesearch() 0 64 12
D freetime() 2 76 18
F split_freetime_daywise() 0 66 17
C export() 0 56 11
C cat_acl() 0 63 15
B setup_history() 0 61 4
F ajax_moveEvent() 30 202 47
C ajax_status() 0 46 9
B ajax_delete() 0 23 4
F ajax_invite() 18 91 15
D mail_import() 21 91 13
F process_edit() 38 833 209
C _create_exception() 11 50 10
D _break_recurring() 0 103 18
F ajax_custom_mail() 4 86 30
B get_title() 0 16 7
A uid_title_cmp() 0 4 1
F edit() 34 471 128

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like calendar_uiforms often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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

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

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-16 by RalfBecker-At-outdoor-training.de
9
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
10
 * @version $Id$
11
 */
12
13
use EGroupware\Api;
14
use EGroupware\Api\Link;
15
use EGroupware\Api\Framework;
16
use EGroupware\Api\Egw;
17
use EGroupware\Api\Acl;
18
use EGroupware\Api\Vfs;
19
use EGroupware\Api\Etemplate;
20
21
/**
22
 * calendar UserInterface forms: view and edit events, freetime search
23
 *
24
 * The new UI, BO and SO classes have a strikt definition, in which time-zone they operate:
25
 *  UI only operates in user-time, so there have to be no conversation at all !!!
26
 *  BO's functions take and return user-time only (!), they convert internaly everything to servertime, because
27
 *  SO operates only on server-time
28
 *
29
 * The state of the UI elements is managed in the uical class, which all UI classes extend.
30
 *
31
 * All permanent debug messages of the calendar-code should done via the debug-message method of the bocal class !!!
32
 */
33
class calendar_uiforms extends calendar_ui
34
{
35
	var $public_functions = array(
36
		'freetimesearch'  => True,
37
		'edit' => true,
38
		'process_edit' => true,
39
		'export' => true,
40
		'import' => true,
41
		'cat_acl' => true,
42
		'meeting' => true,
43
		'mail_import' => true,
44
	);
45
46
	/**
47
	 * Standard durations used in edit and freetime search
48
	 *
49
	 * @var array
50
	 */
51
	var $durations = array();
52
53
	/**
54
	 * default locking time for entries, that are opened by another user
55
	 *
56
	 * @var locktime in seconds
57
	 */
58
	var $locktime_default=1;
59
60
	/**
61
	 * Constructor
62
	 */
63
	function __construct()
64
	{
65
		parent::__construct(true);	// call the parent's constructor
66
67
		for ($n=15; $n <= 16*60; $n+=($n < 60 ? 15 : ($n < 240 ? 30 : ($n < 600 ? 60 : 120))))
68
		{
69
			$this->durations[$n*60] = sprintf('%d:%02d',$n/60,$n%60);
70
		}
71
	}
72
73
	/**
74
	 * Create a default event (adding a new event) by evaluating certain _GET vars
75
	 *
76
	 * @return array event-array
77
	 */
78
	function &default_add_event()
79
	{
80
		$extra_participants = $_GET['participants'] ?
81
			(!is_array($_GET['participants']) ? explode(',',$_GET['participants']) : $_GET['participants']) :
82
			array();
83
84
		// if participant is a contact, add its link title as title
85
		foreach($extra_participants as $uid)
86
		{
87
			if ($uid[0] == 'c')
88
			{
89
				$title = Link::title('addressbook', substr($uid, 1));
90
				break;
91
			}
92
		}
93
94
		if (isset($_GET['owner']))
95
		{
96
			$owner = $_GET['owner'];
97
			if(!is_array($owner) && strpos($owner, ','))
98
			{
99
				$owner = explode(',',$owner);
100
			}
101
			if(is_array($owner))
102
			{
103
				// old behavior "selected" should also be used for not set preference, therefore we need to test for !== '0'
104
				if($this->cal_prefs['default_participant'] !== '0' || count($extra_participants) === 0 && count($owner) === 1)
105
				{
106
					$extra_participants += $owner;
107
				}
108
				$owner = count($owner) > 1 ? $this->user : $owner[0];
109
			}
110
			else if ($owner)
111
			{
112
				$extra_participants[] = $owner;
113
			}
114
		}
115
		else
116
		{
117
			// old behavior "selected" should also be used for not set preference, therefore we need to test for === '0'
118
			$owner = $this->cal_prefs['default_participant'] === '0' ? $this->user : $this->owner;
119
		}
120
		error_log(__METHOD__ . ' owner: ' . array2string($owner));
121
122
		if (!$owner || !is_numeric($owner) || $GLOBALS['egw']->accounts->get_type($owner) != 'u' ||
123
			!$this->bo->check_perms(Acl::ADD,0,$owner))
124
		{
125
			if ($owner)	// make an owner who is no user or we have no add-rights a participant
126
			{
127
				if(!is_array($owner))
128
				{
129
					$owner = explode(',',$owner);
130
				}
131
				// if we come from ressources we don't need any users selected in calendar
132
				if (!isset($_GET['participants']) || $_GET['participants'][0] != 'r')
133
				{
134
					foreach($owner as $uid)
135
					{
136
						$extra_participants[] = $uid;
137
					}
138
				}
139
			}
140
			$owner = $this->user;
141
		}
142
		//error_log("this->owner=$this->owner, _GET[owner]=$_GET[owner], user=$this->user => owner=$owner, extra_participants=".implode(',',$extra_participants).")");
143
144
		if(isset($_GET['start']))
145
		{
146
			$start = Api\DateTime::to($_GET['start'], 'ts');
147
		}
148
		else
149
		{
150
			$start = $this->bo->date2ts(array(
151
				'full' => isset($_GET['date']) && (int) $_GET['date'] ? (int) $_GET['date'] : $this->date,
152
				'hour' => (int) (isset($_GET['hour']) ? $_GET['hour'] : $this->bo->cal_prefs['workdaystarts']),
153
				'minute' => (int) $_GET['minute'],
154
			));
155
		}
156
		//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";
157
158
		$participant_types['u'] = $participant_types = $participants = array();
159
		foreach($extra_participants as $uid)
160
		{
161
			if (isset($participants[$uid])) continue;	// already included
162
163
			if (!$this->bo->check_acl_invite($uid)) continue;	// no right to invite --> ignored
164
165
			if (is_numeric($uid))
166
			{
167
				$participants[$uid] = $participant_types['u'][$uid] =
168
					calendar_so::combine_status($uid == $this->user ? 'A' : 'U',1,
169
					($uid == $this->user || ($uid == $owner && $this->bo->check_perms(Acl::ADD,0,$owner))) ? 'CHAIR' : 'REQ-PARTICIPANT');
170
			}
171
			elseif (is_array($this->bo->resources[$uid[0]]))
172
			{
173
				// Expand mailing lists
174
				if($uid[0] == 'l')
175
				{
176
					foreach($this->bo->enum_mailing_list($uid) as $contact)
177
					{
178
						$participants[$contact] = $participant_types['c'][substr($contact,1)] =
179
							calendar_so::combine_status('U',1,'REQ-PARTICIPANT');
180
					}
181
					continue;
182
				}
183
				// if contact is a user, use the user instead (as the GUI)
184
				if ($uid[0] == 'c' && ($account_id = $GLOBALS['egw']->accounts->name2id(substr($uid,1),'person_id')))
185
				{
186
					$uid = $account_id;
187
					$participants[$uid] = $participant_types['u'][$uid] =
188
						calendar_so::combine_status($uid == $this->user ? 'A' : 'U',1,
189
						($uid == $this->user || ($uid == $owner && $this->bo->check_perms(Acl::ADD,0,$owner))) ? 'CHAIR' : 'REQ-PARTICIPANT');
190
					continue;
191
				}
192
				$res_data = $this->bo->resources[$uid[0]];
193
				list($id,$quantity) = explode(':',substr($uid,1));
194
				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 with message: use autoloadable class-names, instanciate and call method or use static methods

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

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

Loading history...
195
				{
196
					$participants[$uid] = $participant_types[$uid[0]][$id] =
197
						calendar_so::combine_status($status,$quantity,'REQ-PARTICIPANT');
198
				}
199
			}
200
		}
201
		if (!$participants)	// if all participants got removed, include current user
202
		{
203
			$participants[$this->user] = $participant_types['u'][$this->user] = calendar_so::combine_status('A',1,'CHAIR');
204
		}
205
		if(isset($_GET['cat_id']))
206
		{
207
			$cat_id = explode(',',$_GET['cat_id']);
208
			foreach($cat_id as &$cat)
209
			{
210
				$cat = (int)$cat;
211
			}
212
		}
213
		$alarms = array();
214
		// if default alarm set in prefs --> add it
215
		// we assume here that user does NOT have a whole-day but no regular default-alarm, no whole-day!
216
		if ((string)$this->cal_prefs['default-alarm'] !== '')
217
		{
218
			$offset = 60 * $this->cal_prefs['default-alarm'];
219
			$alarms[1] =  array(
220
				'default' => 1,
221
				'offset' => $offset ,
222
				'time'   => $start - $offset,
223
				'all'    => false,
224
				'owner'  => $owner,
225
				'id'	=> 1,
226
			);
227
		}
228
		$duration = isset($_GET['duration']) ? (int)$_GET['duration'] : (int) $this->bo->cal_prefs['defaultlength']*60;
229
		$end = isset($_GET['end']) ? Api\DateTime::to($_GET['end'], 'ts') : $start + $duration;
230
		return array(
231
			'participant_types' => $participant_types,
232
			'participants' => $participants,
233
			'owner' => $owner,
234
			'start' => $start,
235
			'end'   => $end,
236
			'tzid'  => $this->bo->common_prefs['tz'],
237
			'priority' => 2,	// normal
238
			'public'=> $this->cal_prefs['default_private'] ? 0 : 1,
239
			'alarm' => $alarms,
240
			'recur_exception' => array(),
241
			'title' => $title ? $title : '',
242
			'category' => $cat_id,
243
		);
244
	}
245
246
	/**
247
	 * Process the edited event and evtl. call edit to redisplay it
248
	 *
249
	 * @param array $content posted eTemplate content
250
	 * @ToDo add conflict check / available quantity of resources when adding participants
251
	 */
252
	function process_edit($content)
253
	{
254
		if (!is_array($content))	// redirect from etemplate, if POST empty
255
		{
256
			return $this->edit(null,null,strip_tags($_GET['msg']));
257
		}
258
		// clear notification errors
259
		notifications::errors(true);
260
		$messages = null;
261
		$msg_permission_denied_added = false;
262
263
		// We'd like to just refresh the data as that's the fastest, but some changes
264
		// affect more than just one event widget, so require a full refresh.
265
		// $update_type is one of the update types
266
		// (add, edit, update, delete)
267
		$update_type = $content['id'] ? ($content['recur_type'] == MCAL_RECUR_NONE ? 'update' : 'edit') : 'add';
268
269
		list($button) = @each($content['button']);
270
		if (!$button && $content['action']) $button = $content['action'];	// action selectbox
271
		unset($content['button']); unset($content['action']);
272
273
		$view = $content['view'];
274
		if ($button == 'ical')
275
		{
276
			$msg = $this->export($content['id'],true);
277
		}
278
		// delete a recur-exception
279
		if ($content['recur_exception']['delete_exception'])
280
		{
281
			list($date) = each($content['recur_exception']['delete_exception']);
282
			// eT2 converts time to
283
			if (!is_numeric($date)) $date = Api\DateTime::to (str_replace('Z','', $date), 'ts');
284
			unset($content['recur_exception']['delete_exception']);
285
			if (($key = array_search($date,$content['recur_exception'])) !== false)
286
			{
287
				// propagate the exception to a single event
288
				$recur_exceptions = $this->bo->so->get_related($content['uid']);
289
				foreach ($recur_exceptions as $id)
290
				{
291
					if (!($exception = $this->bo->read($id)) ||
292
							$exception['recurrence'] != $content['recur_exception'][$key]) continue;
293
					$exception['uid'] = Api\CalDAV::generate_uid('calendar', $id);
294
					$exception['reference'] = $exception['recurrence'] = 0;
295
					$this->bo->update($exception, true, true,false,true,$messages,$content['no_notifications']);
296
					break;
297
				}
298
				unset($content['recur_exception'][$key]);
299
				$content['recur_exception'] = array_values($content['recur_exception']);
300
			}
301
			$update_type = 'edit';
302
		}
303
		// delete an alarm
304
		if ($content['alarm']['delete_alarm'])
305
		{
306
			list($id) = each($content['alarm']['delete_alarm']);
307
			//echo "delete alarm $id"; _debug_array($content['alarm']['delete_alarm']);
308
309
			if ($content['id'])
310
			{
311
				if ($this->bo->delete_alarm($id))
312
				{
313
					$msg = lang('Alarm deleted');
314
					unset($content['alarm'][$id]);
315
				}
316
				else
317
				{
318
					$msg = lang('Permission denied');
319
				}
320
			}
321
			else
322
			{
323
				unset($content['alarm'][$id]);
324
			}
325
		}
326
		if ($content['duration'])
327
		{
328
			$content['end'] = $content['start'] + $content['duration'];
329
		}
330
		// fix default alarm for a new (whole day) event, to be according to default-alarm(-wholeday) pref
331
		if ($content['alarm'][1]['default'])
332
		{
333
			$def_alarm = $this->cal_prefs['default-alarm'.($content['whole_day'] ? '-wholeday' : '')];
334
			if ((string)$def_alarm === '')
335
			{
336
				unset($content['alarm'][1]);	// '' = no alarm on whole day --> delete it
337
			}
338
			else
339
			{
340
				$content['alarm'][1]['offset'] = $offset = 60 * $def_alarm;
341
				$content['start'][1]['offset'] = $this->bo->date2ts($content['start']) - $offset;
342
			}
343
		}
344
345
		$event = $content;
346
		unset($event['new_alarm']);
347
		unset($event['alarm']['delete_alarm']);
348
		unset($event['duration']);
349
350
		if (in_array($button,array('ignore','freetime','reedit','confirm_edit_series')))
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
351
		{
352
			// no conversation necessary, event is already in the right format
353
		}
354
		else
355
		{
356
			// convert content => event
357
			if ($content['whole_day'])
358
			{
359
				$event['start'] = $this->bo->date2array($event['start']);
360
				$event['start']['hour'] = $event['start']['minute'] = 0; unset($event['start']['raw']);
361
				$event['start'] = $this->bo->date2ts($event['start']);
362
				$event['end'] = $this->bo->date2array($event['end']);
363
				$event['end']['hour'] = 23; $event['end']['minute'] = $event['end']['second'] = 59; unset($event['end']['raw']);
364
				$event['end'] = $this->bo->date2ts($event['end']);
365
			}
366
			// some checks for recurrences, if you give a date, make it a weekly repeating event and visa versa
367
			if ($event['recur_type'] == MCAL_RECUR_NONE && $event['recur_data']) $event['recur_type'] = MCAL_RECUR_WEEKLY;
368 View Code Duplication
			if ($event['recur_type'] == MCAL_RECUR_WEEKLY && !$event['recur_data'])
369
			{
370
				$event['recur_data'] = 1 << (int)date('w',$event['start']);
371
			}
372
			if ($event['recur_type'] != MCAL_RECUR_NONE && !isset($event['recur_enddate']))
373
			{
374
				// No recur end date, make sure it's set to something or it won't be changed
375
				$event['recur_enddate'] = 0;
376
			}
377
			if (isset($content['participants']))
378
			{
379
380
				$event['participants'] = $event['participant_types'] = array();
381
382
				foreach($content['participants'] as $key => $data)
383
				{
384
					switch($key)
385
					{
386
						case 'delete':		// handled in default
387
						case 'quantity':	// handled in new_resource
388
						case 'role':		// handled in add, account or resource
389
						case 'cal_resources':
390
						case 'status_date':
391
							break;
392
						case 'participant':
393
							foreach($data as $participant)
394
							{
395
								if (is_null($participant))
396
								{
397
									continue;
398
								}
399
400
								// email or rfc822 addresse (eg. "Ralf Becker <[email protected]>")
401
								$email = array();
402
								if(preg_match('/^(.*<)?([a-z0-9_.-]+@[a-z0-9_.-]{5,})>?$/i',$participant,$email))
403
								{
404
									$status = calendar_so::combine_status('U',$content['participants']['quantity'],$content['participants']['role']);
405
									if (($data = $GLOBALS['egw']->accounts->name2id($email[2],'account_email')) && $this->bo->check_acl_invite($data))
406
									{
407
										$event['participants'][$data] = $event['participant_types']['u'][$data] = $status;
408
									}
409
									elseif ((list($data) = ExecMethod2('addressbook.addressbook_bo.search',array(
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod2() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

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

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

Loading history...
410
										'email' => $email[2],
411
										'email_home' => $email[2],
412
									),true,'','','',false,'OR')))
413
									{
414
										$event['participants']['c'.$data['id']] = $event['participant_types']['c'][$data['id']] = $status;
415
									}
416
									else
417
									{
418
										$event['participants']['e'.$participant] = $event['participant_types']['e'][$participant] = $status;
419
									}
420
								}
421
								else
422
								{
423
									if(is_numeric($participant))
424
									{
425
										$uid = $participant;
426
										$id = $participant;
427
										$resource = $this->bo->resources[''];
428
									}
429
									else
430
									{
431
										$uid = $participant;
432
										$id = substr($participant,1);
433
										$resource = $this->bo->resources[$participant[0]];
434
									}
435
									if(!$this->bo->check_acl_invite($uid))
436
									{
437
										if(!$msg_permission_denied_added)
438
										{
439
											$msg .= lang('Permission denied!');
440
											$msg_permission_denied_added = true;
441
										}
442
										continue;
443
									}
444
445
									$type = $resource['type'];
446
									$status = isset($this->bo->resources[$type]['new_status']) ?
447
										ExecMethod($this->bo->resources[$type]['new_status'],$id) :
0 ignored issues
show
Deprecated Code introduced by
The function ExecMethod() has been deprecated with message: use autoloadable class-names, instanciate and call method or use static methods

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

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

Loading history...
448
										($uid == $this->bo->user ? 'A' : 'U');
449
450
									// Expand mailing lists
451
									if($type == 'l')
452
									{
453
										foreach($this->bo->enum_mailing_list($participant) as $contact)
454
										{
455
											// Mailing lists can contain users, so allow for that possibility
456
											$_type = is_numeric($contact) ? '' : $contact[0];
457
											$_uid = is_numeric($contact) ? $contact : substr($contact,1);
458
											$event['participants'][$contact] = $event['participant_types'][$_type][$_uid] =
459
												calendar_so::combine_status($status,$content['participants']['quantity'],$content['participants']['role']);
460
										}
461
										continue;
462
									}
463
									if ($status)
464
									{
465
										$res_info = $this->bo->resource_info($uid);
466
										// todo check real availability = maximum - already booked quantity
467
										if (isset($res_info['useable']) && $content['participants']['quantity'] > $res_info['useable'])
468
										{
469
											$msg .= lang('Maximum available quantity of %1 exceeded!',$res_info['useable']);
470
											foreach(array('quantity','resource','role') as $n)
471
											{
472
												$event['participants'][$n] = $content['participants'][$n];
473
											}
474
											continue;
475
										}
476
										else
477
										{
478
											$event['participants'][$uid] = $event['participant_types'][$type][$id] =
479
												calendar_so::combine_status($status,$content['participants']['quantity'],$content['participants']['role']);
480
										}
481
									}
482
								}
483
							}
484
							break;
485
						case 'add':
486
							if (!$content['participants']['participant'])
487
							{
488
								$msg = lang('You need to select an account, contact or resource first!');
489
							}
490
							break;
491
492
						default:		// existing participant row
493
							if (!is_array($data)) continue;	// widgets in participant tab, above participant list
494
							foreach(array('uid','status','quantity','role') as $name)
495
							{
496
								$$name = $data[$name];
497
							}
498
							if ($content['participants']['delete'][$uid] || $content['participants']['delete'][md5($uid)])
499
							{
500
								$uid = false;	// entry has been deleted
501
							}
502
							elseif ($uid)
503
							{
504
								if (is_numeric($uid))
505
								{
506
									$id = $uid;
507
									$type = 'u';
508
								}
509
								else
510
								{
511
									$id = substr($uid,1);
512
									$type = $uid[0];
513
								}
514
								if ($data['old_status'] != $status && !(!$data['old_status'] && $status == 'G'))
515
								{
516
									//echo "<p>$uid: status changed '$data[old_status]' --> '$status<'/p>\n";
517
									$new_status = calendar_so::combine_status($status, $quantity, $role);
0 ignored issues
show
Bug introduced by
The variable $quantity seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $role seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
518
									if ($this->bo->set_status($event['id'],$uid,$new_status,isset($content['edit_single']) ? $content['participants']['status_date'] : 0, false, true, $content['no_notifications']))
519
									{
520
										// Update main window
521
										$d = new Api\DateTime($content['edit_single'], Api\DateTime::$user_timezone);
522
										$client_updated = $this->update_client($event['id'], $d);
523
524
										// refreshing the calendar-view with the changed participant-status
525
										if($event['recur_type'] != MCAL_RECUR_NONE)
526
										{
527
											$msg = lang('Status for all future scheduled days changed');
528
										}
529
										else
530
										{
531
											if(isset($content['edit_single']))
532
											{
533
												$msg = lang('Status for this particular day changed');
534
												// prevent accidentally creating a real exception afterwards
535
												$view = true;
536
												$hide_delete = true;
537
											}
538
											else
539
											{
540
												$msg = lang('Status changed');
541
												//Refresh the event in the main window after changing status
542
												Framework::refresh_opener($msg, 'calendar', $event['id'], $client_updated ? 'update' : 'delete');
543
											}
544
										}
545
										if (!$content['no_popup'])
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
546
										{
547
											//we are handling refreshing for status changes on client side
548
										}
549
										if ($status == 'R' && $event['alarm'])
550
										{
551
											// remove from bo->set_status deleted alarms of rejected users from UI too
552
											foreach($event['alarm'] as $alarm_id => $alarm)
553
											{
554
												if ((string)$alarm['owner'] === (string)$uid)
555
												{
556
													unset($event['alarm'][$alarm_id]);
557
												}
558
											}
559
										}
560
									}
561
								}
562
								if ($uid && $status != 'G')
563
								{
564
									$event['participants'][$uid] = $event['participant_types'][$type][$id] =
565
										calendar_so::combine_status($status,$quantity,$role);
0 ignored issues
show
Bug introduced by
The variable $quantity seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $role seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
566
								}
567
							}
568
							break;
569
					}
570
				}
571
			}
572
		}
573
		$preserv = array(
574
			'view'			=> $view,
575
			'hide_delete'	=> $hide_delete,
576
			'edit_single'	=> $content['edit_single'],
577
			'reference'		=> $content['reference'],
578
			'recurrence'	=> $content['recurrence'],
579
			'actual_date'	=> $content['actual_date'],
580
			'no_popup'		=> $content['no_popup'],
581
			'tabs'			=> $content['tabs'],
582
			'template'      => $content['template'],
583
		);
584
		$noerror=true;
585
586
		//error_log(__METHOD__.$button.'#'.array2string($content['edit_single']).'#');
587
588
		$ignore_conflicts = $status_reset_to_unknown = false;
589
590
		switch((string)$button)
591
		{
592
			case 'ignore':
593
				$ignore_conflicts = true;
594
				$button = $event['button_was'];	// save or apply
595
				unset($event['button_was']);
596
				break;
597
598
		}
599
600
		switch((string)$button)
601
		{
602
		case 'exception':	// create an exception in a recuring event
603
			$msg = $this->_create_exception($event,$preserv);
604
			break;
605
606
		case 'copy':	// create new event with copied content, some content need to be unset to make a "new" event
607
			unset($event['id']);
608
			unset($event['uid']);
609
			unset($event['reference']);
610
			unset($preserv['reference']);
611
			unset($event['recurrence']);
612
			unset($preserv['recurrence']);
613
			unset($event['recur_exception']);
614
			unset($event['edit_single']);	// in case it has been set
615
			unset($event['modified']);
616
			unset($event['modifier']);
617
			unset($event['caldav_name']);
618
			$event['owner'] = !(int)$event['owner'] || !$this->bo->check_perms(Acl::ADD,0,$event['owner']) ? $this->user : $event['owner'];
619
620
			// Clear participant stati
621
			foreach($event['participant_types'] as $type => &$participants)
622
			{
623
				foreach($participants as $id => &$p_response)
624
				{
625
					if($type == 'u' && $id == $event['owner']) continue;
626
					calendar_so::split_status($p_response, $quantity, $role);
627
					// if resource defines callback for status of new status (eg. Resources app acknowledges direct booking acl), call it
628
					$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 with message: use autoloadable class-names, instanciate and call method or use static methods

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

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

Loading history...
629
					$p_response = calendar_so::combine_status($status,$quantity,$role);
630
				}
631
			}
632
633
			// Copy alarms
634
			if (is_array($event['alarm']))
635
			{
636
				foreach($event['alarm'] as $n => &$alarm)
637
				{
638
					unset($alarm['id']);
639
					unset($alarm['cal_id']);
640
				}
641
			}
642
643
			// Get links to be copied
644
			// With no ID, $content['link_to']['to_id'] is used
645
			$content['link_to']['to_id'] = array('to_app' => 'calendar', 'to_id' => 0);
646
			foreach(Link::get_links('calendar', $content['id']) as $link)
647
			{
648 View Code Duplication
				if ($link['app'] != Link::VFS_APPNAME)
649
				{
650
					Link::link('calendar', $content['link_to']['to_id'], $link['app'], $link['id'], $link['remark']);
651
				}
652
				elseif ($link['app'] == Link::VFS_APPNAME)
653
				{
654
					Link::link('calendar', $content['link_to']['to_id'], Link::VFS_APPNAME, array(
655
						'tmp_name' => Link::vfs_path($link['app2'], $link['id2']).'/'.$link['id'],
656
						'name' => $link['id'],
657
					), $link['remark']);
658
				}
659
			}
660
			unset($link);
661
			$preserv['view'] = $preserv['edit_single'] = false;
662
			$msg = lang('%1 copied - the copy can now be edited', lang(Link::get_registry('calendar','entry')));
663
			$event['title'] = lang('Copy of:').' '.$event['title'];
664
			break;
665
666
		case 'mail':
667
		case 'sendrequest':
668
		case 'save':
669
		case 'print':
670
		case 'apply':
671
		case 'infolog':
672
			if ($event['id'] && !$this->bo->check_perms(Acl::EDIT,$event))
673
			{
674
				$msg = lang('Permission denied');
675
				$button = '';
676
				break;
677
			}
678
			if ($event['start'] > $event['end'])
679
			{
680
				$msg = lang('Error: Starttime has to be before the endtime !!!');
681
				$button = '';
682
				break;
683
			}
684
			if ($event['recur_type'] != MCAL_RECUR_NONE && $event['recur_enddate'] && $event['start'] > $event['recur_enddate'])
685
			{
686
				$msg = lang('repetition').': '.lang('Error: Starttime has to be before the endtime !!!');
687
				$button = '';
688
				break;
689
			}
690
			if ($event['recur_type'] != MCAL_RECUR_NONE && $event['end']-$event['start'] > calendar_rrule::recurrence_interval($event['recur_type'], $event['recur_interval']))
691
			{
692
				$msg = lang('Error: Duration of event longer then recurrence interval!');
693
				$button = '';
694
				break;
695
			}
696
			if (!$event['participants'])
697
			{
698
				$msg = lang('Error: no participants selected !!!');
699
				$button = '';
700
				break;
701
			}
702
			// if private event with ressource reservation is forbidden
703
			if (!$event['public'] && $GLOBALS['egw_info']['server']['no_ressources_private'])
704
			{
705
				foreach (array_keys($event['participants']) as $uid)
706
				{
707
					if ($uid[0] == 'r') //ressource detection
708
					{
709
						$msg = lang('Error: ressources reservation in private events is not allowed!!!');
710
						$button = '';
711
						break 2; //break foreach and case
712
					}
713
				}
714
			}
715
			if ($content['edit_single'])	// we edited a single event from a series
716
			{
717
				$event['reference'] = $event['id'];
718
				$event['recurrence'] = $content['edit_single'];
719
				unset($event['id']);
720
				$conflicts = $this->bo->update($event,$ignore_conflicts,true,false,true,$messages,$content['no_notifications']);
721
				if (!is_array($conflicts) && $conflicts)
722
				{
723
					// now we need to add the original start as recur-execption to the series
724
					$recur_event = $this->bo->read($event['reference']);
725
					$recur_event['recur_exception'][] = $content['edit_single'];
726
					// check if we need to move the alarms, because they are next on that exception
727
					foreach($recur_event['alarm'] as $id => $alarm)
728
					{
729
						if ($alarm['time'] == $content['edit_single'] - $alarm['offset'])
730
						{
731
							$rrule = calendar_rrule::event2rrule($recur_event, true);
732
							foreach ($rrule as $time)
733
							{
734 View Code Duplication
								if ($content['edit_single'] < $time->format('ts'))
735
								{
736
									$alarm['time'] = $time->format('ts') - $alarm['offset'];
737
									$this->bo->save_alarm($event['reference'], $alarm);
738
									break;
739
								}
740
							}
741
						}
742
					}
743
					unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
744
					unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
745
					$this->bo->update($recur_event,true);	// no conflict check here
746
747
					// Save links
748
					if($content['links'])
749
					{
750
						Link::link('calendar', $event['id'], $content['links']['to_id']);
751
					}
752
753
					if(Api\Json\Response::isJSONResponse())
754
					{
755
						// Sending null will trigger a removal of the original
756
						// for that date
757
						Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$content['reference'].':'.$content['actual_date'], 'data' => null));
758
					}
759
760
					unset($recur_event);
761
					unset($event['edit_single']);			// if we further edit it, it's just a single event
762
					unset($preserv['edit_single']);
763
				}
764
				else	// conflict or error, we need to reset everything to the state befor we tried to save it
765
				{
766
					$event['id'] = $event['reference'];
767
					$event['reference'] = $event['recurrence'] = 0;
768
					$event['uid'] = $content['uid'];
769
				}
770
				$update_type = 'edit';
771
			}
772
			else	// we edited a non-reccuring event or the whole series
773
			{
774
				if (($old_event = $this->bo->read($event['id'])))
775
				{
776
					if ($event['recur_type'] != MCAL_RECUR_NONE)
777
					{
778
						$update_type = 'edit';
779
780
						// we edit a existing series event
781
						if ($event['start'] != $old_event['start'] ||
782
							$event['whole_day'] != $old_event['whole_day'] ||
783
							$event['end'] != $old_event['end'])
784
						{
785
							// calculate offset against old series start or clicked recurrance,
786
							// depending on which is smaller
787
							$offset = $event['start'] - $old_event['start'];
788
							if (abs($offset) > abs($off2 = $event['start'] - $event['actual_date']))
789
							{
790
								$offset = $off2;
791
							}
792
							$msg = $this->_break_recurring($event, $old_event, $event['actual_date'] + $offset,$content['no_notifications']);
793
							if($msg)
794
							{
795
								$noerror = false;
796
							}
797
						}
798
					}
799
					else
800
					{
801
						if ($old_event['start'] != $event['start'] ||
802
							$old_event['end'] != $event['end'] ||
803
							$event['whole_day'] != $old_event['whole_day'])
804
						{
805
							$sameday = (date('Ymd', $old_event['start']) == date('Ymd', $event['start']));
806
							foreach((array)$event['participants'] as $uid => $status)
807
							{
808
								$q = $r = null;
809
								calendar_so::split_status($status,$q,$r);
810
								if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user && $status != 'U')
811
								{
812
									$preferences = new Api\Preferences($uid);
813
									$part_prefs = $preferences->read_repository();
814
									switch ($part_prefs['calendar']['reset_stati'])
815
									{
816
										case 'no':
817
											break;
818
										case 'startday':
819
											if ($sameday) break;
820
										default:
821
											$status_reset_to_unknown = true;
822
											$event['participants'][$uid] = calendar_so::combine_status('U',$q,$r);
823
											// todo: report reset status to user
824
									}
825
								}
826
							}
827
							// check if we need to move the alarms, because they are relative
828
							$this->bo->check_move_alarms($event, $old_event);
829
						}
830
					}
831
				}
832
				// Adding participants needs to be done as an edit, in case we
833
				// have participants visible in seperate calendars
834
				if(is_array($old_event['participants']) && count(array_diff_key($event['participants'], $old_event['participants'])))
835
				{
836
					$update_type = 'edit';
837
				}
838
				// Changing category may affect event filtering
839
				if($this->cal_prefs['saved_states']['cat_id'] && $old_event['category'] != $event['category'])
840
				{
841
					$update_type = 'edit';
842
				}
843
				$conflicts = $this->bo->update($event,$ignore_conflicts,true,false,true,$messages,$content['no_notifications']);
844
				unset($event['ignore']);
845
			}
846
			if (is_array($conflicts))
847
			{
848
				$event['button_was'] = $button;	// remember for ignore
849
				return $this->conflicts($event,$conflicts,$preserv);
850
			}
851
852
			// Event spans multiple days, need an edit to make sure they all get updated
853
			// We could check old date, as removing from days could still be an update
854
			if(date('Ymd', $event['start']) != date('Ymd', $event['end']))
855
			{
856
				$update_type = 'edit';
857
			}
858
			// check if there are messages from update, eg. removed participants or Api\Categories because of missing rights
859
			if ($messages)
860
			{
861
				$msg  .= ($msg ? ', ' : '').implode(', ',$messages);
862
			}
863
			if ($conflicts === 0)
864
			{
865
				$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
866
							lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
867
								htmlspecialchars(Egw::link('/index.php',array(
868
								'menuaction' => 'calendar.calendar_uiforms.edit',
869
								'cal_id'    => $content['id'],
870
							))).'">','</a>');
871
				$noerror = false;
872
			}
873
			elseif ($conflicts > 0)
874
			{
875
				// series moved by splitting in two --> move alarms and exceptions
876
				if ($old_event && $old_event['id'] != $event['id'])
877
				{
878
					$update_type = 'edit';
879
					foreach ((array)$old_event['alarms'] as $alarm)
880
					{
881
						// check if alarms still needed in old event, if not delete it
882
						$event_time = $alarm['time'] + $alarm['offset'];
883
						if ($event_time >= $this->bo->now_su)
884
						{
885
							$this->bo->delete_alarm($alarm['id']);
886
						}
887
						$alarm['time'] += $offset;
888
						unset($alarm['id']);
889
						// if alarm would be in the past (eg. event moved back) --> move to next possible recurrence
890
						if ($alarm['time'] < $this->bo->now_su)
891
						{
892
							if (($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su+$alarm['offset'], true)))
893
							{
894
								$alarm['time'] =  $next_occurrence['start'] - $alarm['offset'];
895
							}
896
							else
897
							{
898
								$alarm = false;	// no (further) recurence found --> ignore alarm
899
							}
900
						}
901
						// alarm is currently on a previous recurrence --> set for first recurrence of new series
902
						elseif ($event_time < $event['start'])
903
						{
904
							$alarm['time'] =  $event['start'] - $alarm['offset'];
905
						}
906
						if ($alarm)
907
						{
908
							$alarm['id'] = $this->bo->save_alarm($event['id'], $alarm);
909
							$event['alarm'][$alarm['id']] = $alarm;
910
						}
911
					}
912
					// attach all future exceptions to the new series
913
					$events =& $this->bo->search(array(
914
						'query' => array('cal_uid' => $old_event['uid']),
915
						'filter' => 'owner',  // return all possible entries
916
						'daywise' => false,
917
						'date_format' => 'ts',
918
					));
919
					foreach ((array)$events as $exception)
920
					{
921
						if ($exception['recurrence'] > $this->bo->now_su)
922
						{
923
							$exception['recurrence'] += $offset;
924
							$exception['reference'] = $event['id'];
925
							$exception['uid'] = $event['uid'];
926
							$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 update() as the parameter $messages expects a reference.
Loading history...
927
						}
928
					}
929
				}
930
931
				$message = lang('Event saved');
932 View Code Duplication
				if ($status_reset_to_unknown)
933
				{
934
					foreach((array)$event['participants'] as $uid => $status)
935
					{
936
						if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user)
937
						{
938
							calendar_so::split_status($status,$q,$r);
939
							$status = calendar_so::combine_status('U',$q,$r);
940
							$this->bo->set_status($event['id'], $uid, $status, 0, true);
941
						}
942
					}
943
					$message .= lang(', stati of participants reset');
944
				}
945
946
				$response = Api\Json\Response::get();
947
				if($response && $update_type != 'delete')
948
				{
949
					$client_updated = $this->update_client($event['id']);
950
				}
951
952
				$msg = $message . ($msg ? ', ' . $msg : '');
953
				Framework::refresh_opener($msg, 'calendar', $event['id'], $client_updated ? ($event['recur_type'] ? 'edit' : $update_type) : 'delete');
954
				// writing links for new entry, existing ones are handled by the widget itself
955 View Code Duplication
				if (!$content['id'] && is_array($content['link_to']['to_id']))
956
				{
957
					Link::link('calendar',$event['id'],$content['link_to']['to_id']);
958
				}
959
			}
960
			else
961
			{
962
				$msg = lang('Error: saving the event !!!');
963
			}
964
			break;
965
966
		case 'cancel':
967
			if($content['cancel_needs_refresh'])
968
			{
969
				Framework::refresh_opener($msg, 'calendar');
970
			}
971
			break;
972
973
		case 'delete':					// delete of event (regular or series)
974
			$exceptions_kept = null;
975
			if ($this->bo->delete($event['id'], (int)$content['edit_single'], false, $event['no_notifications'],
976
				$content['delete_exceptions'] == 'true', $exceptions_kept))
977
			{
978
				if ($event['recur_type'] != MCAL_RECUR_NONE && $content['reference'] == 0 && !$content['edit_single'])
979
				{
980
					$msg = lang('Series deleted');
981
					if ($exceptions_kept) $msg .= lang(', exceptions preserved');
982
				}
983
				else
984
				{
985
					$msg = lang('Event deleted');
986
				}
987
988
			}
989
			break;
990
991
		case 'freetime':
992
			// the "click" has to be in onload, to make sure the button is already created
993
			$event['button_was'] = $button;
994
			break;
995
996
		case 'add_alarm':
997
			$time = $content['start'];
998
			$offset = $time - $content['new_alarm']['date'];
999
			if ($event['recur_type'] != MCAL_RECUR_NONE &&
1000
				($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + $offset, true)) &&
1001
				$time < $next_occurrence['start'])
1002
			{
1003
				$content['new_alarm']['date'] = $next_occurrence['start'] - $offset;
1004
			}
1005
			if ($this->bo->check_perms(Acl::EDIT,!$content['new_alarm']['owner'] ? $event : 0,$content['new_alarm']['owner']))
0 ignored issues
show
Bug introduced by
It seems like !$content['new_alarm']['owner'] ? $event : 0 can also be of type array; however, calendar_bo::check_perms() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1006
			{
1007
				$alarm = array(
1008
					'offset' => $offset,
1009
					'time'   => $content['new_alarm']['date'],
1010
					'all'    => !$content['new_alarm']['owner'],
1011
					'owner'  => $content['new_alarm']['owner'] ? $content['new_alarm']['owner'] : $this->user,
1012
				);
1013
				if ($alarm['time'] < $this->bo->now_su)
1014
				{
1015
					$msg = lang("Can't add alarms in the past !!!");
1016
				}
1017
				elseif ($event['id'])	// save the alarm immediatly
1018
				{
1019
					if (($alarm_id = $this->bo->save_alarm($event['id'],$alarm)))
1020
					{
1021
						$alarm['id'] = $alarm_id;
1022
						$event['alarm'][$alarm_id] = $alarm;
1023
1024
						$msg = lang('Alarm added');
1025
						Framework::refresh_opener($msg,'calendar', $event['id'], 'update');
1026
					}
1027
					else
1028
					{
1029
						$msg = lang('Error adding the alarm');
1030
					}
1031
				}
1032
				else
1033
				{
1034
					for($alarm['id']=1; isset($event['alarm'][$alarm['id']]); $alarm['id']++) {}	// get a temporary non-conflicting, numeric id
0 ignored issues
show
Unused Code introduced by
This for loop is empty and can be removed.

This check looks for for loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

Consider removing the loop.

Loading history...
1035
					$event['alarm'][$alarm['id']] = $alarm;
1036
				}
1037
			}
1038
			else
1039
			{
1040
				$msg = lang('Permission denied');
1041
			}
1042
			break;
1043
		}
1044
		// add notification-errors, if we have some
1045
		if (($notification_errors = notifications::errors(true)))
1046
		{
1047
			$msg .= ($msg ? "\n" : '').implode("\n", $notification_errors);
1048
		}
1049
		// New event, send data before updating so it's there
1050
		$response = Api\Json\Response::get();
1051
		if($response && !$content['id'] && $event['id'])
1052
		{
1053
			$client_updated = $this->update_client($event['id']);
1054
		}
1055
		if (in_array($button,array('cancel','save','delete','delete_exceptions','delete_keep_exceptions')) && $noerror)
1056
		{
1057
			if ($content['lock_token'])	// remove an existing lock
1058
			{
1059
				Vfs::unlock(Vfs::app_entry_lock_path('calendar',$content['id']),$content['lock_token'],false);
1060
			}
1061
			if ($content['no_popup'])
1062
			{
1063
				Egw::redirect_link('/index.php',array(
1064
					'menuaction' => 'calendar.calendar_uiviews.index',
1065
					'msg'        => $msg,
1066
				));
1067
			}
1068
			if (in_array($button,array('delete_exceptions','delete_keep_exceptions')) || $content['recur_type'] && $button == 'delete')
1069
			{
1070
				Framework::refresh_opener($msg,'calendar');
1071
			}
1072
			else
1073
			{
1074
				Framework::refresh_opener($msg, 'calendar',
1075
					$event['id'] . ($content['edit_single'] ? ':' . (int)$content['edit_single'] : '' ),
1076
					$button == 'save' && $client_updated ? ($content['id'] ? $update_type : 'add') : 'delete'
1077
				);
1078
			}
1079
			Framework::window_close();
1080
			exit();
1081
		}
1082
		unset($event['no_notifications']);
1083
		return $this->edit($event,$preserv,$msg,$event['id'] ? $event['id'] : $content['link_to']['to_id']);
1084
	}
1085
1086
	/**
1087
	 * Create an exception from the clicked event
1088
	 *
1089
	 * It's not stored to the DB unless the user saves it!
1090
	 *
1091
	 * @param array &$event
1092
	 * @param array &$preserv
1093
	 * @return string message that exception was created
1094
	 */
1095
	function _create_exception(&$event,&$preserv)
1096
	{
1097
		// In some cases where the user makes the first day an exception, actual_date may be missing
1098
		$preserv['actual_date'] = $preserv['actual_date'] ? $preserv['actual_date'] : $event['start'];
1099
1100
		$event['end'] += $preserv['actual_date'] - $event['start'];
1101
		$event['reference'] = $preserv['reference'] = $event['id'];
1102
		$event['recurrence'] = $preserv['recurrence'] = $preserv['actual_date'];
1103
		$event['start'] = $preserv['edit_single'] = $preserv['actual_date'];
1104
		$event['recur_type'] = MCAL_RECUR_NONE;
1105
		foreach(array('recur_enddate','recur_interval','recur_exception','recur_data') as $name)
1106
		{
1107
			unset($event[$name]);
1108
		}
1109
		// add all alarms as new alarms to execption
1110
		$event['alarm'] = array_values((array)$event['alarm']);
1111
		foreach($event['alarm'] as &$alarm)
1112
		{
1113
			unset($alarm['uid'], $alarm['id'], $alarm['time']);
1114
		}
1115
1116
		// Copy links
1117
		if(!is_array($event['link_to'])) $event['link_to'] = array();
1118
		$event['link_to']['to_app'] = 'calendar';
1119
		$event['link_to']['to_id'] = 0;
1120
		
1121
		foreach(Link::get_links($event['link_to']['to_app'], $event['id']) as $link)
1122
		{
1123
			if(!$link['id']) continue;
1124 View Code Duplication
			if ($link['app'] != Link::VFS_APPNAME)
1125
			{
1126
				Link::link('calendar', $event['link_to']['to_id'], $link['app'], $link['id'], $link['remark']);
1127
			}
1128
			elseif ($link['app'] == Link::VFS_APPNAME)
1129
			{
1130
				Link::link('calendar', $event['link_to']['to_id'], Link::VFS_APPNAME, array(
1131
					'tmp_name' => Link::vfs_path($link['app2'], $link['id2']).'/'.$link['id'],
1132
					'name' => $link['id'],
1133
				), $link['remark']);
1134
			}
1135
		}
1136
1137
		$event['links'] = $event['link_to'];
1138
1139
		if($this->bo->check_perms(Acl::EDIT,$event))
1140
		{
1141
			return lang('Save event as exception - Delete single occurrence - Edit status or alarms for this particular day');
1142
		}
1143
		return lang('Edit status or alarms for this particular day');
1144
	}
1145
1146
	/**
1147
	 * Since we cannot change recurrences in the past, break a recurring
1148
	 * event (that starts in the past), and create a new event.
1149
	 *
1150
	 * $old_event will be ended (if needed) and $event will be modified with the
1151
	 * new start date and time.  It is not allowed to edit events in the past,
1152
	 * so if $as_of_date is in the past, it will be adjusted to today.
1153
	 *
1154
	 * @param array &$event Event to be modified
1155
	 * @param array $old_event Unmodified (original) event, as read from the database
1156
	 * @param date $as_of_date If provided, the break will be done as of this
1157
	 *	date instead of today
1158
	 * @param boolean $no_notifications Toggle notifications to participants
1159
	 *
1160
	 * @return false or error message
1161
	 */
1162
	function _break_recurring(&$event, $old_event, $as_of_date = null, $no_notifications = true)
1163
	{
1164
		$msg = false;
1165
1166
		if(!$as_of_date )
1167
		{
1168
			$as_of_date = time();
1169
		}
1170
1171
		//error_log(__METHOD__ . Api\DateTime::to($old_event['start']) . ' -> '. Api\DateTime::to($event['start']) . ' as of ' . Api\DateTime::to($as_of_date));
1172
1173
		if(!($next_occurrence = $this->bo->read($event['id'], $this->bo->now_su + 1, true)))
1174
		{
1175
			$msg = lang("Error: You can't shift a series from the past!");
1176
			return $msg;
1177
		}
1178
1179
		// Hold on to this in case something goes wrong
1180
		$orig_event = $event;
1181
1182
		$offset = $event['start'] - $old_event['start'];
1183
		$duration = $event['duration'] ? $event['duration'] : $event['end'] - $event['start'];
1184
1185
		// base start-date of new series on actual / clicked date
1186
		$event['start'] = $as_of_date ;
1187
1188
		if (Api\DateTime::to($old_event['start'],'Ymd') < Api\DateTime::to($as_of_date,'Ymd') ||
1189
			// Adjust for requested date in the past
1190
			Api\DateTime::to($as_of_date,'ts') < time()
1191
		)
1192
		{
1193
1194
			unset($orig_event);
1195
			// copy event by unsetting the id(s)
1196
			unset($event['id']);
1197
			unset($event['uid']);
1198
			unset($event['caldav_name']);
1199
			$event['alarm'] = array();
1200
1201
			// set enddate of existing event
1202
			$rriter = calendar_rrule::event2rrule($old_event, true);
1203
			$rriter->rewind();
1204
			$last = $rriter->current();
1205
			do
1206
			{
1207
				$rriter->next_no_exception();
1208
				$occurrence = $rriter->current();
1209
			}
1210
			while ($rriter->valid()  && (
1211
				Api\DateTime::to($occurrence, 'ts') <= time() ||
1212
				Api\DateTime::to($occurrence, 'Ymd') < Api\DateTime::to($as_of_date,'Ymd')
1213
			) && ($last = $occurrence));
1214
1215
1216
			// Make sure as_of_date is still valid, may have to move forward
1217
			if(Api\DateTime::to($as_of_date,'ts') < Api\DateTime::to($last,'ts') ||
1218
				Api\DateTime::to($as_of_date, 'Ymd') == Api\DateTime::to($last, 'Ymd'))
1219
			{
1220
				$event['start'] = Api\DateTime::to($rriter->current(),'ts') + $offset;
1221
			}
1222
1223
			//error_log(__METHOD__ ." Series should end at " . Api\DateTime::to($last) . " New series starts at " . Api\DateTime::to($event['start']));
1224
			if ($duration)
1225
			{
1226
				$event['end'] = $event['start'] + $duration;
1227
			}
1228
			elseif($event['end'] < $event['start'])
1229
			{
1230
				$event['end'] = $old_event['end'] - $old_event['start'] + $event['start'];
1231
			}
1232
			//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");
1233
1234
			$event['participants'] = $old_event['participants'];
1235
			foreach ($old_event['recur_exception'] as $key => $exdate)
1236
			{
1237
				if ($exdate > Api\DateTime::to($last,'ts'))
1238
				{
1239
					//error_log("Moved exception on " . Api\DateTime::to($exdate));
1240
					unset($old_event['recur_exception'][$key]);
1241
					$event['recur_exception'][$key] += $offset;
1242
				}
1243
				else
1244
				{
1245
					//error_log("Kept exception on ". Api\DateTime::to($exdate));
1246
					unset($event['recur_exception'][$key]);
1247
				}
1248
			}
1249
			$last->setTime(0, 0, 0);
1250
			$old_event['recur_enddate'] = Api\DateTime::to($last, 'ts');
1251
			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 update() as the parameter $messages expects a reference.
Loading history...
1252
			{
1253
				$msg .= ($msg ? ', ' : '') .lang('Error: the entry has been updated since you opened it for editing!').'<br />'.
1254
					lang('Copy your changes to the clipboard, %1reload the entry%2 and merge them.','<a href="'.
1255
						htmlspecialchars(Egw::link('/index.php',array(
1256
							'menuaction' => 'calendar.calendar_uiforms.edit',
1257
							'cal_id'    => $event['id'],
1258
						))).'">','</a>');
1259
				$event = $orig_event;
1260
			}
1261
		}
1262
		$event['start'] = Api\DateTime::to($event['start'],'ts');
1263
		return $msg;
1264
	}
1265
1266
	/**
1267
	 * return javascript to open mail compose window with preset content to mail all participants
1268
	 *
1269
	 * @param array $event
1270
	 * @param boolean $added
1271
	 * @return string javascript window.open command
1272
	 */
1273
	function ajax_custom_mail($event,$added,$asrequest=false)
1274
	{
1275
		$to = array();
1276
1277
		foreach($event['participants'] as $uid => $status)
1278
		{
1279
			//error_log(__METHOD__.__LINE__.' '.$uid.':'.array2string($status));
1280
			if (empty($status)) continue;
1281
			$toadd = '';
1282
			if ((isset($status['status']) && $status['status'] == 'R') || (isset($status['uid']) && $status['uid'] == $this->user)) continue;
1283
1284
			if (isset($status['uid']) && is_numeric($status['uid']) && $GLOBALS['egw']->accounts->get_type($status['uid']) == 'u')
1285
			{
1286
				if (!($email = $GLOBALS['egw']->accounts->id2name($status['uid'],'account_email'))) continue;
1287
1288
				$toadd = $GLOBALS['egw']->accounts->id2name($status['uid'], 'account_firstname').' '.
1289
					$GLOBALS['egw']->accounts->id2name($status['uid'], 'account_lastname').' <'.$email.'>';
1290
1291
				if (!in_array($toadd,$to)) $to[] = $toadd;
1292
			}
1293
			elseif ($uid < 0)
1294
			{
1295
				foreach($GLOBALS['egw']->accounts->members($uid,true) as $uid)
1296
				{
1297
					if (!($email = $GLOBALS['egw']->accounts->id2name($uid,'account_email'))) continue;
1298
1299
					$toadd = $GLOBALS['egw']->accounts->id2name($uid, 'account_firstname').' '.
1300
						$GLOBALS['egw']->accounts->id2name($uid, 'account_lastname').' <'.$email.'>';
1301
1302
					// dont add groupmembers if they already rejected the event, or are the current user
1303
					if (!in_array($toadd,$to) && ($event['participants'][$uid] !== 'R' && $uid != $this->user)) $to[] = $toadd;
1304
				}
1305
			}
1306
			elseif(!empty($status['uid'])&& !is_numeric(substr($status['uid'],0,1)) && ($info = $this->bo->resource_info($status['uid'])))
1307
			{
1308
				$to[] = $info['email'];
1309
				//error_log(__METHOD__.__LINE__.array2string($to));
1310
			}
1311
			elseif(!is_numeric(substr($uid,0,1)) && ($info = $this->bo->resource_info($uid)))
1312
			{
1313
				$to[] = $info['email'];
1314
				//error_log(__METHOD__.__LINE__.array2string($to));
1315
			}
1316
		}
1317
		// prefer event description over standard notification text
1318
		if (empty($event['description']))
1319
		{
1320
			list(,$body) = $this->bo->get_update_message($event,$added ? MSG_ADDED : MSG_MODIFIED);	// update-message is in TZ of the user
1321
		}
1322
		else
1323
		{
1324
			$body = $event['description'];
1325
		}
1326
		// respect user preference about html mail
1327 View Code Duplication
		if ($GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text')
1328
		{
1329
			$body = '<pre>'.$body.'</pre>';
1330
		}
1331
		//error_log(__METHOD__.print_r($event,true));
1332
		$boical = new calendar_ical();
1333
		// we need to pass $event[id] so iCal class reads event again,
1334
		// as event is in user TZ, but iCal class expects server TZ!
1335
		$ics = $boical->exportVCal(array($event['id']),'2.0','REQUEST',false);
1336
1337
		$ics_file = tempnam($GLOBALS['egw_info']['server']['temp_dir'],'ics');
1338
		if(($f = fopen($ics_file,'w')))
1339
		{
1340
			fwrite($f,$ics);
1341
			fclose($f);
1342
		}
1343
		//error_log(__METHOD__.__LINE__.array2string($to));
1344
		$vars = array(
1345
			'menuaction'      => 'mail.mail_compose.compose',
1346
			'mimeType'		  => $GLOBALS['egw_info']['user']['preferences']['mail']['composeOptions'] != 'text' ? 'html' : 'plain',
1347
			'preset[to]'      => $to,
1348
			'preset[subject]' => $event['title'],
1349
			'preset[body]'    => $body,
1350
			'preset[name]'    => 'event.ics',
1351
			'preset[file]'    => $ics_file,
1352
			'preset[type]'    => 'text/calendar'.($asrequest?'; method=REQUEST':''),
1353
			'preset[size]'    => filesize($ics_file),
1354
		);
1355
		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.');
1356
		$response = Api\Json\Response::get();
1357
		$response->call('app.calendar.custom_mail', $vars);
1358
	}
1359
1360
	/**
1361
	 * Get title of a uid / calendar participant
1362
	 *
1363
	 * @param int|string $uid
1364
	 * @return string
1365
	 */
1366
	public function get_title($uid)
1367
	{
1368
		if (is_numeric($uid))
1369
		{
1370
			return Api\Accounts::username($uid);
1371
		}
1372
		elseif (($info = $this->bo->resource_info($uid)))
1373
		{
1374
			if ($uid[0] == 'e' && $info['name'] && $info['name'] != $info['email'])
1375
			{
1376
				return $info['name'].' <'.$info['email'].'>';
1377
			}
1378
			return $info['name'] ? $info['name'] : $info['email'];
1379
		}
1380
		return '#'.$uid;
1381
	}
1382
1383
	/**
1384
	 * Compare two uid by there title
1385
	 *
1386
	 * @param int|string $uid1
1387
	 * @param int|string $uid2
1388
	 * @return int see strnatcasecmp
1389
	 */
1390
	public function uid_title_cmp($uid1, $uid2)
1391
	{
1392
		return strnatcasecmp($this->get_title($uid1), $this->get_title($uid2));
1393
	}
1394
1395
	/**
1396
	 * Edit a calendar event
1397
	 *
1398
	 * @param array $event Event to edit, if not $_GET['cal_id'] contains the event-id
1399
	 * @param array $preserv following keys:
1400
	 *	view boolean view-mode, if no edit-access we automatic fallback to view-mode
1401
	 *	hide_delete boolean hide delete button
1402
	 *	no_popup boolean use a popup or not
1403
	 *	edit_single int timestamp of single event edited, unset/null otherwise
1404
	 * @param string $msg ='' msg to display
1405
	 * @param mixed $link_to_id ='' from or for the link-widget
1406
	 * @param string $msg_type =null default automatic detect, if it contains "error"
1407
	 */
1408
	function edit($event=null,$preserv=null,$msg='',$link_to_id='',$msg_type=null)
1409
	{
1410
		$sel_options = array(
1411
			'recur_type' => &$this->bo->recur_types,
1412
			'status'     => $this->bo->verbose_status,
1413
			'duration'   => $this->durations,
1414
			'role'       => $this->bo->roles,
1415
			'new_alarm[options]' => $this->bo->alarms + array(0 => lang('Custom')),
1416
			'action'     => array(
1417
				'copy' => array('label' => 'Copy', 'title' => 'Copy this event'),
1418
				'ical' => array('label' => 'Export', 'title' => 'Download this event as iCal'),
1419
				'print' => array('label' => 'Print', 'title' => 'Print this event'),
1420
				'infolog' => array('label' => 'InfoLog', 'title' => 'Create an InfoLog from this event'),
1421
				'mail' => array('label' => 'Mail all participants', 'title' => 'Compose a mail to all participants after the event is saved'),
1422
				'sendrequest' => array('label' => 'Meetingrequest to all participants', 'title' => 'Send meetingrequest to all participants after the event is saved'),
1423
			),
1424
		);
1425
		unset($sel_options['status']['G']);
1426
		if (!is_array($event))
1427
		{
1428
			$preserv = array(
1429
				'no_popup' => isset($_GET['no_popup']),
1430
				'template' => isset($_GET['template']) ? $_GET['template'] : (isset($_REQUEST['print']) ? 'calendar.print' : 'calendar.edit'),
1431
			);
1432
			$cal_id = (int) $_GET['cal_id'];
1433
			if($_GET['action'])
1434
			{
1435
				$event = $this->bo->read($cal_id);
1436
				$event['action'] = $_GET['action'];
1437
				unset($event['participants']);
1438
				return $this->process_edit($event);
1439
			}
1440
			// vfs url
1441
			if (!empty($_GET['ical_url']) && parse_url($_GET['ical_url'], PHP_URL_SCHEME) == 'vfs')
1442
			{
1443
				$_GET['ical_vfs'] = parse_url($_GET['ical_url'], PHP_URL_PATH);
1444
			}
1445
			// vfs path
1446
			if (!empty($_GET['ical_vfs']) &&
1447
				(!Vfs::file_exists($_GET['ical_vfs']) || !($_GET['ical'] = file_get_contents(Vfs::PREFIX.$_GET['ical_vfs']))))
1448
			{
1449
				//error_log(__METHOD__."() Error: importing the iCal: vfs file not found '$_GET[ical_vfs]'!");
1450
				$msg = lang('Error: importing the iCal').': '.lang('VFS file not found').': '.$_GET['ical_vfs'];
1451
				$event =& $this->default_add_event();
1452
			}
1453
			if (!empty($_GET['ical_data']) &&
1454
				!($_GET['ical'] = Link::get_data($_GET['ical_data'])))
1455
			{
1456
				//error_log(__METHOD__."() Error: importing the iCal: data not found '$_GET[ical_data]'!");
1457
				$msg = lang('Error: importing the iCal').': '.lang('Data not found').': '.$_GET['ical_data'];
1458
				$event =& $this->default_add_event();
1459
			}
1460
			if (!empty($_GET['ical']))
1461
			{
1462
				$ical = new calendar_ical();
1463
				if (!($events = $ical->icaltoegw($_GET['ical'], '', 'utf-8')))
1464
				{
1465
					error_log(__METHOD__."('$_GET[ical]') error parsing iCal!");
1466
					$msg = lang('Error: importing the iCal');
1467
					$event =& $this->default_add_event();
1468
				}
1469
				else
1470
				{
1471
					if (count($events) > 1)
1472
					{
1473
						$msg = lang('%1 events in iCal file, only first one imported and displayed!', count($events));
1474
						$msg_type = 'notice';	// no not hide automatic
1475
					}
1476
					// as icaltoegw returns timestamps in server-time, we have to convert them here to user-time
1477
					$this->bo->db2data($events, 'ts');
1478
1479
					$event = array_shift($events);
1480
					if (($existing_event = $this->bo->read($event['uid'])))
1481
					{
1482
						$event = $existing_event;
1483
					}
1484
					else
1485
					{
1486
						$event['participant_types'] = array();
1487
						foreach($event['participants'] as $uid => $status)
1488
						{
1489
							$user_type = $user_id = null;
1490
							calendar_so::split_user($uid, $user_type, $user_id);
1491
							$event['participant_types'][$user_type][$user_id] = $status;
1492
						}
1493
					}
1494
					//error_log(__METHOD__."(...) parsed as ".array2string($event));
1495
				}
1496
				unset($ical);
1497
			}
1498
			elseif (!$cal_id || $cal_id && !($event = $this->bo->read($cal_id)))
1499
			{
1500
				if ($cal_id)
1501
				{
1502
					if (!$preserv['no_popup'])
1503
					{
1504
						Framework::window_close(lang('Permission denied'));
1505
					}
1506
					else
1507
					{
1508
						$GLOBALS['egw']->framework->render('<p class="message" align="center">'.lang('Permission denied')."</p>\n",null,true);
1509
						exit();
1510
					}
1511
				}
1512
				$event =& $this->default_add_event();
1513
			}
1514
			else
1515
			{
1516
				$preserv['actual_date'] = $event['start'];		// remember the date clicked
1517
				if ($event['recur_type'] != MCAL_RECUR_NONE)
1518
				{
1519 View Code Duplication
					if (empty($event['whole_day']))
1520
					{
1521
						$date = $_GET['date'];
1522
					}
1523
					else
1524
					{
1525
						$date = $this->bo->so->startOfDay(new Api\DateTime($_GET['date'], Api\DateTime::$user_timezone));
1526
						$date->setUser();
1527
					}
1528
					$event = $this->bo->read($cal_id, $date, true);
1529
					$preserv['actual_date'] = $event['start'];		// remember the date clicked
1530
					if ($_GET['exception'])
1531
					{
1532
						$msg = $this->_create_exception($event,$preserv);
1533
					}
1534
					else
1535
					{
1536
						$event = $this->bo->read($cal_id, null, true);
1537
					}
1538
				}
1539
			}
1540
			// set new start and end if given by $_GET
1541
			if(isset($_GET['start'])) { $event['start'] = Api\DateTime::to($_GET['start'],'ts'); }
1542
			if(isset($_GET['end'])) { $event['end'] = Api\DateTime::to($_GET['end'],'ts'); }
1543
			if(isset($_GET['non_blocking'])) { $event['non_blocking'] = (bool)$_GET['non_blocking']; }
1544
			// check if the event is the whole day
1545
			$start = $this->bo->date2array($event['start']);
1546
			$end = $this->bo->date2array($event['end']);
1547
			$event['whole_day'] = !$start['hour'] && !$start['minute'] && $end['hour'] == 23 && $end['minute'] == 59;
1548
1549
			$link_to_id = $event['id'];
1550
			if (!$event['id'] && isset($_REQUEST['link_app']) && isset($_REQUEST['link_id']))
1551
			{
1552
				$link_ids = is_array($_REQUEST['link_id']) ? $_REQUEST['link_id'] : array($_REQUEST['link_id']);
1553
				foreach(is_array($_REQUEST['link_app']) ? $_REQUEST['link_app'] : array($_REQUEST['link_app']) as $n => $link_app)
1554
				{
1555
					$link_id = $link_ids[$n];
1556
					if(!preg_match('/^[a-z_0-9-]+:[:a-z_0-9-]+$/i',$link_app.':'.$link_id))	// guard against XSS
1557
					{
1558
						continue;
1559
					}
1560
					if(!$n)
1561
					{
1562
						$event['title'] = Link::title($link_app,$link_id);
1563
						// ask first linked app via "calendar_set" hook, for further data to set, incl. links
1564 View Code Duplication
						if (($set = Api\Hooks::single($event+array('location'=>'calendar_set','entry_id'=>$link_id),$link_app)))
1565
						{
1566
							foreach((array)$set['link_app'] as $i => $l_app)
1567
							{
1568
								if (($l_id=$set['link_id'][$i])) Link::link('calendar',$event['link_to']['to_id'],$l_app,$l_id);
1569
							}
1570
							unset($set['link_app']);
1571
							unset($set['link_id']);
1572
1573
							$event = array_merge($event,$set);
1574
						}
1575
					}
1576
					Link::link('calendar',$link_to_id,$link_app,$link_id);
1577
				}
1578
			}
1579
		}
1580
1581
		$etpl = new Etemplate();
1582
		if (!$etpl->read($preserv['template']))
1583
		{
1584
			$etpl->read($preserv['template'] = 'calendar.edit');
1585
		}
1586
		$view = $preserv['view'] = $preserv['view'] || $event['id'] && !$this->bo->check_perms(Acl::EDIT,$event);
1587
		//echo "view=$view, event="; _debug_array($event);
1588
		// shared locking of entries to edit
1589
		if (!$view && ($locktime = $GLOBALS['egw_info']['server']['Lock_Time_Calender']) && $event['id'])
1590
		{
1591
			$lock_path = Vfs::app_entry_lock_path('calendar',$event['id']);
1592
			$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
1593
1594
			if (($preserv['lock_token'] = $event['lock_token']))		// already locked --> refresh the lock
1595
			{
1596
				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 lock() as the parameter $scope expects a reference.
Loading history...
Bug introduced by
$type = 'write' cannot be passed to lock() as the parameter $type expects a reference.
Loading history...
1597
			}
1598
			if (($lock = Vfs::checkLock($lock_path)) && $lock['owner'] != $lock_owner)
1599
			{
1600
				$msg .= ' '.lang('This entry is currently opened by %1!',
1601
					(($lock_uid = $GLOBALS['egw']->accounts->name2id(substr($lock['owner'],7),'account_email')) ?
1602
					Api\Accounts::username($lock_uid) : $lock['owner']));
1603
			}
1604
			elseif($lock)
1605
			{
1606
				$preserv['lock_token'] = $lock['token'];
1607
			}
1608
			elseif(Vfs::lock($lock_path,$preserv['lock_token'],$locktime,$lock_owner,$scope='shared',$type='write',false,false))
0 ignored issues
show
Bug introduced by
$scope = 'shared' cannot be passed to lock() as the parameter $scope expects a reference.
Loading history...
Bug introduced by
$type = 'write' cannot be passed to lock() as the parameter $type expects a reference.
Loading history...
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
1609
			{
1610
				//We handle AJAX_REQUEST in client-side for unlocking the locked entry, in case of closing the entry by X button or close button
1611
			}
1612
			else
1613
			{
1614
				$msg .= ' '.lang("Can't aquire lock!");		// eg. an exclusive lock via CalDAV ...
1615
				$view = true;
1616
			}
1617
		}
1618
		$content = array_merge($event,array(
1619
			'link_to' => array(
1620
				'to_id'  => $link_to_id,
1621
				'to_app' => 'calendar',
1622
			),
1623
			'edit_single' => $preserv['edit_single'],	// need to be in content too, as it is used in the template
1624
			'tabs'   => $preserv['tabs'],
1625
			'view' => $view,
1626
			'query_delete_exceptions' => (int)($event['recur_type'] && $event['recur_exception']),
1627
		));
1628
		Framework::message($msg, $msg_type);
1629
		$content['duration'] = $content['end'] - $content['start'];
1630
		if (isset($this->durations[$content['duration']])) $content['end'] = '';
1631
1632
		$row = 3;
1633
		$readonlys = $content['participants'] = $preserv['participants'] = array();
1634
		// preserve some ui elements, if set eg. under error-conditions
1635
		foreach(array('quantity','resource','role') as $n)
1636
		{
1637
			if (isset($event['participants'][$n])) $content['participants'][$n] = $event['participants'][$n];
1638
		}
1639
		foreach($event['participant_types'] as $type => $participants)
1640
		{
1641
			$name = 'accounts';
1642
			if (isset($this->bo->resources[$type]))
1643
			{
1644
				$name = $this->bo->resources[$type]['app'];
1645
			}
1646
			// sort participants (in there group/app) by title
1647
			uksort($participants, array($this, 'uid_title_cmp'));
1648
			foreach($participants as $id => $status)
1649
			{
1650
				$uid = $type == 'u' ? $id : $type.$id;
1651
				$quantity = $role = null;
1652
				calendar_so::split_status($status,$quantity,$role);
1653
				$preserv['participants'][$row] = $content['participants'][$row] = array(
1654
					'app'      => $name == 'accounts' ? ($GLOBALS['egw']->accounts->get_type($id) == 'g' ? 'Group' : 'User') : $name,
1655
					'uid'      => $uid,
1656
					'status'   => $status,
1657
					'old_status' => $status,
1658
					'quantity' => $quantity > 1 || $uid[0] == 'r' ? $quantity : '',	// only display quantity for resources or if > 1
1659
					'role'     => $role,
1660
				);
1661
				// replace iCal roles with a nicer label and remove regular REQ-PARTICIPANT
1662
				if (isset($this->bo->roles[$role]))
1663
				{
1664
					$content['participants'][$row]['role_label'] = lang($this->bo->roles[$role]);
1665
				}
1666
				// allow third party apps to use categories for roles
1667
				elseif(substr($role,0,6) == 'X-CAT-')
1668
				{
1669
					$content['participants'][$row]['role_label'] = $GLOBALS['egw']->categories->id2name(substr($role,6));
1670
				}
1671
				else
1672
				{
1673
					$content['participants'][$row]['role_label'] = lang(str_replace('X-','',$role));
1674
				}
1675
				$content['participants'][$row]['delete_id'] = strpbrk($uid,'"\'<>') !== false ? md5($uid) : $uid;
1676
				//echo "<p>$uid ($quantity): $role --> {$content['participants'][$row]['role']}</p>\n";
1677
1678
				if (($no_status = !$this->bo->check_status_perms($uid,$event)) || $view)
1679
					$readonlys['participants'][$row]['status'] = $no_status;
1680 View Code Duplication
				if ($preserv['hide_delete'] || !$this->bo->check_perms(Acl::EDIT,$event))
1681
					$readonlys['participants']['delete'][$uid] = true;
1682
				// todo: make the participants available as links with email as title
1683
				$content['participants'][$row++]['title'] = $this->get_title($uid);
1684
				// enumerate group-invitations, so people can accept/reject them
1685
				if ($name == 'accounts' && $GLOBALS['egw']->accounts->get_type($id) == 'g' &&
1686
					($members = $GLOBALS['egw']->accounts->members($id,true)))
1687
				{
1688
					$sel_options['status']['G'] = lang('Select one');
1689
					// sort members by title
1690
					usort($members, array($this, 'uid_title_cmp'));
1691
					foreach($members as $member)
1692
					{
1693
						if (!isset($participants[$member]) && $this->bo->check_perms(Acl::READ,0,$member))
1694
						{
1695
							$preserv['participants'][$row] = $content['participants'][$row] = array(
1696
								'app'      => 'Group invitation',
1697
								'uid'      => $member,
1698
								'status'   => 'G',
1699
							);
1700
							$readonlys['participants'][$row]['quantity'] = $readonlys['participants']['delete'][$member] = true;
1701
							// read access is enough to invite participants, but you need edit rights to change status
1702
							$readonlys['participants'][$row]['status'] = !$this->bo->check_perms(Acl::EDIT,0,$member);
1703
							$content['participants'][$row++]['title'] = Api\Accounts::username($member);
1704
						}
1705
					}
1706
				}
1707
			}
1708
			// resouces / apps we shedule, atm. resources and addressbook
1709
			$content['participants']['cal_resources'] = '';
1710
			foreach($this->bo->resources as $data)
1711
			{
1712
				if ($data['app'] == 'email') continue;	// make no sense, as we cant search for email
1713
				$content['participants']['cal_resources'] .= ','.$data['app'];
1714
			}
1715
		}
1716
		$content['participants']['status_date'] = $preserv['actual_date'];
1717
		$preserved = array_merge($preserv,$content);
1718
		$event['new_alarm']['options'] = $content['new_alarm']['options'];
1719
		if ($event['alarm'])
1720
		{
1721
			// makes keys of the alarm-array starting with 1
1722
			$content['alarm'] = array(false);
1723
			foreach(array_values($event['alarm']) as $id => $alarm)
1724
			{
1725
				if (!$alarm['all'] && !$this->bo->check_perms(Acl::READ,0,$alarm['owner']))
1726
				{
1727
					continue;	// no read rights to the calendar of the alarm-owner, dont show the alarm
1728
				}
1729
				$alarm['all'] = (int) $alarm['all'];
1730
				$after = false;
1731
				if($alarm['offset'] < 0)
1732
				{
1733
					$after = true;
1734
					$alarm['offset'] = -1 * $alarm['offset'];
1735
				}
1736
				$days = (int) ($alarm['offset'] / DAY_s);
1737
				$hours = (int) (($alarm['offset'] % DAY_s) / HOUR_s);
1738
				$minutes = (int) (($alarm['offset'] % HOUR_s) / 60);
1739
				$label = array();
1740
				if ($days) $label[] = $days.' '.lang('days');
1741
				if ($hours) $label[] = $hours.' '.lang('hours');
1742
				if ($minutes) $label[] = $minutes.' '.lang('Minutes');
1743
				$alarm['offset'] = implode(', ',$label) . ' ' . ($after ? lang('after') : lang('before'));
1744
				$content['alarm'][] = $alarm;
1745
1746
				$readonlys['alarm[delete_alarm]['.$alarm['id'].']'] = !$this->bo->check_perms(Acl::EDIT,$alarm['all'] ? $event : 0,$alarm['owner']);
1747
			}
1748
			if (count($content['alarm']) == 1)
1749
			{
1750
				$content['alarm'] = false; // no alarms added to content array
1751
			}
1752
		}
1753
		else
1754
		{
1755
			$content['alarm'] = false;
1756
		}
1757
		$content['msg'] = $msg;
1758
1759
		if ($view)
1760
		{
1761
			$readonlys['__ALL__'] = true;	// making everything readonly, but widgets set explicitly to false
1762
			$readonlys['button[cancel]'] = $readonlys['action'] =
1763
				$readonlys['before_after'] = $readonlys['button[add_alarm]'] = $readonlys['new_alarm[owner]'] =
1764
				$readonlys['new_alarm[options]'] = $readonlys['new_alarm[date]'] = false;
1765
1766
			$content['participants']['no_add'] = true;
1767
1768
			if(!$event['whole_day'])
1769
			{
1770
				$etpl->setElementAttribute('whole_day', 'disabled', true);
1771
			}
1772
1773
			// respect category permissions
1774 View Code Duplication
			if(!empty($event['category']))
1775
			{
1776
				$content['category'] = $this->categories->check_list(Acl::READ, $event['category']);
1777
			}
1778
		}
1779
		else
1780
		{
1781
			$readonlys['recur_exception'] = true;
1782
1783
			if ($event['recur_type'] != MCAL_RECUR_NONE)
1784
			{
1785
				$readonlys['recur_exception'] = !count($content['recur_exception']);	// otherwise we get a delete button
1786
				//$onclick =& $etpl->get_cell_attribute('button[delete]','onclick');
1787
				//$onclick = str_replace('Delete this event','Delete this series of recuring events',$onclick);
1788
			}
1789
			elseif ($event['reference'] != 0)
1790
			{
1791
				$readonlys['recur_type'] = $readonlys['recur_enddate'] = true;
1792
				$readonlys['recur_interval'] = $readonlys['recur_data'] = true;
1793
			}
1794
		}
1795 View Code Duplication
		if($content['category'] && !is_array($content['category']))
1796
		{
1797
			$content['category'] = explode(',',$event['category']);
1798
		}
1799
		// disabling the custom fields tab, if there are none
1800
		$readonlys['tabs'] = array(
1801
			'custom' => !count($this->bo->customfields),
1802
			'participants' => $this->accountsel->account_selection == 'none',
1803
			'history' => !$event['id'],
1804
		);
1805
		if (!isset($GLOBALS['egw_info']['user']['apps']['mail']))	// no mail without mail-app
1806
		{
1807
			unset($sel_options['action']['mail']);
1808
			unset($sel_options['action']['sendmeetingrequest']);
1809
		}
1810
		if (!$event['id'])	// no ical export for new (not saved) events
1811
		{
1812
			$readonlys['action'] = true;
1813
		}
1814
		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'])))
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $readonlys['button[excep...event['recur_enddate']), Probably Intended Meaning: ($readonlys['button[exce...$event['recur_enddate']
Loading history...
1815
		{
1816
			$content['exception_label'] = $this->bo->long_date(max($preserved['actual_date'], $event['start']));
1817
		}
1818
		$readonlys['button[delete]'] = !$event['id'] || $preserved['hide_delete'] || !$this->bo->check_perms(Acl::DELETE,$event);
1819
1820 View Code Duplication
		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
1821
		{
1822
			$sel_options['owner'][0] = lang('All participants');
1823
		}
1824
		if (isset($event['participant_types']['u'][$this->user]))
1825
		{
1826
			$sel_options['owner'][$this->user] = $this->bo->participant_name($this->user);
1827
		}
1828
		foreach((array) $event['participant_types']['u'] as $uid => $status)
1829
		{
1830
			if ($uid != $this->user && $status != 'R' && $this->bo->check_perms(Acl::EDIT,0,$uid))
1831
			{
1832
				$sel_options['owner'][$uid] = $this->bo->participant_name($uid);
1833
			}
1834
		}
1835
		$content['no_add_alarm'] = !count($sel_options['owner']);	// no rights to set any alarm
1836
		if (!$event['id'])
1837
		{
1838
			$etpl->set_cell_attribute('button[new_alarm]','type','checkbox');
1839
		}
1840
		if ($preserved['no_popup'])
1841
		{
1842
			$etpl->set_cell_attribute('button[cancel]','onclick','');
1843
		}
1844
1845
		// Allow admins to restore deleted events
1846
		if($GLOBALS['egw_info']['server']['calendar_delete_history'] && $event['deleted'] )
1847
		{
1848
			$content['deleted'] = $preserved['deleted'] = null;
1849
			$etpl->set_cell_attribute('button[save]', 'label', 'Recover');
1850
			$etpl->set_cell_attribute('button[apply]', 'disabled', true);
1851
		}
1852
		// Allow users to prevent notifications?
1853
		$etpl->set_cell_attribute('no_notifications', 'disabled', !$GLOBALS['egw_info']['server']['calendar_allow_no_notification']);
1854
1855
		// Setup history tab
1856
		$this->setup_history($content, $sel_options);
1857
1858
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - '
1859
			. (!$event['id'] ? lang('Add')
1860
				: ($view ? ($content['edit_single'] ? lang('View exception') : ($content['recur_type'] ? lang('View series') : lang('View')))
1861
					: ($content['edit_single'] ? lang('Create exception') : ($content['recur_type'] ? lang('Edit series') : lang('Edit')))));
1862
1863
		$content['cancel_needs_refresh'] = (bool)$_GET['cancel_needs_refresh'];
1864
1865
		if (!empty($preserved['lock_token'])) $content['lock_token'] = $preserved['lock_token'];
1866
1867
		// non_interactive==true from $_GET calls immediate save action without displaying the edit form
1868
		if(isset($_GET['non_interactive']) && (bool)$_GET['non_interactive'] === true)
1869
		{
1870
			unset($_GET['non_interactive']);	// prevent process_exec <--> edit loops
1871
			$content['button']['save'] = true;
1872
			$this->process_edit(array_merge($content,$preserved));
1873
		}
1874
		else
1875
		{
1876
			$etpl->exec('calendar.calendar_uiforms.process_edit',$content,$sel_options,$readonlys,$preserved,$preserved['no_popup'] ? 0 : 2);
1877
		}
1878
	}
1879
1880
	/**
1881
	 * Remove (shared) lock via ajax, when edit popup get's closed
1882
	 *
1883
	 * @param int $id
1884
	 * @param string $token
1885
	 */
1886
	function ajax_unlock($id,$token)
1887
	{
1888
		$lock_path = Vfs::app_entry_lock_path('calendar',$id);
1889
		$lock_owner = 'mailto:'.$GLOBALS['egw_info']['user']['account_email'];
1890
1891
		if (($lock = Vfs::checkLock($lock_path)) && $lock['owner'] == $lock_owner || $lock['token'] == $token)
1892
		{
1893
			Vfs::unlock($lock_path,$token,false);
1894
		}
1895
	}
1896
1897
	/**
1898
	 * Display for FMail an iCal meeting request and allow to accept, tentative or reject it or a reply and allow to apply it
1899
	 *
1900
	 * @todo Handle situation when user is NOT invited, but eg. can view that mail ...
1901
	 * @param array $event = null; special usage if $event is array('event'=>null,'msg'=>'','useSession'=>true) we
1902
	 * 		are called by new mail-app; and we intend to use the stuff passed on by session
1903
	 * @param string $msg = null
1904
	 */
1905
	function meeting(array $event=null, $msg=null)
1906
	{
1907
		$user = $GLOBALS['egw_info']['user']['account_id'];
1908
		$readonlys['button[apply]'] = true;
1909
		$_usesession=!is_array($event);
1910
		//special usage if $event is array('event'=>null,'msg'=>'','useSession'=>true) we
1911
		//are called by new mail-app; and we intend to use the stuff passed on by session
1912
		if ($event == array('event'=>null,'msg'=>'','useSession'=>true))
1913
		{
1914
			$event=null; // set to null
1915
			$_usesession=true; // trigger session read
1916
		}
1917
		if (!is_array($event))
1918
		{
1919
			$ical_charset = 'utf-8';
1920
			$ical_string = $_GET['ical'];
1921
			if ($ical_string == 'session' || $_usesession)
1922
			{
1923
				$session_data = Api\Cache::getSession('calendar', 'ical');
1924
				$ical_string = $session_data['attachment'];
1925
				$ical_charset = $session_data['charset'];
1926
				$ical_method = $session_data['method'];
1927
				unset($session_data);
1928
			}
1929
			$ical = new calendar_ical();
1930
			if (!($events = $ical->icaltoegw($ical_string, '', $ical_charset)) || count($events) != 1)
1931
			{
1932
				error_log(__METHOD__."('$_GET[ical]') error parsing iCal!");
1933
				$GLOBALS['egw']->framework->render(Api\Html::fieldset('<pre>'.htmlspecialchars($ical_string).'</pre>',
1934
					lang('Error: importing the iCal')));
1935
				return;
1936
			}
1937
			$event = array_shift($events);
1938
1939
			// convert event from servertime returned by calendar_ical to user-time
1940
			$this->bo->server2usertime($event);
1941
1942
			if (($existing_event = $this->bo->read($event['uid'])) && !$existing_event['deleted'])
1943
			{
1944
				switch(strtolower($ical_method))
1945
				{
1946
					case 'reply':
1947
						// first participant is the one replying (our iCal parser adds owner first!)
1948
						$parts = $event['participants'];
1949
						unset($parts[$existing_event['owner']]);
1950
						list($event['ical_sender_uid'], $event['ical_sender_status']) = each($parts);
1951
						$quantity = $role = null;
1952
						calendar_so::split_status($event['ical_sender_status'], $quantity, $role);
1953
1954
						if ($event['ical_sender_uid'] && $this->bo->check_status_perms($event['ical_sender_uid'], $existing_event))
1955
						{
1956
							$existing_status = $existing_event['participants'][$event['ical_sender_uid']];
1957
							calendar_so::split_status($existing_status, $quantity, $role);
1958
							if ($existing_status != $event['ical_sender_status'])
1959
							{
1960
								$readonlys['button[apply]'] = false;
1961
							}
1962
							else
1963
							{
1964
								$msg = lang('Status already applied');
1965
							}
1966
						}
1967
						break;
1968
1969
					case 'request':
1970
						$status = $existing_event['participants'][$user];
1971
						calendar_so::split_status($status, $quantity, $role);
1972
						if (strtolower($ical_method) == 'response' && isset($existing_event['participants'][$user]) &&
1973
							$status != 'U' && isset($this->bo->verbose_status[$status]))
1974
						{
1975
							$msg = lang('You already replied to this invitation with').': '.lang($this->bo->verbose_status[$status]);
1976
						}
1977
						else
1978
						{
1979
							$msg = lang('Using already existing event on server.');
1980
						}
1981
						$user_and_memberships = $GLOBALS['egw']->accounts->memberships($user, true);
1982
						$user_and_memberships[] = $user;
1983
						if (!array_intersect(array_keys($event['participants']), $user_and_memberships))
1984
						{
1985
							$msg .= ($msg ? "\n" : '').lang('You are not invited to that event!');
1986
							if ($event['id'])
1987
							{
1988
								$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
1989
									$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
1990
							}
1991
						}
1992
						break;
1993
				}
1994
				$event['id'] = $existing_event['id'];
1995
			}
1996
			else	// event not in calendar
1997
			{
1998
				$readonlys['button[cancel]'] = true;	// no way to remove a canceled event not in calendar
1999
			}
2000
			$event['participant_types'] = array();
2001
			foreach($event['participants'] as $uid => $status)
2002
			{
2003
				$user_type = $user_id = null;
2004
				calendar_so::split_user($uid, $user_type, $user_id);
2005
				$event['participants'][$uid] = $event['participant_types'][$user_type][$user_id] =
2006
					$status && $status !== 'X' ? $status : 'U';	// X --> no status given --> U = unknown
2007
			}
2008
			//error_log(__METHOD__."(...) parsed as ".array2string($event));
2009
			$event['recure'] = $this->bo->recure2string($event);
2010
			$event['all_participants'] = implode(",\n",$this->bo->participants($event, true));
2011
2012
			// ignore events in the past (for recurring events check enddate!)
2013
			if ($this->bo->date2ts($event['start']) < $this->bo->now_su &&
2014
				(!$event['recur_type'] || $event['recur_enddate'] && $event['recur_enddate'] < $this->bo->now_su))
2015
			{
2016
				$msg = lang('Requested meeting is in the past!');
2017
				$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
2018
					$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
2019
			}
2020
		}
2021
		else
2022
		{
2023
			//_debug_array($event);
2024
			list($button) = each($event['button']);
2025
			unset($event['button']);
2026
2027
			// clear notification errors
2028
			notifications::errors(true);
2029
2030
			switch($button)
2031
			{
2032
				case 'reject':
2033
					if (!$event['id'])
2034
					{
2035
						// send reply to organizer
2036
						$this->bo->send_update(MSG_REJECTED,array('e'.$event['organizer'] => 'DCHAIR'),$event);
2037
						break;	// no need to store rejected event
2038
					}
2039
					// fall-through
2040
				case 'accept':
2041
				case 'tentativ':
2042
					$status = strtoupper($button[0]);	// A, R or T
2043
					if (!$event['id'])
2044
					{
2045
						// if organizer is a EGroupware user, but we have no rights to organizers calendar
2046
						if (isset($event['owner']) && !$this->bo->check_perms(Acl::ADD,0,$event['owner']))
2047
						{
2048
							// --> make organize a participant with role chair and current user the owner
2049
							$event['participant_types']['u'] = $event['participants'][$event['owner']] =
2050
								calendar_so::combine_status('A', 1, 'CHAIR');
2051
							$event['owner'] = $this->user;
2052
						}
2053
						// store event without notifications!
2054
						if (($event['id'] = $this->bo->update($event, $ignore_conflicts=true, true, false, true, $msg, true)))
2055
						{
2056
							$msg[] = lang('Event saved');
2057
						}
2058
						else
2059
						{
2060
							$msg[] = lang('Error saving the event!');
2061
							break;
2062
						}
2063
					}
2064
					// set status and send notification / meeting response
2065
					if ($this->bo->set_status($event['id'], $user, $status))
2066
					{
2067
						if (!$msg) $msg = lang('Status changed');
2068
					}
2069
					break;
2070
2071
				case 'apply':
2072
					// set status and send notification / meeting response
2073
					if ($this->bo->set_status($event['id'], $event['ical_sender_uid'], $event['ical_sender_status']))
2074
					{
2075
						$msg = lang('Status changed');
2076
					}
2077
					break;
2078
2079
				case 'cancel':
2080
					if ($event['id'] && $this->bo->set_status($event['id'], $user, 'R'))
2081
					{
2082
						$msg = lang('Status changed');
2083
					}
2084
					break;
2085
			}
2086
			// add notification-errors, if we have some
2087
			$msg = array_merge((array)$msg, notifications::errors(true));
2088
		}
2089
		Framework::message(implode("\n", (array)$msg));
2090
		$readonlys['button[edit]'] = !$event['id'];
2091
		$event['ics_method'] = $readonlys['ics_method'] = strtolower($ical_method);
2092
		switch(strtolower($ical_method))
2093
		{
2094
			case 'reply':
2095
				$event['ics_method_label'] = lang('Reply to meeting request');
2096
				break;
2097
			case 'cancel':
2098
				$event['ics_method_label'] = lang('Meeting canceled');
2099
				break;
2100
			case 'request':
2101
			default:
2102
				$event['ics_method_label'] = lang('Meeting request');
2103
				break;
2104
		}
2105
		$tpl = new Etemplate('calendar.meeting');
2106
		$tpl->exec('calendar.calendar_uiforms.meeting', $event, array(), $readonlys, $event, 2);
2107
	}
2108
2109
	/**
2110
	 * displays a scheduling conflict
2111
	 *
2112
	 * @param array $event
2113
	 * @param array $conflicts array with conflicting events, the events are not garantied to be readable by the user!
2114
	 * @param array $preserv data to preserv
2115
	 */
2116
	function conflicts($event,$conflicts,$preserv)
2117
	{
2118
		$etpl = new Etemplate('calendar.conflicts');
2119
		$allConflicts = array();
2120
2121
		foreach($conflicts as $k => $conflict)
2122
		{
2123
			$is_readable = $this->bo->check_perms(Acl::READ,$conflict);
2124
2125
			$conflicts[$k] += array(
2126
				'icon_participants' => $is_readable ? (count($conflict['participants']) > 1 ? 'users' : 'single') : 'private',
2127
				'tooltip_participants' => $is_readable ? implode(', ',$this->bo->participants($conflict)) : '',
2128
				'time' => $this->bo->long_date($conflict['start'],$conflict['end'],true),
2129
				'conflicting_participants' => implode(",\n",$this->bo->participants(array(
2130
					'participants' => array_intersect_key((array)$conflict['participants'],$event['participants']),
2131
				),true,true)),	// show group invitations too
2132
				'icon_recur' => $conflict['recur_type'] != MCAL_RECUR_NONE ? 'recur' : '',
2133
				'text_recur' => $conflict['recur_type'] != MCAL_RECUR_NONE ? lang('Recurring event') : ' ',
2134
			);
2135
				$allConflicts += array_intersect_key((array)$conflict['participants'],$event['participants']);
2136
			}
2137
		$content = $event + array(
2138
			'conflicts' => array_values($conflicts),	// conflicts have id-start as key
2139
		);
2140
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('Scheduling conflict');
2141
		$resources_config = Api\Config::read('resources');
2142
		$readonlys = array();
2143
2144
		foreach (array_keys($allConflicts) as $pId)
2145
		{
2146
			if(substr($pId,0,1) == 'r' && $resources_config ) // resources Allow ignore conflicts
2147
			{
2148
2149
				switch ($resources_config['ignoreconflicts'])
2150
				{
2151
					case 'no':
2152
						$readonlys['button[ignore]'] = true;
2153
						break;
2154
					case 'allusers':
2155
						$readonlys['button[ignore]'] = false;
2156
						break;
2157
					default:
2158
						if (!$this->bo->check_status_perms($pId, $event))
2159
						{
2160
							$readonlys['button[ignore]'] = true;
2161
							break;
2162
						}
2163
				}
2164
			}
2165
		}
2166
		$etpl->exec('calendar.calendar_uiforms.process_edit',$content,array(),$readonlys,array_merge($event,$preserv),$preserv['no_popup'] ? 0 : 2);
2167
	}
2168
2169
	/**
2170
	 * Callback for freetimesearch button in edit
2171
	 *
2172
	 * It stores the data of the submitted form in the session under 'freetimesearch_args_'.$edit_content['id'],
2173
	 * for later retrival of the freetimesearch method, called by the returned window.open() command.
2174
	 *
2175
	 * @param array $edit_content
2176
	 * @return string with xajaxResponse
2177
	 */
2178
	function ajax_freetimesearch(array $edit_content)
2179
	{
2180
		$response = Api\Json\Response::get();
2181
		//$response->addAlert(__METHOD__.'('.array2string($edit_content).')');
2182
2183
		// convert start/end date-time values to timestamps
2184
		foreach(array('start', 'end') as $name)
2185
		{
2186
			if (!empty($edit_content[$name]))
2187
			{
2188
				$date = new Api\DateTime($edit_content[$name]);
2189
				$edit_content[$name] = $date->format('ts');
2190
			}
2191
		}
2192
2193
		if ($edit_content['duration'])
2194
		{
2195
			$edit_content['end'] = $edit_content['start'] + $edit_content['duration'];
2196
		}
2197
		if ($edit_content['whole_day'])
2198
		{
2199
			$arr = $this->bo->date2array($edit_content['start']);
2200
			$arr['hour'] = $arr['minute'] = $arr['second'] = 0; unset($arr['raw']);
2201
			$edit_content['start'] = $this->bo->date2ts($arr);
2202
			$earr = $this->bo->date2array($edit_content['end']);
2203
			$earr['hour'] = 23; $earr['minute'] = $earr['second'] = 59; unset($earr['raw']);
2204
			$edit_content['end'] = $this->bo->date2ts($earr);
2205
		}
2206
		$content = array(
2207
			'start'    => $edit_content['start'],
2208
			'duration' => $edit_content['end'] - $edit_content['start'],
2209
			'end'      => $edit_content['end'],
2210
			'cal_id'   => $edit_content['id'],
2211
			'recur_type'   => $edit_content['recur_type'],
2212
			'participants' => array(),
2213
		);
2214
		foreach($edit_content['participants'] as $key => $data)
2215
		{
2216
			if (is_numeric($key) && !$edit_content['participants']['delete'][$data['uid']] &&
2217
				!$edit_content['participants']['delete'][md5($data['uid'])])
2218
			{
2219
				$content['participants'][] = $data['uid'];
2220
			}
2221
			elseif ($key == 'account' && !is_array($data) && $data)
2222
			{
2223
				$content['participants'][] = $data;
2224
			}
2225
		}
2226
		// default search parameters
2227
		$content['start_time'] = $edit_content['whole_day'] ? 0 : $this->cal_prefs['workdaystarts'];
2228
		$content['end_time'] = $this->cal_prefs['workdayends'];
2229
		if ($this->cal_prefs['workdayends']*HOUR_s < $this->cal_prefs['workdaystarts']*HOUR_s+$content['duration'])
2230
		{
2231
			$content['end_time'] = 0;	// no end-time limit, as duration would never fit
2232
		}
2233
		$content['weekdays'] = MCAL_M_WEEKDAYS;
2234
2235
		$content['search_window'] = 7 * DAY_s;
2236
2237
		// store content in session
2238
		Api\Cache::setSession('calendar','freetimesearch_args_'.(int)$edit_content['id'],$content);
2239
2240
		//menuaction=calendar.calendar_uiforms.freetimesearch&values2url('start,end,duration,participants,recur_type,whole_day'),ft_search,700,500
2241
		$link = 'calendar.calendar_uiforms.freetimesearch&cal_id='. $edit_content['id'];
2242
2243
		$response->call('app.calendar.freetime_search_popup',$link);
2244
2245
		//$response->addScriptCall('egw_openWindowCentered2',$link,'ft_search',700,500);
2246
2247
	}
2248
2249
	/**
2250
	 * Freetime search
2251
	 *
2252
	 * As the function is called in a popup via javascript, parametes get initialy transfered via the url
2253
	 * @param array $content=null array with parameters or false (default) to use the get-params
2254
	 * @param string start[str] start-date
2255
	 * @param string start[hour] start-hour
2256
	 * @param string start[min] start-minutes
2257
	 * @param string end[str] end-date
2258
	 * @param string end[hour] end-hour
2259
	 * @param string end[min] end-minutes
2260
	 * @param string participants ':' delimited string of user-id's
2261
	 */
2262
	function freetimesearch($content = null)
2263
	{
2264
		$etpl = new Etemplate('calendar.freetimesearch');
2265
		$sel_options['search_window'] = array(
2266
			7*DAY_s		=> lang('one week'),
2267
			14*DAY_s	=> lang('two weeks'),
2268
			31*DAY_s	=> lang('one month'),
2269
			92*DAY_s	=> lang('three month'),
2270
			365*DAY_s	=> lang('one year'),
2271
		);
2272
		if (!is_array($content))
2273
		{
2274
			// get content from session (and delete it immediatly)
2275
			$content = Api\Cache::getSession('calendar','freetimesearch_args_'.(int)$_GET['cal_id']);
2276
			Api\Cache::unsetSession('calendar','freetimesearch_args_'.(int)$_GET['cal_id']);
2277
			//Since the start_time and end_time from calendar_user_preferences are numbers, not timestamp, in order to show them on date-timeonly
2278
			//widget we need to convert them from numbers to timestamps, only for the first time when we have template without content
2279
			$sTime = $content['start_time'];
2280
			$eTime = $content['end_time'];
2281
			$content['start_time'] = strtotime(((strlen($content['start_time'])<2)?("0".$content['start_time']):$content['start_time']).":00");
2282
			$content['end_time'] = strtotime(((strlen($content['end_time'])<2)?("0".$content['end_time']):$content['end_time']).":00");
2283
2284
			// pick a searchwindow fitting the duration (search for a 10 day slot in a one week window never succeeds)
2285
			foreach(array_keys($sel_options['search_window']) as $window)
2286
			{
2287
				if ($window > $content['duration'])
2288
				{
2289
					$content['search_window'] = $window;
2290
					break;
2291
				}
2292
			}
2293
		}
2294
		else
2295
		{
2296
			if (!$content['duration']) $content['duration'] = $content['end'] - $content['start'];
2297
			$weekds = 0;
2298
			foreach ($content['weekdays'] as &$wdays)
2299
			{
2300
				$weekds = $weekds + $wdays;
2301
			}
2302
			//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
2303
			//always timestamp, we need to convert them to only "hour" string numbers.
2304
			$sTime = date('H', $content['start_time']);
2305
			$eTime = date('H', $content['end_time']);
2306
		}
2307
2308
		if ($content['recur_type'])
2309
		{
2310
			$content['msg'] .= lang('Only the initial date of that recuring event is checked!');
2311
		}
2312
		$content['freetime'] = $this->freetime($content['participants'],$content['start'],$content['start']+$content['search_window'],$content['duration'],$content['cal_id']);
2313
		$content['freetime'] = $this->split_freetime_daywise($content['freetime'],$content['duration'],(is_array($content['weekdays'])?$weekds:$content['weekdays']),$sTime,$eTime,$sel_options);
2314
2315
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('freetime search');
2316
2317
		$sel_options['duration'] = $this->durations;
2318
		if ($content['duration'] && isset($sel_options['duration'][$content['duration']])) $content['end'] = '';
2319
2320
		$etpl->exec('calendar.calendar_uiforms.freetimesearch',$content,$sel_options,NULL,array(
2321
				'participants'	=> $content['participants'],
2322
				'cal_id'		=> $content['cal_id'],
2323
				'recur_type'	=> $content['recur_type'],
2324
			),2);
2325
	}
2326
2327
	/**
2328
	 * calculate the freetime of given $participants in a certain time-span
2329
	 *
2330
	 * @param array $participants user-id's
2331
	 * @param int $start start-time timestamp in user-time
2332
	 * @param int $end end-time timestamp in user-time
2333
	 * @param int $duration min. duration in sec, default 1
2334
	 * @param int $cal_id own id for existing events, to exclude them from being busy-time, default 0
2335
	 * @return array of free time-slots: array with start and end values
2336
	 */
2337
	function freetime($participants,$start,$end,$duration=1,$cal_id=0)
2338
	{
2339 View Code Duplication
		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);
2340
2341
		$busy = $this->bo->search(array(
2342
			'start' => $start,
2343
			'end'	=> $end,
2344
			'users'	=> $participants,
2345
			'ignore_acl' => true,	// otherwise we get only events readable by the user
2346
		));
2347
		$busy[] = array(	// add end-of-search-date as event, to cope with empty search and get freetime til that date
2348
			'start'	=> $end,
2349
			'end'	=> $end,
2350
		);
2351
		$ft_start = $start;
2352
		$freetime = array();
2353
		$n = 0;
2354
		foreach($busy as $event)
0 ignored issues
show
Bug introduced by
The expression $busy of type object<Iterator>|array|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
2355
		{
2356
			if ((int)$cal_id && $event['id'] == (int)$cal_id) continue;	// ignore our own event
2357
2358
 			if ($event['non_blocking']) continue; // ignore non_blocking events
2359
2360
			// check if from all wanted participants at least one has a not rejected status in found event
2361
			$non_rejected_found = false;
2362
			foreach($participants as $uid)
2363
			{
2364
				if ($event['participants'][$uid] == 'R') continue;
2365
2366
				if (isset($event['participants'][$uid]) ||
2367
					$uid > 0 && array_intersect(array_keys((array)$event['participants']),
2368
						$GLOBALS['egw']->accounts->memberships($uid, true)))
2369
				{
2370
					$non_rejected_found = true;
2371
					break;
2372
				}
2373
			}
2374
			if (!$non_rejected_found) continue;
2375
2376
			if ($this->debug)
2377
			{
2378
				echo "<p>ft_start=".date('D d.m.Y H:i',$ft_start)."<br>\n";
2379
				echo "event[title]=$event[title]<br>\n";
2380
				echo "event[start]=".date('D d.m.Y H:i',$event['start'])."<br>\n";
2381
				echo "event[end]=".date('D d.m.Y H:i',$event['end'])."<br>\n";
2382
			}
2383
			// $events ends before our actual position ==> ignore it
2384
			if ($event['end'] < $ft_start)
2385
			{
2386
				//echo "==> event ends before ft_start ==> continue<br>\n";
2387
				continue;
2388
			}
2389
			// $events starts before our actual position ==> set start to it's end and go to next event
2390
			if ($event['start'] < $ft_start)
2391
			{
2392
				//echo "==> event starts before ft_start ==> set ft_start to it's end & continue<br>\n";
2393
				$ft_start = $event['end'];
2394
				continue;
2395
			}
2396
			$ft_end = $event['start'];
2397
2398
			// only show slots equal or bigger to min_length
2399
			if ($ft_end - $ft_start >= $duration)
2400
			{
2401
				$freetime[++$n] = array(
2402
					'start'	=> $ft_start,
2403
					'end'	=> $ft_end,
2404
				);
2405
				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";
2406
			}
2407
			$ft_start = $event['end'];
2408
		}
2409 View Code Duplication
		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);
2410
2411
		return $freetime;
2412
	}
2413
2414
	/**
2415
	 * split the freetime in daywise slot, taking into account weekdays, start- and stop-times
2416
	 *
2417
	 * If the duration is bigger then the difference of start- and end_time, the end_time is ignored
2418
	 *
2419
	 * @param array $freetime free time-slots: array with start and end values
2420
	 * @param int $duration min. duration in sec
2421
	 * @param int $weekdays allowed weekdays, bitfield of MCAL_M_...
2422
	 * @param int $_start_time minimum start-hour 0-23
2423
	 * @param int $_end_time maximum end-hour 0-23, or 0 for none
2424
	 * @param array $sel_options on return options for start-time selectbox
2425
	 * @return array of free time-slots: array with start and end values
2426
	 */
2427
	function split_freetime_daywise($freetime, $duration, $weekdays, $_start_time, $_end_time, &$sel_options)
2428
	{
2429
		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);
2430
2431
		$freetime_daywise = array();
2432
		if (!is_array($sel_options)) $sel_options = array();
2433
		$time_format = $this->common_prefs['timeformat'] == 12 ? 'h:i a' : 'H:i';
2434
2435
		$start_time = (int) $_start_time;	// ignore leading zeros
2436
		$end_time   = (int) $_end_time;
2437
2438
		// ignore the end_time, if duration would never fit
2439
		if (($end_time - $start_time)*HOUR_s < $duration)
2440
		{
2441
			$end_time = 0;
2442
			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);
2443
		}
2444
		$n = 0;
2445
		foreach($freetime as $ft)
2446
		{
2447
			$adaybegin = $this->bo->date2array($ft['start']);
2448
			$adaybegin['hour'] = $adaybegin['minute'] = $adaybegin['second'] = 0;
2449
			unset($adaybegin['raw']);
2450
			$daybegin = $this->bo->date2ts($adaybegin);
2451
2452
			for($t = $daybegin; $t < $ft['end']; $t += DAY_s,$daybegin += DAY_s)
2453
			{
2454
				$dow = date('w',$daybegin+DAY_s/2);	// 0=Sun, .., 6=Sat
2455
				$mcal_dow = pow(2,$dow);
2456
				if (!($weekdays & $mcal_dow))
2457
				{
2458
					//echo "wrong day of week $dow<br>\n";
2459
					continue;	// wrong day of week
2460
				}
2461
				$start = $t < $ft['start'] ? $ft['start'] : $t;
2462
2463
				if ($start-$daybegin < $start_time*HOUR_s)	// start earlier then start_time
2464
				{
2465
					$start = $daybegin + $start_time*HOUR_s;
2466
				}
2467
				// if end_time given use it, else the original slot's end
2468
				$end = $end_time ? $daybegin + $end_time*HOUR_s : $ft['end'];
2469
				if ($end > $ft['end']) $end = $ft['end'];
2470
2471
				// slot to small for duration
2472
				if ($end - $start < $duration)
2473
				{
2474
					//echo "slot to small for duration=$duration<br>\n";
2475
					continue;
2476
				}
2477
				$freetime_daywise[++$n] = array(
2478
					'start'	=> $start,
2479
					'end'	=> $end,
2480
				);
2481
				$times = array();
2482
				for ($s = $start; $s+$duration <= $end && $s < $daybegin+DAY_s; $s += 60*$this->cal_prefs['interval'])
2483
				{
2484
					$e = $s + $duration;
2485
					$end_date = $e-$daybegin > DAY_s ? lang(date('l',$e)).' '.date($this->common_prefs['dateformat'],$e).' ' : '';
2486
					$times[$s] = date($time_format,$s).' - '.$end_date.date($time_format,$e);
2487
				}
2488
				$sel_options[$n.'start'] = $times;
2489
			}
2490
		}
2491
		return $freetime_daywise;
2492
	}
2493
2494
	/**
2495
     * Export events as vCalendar version 2.0 files (iCal)
2496
     *
2497
     * @param int|array $content numeric cal_id or submitted content from etempalte::exec
2498
     * @param boolean $return_error should an error-msg be returned or a regular page with it generated (default)
2499
     * @return string error-msg if $return_error
2500
     */
2501
    function export($content=0,$return_error=false)
2502
    {
2503
		$boical = new calendar_ical();
2504
		#error_log(__METHOD__.print_r($content,true));
2505
		if (is_numeric($cal_id = $content ? $content : $_REQUEST['cal_id']))
2506
		{
2507
			if (!($ical =& $boical->exportVCal(array($cal_id),'2.0','PUBLISH',false)))
2508
			{
2509
				$msg = lang('Permission denied');
2510
2511
				if ($return_error) return $msg;
2512
			}
2513
			else
2514
			{
2515
				html::content_header('event.ics','text/calendar',bytes($ical));
2516
				echo $ical;
2517
				common::egw_exit();
2518
			}
2519
		}
2520
		if (is_array($content))
2521
		{
2522
			$events =& $this->bo->search(array(
2523
				'start' => $content['start'],
2524
				'end'   => $content['end'],
2525
				'enum_recuring' => false,
2526
				'daywise'       => false,
2527
				'owner'         => $this->owner,
2528
				'date_format'   => 'server',    // timestamp in server time for boical class
2529
			));
2530
			if (!$events)
2531
			{
2532
				$msg = lang('No events found');
2533
			}
2534
			else
2535
			{
2536
				$ical =& $boical->exportVCal($events,'2.0','PUBLISH',false);
2537
				html::content_header($content['file'] ? $content['file'] : 'event.ics','text/calendar',bytes($ical));
2538
				echo $ical;
2539
				common::egw_exit();
2540
			}
2541
		}
2542
		if (!is_array($content))
2543
		{
2544
			$content = array(
2545
				'start' => $this->bo->date2ts($_REQUEST['start'] ? $_REQUEST['start'] : $this->date),
2546
				'end'   => $this->bo->date2ts($_REQUEST['end'] ? $_REQUEST['end'] : $this->date),
2547
				'file'  => 'event.ics',
2548
				'version' => '2.0',
2549
			);
2550
		}
2551
		$content['msg'] = $msg;
2552
2553
		$GLOBALS['egw_info']['flags']['app_header'] = lang('calendar') . ' - ' . lang('iCal Export');
2554
		$etpl = new etemplate_new('calendar.export');
2555
		$etpl->exec('calendar.calendar_uiforms.export',$content);
2556
    }
2557
2558
	/**
2559
	 * Edit category ACL (admin only)
2560
	 *
2561
	 * @param array $_content
2562
	 */
2563
	function cat_acl(array $_content=null)
2564
	{
2565
		if (!$GLOBALS['egw_info']['user']['apps']['admin'])
2566
		{
2567
			throw new Api\Exception\NoPermission\Admin();
2568
		}
2569
		if ($_content)
2570
		{
2571
			list($button) = each($_content['button']);
2572
			unset($_content['button']);
2573
			if ($button != 'cancel')	// store changed Acl
2574
			{
2575
				foreach($_content as $data)
2576
				{
2577
					if (!($cat_id = $data['cat_id'])) continue;
2578
					foreach(array_merge((array)$data['add'],(array)$data['status'],array_keys((array)$data['old'])) as $account_id)
2579
					{
2580
						$rights = 0;
2581
						if (in_array($account_id,(array)$data['add'])) $rights |= calendar_boupdate::CAT_ACL_ADD;
2582
						if (in_array($account_id,(array)$data['status'])) $rights |= calendar_boupdate::CAT_ACL_STATUS;
2583
						if ($account_id) $this->bo->set_cat_rights($cat_id,$account_id,$rights);
2584
					}
2585
				}
2586
			}
2587
			if ($button != 'apply')	// end dialog
2588
			{
2589
				Egw::redirect_link('/index.php', array(
2590
					'menuaction' => 'admin.admin_ui.index',
2591
					'ajax' => 'true'
2592
				), 'admin');
2593
			}
2594
		}
2595
		$content= $preserv = array();
2596
		$n = 1;
2597
		foreach($this->bo->get_cat_rights() as $Lcat_id => $data)
2598
		{
2599
			$cat_id = substr($Lcat_id,1);
2600
			$row = array(
2601
				'cat_id' => $cat_id,
2602
				'add' => array(),
2603
				'status' => array(),
2604
			);
2605
			foreach($data as $account_id => $rights)
2606
			{
2607
				if ($rights & calendar_boupdate::CAT_ACL_ADD) $row['add'][] = $account_id;
2608
				if ($rights & calendar_boupdate::CAT_ACL_STATUS) $row['status'][] = $account_id;
2609
			}
2610
			$content[$n] = $row;
2611
			$preserv[$n] = array(
2612
				'cat_id' => $cat_id,
2613
				'old' => $data,
2614
			);
2615
			$readonlys[$n.'[cat_id]'] = true;
2616
			++$n;
2617
		}
2618
		// add empty row for new entries
2619
		$content[] = array('cat_id' => '');
2620
2621
		$GLOBALS['egw_info']['flags']['app_header'] = lang('Calendar').' - '.lang('Category ACL');
2622
		$tmp = new Etemplate('calendar.cat_acl');
2623
		$GLOBALS['egw_info']['flags']['nonavbar'] = 1;
2624
		$tmp->exec('calendar.calendar_uiforms.cat_acl',$content,null,$readonlys,$preserv);
2625
	}
2626
2627
	/**
2628
	* Set up the required fields to get the history tab
2629
	*/
2630
	public function setup_history(&$content, &$sel_options)
2631
	{
2632
		$status = 'history_status';
2633
2634
		$content['history'] = array(
2635
			'id'    =>      $content['id'],
2636
			'app'   =>      'calendar',
2637
			'status-widgets' => array(
2638
				'owner'        => 'select-account',
2639
				'creator'      => 'select-account',
2640
				'category'     => 'select-cat',
2641
				'non_blocking' => array(''=>lang('No'), 1=>lang('Yes')),
2642
				'public'       => array(''=>lang('No'), 1=>lang('Yes')),
2643
2644
				'start'		   => 'date-time',
2645
				'end'		   => 'date-time',
2646
				'deleted'      => 'date-time',
2647
				'recur_enddate'=> 'date',
2648
2649
				'tz_id'        => 'select-timezone',
2650
2651
				// Participants
2652
				'participants'	=>	array(
2653
					'select-account',
2654
					$sel_options['status'],
2655
					$sel_options['role']
2656
				),
2657
				'participants-c'	=>	array(
2658
					'link:addressbook',
2659
					$sel_options['status'],
2660
					'label',
2661
					$sel_options['role']
2662
				),
2663
				'participants-r'	=>	array(
2664
					'link:resources',
2665
					$sel_options['status'],
2666
					'label',
2667
					$sel_options['role']
2668
				),
2669
			),
2670
		);
2671
2672
2673
		// Get participants for only this one, if it's recurring.  The date is on the end of the value.
2674
		if($content['recur_type'] || $content['recurrence'])
2675
		{
2676
			$content['history']['filter'] = array(
2677
				'(history_status NOT LIKE \'participants%\' OR (history_status LIKE \'participants%\' AND (
2678
					history_new_value LIKE \'%' . Api\Storage\Tracking::ONE2N_SEPERATOR . $content['recurrence'] . '\' OR
2679
					history_old_value LIKE \'%' . Api\Storage\Tracking::ONE2N_SEPERATOR . $content['recurrence'] . '\')))'
2680
			);
2681
		}
2682
2683
		// Translate labels
2684
		$tracking = new calendar_tracking();
2685
		foreach($tracking->field2label as $field => $label)
2686
		{
2687
			$sel_options[$status][$field] = lang($label);
2688
		}
2689
		// custom fields are now "understood" directly by historylog widget
2690
	}
2691
2692
	/**
2693
	 * moves an event to another date/time
2694
	 *
2695
	 * @param string $_eventId id of the event which has to be moved
2696
	 * @param string $calendarOwner the owner of the calendar the event is in
2697
	 * @param string $targetDateTime the datetime where the event should be moved to, format: YYYYMMDD
2698
	 * @param string|string[] $targetOwner the owner of the target calendar
2699
	 * @param string $durationT the duration to support resizable calendar event
2700
	 * @param string $seriesInstance If moving a whole series, not an exception, this is
2701
	 *	which particular instance was dragged
2702
	 * @return string XML response if no error occurs
2703
	 */
2704
	function ajax_moveEvent($_eventId,$calendarOwner,$targetDateTime,$targetOwner,$durationT=null,$seriesInstance=null)
2705
	{
2706
		list($eventId, $date) = explode(':', $_eventId,2);
2707
		$ignore_conflicts = false;
2708
2709
		// we do not allow dragging into another users calendar ATM
2710
		if($targetOwner < 0)
2711
		{
2712
			$targetOwner = array($targetOwner);
2713
		}
2714
		if($targetOwner == 0 || is_array($targetOwner) && $targetOwner[0] == 0)
2715
		{
2716
			$targetOwner = $calendarOwner;
2717
		}
2718
		// But you may be viewing multiple users, or a group calendar and
2719
		// dragging your event - dragging across calendars does not change owner
2720
		if(is_array($targetOwner) && !in_array($calendarOwner, $targetOwner))
2721
		{
2722
			$return = true;
2723
			foreach($targetOwner as $owner)
2724
			{
2725
				if($owner < 0 && in_array($calendarOwner, $GLOBALS['egw']->accounts->members($owner,true)))
2726
				{
2727
					$return = false;
2728
					break;
2729
				}
2730
				else if ($owner > 0 && $this->bo->check_perms(Acl::EDIT, $eventId,0,'ts',$date))
2731
				{
2732
					$return = false;
2733
					break;
2734
				}
2735
			}
2736
			if($return) return;
2737
		}
2738
		$old_event=$event=$this->bo->read($eventId);
2739
		if (!$durationT)
2740
		{
2741
			$duration=$event['end']-$event['start'];
2742
		}
2743
		// Drag a normal event to whole day non-blocking
2744
		else if ($durationT == 'whole_day')
2745
		{
2746
			$event['whole_day'] = true;
2747
			$event['non_blocking'] = true;
2748
			// Make duration whole days, less 1 second
2749
			$duration = round(($event['end']-$event['start'])/DAY_s) * DAY_s - 1;
2750
		}
2751
		else
2752
		{
2753
			$duration = (int)$durationT;
2754
		}
2755
2756
		// If we have a recuring event for a particular day, make an exception
2757
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
2758
		{
2759
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
2760
			if (!empty($event['whole_day']))
2761
			{
2762
				$d =& $this->bo->so->startOfDay($d);
2763
				$d->setUser();
2764
			}
2765
			$event = $this->bo->read($eventId, $d, true);
2766
2767
			// For DnD, create an exception if they gave the date
2768
			$preserv = null;
2769
			$this->_create_exception($event,$preserv);
2770
			unset($event['id']);
2771
			$links = $event['link_to']['to_id'];
2772
2773
			$messages = null;
2774
			$conflicts = $this->bo->update($event,false,true,false,true,$messages);
2775 View Code Duplication
			if (!is_array($conflicts) && $conflicts)
2776
			{
2777
				// now we need to add the original start as recur-execption to the series
2778
				$recur_event = $this->bo->read($event['reference']);
2779
				$recur_event['recur_exception'][] = $d->format('ts');
2780
				// check if we need to move the alarms, because they are next on that exception
2781
				$this->bo->check_move_alarms($recur_event, null, $d);
2782
				unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
2783
				unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
2784
				$this->bo->update($recur_event,true);	// no conflict check here
2785
2786
				// Sending null will trigger a removal of the original for that date
2787
				Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$_eventId, 'data' => null));
2788
2789
				unset($recur_event);
2790
				unset($event['edit_single']);			// if we further edit it, it's just a single event
2791
				unset($preserv['edit_single']);
2792
			}
2793
		}
2794
2795
		$d = new Api\DateTime($targetDateTime, Api\DateTime::$user_timezone);
2796
		$event['start'] = $d->format('ts');
2797
		$event['end'] = $event['start']+$duration;
2798
2799
		if ($event['recur_type'] != MCAL_RECUR_NONE && !$date && $seriesInstance)
2800
		{
2801
			// calculate offset against clicked recurrance,
2802
			// depending on which is smaller
2803
			$offset = Api\DateTime::to($targetDateTime,'ts') - Api\DateTime::to($seriesInstance,'ts');
2804
			$event['start'] = $old_event['start'] + $offset;
2805
			$event['duration'] = $duration;
2806
2807
			// We have a recurring event starting in the past -
2808
			// stop it & create a new one.
2809
			$this->_break_recurring($event, $old_event, $this->bo->date2ts($targetDateTime));
2810
2811
			// Can't handle conflict.  Just ignore it.
2812
			$ignore_conflicts = true;
2813
		}
2814
		if(!$event['recur_type'])
2815
		{
2816
			$this->bo->check_move_alarms($event, $old_event);
2817
		}
2818
2819
		// Drag a whole day to a time
2820
		if($durationT && $durationT != 'whole_day')
2821
		{
2822
			$event['whole_day'] = ($duration == DAY_s);
2823
			$event['non_blocking'] = false;
2824
			// If there's a conflict, it won't save the change and the conflict popup will be blank
2825
			// so save the change now, and then let the conflict check happen.
2826
			$message = null;
2827
			$this->bo->update($event,true, true, false, true, $message,true);
2828
2829
			// Whole day non blocking with DAY_s would add a day
2830
			if($duration==DAY_s) $duration=0;
2831
		}
2832
2833
		$status_reset_to_unknown = false;
2834
		$sameday = (date('Ymd', $old_event['start']) == date('Ymd', $event['start']));
2835
		foreach((array)$event['participants'] as $uid => $status)
2836
		{
2837
			$q = $r = null;
2838
			calendar_so::split_status($status,$q,$r);
2839
			if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user && $status != 'U')
2840
			{
2841
				$preferences = new Api\Preferences($uid);
2842
				$part_prefs = $preferences->read_repository();
2843
				switch ($part_prefs['calendar']['reset_stati'])
2844
				{
2845
					case 'no':
2846
						break;
2847
					case 'startday':
2848
						if ($sameday) break;
2849
					default:
2850
						$status_reset_to_unknown = true;
2851
						$event['participants'][$uid] = calendar_so::combine_status('U',$q,$r);
2852
						// todo: report reset status to user
2853
				}
2854
			}
2855
		}
2856
2857
		$message = false;
2858
		$conflicts=$this->bo->update($event,$ignore_conflicts, true, false, true, $message);
2859
2860
		// Save links
2861
		if($links)
2862
		{
2863
			Link::link('calendar', $event['id'], $links);
2864
		}
2865
2866
		$this->update_client($event['id'],$d);
2867
		$response = Api\Json\Response::get();
2868
		if(!is_array($conflicts) && $conflicts)
2869
		{
2870
			if(is_int($conflicts))
2871
			{
2872
				$event['id'] = $conflicts;
2873
				$response->call('egw.refresh', '','calendar',$event['id'],'edit');
2874
			}
2875
		}
2876
		else if ($conflicts)
2877
		{
2878
			$response->call(
2879
				'egw_openWindowCentered2',
2880
				$GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit
2881
					&cal_id='.$event['id']
2882
					.'&start='.$event['start']
2883
					.'&end='.$event['end']
2884
					.'&non_interactive=true'
2885
					.'&cancel_needs_refresh=true',
2886
				'',750,410);
2887
		}
2888
		else if ($message)
2889
		{
2890
			$response->call('egw.message',  implode('<br />', $message));
2891
		}
2892
		if($event['id'] != $eventId ) $this->update_client($_eventId);
2893 View Code Duplication
		if ($status_reset_to_unknown)
2894
		{
2895
			foreach((array)$event['participants'] as $uid => $status)
2896
			{
2897
				if ($uid[0] != 'c' && $uid[0] != 'e' && $uid != $this->bo->user)
2898
				{
2899
					calendar_so::split_status($status,$q,$r);
2900
					$status = calendar_so::combine_status('U',$q,$r);
2901
					$this->bo->set_status($event['id'], $uid, $status, 0, true);
2902
				}
2903
			}
2904
		}
2905
	}
2906
2907
	/**
2908
	 * Change the status via ajax
2909
	 * @param string $_eventId
2910
	 * @param integer $uid
2911
	 * @param string $status
2912
	 */
2913
	function ajax_status($_eventId, $uid, $status)
2914
	{
2915
		list($eventId, $date) = explode(':', $_eventId);
2916
		$event = $this->bo->read($eventId);
2917
		if($date)
2918
		{
2919
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
2920
		}
2921
2922
		// If we have a recuring event for a particular day, make an exception
2923
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
2924
		{
2925
			if (!empty($event['whole_day']))
2926
			{
2927
				$d =& $this->bo->so->startOfDay($date);
2928
				$d->setUser();
2929
			}
2930
			$event = $this->bo->read($eventId, $d, true);
2931
			$date = $d->format('ts');
2932
		}
2933
		if($event['participants'][$uid])
2934
		{
2935
			$q = $r = null;
2936
			calendar_so::split_status($event['participants'][$uid],$q,$r);
2937
			$event['participants'][$uid] = $status = calendar_so::combine_status($status,$q,$r);
2938
			$this->bo->set_status($event['id'],$uid,$status,$date,true);
2939
		}
2940
		else
2941
		{
2942
			// Group membership
2943
			foreach($event['participants'] as $id => $status)
2944
			{
2945
				if($GLOBALS['egw']->accounts->get_type($id) == 'g' && in_array($uid,$GLOBALS['egw']->accounts->members($id,true)))
2946
				{
2947
					calendar_so::split_status($event['participants'][$uid],$q,$r);
2948
					$event['participants'][$uid] = $status = calendar_so::combine_status($status,$q,$r);
2949
					$this->bo->set_status($event['id'],$uid,$status,$date,true);
2950
					break;
2951
				}
2952
			}
2953
		}
2954
2955
		// Directly update stored data.  If event is still visible, it will
2956
		// be notified & update itself.
2957
		$this->update_client($eventId,$d);
2958
	}
2959
2960
	/**
2961
	 * Deletes an event
2962
	 */
2963
	public function ajax_delete($eventId)
2964
	{
2965
		list($id, $date) = explode(':',$eventId);
2966
		$event=$this->bo->read($id);
2967
		$response = Api\Json\Response::get();
2968
2969
		if ($this->bo->delete($event['id'], (int)$date))
2970
		{
2971
			if ($event['recur_type'] != MCAL_RECUR_NONE && !$date)
2972
			{
2973
				$msg = lang('Series deleted');
2974
			}
2975
			else
2976
			{
2977
				$msg = lang('Event deleted');
2978
			}
2979
			$response->apply('egw.refresh', Array($msg,'calendar',$eventId,'delete'));
2980
		}
2981
		else
2982
		{
2983
			$response->apply('egw.message', Array(lang('Error')),'error');
0 ignored issues
show
Unused Code introduced by
The call to Response::apply() has too many arguments starting with '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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2984
		}
2985
	}
2986
2987
	/**
2988
	 *
2989
	 * @param string $_eventId id of the event to be changed.  For recurring events
2990
	 *	it may contain the instance date
2991
	 * @param string[] $invite Resources to invite
2992
	 * @param string[] $remove Remove resource from participants
2993
	 */
2994
	public function ajax_invite($_eventId, $invite = array(), $remove = array())
2995
	{
2996
		list($eventId, $date) = explode(':', $_eventId,2);
2997
2998
		$event = $this->bo->read($eventId);
2999
		if($date)
3000
		{
3001
			$d = new Api\DateTime($date, Api\DateTime::$user_timezone);
3002
		}
3003
3004
		// If we have a recuring event for a particular day, make an exception
3005
		if ($event['recur_type'] != MCAL_RECUR_NONE && $date)
3006
		{
3007
			if (!empty($event['whole_day']))
3008
			{
3009
				$d =& $this->bo->so->startOfDay($date);
3010
				$d->setUser();
3011
			}
3012
			$event = $this->bo->read($eventId, $d, true);
3013
			// For DnD, create an exception if they gave the date
3014
			$preserv = null;
3015
			$this->_create_exception($event,$preserv);
3016
			unset($event['id']);
3017
3018
			$messages = null;
3019
			$conflicts = $this->bo->update($event,true,true,false,true,$messages);
3020 View Code Duplication
			if (!is_array($conflicts) && $conflicts)
3021
			{
3022
				// now we need to add the original start as recur-execption to the series
3023
				$recur_event = $this->bo->read($event['reference']);
3024
				$recur_event['recur_exception'][] = $d->format('ts');
3025
				// check if we need to move the alarms, because they are next on that exception
3026
				$this->bo->check_move_alarms($recur_event, null, $d);
3027
				unset($recur_event['start']); unset($recur_event['end']);	// no update necessary
3028
				unset($recur_event['alarm']);	// unsetting alarms too, as they cant be updated without start!
3029
				$this->bo->update($recur_event,true);	// no conflict check here
3030
3031
				// Sending null will trigger a removal of the original for that date
3032
				Api\Json\Response::get()->generic('data', array('uid' => 'calendar::'.$_eventId, 'data' => null));
3033
3034
				unset($recur_event);
3035
				unset($event['edit_single']);			// if we further edit it, it's just a single event
3036
				unset($preserv['edit_single']);
3037
			}
3038
		}
3039
		foreach($remove as $participant)
3040
		{
3041
			unset($event['participants'][$participant]);
3042
		}
3043
		foreach($invite as $participant)
3044
		{
3045
			$event['participants'][$participant] = 'U';
3046
		}
3047
		$message = null;
3048
		$conflicts=$this->bo->update($event,false, true, false, true, $message);
3049
3050
		$response = Api\Json\Response::get();
3051
3052
		if (is_array($conflicts) && $conflicts)
3053
		{
3054
			// Save it anyway, was done with explicit user interaction,
3055
			// and if we don't we lose the invite
3056
			$this->bo->update($event,true);	// no conflict check here
3057
			$this->update_client($event['id'],$d);
3058
			$response->call(
3059
				'egw_openWindowCentered2',
3060
				$GLOBALS['egw_info']['server']['webserver_url'].'/index.php?menuaction=calendar.calendar_uiforms.edit
3061
					&cal_id='.$event['id']
3062
					.'&start='.$event['start']
3063
					.'&end='.$event['end']
3064
					.'&non_interactive=true'
3065
					.'&cancel_needs_refresh=true',
3066
				'',750,410);
3067
		}
3068
		else if ($message)
3069
		{
3070
			$response->call('egw.message',  implode('<br />', $message));
3071
		}
3072
		if($conflicts)
3073
		{
3074
			$this->update_client($event['id'],$d);
3075
			if(is_int($conflicts))
3076
			{
3077
				$event['id'] = $conflicts;
3078
			}
3079
			if($event['id'])
3080
			{
3081
				$response->call('egw.refresh', '','calendar',$event['id'],'edit');
3082
			}
3083
		}
3084
	}
3085
3086
	/**
3087
	 * imports a mail as Calendar
3088
	 *
3089
	 * @param array $mailContent = null mail content
3090
	 * @return  array
3091
	 */
3092
	function mail_import(array $mailContent=null)
3093
	{
3094
		// It would get called from compose as a popup with egw_data
3095 View Code Duplication
		if (!is_array($mailContent) && ($_GET['egw_data']))
3096
		{
3097
			// get raw mail data
3098
			Link::get_data ($_GET['egw_data']);
3099
			return false;
3100
		}
3101
3102
		if (is_array($mailContent))
3103
		{
3104
			// Addressbook
3105
			$AB = new Api\Contacts();
3106
			$accounts = array(0 => $GLOBALS['egw_info']['user']['account_id']);
3107
3108
			$participants[0] = array (
3109
				'uid' => $GLOBALS['egw_info']['user']['account_id'],
3110
				'delete_id' => $GLOBALS['egw_info']['user']['account_id'],
3111
				'status' => 'A',
3112
				'old_status' => 'A',
3113
				'app' => 'User',
3114
				'role' => 'REQ-PARTICIPANT'
3115
			);
3116
			foreach($mailContent['addresses'] as $address)
3117
			{
3118
				// Get available contacts from the email
3119
				$contacts = $AB->search(array(
3120
						'email' => $address['email'],
3121
						'email_home' => $address['email']
3122
					),'contact_id,contact_email,contact_email_home,egw_addressbook.account_id as account_id','','','',false,'OR',false,array('owner' => 0),'',false);
3123
				if (is_array($contacts))
3124
				{
3125
					foreach($contacts as $account)
3126
					{
3127
						$accounts[] = $account['account_id'];
3128
					}
3129
				}
3130
				else
3131
				{
3132
					$participants []= array (
3133
						'app' => 'email',
3134
						'uid' => 'e'.$address['email'],
3135
						'status' => 'U',
3136
						'old_status' => 'U'
3137
					);
3138
				}
3139
			}
3140
			$participants = array_merge($participants , array(
3141
				"participant" => $accounts,
3142
				"role" => "REQ-PARTICIPANT",
3143
				"add" => "pressed"
3144
			));
3145
3146
			// Prepare calendar event draft
3147
			$event = array(
3148
				'title' => $mailContent['subject'],
3149
				'description' => $mailContent['message'],
3150
				'participants' => $participants,
3151
				'link_to' => array(
3152
					'to_app' => 'calendar',
3153
					'to_id' => 0,
3154
				),
3155
				'start' => $mailContent['date'],
3156
				'duration' => 60 * $this->cal_prefs['interval'],
3157
				'owner' => $GLOBALS['egw_info']['user']['account_id']
3158
			);
3159
3160 View Code Duplication
			if (is_array($mailContent['attachments']))
3161
			{
3162
				foreach ($mailContent['attachments'] as $attachment)
3163
				{
3164
					if($attachment['egw_data'])
3165
					{
3166
						Link::link('calendar',$event['link_to']['to_id'],Link::DATA_APPNAME,  $attachment);
3167
					}
3168
					else if(is_readable($attachment['tmp_name']) ||
3169
						(Vfs::is_readable($attachment['tmp_name']) && parse_url($attachment['tmp_name'], PHP_URL_SCHEME) === 'vfs'))
3170
					{
3171
						Link::link('calendar',$event['link_to']['to_id'],'file',  $attachment);
3172
					}
3173
				}
3174
			}
3175
		}
3176
		else
3177
		{
3178
			Framework::window_close(lang('No content found to show up as calendar entry.'));
3179
		}
3180
3181
		return $this->process_edit($event);
3182
	}
3183
}
3184