Total Complexity | 475 |
Total Lines | 2217 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like calendar_bo often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use calendar_bo, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
50 | class calendar_bo |
||
51 | { |
||
52 | /** |
||
53 | * Gives read access to the calendar, but all events the user is not participating are private! |
||
54 | * Used by addressbook. |
||
55 | */ |
||
56 | const ACL_READ_FOR_PARTICIPANTS = Acl::CUSTOM1; |
||
57 | /** |
||
58 | * Right to see free/busy data only |
||
59 | */ |
||
60 | const ACL_FREEBUSY = Acl::CUSTOM2; |
||
61 | /** |
||
62 | * Allows to invite an other user (if configured to be used!) |
||
63 | */ |
||
64 | const ACL_INVITE = Acl::CUSTOM3; |
||
65 | |||
66 | /** |
||
67 | * @var int $debug name of method to debug or level of debug-messages: |
||
68 | * False=Off as higher as more messages you get ;-) |
||
69 | * 1 = function-calls incl. parameters to general functions like search, read, write, delete |
||
70 | * 2 = function-calls to exported helper-functions like check_perms |
||
71 | * 4 = function-calls to exported conversation-functions like date2ts, date2array, ... |
||
72 | * 5 = function-calls to private functions |
||
73 | */ |
||
74 | var $debug=false; |
||
75 | |||
76 | /** |
||
77 | * @var int $now timestamp in server-time |
||
78 | */ |
||
79 | var $now; |
||
80 | |||
81 | /** |
||
82 | * @var int $now_su timestamp of actual user-time |
||
83 | */ |
||
84 | var $now_su; |
||
85 | |||
86 | /** |
||
87 | * @var array $cal_prefs calendar-specific prefs |
||
88 | */ |
||
89 | var $cal_prefs; |
||
90 | |||
91 | /** |
||
92 | * @var array $common_prefs common preferences |
||
93 | */ |
||
94 | var $common_prefs; |
||
95 | /** |
||
96 | * Custom fields read from the calendar config |
||
97 | * |
||
98 | * @var array |
||
99 | */ |
||
100 | var $customfields = array(); |
||
101 | /** |
||
102 | * @var int $user nummerical id of the current user-id |
||
103 | */ |
||
104 | var $user=0; |
||
105 | |||
106 | /** |
||
107 | * @var array $grants grants of the current user, array with user-id / ored-ACL-rights pairs |
||
108 | */ |
||
109 | var $grants=array(); |
||
110 | |||
111 | /** |
||
112 | * @var array $verbose_status translated 1-char status values to a verbose name, run through lang() by the constructor |
||
113 | */ |
||
114 | var $verbose_status = array( |
||
115 | 'A' => 'Accepted', |
||
116 | 'R' => 'Rejected', |
||
117 | 'T' => 'Tentative', |
||
118 | 'U' => 'No Response', |
||
119 | 'D' => 'Delegated', |
||
120 | 'G' => 'Group invitation', |
||
121 | ); |
||
122 | /** |
||
123 | * @var array recur_types translates MCAL recur-types to verbose labels |
||
124 | */ |
||
125 | var $recur_types = Array( |
||
126 | MCAL_RECUR_NONE => 'No recurrence', |
||
127 | MCAL_RECUR_DAILY => 'Daily', |
||
128 | MCAL_RECUR_WEEKLY => 'Weekly', |
||
129 | MCAL_RECUR_MONTHLY_WDAY => 'Monthly (by day)', |
||
130 | MCAL_RECUR_MONTHLY_MDAY => 'Monthly (by date)', |
||
131 | MCAL_RECUR_YEARLY => 'Yearly' |
||
132 | ); |
||
133 | /** |
||
134 | * @var array recur_days translates MCAL recur-days to verbose labels |
||
135 | */ |
||
136 | var $recur_days = array( |
||
137 | MCAL_M_MONDAY => 'Monday', |
||
138 | MCAL_M_TUESDAY => 'Tuesday', |
||
139 | MCAL_M_WEDNESDAY => 'Wednesday', |
||
140 | MCAL_M_THURSDAY => 'Thursday', |
||
141 | MCAL_M_FRIDAY => 'Friday', |
||
142 | MCAL_M_SATURDAY => 'Saturday', |
||
143 | MCAL_M_SUNDAY => 'Sunday', |
||
144 | ); |
||
145 | /** |
||
146 | * Standard iCal attendee roles |
||
147 | * |
||
148 | * @var array |
||
149 | */ |
||
150 | var $roles = array( |
||
151 | 'REQ-PARTICIPANT' => 'Requested', |
||
152 | 'CHAIR' => 'Chair', |
||
153 | 'OPT-PARTICIPANT' => 'Optional', |
||
154 | 'NON-PARTICIPANT' => 'None', |
||
155 | ); |
||
156 | /** |
||
157 | * Alarm times |
||
158 | * |
||
159 | * @var array |
||
160 | */ |
||
161 | var $alarms = array( |
||
162 | 300 => '5 Minutes', |
||
163 | 600 => '10 Minutes', |
||
164 | 900 => '15 Minutes', |
||
165 | 1800 => '30 Minutes', |
||
166 | 3600 => '1 Hour', |
||
167 | 7200 => '2 Hours', |
||
168 | 43200 => '12 Hours', |
||
169 | 86400 => '1 Day', |
||
170 | 172800 => '2 Days', |
||
171 | 604800 => '1 Week', |
||
172 | ); |
||
173 | /** |
||
174 | * @var array $resources registered scheduling resources of the calendar (gets cached in the session for performance reasons) |
||
175 | */ |
||
176 | var $resources; |
||
177 | /** |
||
178 | * @var array $cached_event here we do some caching to read single events only once |
||
179 | */ |
||
180 | protected static $cached_event = array(); |
||
181 | protected static $cached_event_date_format = false; |
||
182 | protected static $cached_event_date = 0; |
||
183 | |||
184 | /** |
||
185 | * Instance of the socal class |
||
186 | * |
||
187 | * @var calendar_so |
||
188 | */ |
||
189 | var $so; |
||
190 | /** |
||
191 | * Instance of the categories class |
||
192 | * |
||
193 | * @var Api\Categories |
||
194 | */ |
||
195 | var $categories; |
||
196 | /** |
||
197 | * Config values for "calendar", only used for horizont, regular calendar config is under phpgwapi |
||
198 | * |
||
199 | * @var array |
||
200 | */ |
||
201 | var $config; |
||
202 | |||
203 | /** |
||
204 | * Does a user require an extra invite grant, to be able to invite an other user, default no |
||
205 | * |
||
206 | * @var string 'all', 'groups' or null |
||
207 | */ |
||
208 | public $require_acl_invite = null; |
||
209 | |||
210 | /** |
||
211 | * Warnings to show in regular UI |
||
212 | * |
||
213 | * @var array |
||
214 | */ |
||
215 | var $warnings = array(); |
||
216 | |||
217 | /** |
||
218 | * Constructor |
||
219 | */ |
||
220 | function __construct() |
||
221 | { |
||
222 | if ($this->debug > 0) $this->debug_message('calendar_bo::bocal() started',True); |
||
223 | |||
224 | $this->so = new calendar_so(); |
||
225 | |||
226 | $this->common_prefs =& $GLOBALS['egw_info']['user']['preferences']['common']; |
||
227 | $this->cal_prefs =& $GLOBALS['egw_info']['user']['preferences']['calendar']; |
||
228 | |||
229 | $this->now = time(); |
||
230 | $this->now_su = Api\DateTime::server2user($this->now,'ts'); |
||
231 | |||
232 | $this->user = $GLOBALS['egw_info']['user']['account_id']; |
||
233 | |||
234 | $this->grants = $GLOBALS['egw']->acl->get_grants('calendar'); |
||
235 | |||
236 | if (!is_array($this->resources = Api\Cache::getSession('calendar', 'resources'))) |
||
237 | { |
||
238 | $this->resources = array(); |
||
239 | foreach(Api\Hooks::process('calendar_resources') as $app => $data) |
||
240 | { |
||
241 | if ($data && $data['type']) |
||
242 | { |
||
243 | $this->resources[$data['type']] = $data + array('app' => $app); |
||
244 | } |
||
245 | } |
||
246 | $this->resources['e'] = array( |
||
247 | 'type' => 'e', |
||
248 | 'info' => __CLASS__.'::email_info', |
||
249 | 'app' => 'email', |
||
250 | ); |
||
251 | $this->resources['l'] = array( |
||
252 | 'type' => 'l',// one char type-identifier for this resources |
||
253 | 'info' => __CLASS__ .'::mailing_lists',// info method, returns array with id, type & name for a given id |
||
254 | 'app' => 'Distribution list' |
||
255 | ); |
||
256 | $this->resources[''] = array( |
||
257 | 'type' => '', |
||
258 | 'app' => 'api-accounts', |
||
259 | ); |
||
260 | Api\Cache::setSession('calendar', 'resources', $this->resources); |
||
261 | } |
||
262 | //error_log(__METHOD__ . " registered resources=". array2string($this->resources)); |
||
263 | |||
264 | $this->config = Api\Config::read('calendar'); // only used for horizont, regular calendar config is under phpgwapi |
||
265 | $this->require_acl_invite = $GLOBALS['egw_info']['server']['require_acl_invite']; |
||
266 | |||
267 | $this->categories = new Api\Categories($this->user,'calendar'); |
||
268 | |||
269 | $this->customfields = Api\Storage\Customfields::get('calendar'); |
||
270 | |||
271 | foreach($this->alarms as $secs => &$label) |
||
272 | { |
||
273 | $label = self::secs2label($secs); |
||
274 | } |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Generate translated label for a given number of seconds |
||
279 | * |
||
280 | * @param int $secs |
||
281 | * @return string |
||
282 | */ |
||
283 | static public function secs2label($secs) |
||
284 | { |
||
285 | if ($secs <= 3600) |
||
286 | { |
||
287 | $label = lang('%1 minutes', $secs/60); |
||
288 | } |
||
289 | elseif($secs <= 86400) |
||
290 | { |
||
291 | $label = lang('%1 hours', $secs/3600); |
||
292 | } |
||
293 | else |
||
294 | { |
||
295 | $label = lang('%1 days', $secs/86400); |
||
296 | } |
||
297 | return $label; |
||
298 | } |
||
299 | |||
300 | /** |
||
301 | * returns info about email addresses as participants |
||
302 | * |
||
303 | * @param int|array $ids single contact-id or array of id's |
||
304 | * @return array |
||
305 | */ |
||
306 | static function email_info($ids) |
||
307 | { |
||
308 | if (!$ids) return null; |
||
309 | |||
310 | $data = array(); |
||
311 | foreach((array)$ids as $id) |
||
312 | { |
||
313 | $email = $id; |
||
314 | $name = ''; |
||
315 | $matches = null; |
||
316 | if (preg_match('/^(.*) *<([a-z0-9_.@-]{8,})>$/iU',$email,$matches)) |
||
317 | { |
||
318 | $name = $matches[1]; |
||
319 | $email = $matches[2]; |
||
320 | } |
||
321 | $data[] = array( |
||
322 | 'res_id' => $id, |
||
323 | 'email' => $email, |
||
324 | 'rights' => self::ACL_READ_FOR_PARTICIPANTS | self::ACL_INVITE, |
||
325 | 'name' => $name, |
||
326 | ); |
||
327 | } |
||
328 | //error_log(__METHOD__.'('.array2string($ids).')='.array2string($data).' '.function_backtrace()); |
||
329 | return $data; |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * returns info about mailing lists as participants |
||
334 | * |
||
335 | * @param int|array $ids single mailing list ID or array of id's |
||
336 | * @return array |
||
337 | */ |
||
338 | static function mailing_lists($ids) |
||
339 | { |
||
340 | if(!is_array($ids)) |
||
341 | { |
||
342 | $ids = array($ids); |
||
343 | } |
||
344 | $data = array(); |
||
345 | |||
346 | // Email list |
||
347 | $contacts_obj = new Api\Contacts(); |
||
348 | $bo = new calendar_bo(); |
||
349 | foreach($ids as $id) |
||
350 | { |
||
351 | $list = $contacts_obj->read_list((int)$id); |
||
352 | |||
353 | $data[] = array( |
||
354 | 'res_id' => $id, |
||
355 | 'rights' => self::ACL_READ_FOR_PARTICIPANTS, |
||
356 | 'name' => $list['list_name'], |
||
357 | 'resources' => $bo->enum_mailing_list('l'.$id, false, false) |
||
358 | ); |
||
359 | } |
||
360 | |||
361 | return $data; |
||
362 | } |
||
363 | |||
364 | /** |
||
365 | * Enumerates the contacts in a contact list, and returns the list of contact IDs |
||
366 | * |
||
367 | * This is used to enable mailing lists as owner/participant |
||
368 | * |
||
369 | * @param string $id Mailing list participant ID, which is the mailing list |
||
370 | * ID prefixed with 'l' |
||
371 | * @param boolean $ignore_acl = false Flag to skip ACL checks |
||
372 | * @param boolean $use_freebusy =true should freebusy rights are taken into account, default true, can be set to false eg. for a search |
||
373 | * |
||
374 | * @return array |
||
375 | */ |
||
376 | public function enum_mailing_list($id, $ignore_acl= false, $use_freebusy = true) |
||
377 | { |
||
378 | $contact_list = array(); |
||
379 | $contacts = new Api\Contacts(); |
||
380 | if($contacts->check_list((int)substr($id,1), ACL::READ) || (int)substr($id,1) < 0) |
||
381 | { |
||
382 | $options = array('list' => substr($id,1)); |
||
383 | $lists = $contacts->search('',true,'','','',false,'AND',false,$options); |
||
384 | if(!$lists) |
||
385 | { |
||
386 | return $contact_list; |
||
387 | } |
||
388 | foreach($lists as &$contact) |
||
389 | { |
||
390 | // Check for user account |
||
391 | if (($account_id = $GLOBALS['egw']->accounts->name2id($contact['id'],'person_id'))) |
||
392 | { |
||
393 | $contact = ''.$account_id; |
||
394 | } |
||
395 | else |
||
396 | { |
||
397 | $contact = 'c'.$contact['id']; |
||
398 | } |
||
399 | if ($ignore_acl || $this->check_perms(ACL::READ|self::ACL_READ_FOR_PARTICIPANTS|($use_freebusy?self::ACL_FREEBUSY:0),0,$contact)) |
||
400 | { |
||
401 | if ($contact && !in_array($contact,$contact_list)) // already added? |
||
402 | { |
||
403 | $contact_list[] = $contact; |
||
404 | } |
||
405 | } |
||
406 | } |
||
407 | } |
||
408 | return $contact_list; |
||
409 | } |
||
410 | |||
411 | /** |
||
412 | * Add group-members as participants with status 'G' |
||
413 | * |
||
414 | * @param array $event event-array |
||
415 | * @return int number of added participants |
||
416 | */ |
||
417 | function enum_groups(&$event) |
||
418 | { |
||
419 | $added = 0; |
||
420 | foreach(array_keys($event['participants']) as $uid) |
||
421 | { |
||
422 | if (is_numeric($uid) && $GLOBALS['egw']->accounts->get_type($uid) == 'g' && |
||
423 | ($members = $GLOBALS['egw']->accounts->members($uid, true))) |
||
424 | { |
||
425 | foreach($members as $member) |
||
426 | { |
||
427 | if (!isset($event['participants'][$member])) |
||
428 | { |
||
429 | $event['participants'][$member] = 'G'; |
||
430 | ++$added; |
||
431 | } |
||
432 | } |
||
433 | } |
||
434 | } |
||
435 | return $added; |
||
436 | } |
||
437 | |||
438 | /** |
||
439 | * Resolve users to add memberships for users and members for groups |
||
440 | * |
||
441 | * @param int|array $_users |
||
442 | * @param boolean $no_enum_groups =true |
||
443 | * @param boolean $ignore_acl =false |
||
444 | * @param boolean $use_freebusy =true should freebusy rights are taken into account, default true, can be set to false eg. for a search |
||
445 | * @return array of user-ids |
||
446 | */ |
||
447 | private function resolve_users($_users, $no_enum_groups=true, $ignore_acl=false, $use_freebusy=true) |
||
448 | { |
||
449 | if (!is_array($_users)) |
||
450 | { |
||
451 | $_users = $_users ? array($_users) : array(); |
||
452 | } |
||
453 | // only query calendars of users, we have READ-grants from |
||
454 | $users = array(); |
||
455 | foreach($_users as $user) |
||
456 | { |
||
457 | $user = trim($user); |
||
458 | |||
459 | // Handle email lists |
||
460 | if(!is_numeric($user) && $user[0] == 'l') |
||
461 | { |
||
462 | foreach($this->enum_mailing_list($user, $ignore_acl, $use_freebusy) as $contact) |
||
463 | { |
||
464 | if ($contact && !in_array($contact,$users)) // already added? |
||
465 | { |
||
466 | $users[] = $contact; |
||
467 | } |
||
468 | } |
||
469 | continue; |
||
470 | } |
||
471 | if ($ignore_acl || $this->check_perms(ACL::READ|self::ACL_READ_FOR_PARTICIPANTS|($use_freebusy?self::ACL_FREEBUSY:0),0,$user)) |
||
472 | { |
||
473 | if ($user && !in_array($user,$users)) // already added? |
||
474 | { |
||
475 | // General expansion check |
||
476 | if (!is_numeric($user) && $this->resources[$user[0]]['info']) |
||
477 | { |
||
478 | $info = $this->resource_info($user); |
||
479 | if($info && $info['resources']) |
||
480 | { |
||
481 | foreach($info['resources'] as $_user) |
||
482 | { |
||
483 | if($_user && !in_array($_user, $users)) |
||
484 | { |
||
485 | $users[] = $_user; |
||
486 | } |
||
487 | } |
||
488 | continue; |
||
489 | } |
||
490 | } |
||
491 | $users[] = $user; |
||
492 | } |
||
493 | } |
||
494 | elseif ($GLOBALS['egw']->accounts->get_type($user) != 'g') |
||
495 | { |
||
496 | continue; // for non-groups (eg. users), we stop here if we have no read-rights |
||
497 | } |
||
498 | // the further code is only for real users |
||
499 | if (!is_numeric($user)) continue; |
||
500 | |||
501 | // for groups we have to include the members |
||
502 | if ($GLOBALS['egw']->accounts->get_type($user) == 'g') |
||
503 | { |
||
504 | if ($no_enum_groups) continue; |
||
505 | |||
506 | $members = $GLOBALS['egw']->accounts->members($user, true); |
||
507 | if (is_array($members)) |
||
508 | { |
||
509 | foreach($members as $member) |
||
510 | { |
||
511 | // use only members which gave the user a read-grant |
||
512 | if (!in_array($member, $users) && |
||
513 | ($ignore_acl || $this->check_perms(Acl::READ|($use_freebusy?self::ACL_FREEBUSY:0),0,$member))) |
||
514 | { |
||
515 | $users[] = $member; |
||
516 | } |
||
517 | } |
||
518 | } |
||
519 | } |
||
520 | else // for users we have to include all the memberships, to get the group-events |
||
521 | { |
||
522 | $memberships = $GLOBALS['egw']->accounts->memberships($user, true); |
||
523 | if (is_array($memberships)) |
||
524 | { |
||
525 | foreach($memberships as $group) |
||
526 | { |
||
527 | if (!in_array($group,$users)) |
||
528 | { |
||
529 | $users[] = $group; |
||
530 | } |
||
531 | } |
||
532 | } |
||
533 | } |
||
534 | } |
||
535 | return $users; |
||
536 | } |
||
537 | |||
538 | /** |
||
539 | * Searches / lists calendar entries, including repeating ones |
||
540 | * |
||
541 | * @param array $params array with the following keys |
||
542 | * start date startdate of the search/list, defaults to today |
||
543 | * end date enddate of the search/list, defaults to start + one day |
||
544 | * users int|array integer user-id or array of user-id's to use, defaults to the current user |
||
545 | * cat_id int|array category-id or array of cat-id's (incl. all sub-categories), default 0 = all |
||
546 | * filter string all (not rejected), accepted, unknown, tentative, rejected, hideprivate or everything (incl. rejected, deleted) |
||
547 | * query string pattern so search for, if unset or empty all matching entries are returned (no search) |
||
548 | * Please Note: a search never returns repeating events more then once AND does not honor start+end date !!! |
||
549 | * daywise boolean on True it returns an array with YYYYMMDD strings as keys and an array with events |
||
550 | * (events spanning multiple days are returned each day again (!)) otherwise it returns one array with |
||
551 | * the events (default), not honored in a search ==> always returns an array of events! |
||
552 | * date_format string date-formats: 'ts'=timestamp (default), 'array'=array, or string with format for date |
||
553 | * offset boolean|int false (default) to return all entries or integer offset to return only a limited result |
||
554 | * enum_recuring boolean if true or not set (default) or daywise is set, each recurence of a recuring events is returned, |
||
555 | * otherwise the original recuring event (with the first start- + enddate) is returned |
||
556 | * num_rows int number of entries to return, default or if 0, max_entries from the prefs |
||
557 | * order column-names plus optional DESC|ASC separted by comma |
||
558 | * private_allowed array Array of user IDs that are allowed when clearing private |
||
559 | * info, defaults to users |
||
560 | * ignore_acl if set and true no check_perms for a general Acl::READ grants is performed |
||
561 | * enum_groups boolean if set and true, group-members will be added as participants with status 'G' |
||
562 | * cols string|array columns to select, if set an iterator will be returned |
||
563 | * append string to append to the query, eg. GROUP BY |
||
564 | * cfs array if set, query given custom fields or all for empty array, none are returned, if not set (default) |
||
565 | * master_only boolean default false, true only take into account participants/status from master (for AS) |
||
566 | * @param string $sql_filter =null sql to be and'ed into query (fully quoted), default none |
||
567 | * @return iterator|array|boolean array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param) |
||
568 | * or false if there are no read-grants from _any_ of the requested users or iterator/recordset if cols are given |
||
569 | */ |
||
570 | function &search($params,$sql_filter=null) |
||
571 | { |
||
572 | $params_in = $params; |
||
573 | |||
574 | $params['sql_filter'] = $sql_filter; // dont allow to set it via UI or xmlrpc |
||
575 | |||
576 | // check if any resource wants to hook into |
||
577 | foreach($this->resources as $data) |
||
578 | { |
||
579 | if (isset($data['search_filter'])) |
||
580 | { |
||
581 | $params = ExecMethod($data['search_filter'],$params); |
||
582 | } |
||
583 | } |
||
584 | |||
585 | if (empty($params['users']) || |
||
586 | is_array($params['users']) && count($params['users']) == 1 && empty($params['users'][0])) // null or '' casted to an array |
||
587 | { |
||
588 | // for a search use all account you have read grants from |
||
589 | $params['users'] = $params['query'] ? array_keys($this->grants) : $this->user; |
||
590 | } |
||
591 | // resolve users to add memberships for users and members for groups |
||
592 | // for search, do NOT use freebusy rights, as it would allow to probe the content of event entries |
||
593 | $users = $this->resolve_users($params['users'], $params['filter'] == 'no-enum-groups', $params['ignore_acl'], empty($params['query'])); |
||
594 | if($params['private_allowed']) |
||
595 | { |
||
596 | $params['private_allowed'] = $this->resolve_users($params['private_allowed'],$params['filter'] == 'no-enum-groups',$params['ignore_acl'], empty($params['query'])); |
||
597 | } |
||
598 | |||
599 | // supply so with private_grants, to not query them again from the database |
||
600 | if (!empty($params['query'])) |
||
601 | { |
||
602 | $params['private_grants'] = array(); |
||
603 | foreach($this->grants as $user => $rights) |
||
604 | { |
||
605 | if ($rights & Acl::PRIVAT) $params['private_grants'][] = $user; |
||
606 | } |
||
607 | } |
||
608 | |||
609 | // replace (by so not understood filter 'no-enum-groups' with 'default' filter |
||
610 | if ($params['filter'] == 'no-enum-groups') |
||
611 | { |
||
612 | $params['filter'] = 'default'; |
||
613 | } |
||
614 | // if we have no grants from the given user(s), we directly return no events / an empty array, |
||
615 | // as calling the so-layer without users would give the events of all users (!) |
||
616 | if (!count($users) && !$params['ignore_acl']) |
||
617 | { |
||
618 | return false; |
||
619 | } |
||
620 | if (isset($params['start'])) $start = $this->date2ts($params['start']); |
||
621 | |||
622 | if (isset($params['end'])) |
||
623 | { |
||
624 | $end = $this->date2ts($params['end']); |
||
625 | $this->check_move_horizont($end); |
||
626 | } |
||
627 | $daywise = !isset($params['daywise']) ? False : !!$params['daywise']; |
||
628 | $params['enum_recuring'] = $enum_recuring = $daywise || !isset($params['enum_recuring']) || !!$params['enum_recuring']; |
||
629 | $cat_id = isset($params['cat_id']) ? $params['cat_id'] : 0; |
||
630 | $filter = isset($params['filter']) ? $params['filter'] : 'all'; |
||
631 | $offset = isset($params['offset']) && $params['offset'] !== false ? (int) $params['offset'] : false; |
||
632 | // socal::search() returns rejected group-invitations, as only the user not also the group is rejected |
||
633 | // as we cant remove them efficiantly in SQL, we kick them out here, but only if just one user is displayed |
||
634 | $users_in = (array)$params_in['users']; |
||
635 | $remove_rejected_by_user = !in_array($filter,array('all','rejected','everything')) && |
||
636 | count($users_in) == 1 && $users_in[0] > 0 ? $users_in[0] : null; |
||
637 | //error_log(__METHOD__.'('.array2string($params_in).", $sql_filter) params[users]=".array2string($params['users']).' --> remove_rejected_by_user='.array2string($remove_rejected_by_user)); |
||
638 | |||
639 | if ($this->debug && ($this->debug > 1 || $this->debug == 'search')) |
||
640 | { |
||
641 | $this->debug_message('calendar_bo::search(%1) start=%2, end=%3, daywise=%4, cat_id=%5, filter=%6, query=%7, offset=%8, num_rows=%9, order=%10, sql_filter=%11)', |
||
642 | True,$params,$start,$end,$daywise,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$params['sql_filter']); |
||
643 | } |
||
644 | // date2ts(,true) converts to server time, db2data converts again to user-time |
||
645 | $events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null, |
||
646 | $users,$cat_id,$filter,$offset,(int)$params['num_rows'],$params,$remove_rejected_by_user); |
||
647 | |||
648 | if (isset($params['cols'])) |
||
649 | { |
||
650 | return $events; |
||
651 | } |
||
652 | $this->total = $this->so->total; |
||
653 | $this->db2data($events,isset($params['date_format']) ? $params['date_format'] : 'ts'); |
||
654 | |||
655 | //echo "<p align=right>remove_rejected_by_user=$remove_rejected_by_user, filter=$filter, params[users]=".print_r($param['users'])."</p>\n"; |
||
656 | foreach($events as $id => $event) |
||
657 | { |
||
658 | if ($params['enum_groups'] && $this->enum_groups($event)) |
||
659 | { |
||
660 | $events[$id] = $event; |
||
661 | } |
||
662 | $matches = null; |
||
663 | if (!(int)$event['id'] && preg_match('/^([a-z_]+)([0-9]+)$/',$event['id'],$matches)) |
||
664 | { |
||
665 | $is_private = self::integration_get_private($matches[1],$matches[2],$event); |
||
666 | } |
||
667 | else |
||
668 | { |
||
669 | $is_private = !$this->check_perms(Acl::READ,$event); |
||
670 | } |
||
671 | if (!$params['ignore_acl'] && ($is_private || (!$event['public'] && $filter == 'hideprivate'))) |
||
672 | { |
||
673 | $this->clear_private_infos($events[$id],$params['private_allowed'] ? $params['private_allowed'] : $users); |
||
674 | } |
||
675 | } |
||
676 | |||
677 | if ($daywise) |
||
678 | { |
||
679 | if ($this->debug && ($this->debug > 2 || $this->debug == 'search')) |
||
680 | { |
||
681 | $this->debug_message('socalendar::search daywise sorting from %1 to %2 of %3',False,$start,$end,$events); |
||
682 | } |
||
683 | // create empty entries for each day in the reported time |
||
684 | for($ts = $start; $ts <= $end; $ts += DAY_s) // good enough for array creation, but see while loop below. |
||
685 | { |
||
686 | $daysEvents[$this->date2string($ts)] = array(); |
||
687 | } |
||
688 | foreach($events as $k => $event) |
||
689 | { |
||
690 | $e_start = max($this->date2ts($event['start']),$start); |
||
691 | // $event['end']['raw']-1 to allow events to end on a full hour/day without the need to enter it as minute=59 |
||
692 | $e_end = min($this->date2ts($event['end'])-1,$end); |
||
693 | |||
694 | // add event to each day in the reported time |
||
695 | $ts = $e_start; |
||
696 | // $ts += DAY_s in a 'for' loop does not work for daylight savings in week view |
||
697 | // because the day is longer than DAY_s: Fullday events will be added twice. |
||
698 | $ymd = null; |
||
699 | while ($ts <= $e_end) |
||
700 | { |
||
701 | $daysEvents[$ymd = $this->date2string($ts)][] =& $events[$k]; |
||
702 | $ts = strtotime("+1 day",$ts); |
||
703 | } |
||
704 | if ($ymd != ($last = $this->date2string($e_end))) |
||
705 | { |
||
706 | $daysEvents[$last][] =& $events[$k]; |
||
707 | } |
||
708 | } |
||
709 | $events =& $daysEvents; |
||
710 | if ($this->debug && ($this->debug > 2 || $this->debug == 'search')) |
||
711 | { |
||
712 | $this->debug_message('socalendar::search daywise events=%1',False,$events); |
||
713 | } |
||
714 | } |
||
715 | if ($this->debug && ($this->debug > 0 || $this->debug == 'search')) |
||
716 | { |
||
717 | $this->debug_message('calendar_bo::search(%1)=%2',True,$params,$events); |
||
718 | } |
||
719 | //error_log(__METHOD__."() returning ".count($events)." entries, total=$this->total ".function_backtrace()); |
||
720 | return $events; |
||
721 | } |
||
722 | |||
723 | /** |
||
724 | * Get integration data for a given app of a part (value for a certain key) of it |
||
725 | * |
||
726 | * @param string $app |
||
727 | * @param string $part |
||
728 | * @return array |
||
729 | */ |
||
730 | static function integration_get_data($app,$part=null) |
||
731 | { |
||
732 | static $integration_data=null; |
||
733 | |||
734 | if (!isset($integration_data)) |
||
735 | { |
||
736 | $integration_data = calendar_so::get_integration_data(); |
||
737 | } |
||
738 | |||
739 | if (!isset($integration_data[$app])) return null; |
||
740 | |||
741 | return $part ? $integration_data[$app][$part] : $integration_data[$app]; |
||
742 | } |
||
743 | |||
744 | /** |
||
745 | * Get private attribute for an integration event |
||
746 | * |
||
747 | * Attribute 'is_private' is either a boolean value, eg. false to make all events of $app public |
||
748 | * or an ExecMethod callback with parameters $id,$event |
||
749 | * |
||
750 | * @param string $app |
||
751 | * @param int|string $id |
||
752 | * @return string |
||
753 | */ |
||
754 | static function integration_get_private($app,$id,$event) |
||
755 | { |
||
756 | $app_data = self::integration_get_data($app,'is_private'); |
||
757 | |||
758 | // no method, fall back to link title |
||
759 | if (is_null($app_data)) |
||
760 | { |
||
761 | $is_private = !Link::title($app,$id); |
||
762 | } |
||
763 | // boolean value to make all events of $app public (false) or private (true) |
||
764 | elseif (is_bool($app_data)) |
||
765 | { |
||
766 | $is_private = $app_data; |
||
767 | } |
||
768 | else |
||
769 | { |
||
770 | $is_private = (bool)ExecMethod2($app_data,$id,$event); |
||
771 | } |
||
772 | //echo '<p>'.__METHOD__."($app,$id,) app_data=".array2string($app_data).' returning '.array2string($is_private)."</p>\n"; |
||
773 | return $is_private; |
||
774 | } |
||
775 | |||
776 | /** |
||
777 | * Clears all non-private info from a privat event |
||
778 | * |
||
779 | * That function only returns the infos allowed to be viewed by people without Acl::PRIVAT grants |
||
780 | * |
||
781 | * @param array &$event |
||
782 | * @param array $allowed_participants ids of the allowed participants, eg. the ones the search is over or eg. the owner of the calendar |
||
783 | */ |
||
784 | function clear_private_infos(&$event,$allowed_participants = array()) |
||
785 | { |
||
786 | if ($event == false) return; |
||
787 | if (!is_array($event['participants'])) error_log(__METHOD__.'('.array2string($event).', '.array2string($allowed_participants).') NO PARTICIPANTS '.function_backtrace()); |
||
788 | |||
789 | $event = array( |
||
790 | 'id' => $event['id'], |
||
791 | 'start' => $event['start'], |
||
792 | 'end' => $event['end'], |
||
793 | 'whole_day' => $event['whole_day'], |
||
794 | 'tzid' => $event['tzid'], |
||
795 | 'title' => lang('private'), |
||
796 | 'modified' => $event['modified'], |
||
797 | 'owner' => $event['owner'], |
||
798 | 'uid' => $event['uid'], |
||
799 | 'etag' => $event['etag'], |
||
800 | 'participants' => array_intersect_key($event['participants'],array_flip($allowed_participants)), |
||
801 | 'public'=> 0, |
||
802 | 'category' => $event['category'], // category is visible anyway, eg. by using planner by cat |
||
803 | 'non_blocking' => $event['non_blocking'], |
||
804 | 'caldav_name' => $event['caldav_name'], |
||
805 | // we need full recurrence information, as they are relevant free/busy information |
||
806 | )+($event['recur_type'] ? array( |
||
807 | 'recur_type' => $event['recur_type'], |
||
808 | 'recur_interval' => $event['recur_interval'], |
||
809 | 'recur_data' => $event['recur_data'], |
||
810 | 'recur_enddate' => $event['recur_enddate'], |
||
811 | 'recur_exception'=> $event['recur_exception'], |
||
812 | ):array( |
||
813 | 'reference' => $event['reference'], |
||
814 | 'recurrence' => $event['recurrence'], |
||
815 | )); |
||
816 | } |
||
817 | |||
818 | /** |
||
819 | * check and evtl. move the horizont (maximum date for unlimited recuring events) to a new date |
||
820 | * |
||
821 | * @internal automaticaly called by search |
||
822 | * @param mixed $_new_horizont time to set the horizont to (user-time) |
||
823 | */ |
||
824 | function check_move_horizont($_new_horizont) |
||
825 | { |
||
826 | if ((int) $this->debug >= 2 || $this->debug == 'check_move_horizont') |
||
827 | { |
||
828 | $this->debug_message('calendar_bo::check_move_horizont(%1) horizont=%2',true,$_new_horizont,(int)$this->config['horizont']); |
||
829 | } |
||
830 | $new_horizont = $this->date2ts($_new_horizont,true); // now we are in server-time, where this function operates |
||
831 | |||
832 | if ($new_horizont <= $this->config['horizont']) // no move necessary |
||
833 | { |
||
834 | if ($this->debug == 'check_move_horizont') $this->debug_message('calendar_bo::check_move_horizont(%1) horizont=%2 is bigger ==> nothing to do',true,$new_horizont,(int)$this->config['horizont']); |
||
835 | return; |
||
836 | } |
||
837 | if (!empty($GLOBALS['egw_info']['server']['calendar_horizont'])) |
||
838 | { |
||
839 | $maxdays = abs($GLOBALS['egw_info']['server']['calendar_horizont']); |
||
840 | } |
||
841 | if (empty($maxdays)) $maxdays = 1000; // old default |
||
842 | if ($new_horizont > time()+$maxdays*DAY_s) // some user tries to "look" more then the maximum number of days in the future |
||
843 | { |
||
844 | if ($this->debug == 'check_move_horizont') $this->debug_message('calendar_bo::check_move_horizont(%1) horizont=%2 new horizont more then %3 days from now --> ignoring it',true,$new_horizont,(int)$this->config['horizont'],$maxdays); |
||
845 | $this->warnings['horizont'] = lang('Requested date %1 outside allowed range of %2 days: recurring events obmitted!', Api\DateTime::to($new_horizont,true), $maxdays); |
||
846 | return; |
||
847 | } |
||
848 | if ($new_horizont < time()+31*DAY_s) |
||
849 | { |
||
850 | $new_horizont = time()+31*DAY_s; |
||
851 | } |
||
852 | $old_horizont = $this->config['horizont']; |
||
853 | $this->config['horizont'] = $new_horizont; |
||
854 | |||
855 | // create further recurrences for all recurring and not yet (at the old horizont) ended events |
||
856 | if (($recuring = $this->so->unfinished_recuring($old_horizont))) |
||
857 | { |
||
858 | @set_time_limit(0); // disable time-limit, in case it takes longer to calculate the recurrences |
||
859 | foreach($this->read(array_keys($recuring)) as $cal_id => $event) |
||
860 | { |
||
861 | if ($this->debug == 'check_move_horizont') |
||
862 | { |
||
863 | $this->debug_message('calendar_bo::check_move_horizont(%1): calling set_recurrences(%2,%3)',true,$new_horizont,$event,$old_horizont); |
||
864 | } |
||
865 | // insert everything behind max(cal_start), which can be less then $old_horizont because of bugs in the past |
||
866 | $this->set_recurrences($event,Api\DateTime::server2user($recuring[$cal_id]+1)); // set_recurences operates in user-time! |
||
867 | } |
||
868 | } |
||
869 | // update the horizont |
||
870 | Api\Config::save_value('horizont',$this->config['horizont'],'calendar'); |
||
871 | |||
872 | if ($this->debug == 'check_move_horizont') $this->debug_message('calendar_bo::check_move_horizont(%1) new horizont=%2, exiting',true,$new_horizont,(int)$this->config['horizont']); |
||
873 | } |
||
874 | |||
875 | /** |
||
876 | * set all recurrences for an event until the defined horizont $this->config['horizont'] |
||
877 | * |
||
878 | * This methods operates in usertime, while $this->config['horizont'] is in servertime! |
||
879 | * |
||
880 | * @param array $event |
||
881 | * @param mixed $start =0 minimum start-time for new recurrences or !$start = since the start of the event |
||
882 | */ |
||
883 | function set_recurrences($event,$start=0) |
||
884 | { |
||
885 | if ($this->debug && ((int) $this->debug >= 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont')) |
||
886 | { |
||
887 | $this->debug_message('calendar_bo::set_recurrences(%1,%2)',true,$event,$start); |
||
888 | } |
||
889 | // check if the caller gave us enough information and if not read it from the DB |
||
890 | if (!isset($event['participants']) || !isset($event['start']) || !isset($event['end'])) |
||
891 | { |
||
892 | $event_read = current($this->so->read($event['id'])); |
||
893 | if (!isset($event['participants'])) |
||
894 | { |
||
895 | $event['participants'] = $event_read['participants']; |
||
896 | } |
||
897 | if (!isset($event['start']) || !isset($event['end'])) |
||
898 | { |
||
899 | $event['start'] = $this->date2usertime($event_read['start']); |
||
900 | $event['end'] = $this->date2usertime($event_read['end']); |
||
901 | } |
||
902 | } |
||
903 | if (!$start) $start = $event['start']; |
||
904 | |||
905 | $events = array(); |
||
906 | |||
907 | $this->insert_all_recurrences($event,$start,$this->date2usertime($this->config['horizont']),$events); |
||
908 | |||
909 | $exceptions = array(); |
||
910 | foreach((array)$event['recur_exception'] as $exception) |
||
911 | { |
||
912 | $exceptions[] = Api\DateTime::to($exception, true); // true = date |
||
913 | } |
||
914 | foreach($events as $event) |
||
915 | { |
||
916 | $is_exception = in_array(Api\DateTime::to($event['start'], true), $exceptions); |
||
917 | $start = $this->date2ts($event['start'],true); |
||
918 | if ($event['whole_day']) |
||
919 | { |
||
920 | $start = new Api\DateTime($event['start'], Api\DateTime::$server_timezone); |
||
921 | $start->setTime(0,0,0); |
||
922 | $start = $start->format('ts'); |
||
923 | $time = $this->so->startOfDay(new Api\DateTime($event['end'], Api\DateTime::$user_timezone)); |
||
924 | $time->setTime(23, 59, 59); |
||
925 | $end = $this->date2ts($time,true); |
||
926 | } |
||
927 | else |
||
928 | { |
||
929 | $end = $this->date2ts($event['end'],true); |
||
930 | } |
||
931 | //error_log(__METHOD__."() start=".Api\DateTime::to($start).", is_exception=".array2string($is_exception)); |
||
932 | $this->so->recurrence($event['id'], $start, $end, $event['participants'], $is_exception); |
||
933 | } |
||
934 | } |
||
935 | |||
936 | /** |
||
937 | * Convert data read from the db, eg. convert server to user-time |
||
938 | * |
||
939 | * Also make sure all timestamps comming from DB as string are converted to integer, |
||
940 | * to avoid misinterpretation by Api\DateTime as Ymd string. |
||
941 | * |
||
942 | * @param array &$events array of event-arrays (reference) |
||
943 | * @param $date_format ='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format |
||
944 | */ |
||
945 | function db2data(&$events,$date_format='ts') |
||
946 | { |
||
947 | if (!is_array($events)) echo "<p>calendar_bo::db2data(\$events,$date_format) \$events is no array<br />\n".function_backtrace()."</p>\n"; |
||
948 | foreach ($events as &$event) |
||
949 | { |
||
950 | // convert timezone id of event to tzid (iCal id like 'Europe/Berlin') |
||
951 | if (empty($event['tzid']) && (!$event['tz_id'] || !($event['tzid'] = calendar_timezones::id2tz($event['tz_id'])))) |
||
952 | { |
||
953 | $event['tzid'] = Api\DateTime::$server_timezone->getName(); |
||
954 | } |
||
955 | // database returns timestamps as string, convert them to integer |
||
956 | // to avoid misinterpretation by Api\DateTime as Ymd string |
||
957 | // (this will fail on 32bit systems for times > 2038!) |
||
958 | $event['start'] = (int)$event['start']; // this is for isWholeDay(), which also calls Api\DateTime |
||
959 | $event['end'] = (int)$event['end']; |
||
960 | $event['whole_day'] = self::isWholeDay($event); |
||
961 | if ($event['whole_day'] && $date_format != 'server') |
||
962 | { |
||
963 | // Adjust dates to user TZ |
||
964 | $stime =& $this->so->startOfDay(new Api\DateTime((int)$event['start'], Api\DateTime::$server_timezone), $event['tzid']); |
||
965 | $event['start'] = Api\DateTime::to($stime, $date_format); |
||
966 | $time =& $this->so->startOfDay(new Api\DateTime((int)$event['end'], Api\DateTime::$server_timezone), $event['tzid']); |
||
967 | $time->setTime(23, 59, 59); |
||
968 | $event['end'] = Api\DateTime::to($time, $date_format); |
||
969 | if (!empty($event['recurrence'])) |
||
970 | { |
||
971 | $time =& $this->so->startOfDay(new Api\DateTime((int)$event['recurrence'], Api\DateTime::$server_timezone), $event['tzid']); |
||
972 | $event['recurrence'] = Api\DateTime::to($time, $date_format); |
||
973 | } |
||
974 | if (!empty($event['recur_enddate'])) |
||
975 | { |
||
976 | $time =& $this->so->startOfDay(new Api\DateTime((int)$event['recur_enddate'], Api\DateTime::$server_timezone), $event['tzid']); |
||
977 | $time->setTime(23, 59, 59); |
||
978 | $event['recur_enddate'] = Api\DateTime::to($time, $date_format); |
||
979 | } |
||
980 | $timestamps = array('modified','created','deleted'); |
||
981 | } |
||
982 | else |
||
983 | { |
||
984 | $timestamps = array('start','end','modified','created','recur_enddate','recurrence','recur_date','deleted'); |
||
985 | } |
||
986 | // we convert here from the server-time timestamps to user-time and (optional) to a different date-format! |
||
987 | foreach ($timestamps as $ts) |
||
988 | { |
||
989 | if (!empty($event[$ts])) |
||
990 | { |
||
991 | $event[$ts] = $this->date2usertime((int)$event[$ts],$date_format); |
||
992 | } |
||
993 | } |
||
994 | // same with the recur exceptions |
||
995 | if (isset($event['recur_exception']) && is_array($event['recur_exception'])) |
||
996 | { |
||
997 | foreach($event['recur_exception'] as &$date) |
||
998 | { |
||
999 | if ($event['whole_day'] && $date_format != 'server') |
||
1000 | { |
||
1001 | // Adjust dates to user TZ |
||
1002 | $time =& $this->so->startOfDay(new Api\DateTime((int)$date, Api\DateTime::$server_timezone), $event['tzid']); |
||
1003 | $date = Api\DateTime::to($time, $date_format); |
||
1004 | } |
||
1005 | else |
||
1006 | { |
||
1007 | $date = $this->date2usertime((int)$date,$date_format); |
||
1008 | } |
||
1009 | } |
||
1010 | } |
||
1011 | // same with the alarms |
||
1012 | if (isset($event['alarm']) && is_array($event['alarm'])) |
||
1013 | { |
||
1014 | foreach($event['alarm'] as &$alarm) |
||
1015 | { |
||
1016 | $alarm['time'] = $this->date2usertime((int)$alarm['time'],$date_format); |
||
1017 | } |
||
1018 | } |
||
1019 | } |
||
1020 | } |
||
1021 | |||
1022 | /** |
||
1023 | * convert a date from server to user-time |
||
1024 | * |
||
1025 | * @param int $ts timestamp in server-time |
||
1026 | * @param string $date_format ='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format |
||
1027 | * @return mixed depending of $date_format |
||
1028 | */ |
||
1029 | function date2usertime($ts,$date_format='ts') |
||
1030 | { |
||
1031 | if (empty($ts) || $date_format == 'server') return $ts; |
||
1032 | |||
1033 | return Api\DateTime::server2user($ts,$date_format); |
||
1034 | } |
||
1035 | |||
1036 | /** |
||
1037 | * Reads a calendar-entry |
||
1038 | * |
||
1039 | * @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid |
||
1040 | * @param mixed $date =null date to specify a single event of a series |
||
1041 | * @param boolean $ignore_acl should we ignore the acl, default False for a single id, true for multiple id's |
||
1042 | * @param string $date_format ='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in servertime, 'array'=array, or string with date-format |
||
1043 | * @param array|int $clear_private_infos_users =null if not null, return events with self::ACL_FREEBUSY too, |
||
1044 | * but call clear_private_infos() with the given users |
||
1045 | * @param boolean $read_recurrence =false true: read the exception, not the series master (only for recur_date && $ids='<uid>'!) |
||
1046 | * @return boolean|array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found |
||
1047 | */ |
||
1048 | function read($ids,$date=null, $ignore_acl=False, $date_format='ts', $clear_private_infos_users=null, $read_recurrence=false) |
||
1049 | { |
||
1050 | if (!$ids) return false; |
||
1051 | |||
1052 | if ($date) $date = $this->date2ts($date); |
||
1053 | |||
1054 | $return = null; |
||
1055 | |||
1056 | $check = $clear_private_infos_users ? self::ACL_FREEBUSY : Acl::READ; |
||
1057 | if ($ignore_acl || is_array($ids) || ($return = $this->check_perms($check,$ids,0,$date_format,$date))) |
||
1058 | { |
||
1059 | if (is_array($ids) || !isset(self::$cached_event['id']) || self::$cached_event['id'] != $ids || |
||
1060 | self::$cached_event_date_format != $date_format || $read_recurrence || |
||
1061 | self::$cached_event['recur_type'] != MCAL_RECUR_NONE && self::$cached_event_date != $date) |
||
1062 | { |
||
1063 | $events = $this->so->read($ids,$date ? $this->date2ts($date,true) : 0, $read_recurrence); |
||
1064 | |||
1065 | if ($events) |
||
1066 | { |
||
1067 | $this->db2data($events,$date_format); |
||
1068 | |||
1069 | if (is_array($ids)) |
||
1070 | { |
||
1071 | $return =& $events; |
||
1072 | } |
||
1073 | else |
||
1074 | { |
||
1075 | self::$cached_event = array_shift($events); |
||
1076 | self::$cached_event_date_format = $date_format; |
||
1077 | self::$cached_event_date = $date; |
||
1078 | $return = self::$cached_event; |
||
1079 | } |
||
1080 | } |
||
1081 | } |
||
1082 | else |
||
1083 | { |
||
1084 | $return = self::$cached_event; |
||
1085 | } |
||
1086 | } |
||
1087 | if ($clear_private_infos_users && !is_array($ids) && !$this->check_perms(Acl::READ,$return)) |
||
1088 | { |
||
1089 | $this->clear_private_infos($return, (array)$clear_private_infos_users); |
||
1090 | } |
||
1091 | if ($this->debug && ($this->debug > 1 || $this->debug == 'read')) |
||
1092 | { |
||
1093 | $this->debug_message('calendar_bo::read(%1,%2,%3,%4,%5)=%6',True,$ids,$date,$ignore_acl,$date_format,$clear_private_infos_users,$return); |
||
1094 | } |
||
1095 | return $return; |
||
1096 | } |
||
1097 | |||
1098 | /** |
||
1099 | * Inserts all repetions of $event in the timespan between $start and $end into $events |
||
1100 | * |
||
1101 | * The new entries are just appended to $events, so $events is no longer sorted by startdate !!! |
||
1102 | * |
||
1103 | * Recurrences get calculated by rrule iterator implemented in calendar_rrule class. |
||
1104 | * |
||
1105 | * @param array $event repeating event whos repetions should be inserted |
||
1106 | * @param mixed $start start-date |
||
1107 | * @param mixed $end end-date |
||
1108 | * @param array $events where the repetions get inserted |
||
1109 | * @param array $recur_exceptions with date (in Ymd) as key (and True as values), seems not to be used anymore |
||
1110 | */ |
||
1111 | function insert_all_recurrences($event,$_start,$end,&$events) |
||
1112 | { |
||
1113 | if ((int) $this->debug >= 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_recurrences') |
||
1114 | { |
||
1115 | $this->debug_message(__METHOD__.'(%1,%2,%3,&$events)',true,$event,$_start,$end); |
||
1116 | } |
||
1117 | $end_in = $end; |
||
1118 | |||
1119 | $start = $this->date2ts($_start); |
||
1120 | $event_start_ts = $this->date2ts($event['start']); |
||
1121 | $event_length = $this->date2ts($event['end']) - $event_start_ts; // we use a constant event-length, NOT a constant end-time! |
||
1122 | |||
1123 | // if $end is before recur_enddate, use it instead |
||
1124 | if (!$event['recur_enddate'] || $this->date2ts($event['recur_enddate']) > $this->date2ts($end)) |
||
1125 | { |
||
1126 | //echo "<p>recur_enddate={$event['recur_enddate']}=".Api\DateTime::to($event['recur_enddate'])." > end=$end=".Api\DateTime::to($end)." --> using end instead of recur_enddate</p>\n"; |
||
1127 | // insert at least the event itself, if it's behind the horizont |
||
1128 | $event['recur_enddate'] = $this->date2ts($end) < $this->date2ts($event['end']) ? $event['end'] : $end; |
||
1129 | } |
||
1130 | $event['recur_enddate'] = is_a($event['recur_enddate'],'DateTime') ? |
||
1131 | $event['recur_enddate'] : |
||
1132 | new Api\DateTime($event['recur_enddate'], calendar_timezones::DateTimeZone($event['tzid'])); |
||
1133 | |||
1134 | // unset exceptions, as we need to add them as recurrence too, but marked as exception |
||
1135 | unset($event['recur_exception']); |
||
1136 | // loop over all recurrences and insert them, if they are after $start |
||
1137 | $rrule = calendar_rrule::event2rrule($event, !$event['whole_day'], Api\DateTime::$user_timezone->getName()); // true = we operate in usertime, like the rest of calendar_bo |
||
1138 | foreach($rrule as $time) |
||
1139 | { |
||
1140 | $time->setUser(); // $time is in timezone of event, convert it to usertime used here |
||
1141 | if($event['whole_day']) |
||
1142 | { |
||
1143 | // All day events are processed in server timezone |
||
1144 | $time->setServer(); |
||
1145 | $time->setTime(0,0,0); |
||
1146 | } |
||
1147 | if (($ts = $this->date2ts($time)) < $start-$event_length) |
||
1148 | { |
||
1149 | //echo "<p>".$time." --> ignored as $ts < $start-$event_length</p>\n"; |
||
1150 | continue; // to early or original event (returned by interator too) |
||
1151 | } |
||
1152 | |||
1153 | $ts_end = $ts + $event_length; |
||
1154 | // adjust ts_end for whole day events in case it does not fit due to |
||
1155 | // spans over summer/wintertime adjusted days |
||
1156 | if($event['whole_day'] && ($arr_end = $this->date2array($ts_end)) && |
||
1157 | !($arr_end['hour'] == 23 && $arr_end['minute'] == 59 && $arr_end['second'] == 59)) |
||
1158 | { |
||
1159 | $arr_end['hour'] = 23; |
||
1160 | $arr_end['minute'] = 59; |
||
1161 | $arr_end['second'] = 59; |
||
1162 | $ts_end_guess = $this->date2ts($arr_end); |
||
1163 | if($ts_end_guess - $ts_end > DAY_s/2) |
||
1164 | { |
||
1165 | $ts_end = $ts_end_guess - DAY_s; // $ts_end_guess was one day too far in the future |
||
1166 | } |
||
1167 | else |
||
1168 | { |
||
1169 | $ts_end = $ts_end_guess; // $ts_end_guess was ok |
||
1170 | } |
||
1171 | } |
||
1172 | |||
1173 | $event['start'] = $ts; |
||
1174 | $event['end'] = $ts_end; |
||
1175 | $events[] = $event; |
||
1176 | } |
||
1177 | if ($this->debug && ((int) $this->debug > 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_recurrences')) |
||
1178 | { |
||
1179 | $event['start'] = $event_start_ts; |
||
1180 | $event['end'] = $event_start_ts + $event_length; |
||
1181 | $this->debug_message(__METHOD__.'(%1,start=%2,end=%3,events) events=%5',True,$event,$_start,$end_in,$events); |
||
1182 | } |
||
1183 | } |
||
1184 | |||
1185 | /** |
||
1186 | * Adds one repetion of $event for $date_ymd to the $events array, after adjusting its start- and end-time |
||
1187 | * |
||
1188 | * @param array $events array in which the event gets inserted |
||
1189 | * @param array $event event to insert, it has start- and end-date of the first recurrence, not of $date_ymd |
||
1190 | * @param int|string $date_ymd of the date of the event |
||
1191 | */ |
||
1192 | function add_adjusted_event(&$events,$event,$date_ymd) |
||
1193 | { |
||
1194 | $event_in = $event; |
||
1195 | // calculate the new start- and end-time |
||
1196 | $length_s = $this->date2ts($event['end']) - $this->date2ts($event['start']); |
||
1197 | $event_start_arr = $this->date2array($event['start']); |
||
1198 | |||
1199 | $date_arr = $this->date2array((string) $date_ymd); |
||
1200 | $date_arr['hour'] = $event_start_arr['hour']; |
||
1201 | $date_arr['minute'] = $event_start_arr['minute']; |
||
1202 | $date_arr['second'] = $event_start_arr['second']; |
||
1203 | unset($date_arr['raw']); // else date2ts would use it |
||
1204 | $event['start'] = $this->date2ts($date_arr); |
||
1205 | $event['end'] = $event['start'] + $length_s; |
||
1206 | |||
1207 | $events[] = $event; |
||
1208 | |||
1209 | if ($this->debug && ($this->debug > 2 || $this->debug == 'add_adjust_event')) |
||
1210 | { |
||
1211 | $this->debug_message('calendar_bo::add_adjust_event(,%1,%2) as %3',True,$event_in,$date_ymd,$event); |
||
1212 | } |
||
1213 | } |
||
1214 | |||
1215 | /** |
||
1216 | * Fetch information about a resource |
||
1217 | * |
||
1218 | * We do some caching here, as the resource itself might not do it. |
||
1219 | * |
||
1220 | * @param string $uid string with one-letter resource-type and numerical resource-id, eg. "r19" |
||
1221 | * @return array|boolean array with keys res_id,cat_id,name,useable (name definied by max_quantity in $this->resources),rights,responsible or false if $uid is not found |
||
1222 | */ |
||
1223 | function resource_info($uid) |
||
1224 | { |
||
1225 | static $res_info_cache = array(); |
||
1226 | |||
1227 | if (!is_scalar($uid)) throw new Api\Exception\WrongParameter(__METHOD__.'('.array2string($uid).') parameter must be scalar'); |
||
1228 | |||
1229 | if (!isset($res_info_cache[$uid])) |
||
1230 | { |
||
1231 | if (is_numeric($uid)) |
||
1232 | { |
||
1233 | $info = array( |
||
1234 | 'res_id' => $uid, |
||
1235 | 'email' => $GLOBALS['egw']->accounts->id2name($uid,'account_email'), |
||
1236 | 'name' => trim($GLOBALS['egw']->accounts->id2name($uid,'account_firstname'). ' ' . |
||
1237 | $GLOBALS['egw']->accounts->id2name($uid,'account_lastname')), |
||
1238 | 'type' => $GLOBALS['egw']->accounts->get_type($uid), |
||
1239 | 'app' => 'accounts', |
||
1240 | ); |
||
1241 | } |
||
1242 | else |
||
1243 | { |
||
1244 | list($info) = $this->resources[$uid[0]]['info'] ? ExecMethod($this->resources[$uid[0]]['info'],substr($uid,1)) : false; |
||
1245 | if ($info) |
||
1246 | { |
||
1247 | $info['type'] = $uid[0]; |
||
1248 | if (!$info['email'] && $info['responsible']) |
||
1249 | { |
||
1250 | $info['email'] = $GLOBALS['egw']->accounts->id2name($info['responsible'],'account_email'); |
||
1251 | } |
||
1252 | $info['app'] = $this->resources[$uid[0]]['app']; |
||
1253 | } |
||
1254 | } |
||
1255 | $res_info_cache[$uid] = $info; |
||
1256 | } |
||
1257 | if ($this->debug && ($this->debug > 2 || $this->debug == 'resource_info')) |
||
1258 | { |
||
1259 | $this->debug_message('calendar_bo::resource_info(%1) = %2',True,$uid,$res_info_cache[$uid]); |
||
1260 | } |
||
1261 | return $res_info_cache[$uid]; |
||
1262 | } |
||
1263 | |||
1264 | /** |
||
1265 | * Checks if the current user has the necessary ACL rights |
||
1266 | * |
||
1267 | * The check is performed on an event or generally on the cal of an other user |
||
1268 | * |
||
1269 | * Note: Participating in an event is considered as haveing read-access on that event, |
||
1270 | * even if you have no general read-grant from that user. |
||
1271 | * |
||
1272 | * @param int $needed necessary ACL right: Acl::{READ|EDIT|DELETE} |
||
1273 | * @param mixed $event event as array or the event-id or 0 for a general check |
||
1274 | * @param int $other uid to check (if event==0) or 0 to check against $this->user |
||
1275 | * @param string $date_format ='ts' date-format used for reading: 'ts'=timestamp, 'array'=array, 'string'=iso8601 string for xmlrpc |
||
1276 | * @param mixed $date_to_read =null date used for reading, internal param for the caching |
||
1277 | * @param int $user =null for which user to check, default current user |
||
1278 | * @return boolean true permission granted, false for permission denied or null if event not found |
||
1279 | */ |
||
1280 | function check_perms($needed,$event=0,$other=0,$date_format='ts',$date_to_read=null,$user=null) |
||
1281 | { |
||
1282 | if (!$user) $user = $this->user; |
||
1283 | if ($user == $this->user) |
||
1284 | { |
||
1285 | $grants = $this->grants; |
||
1286 | } |
||
1287 | else |
||
1288 | { |
||
1289 | $grants = $GLOBALS['egw']->acl->get_grants('calendar',true,$user); |
||
1290 | } |
||
1291 | |||
1292 | if ($other && !is_numeric($other)) |
||
1293 | { |
||
1294 | $resource = $this->resource_info($other); |
||
1295 | return $needed & $resource['rights']; |
||
1296 | } |
||
1297 | if (is_int($event) && $event == 0) |
||
1298 | { |
||
1299 | $owner = $other ? $other : $user; |
||
1300 | } |
||
1301 | else |
||
1302 | { |
||
1303 | if (!is_array($event)) |
||
1304 | { |
||
1305 | $event = $this->read($event,$date_to_read,true,$date_format); // = no ACL check !!! |
||
1306 | } |
||
1307 | if (!is_array($event)) |
||
1308 | { |
||
1309 | if ($this->xmlrpc) |
||
1310 | { |
||
1311 | $GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['not_exist'],$GLOBALS['xmlrpcstr']['not_exist']); |
||
1312 | } |
||
1313 | return null; // event not found |
||
1314 | } |
||
1315 | $owner = $event['owner']; |
||
1316 | $private = !$event['public']; |
||
1317 | } |
||
1318 | $grant = $grants[$owner]; |
||
1319 | |||
1320 | // now any ACL rights (but invite rights!) implicate FREEBUSY rights (at least READ has to include FREEBUSY) |
||
1321 | if ($grant & ~self::ACL_INVITE) $grant |= self::ACL_FREEBUSY; |
||
1322 | |||
1323 | if (is_array($event) && ($needed == Acl::READ || $needed == self::ACL_FREEBUSY)) |
||
1324 | { |
||
1325 | // Check if the $user is one of the participants or has a read-grant from one of them |
||
1326 | // in that case he has an implicite READ grant for that event |
||
1327 | // |
||
1328 | if ($event['participants'] && is_array($event['participants'])) |
||
1329 | { |
||
1330 | foreach(array_keys($event['participants']) as $uid) |
||
1331 | { |
||
1332 | if ($uid == $user || $uid < 0 && in_array($user, (array)$GLOBALS['egw']->accounts->members($uid,true))) |
||
1333 | { |
||
1334 | // if we are a participant, we have an implicite FREEBUSY, READ and PRIVAT grant |
||
1335 | $grant |= self::ACL_FREEBUSY | Acl::READ | Acl::PRIVAT; |
||
1336 | break; |
||
1337 | } |
||
1338 | elseif ($grants[$uid] & Acl::READ) |
||
1339 | { |
||
1340 | // if we have a READ grant from a participant, we dont give an implicit privat grant too |
||
1341 | $grant |= self::ACL_FREEBUSY | Acl::READ; |
||
1342 | // we cant break here, as we might be a participant too, and would miss the privat grant |
||
1343 | } |
||
1344 | elseif (!is_numeric($uid)) |
||
1345 | { |
||
1346 | // if the owner only grants self::ACL_FREEBUSY we are not interested in the recources explicit rights |
||
1347 | if ($grant == self::ACL_FREEBUSY) continue; |
||
1348 | // if we have a resource as participant |
||
1349 | $resource = $this->resource_info($uid); |
||
1350 | $grant |= $resource['rights']; |
||
1351 | } |
||
1352 | } |
||
1353 | } |
||
1354 | } |
||
1355 | if ($GLOBALS['egw']->accounts->get_type($owner) == 'g' && $needed == Acl::ADD) |
||
1356 | { |
||
1357 | $access = False; // a group can't be the owner of an event |
||
1358 | } |
||
1359 | else |
||
1360 | { |
||
1361 | $access = $user == $owner || $grant & $needed |
||
1362 | && ($needed == self::ACL_FREEBUSY || !$private || $grant & Acl::PRIVAT); |
||
1363 | } |
||
1364 | // do NOT allow users to purge deleted events, if we dont have 'userpurge' enabled |
||
1365 | if ($access && $needed == Acl::DELETE && $event['deleted'] && |
||
1366 | !$GLOBALS['egw_info']['user']['apps']['admin'] && |
||
1367 | $GLOBALS['egw_info']['server']['calendar_delete_history'] != 'userpurge') |
||
1368 | { |
||
1369 | $access = false; |
||
1370 | } |
||
1371 | if ($this->debug && ($this->debug > 2 || $this->debug == 'check_perms')) |
||
1372 | { |
||
1373 | $this->debug_message('calendar_bo::check_perms(%1,%2,other=%3,%4,%5,user=%6)=%7',True,ACL_TYPE_IDENTIFER.$needed,$event,$other,$date_format,$date_to_read,$user,$access); |
||
1374 | } |
||
1375 | //error_log(__METHOD__."($needed,".array2string($event).",$other,...,$user) returning ".array2string($access)); |
||
1376 | return $access; |
||
1377 | } |
||
1378 | |||
1379 | /** |
||
1380 | * Converts several date-types to a timestamp and optionally converts user- to server-time |
||
1381 | * |
||
1382 | * @param mixed $date date to convert, should be one of the following types |
||
1383 | * string (!) in form YYYYMMDD or iso8601 YYYY-MM-DDThh:mm:ss or YYYYMMDDThhmmss |
||
1384 | * int already a timestamp |
||
1385 | * array with keys 'second', 'minute', 'hour', 'day' or 'mday' (depricated !), 'month' and 'year' |
||
1386 | * @param boolean $user2server =False conversion between user- and server-time; default False == Off |
||
1387 | */ |
||
1388 | static function date2ts($date,$user2server=False) |
||
1389 | { |
||
1390 | return $user2server ? Api\DateTime::user2server($date,'ts') : Api\DateTime::to($date,'ts'); |
||
1391 | } |
||
1392 | |||
1393 | /** |
||
1394 | * Converts a date to an array and optionally converts server- to user-time |
||
1395 | * |
||
1396 | * @param mixed $date date to convert |
||
1397 | * @param boolean $server2user conversation between user- and server-time default False == Off |
||
1398 | * @return array with keys 'second', 'minute', 'hour', 'day', 'month', 'year', 'raw' (timestamp) and 'full' (Ymd-string) |
||
1399 | */ |
||
1400 | static function date2array($date,$server2user=False) |
||
1401 | { |
||
1402 | return $server2user ? Api\DateTime::server2user($date,'array') : Api\DateTime::to($date,'array'); |
||
1403 | } |
||
1404 | |||
1405 | /** |
||
1406 | * Converts a date as timestamp or array to a date-string and optionaly converts server- to user-time |
||
1407 | * |
||
1408 | * @param mixed $date integer timestamp or array with ('year','month',..,'second') to convert |
||
1409 | * @param boolean $server2user conversation between user- and server-time default False == Off, not used if $format ends with \Z |
||
1410 | * @param string $format ='Ymd' format of the date to return, eg. 'Y-m-d\TH:i:sO' (2005-11-01T15:30:00+0100) |
||
1411 | * @return string date formatted according to $format |
||
1412 | */ |
||
1413 | static function date2string($date,$server2user=False,$format='Ymd') |
||
1414 | { |
||
1415 | return $server2user ? Api\DateTime::server2user($date,$format) : Api\DateTime::to($date,$format); |
||
1416 | } |
||
1417 | |||
1418 | /** |
||
1419 | * Formats a date given as timestamp or array |
||
1420 | * |
||
1421 | * @param mixed $date integer timestamp or array with ('year','month',..,'second') to convert |
||
1422 | * @param string|boolean $format ='' default common_prefs[dateformat], common_prefs[timeformat], false=time only, true=date only |
||
1423 | * @return string the formated date (incl. time) |
||
1424 | */ |
||
1425 | static function format_date($date,$format='') |
||
1428 | } |
||
1429 | |||
1430 | /** |
||
1431 | * Gives out a debug-message with certain parameters |
||
1432 | * |
||
1433 | * All permanent debug-messages in the calendar should be done by this function !!! |
||
1434 | * (In future they may be logged or sent as xmlrpc-faults back.) |
||
1435 | * |
||
1436 | * Permanent debug-message need to make sure NOT to give secret information like passwords !!! |
||
1437 | * |
||
1438 | * This function do NOT honor the setting of the debug variable, you may use it like |
||
1439 | * if ($this->debug > N) $this->debug_message('Error ;-)'); |
||
1440 | * |
||
1441 | * The parameters get formated depending on their type. ACL-values need a ACL_TYPE_IDENTIFER prefix. |
||
1442 | * |
||
1443 | * @param string $msg message with parameters/variables like lang(), eg. '%1' |
||
1444 | * @param boolean $backtrace =True include a function-backtrace, default True=On |
||
1445 | * should only be set to False=Off, if your code ensures a call with backtrace=On was made before !!! |
||
1446 | * @param mixed $param a variable number of parameters, to be inserted in $msg |
||
1447 | * arrays get serialized with print_r() ! |
||
1448 | */ |
||
1449 | static function debug_message($msg,$backtrace=True) |
||
1450 | { |
||
1451 | static $acl2string = array( |
||
1452 | 0 => 'ACL-UNKNOWN', |
||
1453 | Acl::READ => 'ACL_READ', |
||
1454 | Acl::ADD => 'ACL_ADD', |
||
1455 | Acl::EDIT => 'ACL_EDIT', |
||
1456 | Acl::DELETE => 'ACL_DELETE', |
||
1457 | Acl::PRIVAT => 'ACL_PRIVATE', |
||
1458 | self::ACL_FREEBUSY => 'ACL_FREEBUSY', |
||
1459 | ); |
||
1460 | for($i = 2; $i < func_num_args(); ++$i) |
||
1461 | { |
||
1462 | $param = func_get_arg($i); |
||
1463 | |||
1464 | if (is_null($param)) |
||
1465 | { |
||
1466 | $param='NULL'; |
||
1467 | } |
||
1468 | else |
||
1469 | { |
||
1470 | switch(gettype($param)) |
||
1471 | { |
||
1472 | case 'string': |
||
1473 | if (substr($param,0,strlen(ACL_TYPE_IDENTIFER))== ACL_TYPE_IDENTIFER) |
||
1474 | { |
||
1475 | $param = (int) substr($param,strlen(ACL_TYPE_IDENTIFER)); |
||
1476 | $param = (isset($acl2string[$param]) ? $acl2string[$param] : $acl2string[0])." ($param)"; |
||
1477 | } |
||
1478 | else |
||
1479 | { |
||
1480 | $param = "'$param'"; |
||
1481 | } |
||
1482 | break; |
||
1483 | case 'EGroupware\\Api\\DateTime': |
||
1484 | case 'egw_time': |
||
1485 | case 'datetime': |
||
1486 | $p = $param; |
||
1487 | unset($param); |
||
1488 | $param = $p->format('l, Y-m-d H:i:s').' ('.$p->getTimeZone()->getName().')'; |
||
1489 | break; |
||
1490 | case 'array': |
||
1491 | case 'object': |
||
1492 | $param = array2string($param); |
||
1493 | break; |
||
1494 | case 'boolean': |
||
1495 | $param = $param ? 'True' : 'False'; |
||
1496 | break; |
||
1497 | case 'integer': |
||
1498 | if ($param >= mktime(0,0,0,1,1,2000)) $param = adodb_date('Y-m-d H:i:s',$param)." ($param)"; |
||
1499 | break; |
||
1500 | } |
||
1501 | } |
||
1502 | $msg = str_replace('%'.($i-1),$param,$msg); |
||
1503 | } |
||
1504 | error_log($msg); |
||
1505 | if ($backtrace) error_log(function_backtrace(1)); |
||
1506 | } |
||
1507 | |||
1508 | /** |
||
1509 | * Formats one or two dates (range) as long date (full monthname), optionaly with a time |
||
1510 | * |
||
1511 | * @param mixed $_first first date |
||
1512 | * @param mixed $last =0 last date if != 0 (default) |
||
1513 | * @param boolean $display_time =false should a time be displayed too |
||
1514 | * @param boolean $display_day =false should a day-name prefix the date, eg. monday June 20, 2006 |
||
1515 | * @return string with formated date |
||
1516 | */ |
||
1517 | function long_date($_first,$last=0,$display_time=false,$display_day=false) |
||
1518 | { |
||
1519 | $first = $this->date2array($_first); |
||
1520 | if ($last) |
||
1521 | { |
||
1522 | $last = $this->date2array($last); |
||
1523 | } |
||
1524 | $datefmt = $this->common_prefs['dateformat']; |
||
1525 | $timefmt = $this->common_prefs['timeformat'] == 12 ? 'h:i a' : 'H:i'; |
||
1526 | |||
1527 | $month_before_day = strtolower($datefmt[0]) == 'm' || |
||
1528 | strtolower($datefmt[2]) == 'm' && $datefmt[4] == 'd'; |
||
1529 | |||
1530 | if ($display_day) |
||
1531 | { |
||
1532 | $range = lang(adodb_date('l',$first['raw'])).($this->common_prefs['dateformat'][0] != 'd' ? ' ' : ', '); |
||
1533 | } |
||
1534 | for ($i = 0; $i < 5; $i += 2) |
||
1535 | { |
||
1536 | switch($datefmt[$i]) |
||
1537 | { |
||
1538 | case 'd': |
||
1539 | $range .= $first['day'] . ($datefmt[1] == '.' ? '.' : ''); |
||
1540 | if ($first['month'] != $last['month'] || $first['year'] != $last['year']) |
||
1541 | { |
||
1542 | if (!$month_before_day) |
||
1543 | { |
||
1544 | $range .= ' '.lang(strftime('%B',$first['raw'])); |
||
1545 | } |
||
1546 | if ($first['year'] != $last['year'] && $datefmt[0] != 'Y') |
||
1547 | { |
||
1548 | $range .= ($datefmt[0] != 'd' ? ', ' : ' ') . $first['year']; |
||
1549 | } |
||
1550 | if ($display_time) |
||
1551 | { |
||
1552 | $range .= ' '.adodb_date($timefmt,$first['raw']); |
||
1553 | } |
||
1554 | if (!$last) |
||
1555 | { |
||
1556 | return $range; |
||
1557 | } |
||
1558 | $range .= ' - '; |
||
1559 | |||
1560 | if ($first['year'] != $last['year'] && $datefmt[0] == 'Y') |
||
1561 | { |
||
1562 | $range .= $last['year'] . ', '; |
||
1563 | } |
||
1564 | |||
1565 | if ($month_before_day) |
||
1566 | { |
||
1567 | $range .= lang(strftime('%B',$last['raw'])); |
||
1568 | } |
||
1569 | } |
||
1570 | else |
||
1571 | { |
||
1572 | if ($display_time) |
||
1573 | { |
||
1574 | $range .= ' '.adodb_date($timefmt,$first['raw']); |
||
1575 | } |
||
1576 | $range .= ' - '; |
||
1577 | } |
||
1578 | $range .= ' ' . $last['day'] . ($datefmt[1] == '.' ? '.' : ''); |
||
1579 | break; |
||
1580 | case 'm': |
||
1581 | case 'M': |
||
1582 | $range .= ' '.lang(strftime('%B',$month_before_day ? $first['raw'] : $last['raw'])) . ' '; |
||
1583 | break; |
||
1584 | case 'Y': |
||
1585 | if ($datefmt[0] != 'm') |
||
1586 | { |
||
1587 | $range .= ' ' . ($datefmt[0] == 'Y' ? $first['year'].($datefmt[2] == 'd' ? ', ' : ' ') : $last['year'].' '); |
||
1588 | } |
||
1589 | break; |
||
1590 | } |
||
1591 | } |
||
1592 | if ($display_time && $last) |
||
1593 | { |
||
1594 | $range .= ' '.adodb_date($timefmt,$last['raw']); |
||
1595 | } |
||
1596 | if ($datefmt[4] == 'Y' && $datefmt[0] == 'm') |
||
1597 | { |
||
1598 | $range .= ', ' . $last['year']; |
||
1599 | } |
||
1600 | return $range; |
||
1601 | } |
||
1602 | |||
1603 | /** |
||
1604 | * Displays a timespan, eg. $both ? "10:00 - 13:00: 3h" (10:00 am - 1 pm: 3h) : "10:00 3h" (10:00 am 3h) |
||
1605 | * |
||
1606 | * @param int $start_m start time in minutes since 0h |
||
1607 | * @param int $end_m end time in minutes since 0h |
||
1608 | * @param boolean $both =false display the end-time too, duration is always displayed |
||
1609 | */ |
||
1610 | function timespan($start_m,$end_m,$both=false) |
||
1611 | { |
||
1612 | $duration = $end_m - $start_m; |
||
1613 | if ($end_m == 24*60-1) ++$duration; |
||
1614 | $duration = floor($duration/60).lang('h').($duration%60 ? $duration%60 : ''); |
||
1615 | |||
1616 | $timespan = $t = Api\DateTime::to('20000101T'.sprintf('%02d',$start_m/60).sprintf('%02d',$start_m%60).'00', false); |
||
1617 | |||
1618 | if ($both) // end-time too |
||
1619 | { |
||
1620 | $timespan .= ' - '.Api\DateTime::to('20000101T'.sprintf('%02d',$end_m/60).sprintf('%02d',$end_m%60).'00', false); |
||
1621 | // dont double am/pm if they are the same in both times |
||
1622 | if ($this->common_prefs['timeformat'] == 12 && substr($timespan,-2) == substr($t,-2)) |
||
1623 | { |
||
1624 | $timespan = str_replace($t,substr($t,0,-3),$timespan); |
||
1625 | } |
||
1626 | $timespan .= ':'; |
||
1627 | } |
||
1628 | return $timespan . ' ' . $duration; |
||
1629 | } |
||
1630 | |||
1631 | /** |
||
1632 | * Converts a participant into a (readable) user- or resource-name |
||
1633 | * |
||
1634 | * @param string|int $id id of user or resource |
||
1635 | * @param string|boolean $use_type =false type-letter or false |
||
1636 | * @param boolean $append_email =false append email (Name <email>) |
||
1637 | * @return string with name |
||
1638 | */ |
||
1639 | function participant_name($id,$use_type=false, $append_email=false) |
||
1640 | { |
||
1641 | static $id2lid = array(); |
||
1642 | static $id2email = array(); |
||
1643 | |||
1644 | if ($use_type && $use_type != 'u') $id = $use_type.$id; |
||
1645 | |||
1646 | if (!isset($id2lid[$id])) |
||
1647 | { |
||
1648 | if (!is_numeric($id)) |
||
1649 | { |
||
1650 | $id2lid[$id] = '#'.$id; |
||
1651 | if (($info = $this->resource_info($id))) |
||
1652 | { |
||
1653 | $id2lid[$id] = $info['name'] ? $info['name'] : $info['email']; |
||
1654 | if ($info['name']) $id2email[$id] = $info['email']; |
||
1655 | } |
||
1656 | } |
||
1657 | else |
||
1658 | { |
||
1659 | $id2lid[$id] = Api\Accounts::username($id); |
||
1660 | $id2email[$id] = $GLOBALS['egw']->accounts->id2name($id,'account_email'); |
||
1661 | } |
||
1662 | } |
||
1663 | return $id2lid[$id].(($append_email || $id[0] == 'e') && $id2email[$id] ? ' <'.$id2email[$id].'>' : ''); |
||
1664 | } |
||
1665 | |||
1666 | /** |
||
1667 | * Converts participants array of an event into array of (readable) participant-names with status |
||
1668 | * |
||
1669 | * @param array $event event-data |
||
1670 | * @param boolean $long_status =false should the long/verbose status or an icon be use |
||
1671 | * @param boolean $show_group_invitation =false show group-invitations (status == 'G') or not (default) |
||
1672 | * @return array with id / names with status pairs |
||
1673 | */ |
||
1674 | function participants($event,$long_status=false,$show_group_invitation=false) |
||
1675 | { |
||
1676 | //error_log(__METHOD__.__LINE__.array2string($event['participants'])); |
||
1677 | $names = array(); |
||
1678 | foreach((array)$event['participants'] as $id => $status) |
||
1679 | { |
||
1680 | if (!is_string($status)) continue; |
||
1681 | $quantity = $role = null; |
||
1682 | calendar_so::split_status($status,$quantity,$role); |
||
1683 | |||
1684 | if ($status == 'G' && !$show_group_invitation) continue; // dont show group-invitation |
||
1685 | |||
1686 | $lang_status = lang($this->verbose_status[$status]); |
||
1687 | if (!$long_status) |
||
1688 | { |
||
1689 | switch($status[0]) |
||
1690 | { |
||
1691 | case 'A': // accepted |
||
1692 | $status = Api\Html::image('calendar','accepted',$lang_status); |
||
1693 | break; |
||
1694 | case 'R': // rejected |
||
1695 | $status = Api\Html::image('calendar','rejected',$lang_status); |
||
1696 | break; |
||
1697 | case 'T': // tentative |
||
1698 | $status = Api\Html::image('calendar','tentative',$lang_status); |
||
1699 | break; |
||
1700 | case 'U': // no response = unknown |
||
1701 | $status = Api\Html::image('calendar','needs-action',$lang_status); |
||
1702 | break; |
||
1703 | case 'D': // delegated |
||
1704 | $status = Api\Html::image('calendar','forward',$lang_status); |
||
1705 | break; |
||
1706 | case 'G': // group invitation |
||
1707 | // Todo: Image, seems not to be used |
||
1708 | $status = '('.$lang_status.')'; |
||
1709 | break; |
||
1710 | } |
||
1711 | } |
||
1712 | else |
||
1713 | { |
||
1714 | $status = '('.$lang_status.')'; |
||
1715 | } |
||
1716 | $names[$id] = Api\Html::htmlspecialchars($this->participant_name($id)).($quantity > 1 ? ' ('.$quantity.')' : '').' '.$status; |
||
1717 | |||
1718 | // add role, if not a regular participant |
||
1719 | if ($role != 'REQ-PARTICIPANT') |
||
1720 | { |
||
1721 | if (isset($this->roles[$role])) |
||
1722 | { |
||
1723 | $role = lang($this->roles[$role]); |
||
1724 | } |
||
1725 | // allow to use cats as roles (beside regular iCal ones) |
||
1726 | elseif (substr($role,0,6) == 'X-CAT-' && ($cat_id = (int)substr($role,6)) > 0) |
||
1727 | { |
||
1728 | $role = $GLOBALS['egw']->categories->id2name($cat_id); |
||
1729 | } |
||
1730 | else |
||
1731 | { |
||
1732 | $role = lang(str_replace('X-','',$role)); |
||
1733 | } |
||
1734 | $names[$id] .= ' '.$role; |
||
1735 | } |
||
1736 | } |
||
1737 | natcasesort($names); |
||
1738 | |||
1739 | return $names; |
||
1740 | } |
||
1741 | |||
1742 | /** |
||
1743 | * Converts category string of an event into array of (readable) category-names |
||
1744 | * |
||
1745 | * @param string $category cat-id (multiple id's commaseparated) |
||
1746 | * @param int $color color of the category, if multiple cats, the color of the last one with color is returned |
||
1747 | * @return array with id / names |
||
1748 | */ |
||
1749 | function categories($category,&$color) |
||
1750 | { |
||
1751 | static $id2cat = array(); |
||
1752 | $cats = array(); |
||
1753 | $color = 0; |
||
1754 | |||
1755 | foreach(explode(',',$category) as $cat_id) |
||
1756 | { |
||
1757 | if (!$cat_id) continue; |
||
1758 | |||
1759 | if (!isset($id2cat[$cat_id])) |
||
1760 | { |
||
1761 | $id2cat[$cat_id] = Api\Categories::read($cat_id); |
||
1762 | } |
||
1763 | $cat = $id2cat[$cat_id]; |
||
1764 | |||
1765 | $parts = null; |
||
1766 | if (is_array($cat['data']) && !empty($cat['data']['color'])) |
||
1767 | { |
||
1768 | $color = $cat['data']['color']; |
||
1769 | } |
||
1770 | elseif(preg_match('/(#[0-9A-Fa-f]{6})/', $cat['description'], $parts)) |
||
1771 | { |
||
1772 | $color = $parts[1]; |
||
1773 | } |
||
1774 | $cats[$cat_id] = stripslashes($cat['name']); |
||
1775 | } |
||
1776 | return $cats; |
||
1777 | } |
||
1778 | |||
1779 | /** |
||
1780 | * This is called only by list_cals(). It was moved here to remove fatal error in php5 beta4 |
||
1781 | */ |
||
1782 | private static function _list_cals_add($id,&$users,&$groups) |
||
1783 | { |
||
1784 | $name = Api\Accounts::username($id); |
||
1785 | if (!($egw_name = $GLOBALS['egw']->accounts->id2name($id))) |
||
1786 | { |
||
1787 | return; // do not return no longer existing accounts which eg. still mentioned in acl |
||
1788 | } |
||
1789 | if (($type = $GLOBALS['egw']->accounts->get_type($id)) == 'g') |
||
1790 | { |
||
1791 | $arr = &$groups; |
||
1792 | } |
||
1793 | else |
||
1794 | { |
||
1795 | $arr = &$users; |
||
1796 | } |
||
1797 | $arr[$id] = array( |
||
1798 | 'grantor' => $id, |
||
1799 | 'value' => ($type == 'g' ? 'g_' : '') . $id, |
||
1800 | 'name' => $name, |
||
1801 | 'sname' => $egw_name |
||
1802 | ); |
||
1803 | } |
||
1804 | |||
1805 | /** |
||
1806 | * generate list of user- / group-calendars for the selectbox in the header |
||
1807 | * |
||
1808 | * @return array alphabeticaly sorted array with users first and then groups: array('grantor'=>$id,'value'=>['g_'.]$id,'name'=>$name) |
||
1809 | */ |
||
1810 | function list_cals() |
||
1811 | { |
||
1812 | return self::list_calendars($GLOBALS['egw_info']['user']['account_id'], $this->grants); |
||
1813 | } |
||
1814 | |||
1815 | /** |
||
1816 | * generate list of user- / group-calendars or a given user |
||
1817 | * |
||
1818 | * @param int $user account_id of user to generate list for |
||
1819 | * @param array $grants =null calendar grants from user, or null to query them from acl class |
||
1820 | */ |
||
1821 | public static function list_calendars($user, array $grants=null) |
||
1822 | { |
||
1823 | if (is_null($grants)) $grants = $GLOBALS['egw']->acl->get_grants('calendar', true, $user); |
||
1824 | |||
1825 | $users = $groups = array(); |
||
1826 | foreach(array_keys($grants) as $id) |
||
1827 | { |
||
1828 | self::_list_cals_add($id,$users,$groups); |
||
1829 | } |
||
1830 | if (($memberships = $GLOBALS['egw']->accounts->memberships($user, true))) |
||
1831 | { |
||
1832 | foreach($memberships as $group) |
||
1833 | { |
||
1834 | self::_list_cals_add($group,$users,$groups); |
||
1835 | |||
1836 | if (($account_perms = $GLOBALS['egw']->acl->get_ids_for_location($group,Acl::READ,'calendar'))) |
||
1837 | { |
||
1838 | foreach($account_perms as $id) |
||
1839 | { |
||
1840 | self::_list_cals_add($id,$users,$groups); |
||
1841 | } |
||
1842 | } |
||
1843 | } |
||
1844 | } |
||
1845 | usort($users, array(__CLASS__, 'name_cmp')); |
||
1846 | usort($groups, array(__CLASS__, 'name_cmp')); |
||
1847 | |||
1848 | return array_merge($users, $groups); // users first and then groups, both alphabeticaly |
||
1849 | } |
||
1850 | |||
1851 | /** |
||
1852 | * Compare function for sort by value of key 'name' |
||
1853 | * |
||
1854 | * @param array $a |
||
1855 | * @param array $b |
||
1856 | * @return int |
||
1857 | */ |
||
1858 | public static function name_cmp(array $a, array $b) |
||
1859 | { |
||
1860 | return strnatcasecmp($a['name'], $b['name']); |
||
1861 | } |
||
1862 | |||
1863 | /** |
||
1864 | * Convert the recurrence-information of an event, into a human readable string |
||
1865 | * |
||
1866 | * @param array $event |
||
1867 | * @return string |
||
1868 | */ |
||
1869 | function recure2string($event) |
||
1870 | { |
||
1871 | if (!is_array($event)) return false; |
||
1872 | return (string)calendar_rrule::event2rrule($event); |
||
1873 | } |
||
1874 | |||
1875 | /** |
||
1876 | * Read the holidays for a given $year |
||
1877 | * |
||
1878 | * The holidays get cached in the session (performance), so changes in holidays or birthdays do NOT affect a current session!!! |
||
1879 | * |
||
1880 | * @param int $year =0 year, defaults to 0 = current year |
||
1881 | * @return array indexed with Ymd of array of holidays. A holiday is an array with the following fields: |
||
1882 | * name: string |
||
1883 | * title: optional string with description |
||
1884 | * day: numerical day in month |
||
1885 | * month: numerical month |
||
1886 | * occurence: numerical year or 0 for every year |
||
1887 | */ |
||
1888 | function read_holidays($year=0) |
||
1889 | { |
||
1890 | if (!$year) $year = (int) date('Y',$this->now_su); |
||
1891 | |||
1892 | $holidays = calendar_holidays::read( |
||
1893 | !empty($GLOBALS['egw_info']['server']['ical_holiday_url']) ? |
||
1894 | $GLOBALS['egw_info']['server']['ical_holiday_url'] : |
||
1895 | $GLOBALS['egw_info']['user']['preferences']['common']['country'], $year); |
||
1896 | |||
1897 | // search for birthdays |
||
1898 | if ($GLOBALS['egw_info']['server']['hide_birthdays'] != 'yes') |
||
1899 | { |
||
1900 | $contacts = new Api\Contacts(); |
||
1901 | foreach($contacts->get_addressbooks() as $owner => $name) |
||
1902 | { |
||
1903 | $birthdays = $contacts->read_birthdays($owner, $year); |
||
1904 | |||
1905 | // Add them in, being careful not to override any existing |
||
1906 | foreach($birthdays as $date => $bdays) |
||
1907 | { |
||
1908 | if(!array_key_exists($date, $holidays)) |
||
1909 | { |
||
1910 | $holidays[$date] = array(); |
||
1911 | } |
||
1912 | foreach($bdays as $birthday) |
||
1913 | { |
||
1914 | // Skip if name / date are already there - duplicate contacts |
||
1915 | if(in_array($birthday['name'], array_column($holidays[$date], 'name'))) continue; |
||
1916 | $holidays[$date][] = $birthday; |
||
1917 | } |
||
1918 | } |
||
1919 | } |
||
1920 | } |
||
1921 | |||
1922 | if ((int) $this->debug >= 2 || $this->debug == 'read_holidays') |
||
1923 | { |
||
1924 | $this->debug_message('calendar_bo::read_holidays(%1)=%2',true,$year,$holidays); |
||
1925 | } |
||
1926 | return $holidays; |
||
1927 | } |
||
1928 | |||
1929 | /** |
||
1930 | * Get translated calendar event fields, presenting as link title options |
||
1931 | * |
||
1932 | * @param type $event |
||
1933 | * @return array array of selected calendar fields |
||
1934 | */ |
||
1935 | public static function get_link_options ($event = array()) |
||
1936 | { |
||
1937 | unset($event); // not used, but required by function signature |
||
1938 | $options = array ( |
||
1939 | 'end' => lang('End date'), |
||
1940 | 'id' => lang('ID'), |
||
1941 | 'owner' => lang('Owner'), |
||
1942 | 'category' => lang('Category'), |
||
1943 | 'location' => lang('Location'), |
||
1944 | 'creator' => lang('Creator'), |
||
1945 | 'participants' => lang('Participants') |
||
1946 | ); |
||
1947 | return $options; |
||
1948 | } |
||
1949 | |||
1950 | /** |
||
1951 | * get title for an event identified by $event |
||
1952 | * |
||
1953 | * Is called as hook to participate in the linking |
||
1954 | * |
||
1955 | * @param int|array $entry int cal_id or array with event |
||
1956 | * @param string|boolean string with title, null if not found or false if not read perms |
||
1957 | */ |
||
1958 | function link_title($event) |
||
1959 | { |
||
1960 | if (!is_array($event) && strpos($event, '-') !== false) |
||
1961 | { |
||
1962 | list($id, $recur) = explode('-', $event, 2); |
||
1963 | $event = $this->read($id, $recur); |
||
1964 | } |
||
1965 | else if (!is_array($event) && (int) $event > 0) |
||
1966 | { |
||
1967 | $event = $this->read($event); |
||
1968 | } |
||
1969 | if (!is_array($event)) |
||
1970 | { |
||
1971 | return $event; |
||
1972 | } |
||
1973 | $type = explode(',',$this->cal_prefs['link_title']); |
||
1974 | if (is_array($type)) |
||
1975 | { |
||
1976 | foreach ($type as &$val) |
||
1977 | { |
||
1978 | switch ($val) |
||
1979 | { |
||
1980 | case 'end': |
||
1981 | case 'modified': |
||
1982 | $extra_fields [$val] = $this->format_date($event[$val]); |
||
1983 | break; |
||
1984 | case 'participants': |
||
1985 | foreach (array_keys($event[$val]) as $key) |
||
1986 | { |
||
1987 | $extra_fields [$val] = Api\Accounts::id2name($key, 'account_fullname'); |
||
1988 | } |
||
1989 | break; |
||
1990 | case 'modifier': |
||
1991 | case 'creator': |
||
1992 | case 'owner': |
||
1993 | $extra_fields [$val] = Api\Accounts::id2name($event[$val], 'account_fullname'); |
||
1994 | break; |
||
1995 | case 'category': |
||
1996 | $extra_fields [$val] = Api\Categories::id2name($event[$val]); |
||
1997 | break; |
||
1998 | default: |
||
1999 | $extra_fields [] = $event[$val]; |
||
2000 | } |
||
2001 | } |
||
2002 | $str_fields = implode(', ',$extra_fields); |
||
2003 | if (is_array($extra_fields)) return $this->format_date($event['start']) . ': ' . $event['title'] . ($str_fields? ', ' . $str_fields:''); |
||
2004 | } |
||
2005 | return $this->format_date($event['start']) . ': ' . $event['title']; |
||
2006 | } |
||
2007 | |||
2008 | /** |
||
2009 | * query calendar for events matching $pattern |
||
2010 | * |
||
2011 | * Is called as hook to participate in the linking |
||
2012 | * |
||
2013 | * @param string $pattern pattern to search |
||
2014 | * @return array with cal_id - title pairs of the matching entries |
||
2015 | */ |
||
2016 | function link_query($pattern, Array &$options = array()) |
||
2033 | } |
||
2034 | |||
2035 | /** |
||
2036 | * Check access to the file store |
||
2037 | * |
||
2038 | * @param int $id id of entry |
||
2039 | * @param int $check Acl::READ for read and Acl::EDIT for write or delete access |
||
2040 | * @param string $rel_path =null currently not used in calendar |
||
2041 | * @param int $user =null for which user to check, default current user |
||
2042 | * @return boolean true if access is granted or false otherwise |
||
2043 | */ |
||
2044 | function file_access($id,$check,$rel_path,$user=null) |
||
2045 | { |
||
2046 | unset($rel_path); // not used, but required by function signature |
||
2047 | |||
2048 | return $this->check_perms($check,$id,0,'ts',null,$user); |
||
2049 | } |
||
2050 | |||
2051 | /** |
||
2052 | * sets the default prefs, if they are not already set (on a per pref. basis) |
||
2053 | * |
||
2054 | * It sets a flag in the app-session-data to be called only once per session |
||
2055 | */ |
||
2056 | function check_set_default_prefs() |
||
2094 | } |
||
2095 | } |
||
2096 | |||
2097 | /** |
||
2098 | * Get the freebusy URL of a user |
||
2099 | * |
||
2100 | * @param int|string $user account_id or account_lid |
||
2101 | * @param string $pw =null password |
||
2102 | */ |
||
2103 | static function freebusy_url($user='',$pw=null) |
||
2104 | { |
||
2105 | if (is_numeric($user)) $user = $GLOBALS['egw']->accounts->id2name($user); |
||
2106 | |||
2107 | $credentials = ''; |
||
2108 | |||
2109 | if ($pw) |
||
2110 | { |
||
2111 | $credentials = '&password='.urlencode($pw); |
||
2112 | } |
||
2113 | elseif ($GLOBALS['egw_info']['user']['preferences']['calendar']['freebusy'] == 2) |
||
2114 | { |
||
2115 | $credentials = $GLOBALS['egw_info']['user']['account_lid'] |
||
2116 | . ':' . $GLOBALS['egw_info']['user']['passwd']; |
||
2117 | $credentials = '&cred=' . base64_encode($credentials); |
||
2118 | } |
||
2119 | return Api\Framework::getUrl($GLOBALS['egw_info']['server']['webserver_url']). |
||
2120 | '/calendar/freebusy.php/?user='.urlencode($user).$credentials; |
||
2121 | } |
||
2122 | |||
2123 | /** |
||
2124 | * Check if the event is the whole day |
||
2125 | * |
||
2126 | * @param array $event event |
||
2127 | * @return boolean true if whole day event, false othwerwise |
||
2128 | */ |
||
2129 | public static function isWholeDay($event) |
||
2136 | } |
||
2137 | |||
2138 | /** |
||
2139 | * Get the etag for an entry |
||
2140 | * |
||
2141 | * As all update routines (incl. set_status and add/delete alarms) update (series master) modified timestamp, |
||
2142 | * we do NOT need any special handling for series master anymore |
||
2143 | * |
||
2144 | * @param array|int|string $entry array with event or cal_id, or cal_id:recur_date for virtual exceptions |
||
2145 | * @param string &$schedule_tag=null on return schedule-tag (egw_cal.cal_id:egw_cal.cal_etag, no participant modifications!) |
||
2146 | * @return string|boolean string with etag or false |
||
2147 | */ |
||
2148 | function get_etag($entry, &$schedule_tag=null) |
||
2149 | { |
||
2150 | if (!is_array($entry)) |
||
2151 | { |
||
2152 | list($id,$recur_date) = explode(':',$entry); |
||
2153 | $entry = $this->read($id, $recur_date, true, 'server'); |
||
2154 | } |
||
2155 | $etag = $schedule_tag = $entry['id'].':'.$entry['etag']; |
||
2156 | $etag .= ':'.$entry['modified']; |
||
2157 | |||
2158 | //error_log(__METHOD__ . "($entry[id],$client_share_uid_excpetions) entry=".array2string($entry)." --> etag=$etag"); |
||
2159 | return $etag; |
||
2160 | } |
||
2161 | |||
2162 | /** |
||
2163 | * Query ctag for calendar |
||
2164 | * |
||
2165 | * @param int|string|array $user integer user-id or array of user-id's to use, defaults to the current user |
||
2166 | * @param string $filter ='owner' all (not rejected), accepted, unknown, tentative, rejected or hideprivate |
||
2167 | * @param boolean $master_only =false only check recurance master (egw_cal_user.recur_date=0) |
||
2168 | * @return integer |
||
2169 | */ |
||
2170 | public function get_ctag($user, $filter='owner', $master_only=false) |
||
2171 | { |
||
2172 | if ($this->debug > 1) $startime = microtime(true); |
||
2173 | |||
2174 | // resolve users to add memberships for users and members for groups |
||
2175 | $users = $this->resolve_users($user); |
||
2176 | $ctag = $users ? $this->so->get_ctag($users, $filter == 'owner', $master_only) : 0; // no rights, return 0 as ctag (otherwise we get SQL error!) |
||
2177 | |||
2178 | if ($this->debug > 1) error_log(__METHOD__. "($user, '$filter', $master_only) = $ctag = ".date('Y-m-d H:i:s',$ctag)." took ".(microtime(true)-$startime)." secs"); |
||
2179 | return $ctag; |
||
2180 | } |
||
2181 | |||
2182 | /** |
||
2183 | * Hook for infolog to set some extra data and links |
||
2184 | * |
||
2185 | * @param array $data event-array preset by infolog plus |
||
2186 | * @param int $data[id] cal_id |
||
2187 | * @return array with key => value pairs to set in new event and link_app/link_id arrays |
||
2188 | */ |
||
2189 | function infolog_set($data) |
||
2232 | } |
||
2233 | |||
2234 | /** |
||
2235 | * Hook for timesheet to set some extra data and links |
||
2236 | * |
||
2237 | * @param array $data |
||
2238 | * @param int $data[id] cal_id:recurrence |
||
2239 | * @return array with key => value pairs to set in new timesheet and link_app/link_id arrays |
||
2240 | */ |
||
2241 | function timesheet_set($data) |
||
2267 | } |
||
2268 | } |
||
2269 |