| [ Index ] |
|
Code source de eGroupWare 1.2.106-2 |
1 <?php 2 /**************************************************************************\ 3 * eGroupWare - Calendar's buisness-object - access only * 4 * http://www.egroupware.org * 5 * Written and (c) 2004/5 by Ralf Becker <RalfBecker@outdoor-training.de> * 6 * -------------------------------------------- * 7 * This program is free software; you can redistribute it and/or modify it * 8 * under the terms of the GNU General Public License as published by the * 9 * Free Software Foundation; either version 2 of the License, or (at your * 10 * option) any later version. * 11 \**************************************************************************/ 12 13 /* $Id: class.bocal.inc.php 23152 2006-12-29 08:47:28Z ralfbecker $ */ 14 15 require_once (EGW_INCLUDE_ROOT.'/calendar/inc/class.socal.inc.php'); 16 17 if (!defined('ACL_TYPE_IDENTIFER')) // used to mark ACL-values for the debug_message methode 18 { 19 define('ACL_TYPE_IDENTIFER','***ACL***'); 20 } 21 22 define('HOUR_s',60*60); 23 define('DAY_s',24*HOUR_s); 24 define('WEEK_s',7*DAY_s); 25 26 /** 27 * Class to access all calendar data 28 * 29 * For updating calendar data look at the bocalupdate class, which extends this class. 30 * 31 * The new UI, BO and SO classes have a strikt definition, in which time-zone they operate: 32 * UI only operates in user-time, so there have to be no conversation at all !!! 33 * BO's functions take and return user-time only (!), they convert internaly everything to servertime, because 34 * SO operates only in server-time 35 * 36 * As this BO class deals with dates/times of several types and timezone, each variable should have a postfix 37 * appended, telling with type it is: _s = seconds, _su = secs in user-time, _ss = secs in server-time, _h = hours 38 * 39 * All new BO code (should be true for eGW in general) NEVER use any $_REQUEST ($_POST or $_GET) vars itself. 40 * Nor does it store the state of any UI-elements (eg. cat-id selectbox). All this is the task of the UI class(es) !!! 41 * 42 * All permanent debug messages of the calendar-code should done via the debug-message method of this class !!! 43 * 44 * @package calendar 45 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de> 46 * @copyright (c) 2004/5 by RalfBecker-At-outdoor-training.de 47 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License 48 */ 49 50 class bocal 51 { 52 /** 53 * @var int $debug name of method to debug or level of debug-messages: 54 * False=Off as higher as more messages you get ;-) 55 * 1 = function-calls incl. parameters to general functions like search, read, write, delete 56 * 2 = function-calls to exported helper-functions like check_perms 57 * 4 = function-calls to exported conversation-functions like date2ts, date2array, ... 58 * 5 = function-calls to private functions 59 */ 60 var $debug=false; 61 62 /** 63 * @var int $tz_offset_s offset in secconds between user and server-time, 64 * it need to be add to a server-time to get the user-time or substracted from a user-time to get the server-time 65 */ 66 var $tz_offset_s; 67 68 /** 69 * @var int $now_su timestamp of actual user-time 70 */ 71 var $now_su; 72 73 /** 74 * @var array $cal_prefs calendar-specific prefs 75 */ 76 var $cal_prefs; 77 78 /** 79 * @var array $common_prefs common preferences 80 */ 81 var $common_prefs; 82 83 /** 84 * @var int $user nummerical id of the current user-id 85 */ 86 var $user=0; 87 88 /** 89 * @var array $grants grants of the current user, array with user-id / ored-ACL-rights pairs 90 */ 91 var $grants=array(); 92 93 /** 94 * @var array $verbose_status translated 1-char status values to a verbose name, run through lang() by the constructor 95 */ 96 var $verbose_status = array( 97 'A' => 'Accepted', 98 'R' => 'Rejected', 99 'T' => 'Tentative', 100 'U' => 'No Response', 101 'G' => 'Group invitation', 102 ); 103 /** 104 * @var array recur_types translates MCAL recur-types to verbose labels 105 */ 106 var $recur_types = Array( 107 MCAL_RECUR_NONE => 'None', 108 MCAL_RECUR_DAILY => 'Daily', 109 MCAL_RECUR_WEEKLY => 'Weekly', 110 MCAL_RECUR_MONTHLY_WDAY => 'Monthly (by day)', 111 MCAL_RECUR_MONTHLY_MDAY => 'Monthly (by date)', 112 MCAL_RECUR_YEARLY => 'Yearly' 113 ); 114 /** 115 * @var array recur_days translates MCAL recur-days to verbose labels 116 */ 117 var $recur_days = array( 118 MCAL_M_MONDAY => 'Monday', 119 MCAL_M_TUESDAY => 'Tuesday', 120 MCAL_M_WEDNESDAY => 'Wednesday', 121 MCAL_M_THURSDAY => 'Thursday', 122 MCAL_M_FRIDAY => 'Friday', 123 MCAL_M_SATURDAY => 'Saturday', 124 MCAL_M_SUNDAY => 'Sunday', 125 ); 126 var $so,$datetime; 127 /** 128 * @var array $resources registered scheduling resources of the calendar (gets chached in the session for performance reasons) 129 */ 130 var $resources; 131 /** 132 * @internal 133 * @var array $cached_event here we do some caching to read single events only once 134 */ 135 var $cached_event = array(); 136 var $cached_event_date_format = false; 137 /** 138 * @var array $cached_holidays holidays plus birthdays (gets cached in the session for performance reasons) 139 */ 140 var $cached_holidays; 141 142 /** 143 * Constructor 144 */ 145 function bocal() 146 { 147 if ($this->debug > 0) $this->debug_message('bocal::bocal() started',True,$param); 148 149 foreach(array( 150 'so' => 'calendar.socal', 151 'datetime' => 'phpgwapi.datetime', 152 ) as $my => $app_class) 153 { 154 list(,$class) = explode('.',$app_class); 155 156 if (!is_object($GLOBALS['egw']->$class)) 157 { 158 //echo "<p>calling CreateObject($app_class)</p>\n".str_repeat(' ',4096); 159 $GLOBALS['egw']->$class = CreateObject($app_class); 160 } 161 $this->$my = &$GLOBALS['egw']->$class; 162 } 163 $this->common_prefs =& $GLOBALS['egw_info']['user']['preferences']['common']; 164 $this->cal_prefs =& $GLOBALS['egw_info']['user']['preferences']['calendar']; 165 $this->check_set_default_prefs(); 166 167 $this->tz_offset_s = $this->datetime->tz_offset; 168 169 $this->now_su = time() + $this->tz_offset_s; 170 171 $this->user = $GLOBALS['egw_info']['user']['account_id']; 172 173 $this->grants = $GLOBALS['egw']->acl->get_grants('calendar'); 174 175 foreach($this->verbose_status as $status => $text) 176 { 177 $this->verbose_status[$status] = lang($text); 178 } 179 if (!is_array($this->resources = $GLOBALS['egw']->session->appsession('resources','calendar'))) 180 { 181 $this->resources = array(); 182 foreach($GLOBALS['egw']->hooks->process('calendar_resources') as $app => $data) 183 { 184 if ($data && $data['type']) 185 { 186 $this->resources[$data['type']] = $data + array('app' => $app); 187 } 188 } 189 $GLOBALS['egw']->session->appsession('resources','calendar',$this->resources); 190 } 191 //echo "registered resources="; _debug_array($this->resources); 192 193 $config =& CreateObject('phpgwapi.config','calendar'); 194 $this->config =& $config->read_repository(); 195 unset($config); 196 } 197 198 /** 199 * Add group-members as participants with status 'G' 200 * 201 * @param array $event event-array 202 * @return int number of added participants 203 */ 204 function enum_groups(&$event) 205 { 206 $added = 0; 207 foreach($event['participants'] as $uid => $status) 208 { 209 if (is_numeric($uid) && $GLOBALS['egw']->accounts->get_type($uid) == 'g' && 210 ($members = $GLOBALS['egw']->accounts->member($uid))) 211 { 212 foreach($members as $member) 213 { 214 $member = $member['account_id']; 215 if (!isset($event['participants'][$member])) 216 { 217 $event['participants'][$member] = 'G'; 218 ++$added; 219 } 220 } 221 } 222 } 223 return $added; 224 } 225 226 /** 227 * Searches / lists calendar entries, including repeating ones 228 * 229 * @param params array with the following keys 230 * start date startdate of the search/list, defaults to today 231 * end date enddate of the search/list, defaults to start + one day 232 * users mixed integer user-id or array of user-id's to use, defaults to the current user 233 * cat_id mixed category-id or array of cat-id's, defaults to all if unset, 0 or False 234 * Please note: only a single cat-id, will include all sub-cats (if the common-pref 'cats_no_subs' is False) 235 * filter string space delimited filter-names, atm. 'all' or 'private' 236 * query string pattern so search for, if unset or empty all matching entries are returned (no search) 237 * Please Note: a search never returns repeating events more then once AND does not honor start+end date !!! 238 * dayswise boolean on True it returns an array with YYYYMMDD strings as keys and an array with events 239 * (events spanning multiple days are returned each day again (!)) otherwise it returns one array with 240 * the events (default), not honored in a search ==> always returns an array of events ! 241 * date_format string date-formats: 'ts'=timestamp (default), 'array'=array, or string with format for date 242 * offset boolean/int false (default) to return all entries or integer offset to return only a limited result 243 * enum_recuring boolean if true or not set (default) or daywise is set, each recurence of a recuring events is returned, 244 * otherwise the original recuring event (with the first start- + enddate) is returned 245 * num_rows int number of entries to return, default or if 0, max_entries from the prefs 246 * order column-names plus optional DESC|ASC separted by comma 247 * show_rejected if set rejected invitation are shown only when true, otherwise it depends on the cal-pref or a running query 248 * ignore_acl if set and true no check_perms for a general EGW_ACL_READ grants is performed 249 * enum_groups boolean if set and true, group-members will be added as participants with status 'G' 250 * @return array of events or array with YYYYMMDD strings / array of events pairs (depending on $daywise param) 251 * or false if there are no read-grants from _any_ of the requested users 252 */ 253 function &search($params) 254 { 255 $params_in = $params; 256 257 if (!isset($params['users']) || !$params['users']) 258 { 259 // for a search use all account you have read grants from 260 $params['users'] = $params['query'] ? array_keys($this->grants) : $this->user; 261 } 262 if (!is_array($params['users'])) 263 { 264 $params['users'] = array($params['users']); 265 } 266 // only query calendars of users, we have READ-grants from 267 $users = array(); 268 foreach($params['users'] as $user) 269 { 270 if ($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ,0,$user)) 271 { 272 if (!in_array($user,$users)) // already added? 273 { 274 $users[] = $user; 275 } 276 } 277 elseif ($GLOBALS['egw']->accounts->get_type($user) != 'g') 278 { 279 continue; // for non-groups (eg. users), we stop here if we have no read-rights 280 } 281 // the further code is only for real users 282 if (!is_numeric($user)) continue; 283 284 // for groups we have to include the members 285 if ($GLOBALS['egw']->accounts->get_type($user) == 'g') 286 { 287 $members = $GLOBALS['egw']->accounts->member($user); 288 if (is_array($members)) 289 { 290 foreach($members as $member) 291 { 292 // use only members which gave the user a read-grant 293 if (!in_array($member['account_id'],$users) && 294 ($params['ignore_acl'] || $this->check_perms(EGW_ACL_READ,0,$member['account_id']))) 295 { 296 $users[] = $member['account_id']; 297 } 298 } 299 } 300 } 301 else // for users we have to include all the memberships, to get the group-events 302 { 303 $memberships = $GLOBALS['egw']->accounts->membership($user); 304 if (is_array($memberships)) 305 { 306 foreach($memberships as $group) 307 { 308 if (!in_array($group['account_id'],$users)) 309 { 310 $users[] = $group['account_id']; 311 } 312 } 313 } 314 } 315 } 316 // if we have no grants from the given user(s), we directly return no events / an empty array, 317 // as calling the so-layer without users would give the events of all users (!) 318 if (!count($users)) 319 { 320 return false; 321 } 322 if (isset($params['start'])) $start = $this->date2ts($params['start']); 323 324 if (isset($params['end'])) 325 { 326 $end = $this->date2ts($params['end']); 327 $this->check_move_horizont($end); 328 } 329 $daywise = !isset($params['daywise']) ? False : !!$params['daywise']; 330 $enum_recuring = $daywise || !isset($params['enum_recuring']) || !!$params['enum_recuring']; 331 $cat_id = isset($params['cat_id']) ? $params['cat_id'] : 0; 332 $filter = isset($params['filter']) ? $params['filter'] : 'all'; 333 $offset = isset($params['offset']) && $params['offset'] !== false ? (int) $params['offset'] : false; 334 $show_rejected = isset($params['show_rejected']) ? $params['show_rejected'] : $this->cal_prefs['show_rejected'] || $params['query']; 335 if ($this->debug && ($this->debug > 1 || $this->debug == 'search')) 336 { 337 $this->debug_message('bocal::search(%1) start=%2, end=%3, daywise=%4, cat_id=%5, filter=%6, query=%7, offset=%8, num_rows=%9, order=%10, show_rejected=%11)', 338 True,$params,$start,$end,$daywise,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected); 339 } 340 // date2ts(,true) converts to server time, db2data converts again to user-time 341 $events =& $this->so->search(isset($start) ? $this->date2ts($start,true) : null,isset($end) ? $this->date2ts($end,true) : null, 342 $users,$cat_id,$filter,$params['query'],$offset,(int)$params['num_rows'],$params['order'],$show_rejected); 343 $this->total = $this->so->total; 344 $this->db2data($events,isset($params['date_format']) ? $params['date_format'] : 'ts'); 345 346 // socal::search() returns rejected group-invitations, as only the user not also the group is rejected 347 // as we cant remove them efficiantly in SQL, we kick them out here, but only if just one user is displayed 348 $remove_rejected_by_user = !$show_rejected && count($params['users']) == 1 ? $params['users'][0] : false; 349 //echo "<p align=right>remove_rejected_by_user=$remove_rejected_by_user, show_rejected=$show_rejected, params[users]=".print_r($param['users'])."</p>\n"; 350 foreach($events as $id => $event) 351 { 352 if ($remove_rejected_by_user && $event['participants'][$remove_rejected_by_user] == 'R') 353 { 354 unset($events[$id]); // remove the rejected event 355 continue; 356 } 357 if ($params['enum_groups'] && $this->enum_groups($event)) 358 { 359 $events[$id] = $event; 360 } 361 if (!$this->check_perms(EGW_ACL_READ,$event)) 362 { 363 $this->clear_private_infos($events[$id],$users); 364 } 365 } 366 367 if ($daywise) 368 { 369 if ($this->debug && ($this->debug > 2 || $this->debug == 'search')) 370 { 371 $this->debug_message('socalendar::search daywise sorting from %1 to %2 of %3',False,$start,$end,$events); 372 } 373 // create empty entries for each day in the reported time 374 for($ts = $start; $ts <= $end; $ts += DAY_s) 375 { 376 $daysEvents[$this->date2string($ts)] = array(); 377 } 378 foreach($events as $k => $event) 379 { 380 $e_start = max($this->date2ts($event['start']),$start); 381 // $event['end']['raw']-1 to allow events to end on a full hour/day without the need to enter it as minute=59 382 $e_end = min($this->date2ts($event['end'])-1,$end); 383 384 // add event to each day in the reported time 385 for($ts = $e_start; $ts <= $e_end; $ts += DAY_s) 386 { 387 $daysEvents[$ymd = $this->date2string($ts)][] =& $events[$k]; 388 } 389 if ($ymd != ($last = $this->date2string($e_end))) 390 { 391 $daysEvents[$last][] =& $events[$k]; 392 } 393 } 394 $events =& $daysEvents; 395 if ($this->debug && ($this->debug > 2 || $this->debug == 'search')) 396 { 397 $this->debug_message('socalendar::search daywise events=%1',False,$events); 398 } 399 } 400 elseif(!$enum_recuring) 401 { 402 $recur_ids = array(); 403 foreach($events as $k => $event) 404 { 405 if ($event['recur_type'] != MCAL_RECUR_NONE) 406 { 407 if (!in_array($event['id'],$recur_ids)) 408 { 409 $recur_ids[] = $event['id']; 410 } 411 unset($events[$k]); 412 } 413 } 414 if (count($recur_ids)) 415 { 416 $events = array_merge($this->read($recur_ids,null,false,$params['date_format']),$events); 417 } 418 } 419 if ($this->debug && ($this->debug > 0 || $this->debug == 'search')) 420 { 421 $this->debug_message('bocal::search(%1)=%2',True,$params,$events); 422 } 423 return $events; 424 } 425 426 /** 427 * Clears all non-private info from a privat event 428 * 429 * That function only returns the infos allowed to be viewed by people without EGW_ACL_PRIVATE grants 430 * 431 * @param array &$event 432 * @param array $allowed_participants ids of the allowed participants, eg. the ones the search is over or eg. the owner of the calendar 433 */ 434 function clear_private_infos(&$event,$allowed_participants = array()) 435 { 436 $event = array( 437 'id' => $event['id'], 438 'start' => $event['start'], 439 'end' => $event['end'], 440 'title' => lang('private'), 441 'participants' => array_intersect_key($event['participants'],array_flip($allowed_participants)), 442 'public'=> 0, 443 'category' => $event['category'], // category is visible anyway, eg. by using planner by cat 444 'non_blocking' => $event['non_blocking'], 445 ); 446 } 447 448 /** 449 * check and evtl. move the horizont (maximum date for unlimited recuring events) to a new date 450 * 451 * @internal automaticaly called by search 452 * @param mixed $new_horizont time to set the horizont to (user-time) 453 */ 454 function check_move_horizont($new_horizont) 455 { 456 if ((int) $this->debug >= 2 || $this->debug == 'check_move_horizont') 457 { 458 $this->debug_message('bocal::check_move_horizont(%1) horizont=%2',true,$new_horizont,$this->config['horizont']); 459 } 460 $new_horizont = $this->date2ts($new_horizont,true); // now we are in server-time, where this function operates 461 462 if ($new_horizont <= $this->config['horizont']) // no move necessary 463 { 464 if ($this->debug == 'check_move_horizont') $this->debug_message('bocal::check_move_horizont(%1) horizont=%2 is bigger ==> nothing to do',true,$new_horizont,$this->config['horizont']); 465 return; 466 } 467 if ($new_horizont < time()+31*DAY_s) 468 { 469 $new_horizont = time()+31*DAY_s; 470 } 471 $old_horizont = $this->config['horizont']; 472 $this->config['horizont'] = $new_horizont; 473 474 // create further recurances for all recuring and not yet (at the old horizont) ended events 475 if (($recuring = $this->so->unfinished_recuring($old_horizont))) 476 { 477 foreach($this->read(array_keys($recuring)) as $cal_id => $event) 478 { 479 if ($this->debug == 'check_move_horizont') 480 { 481 $this->debug_message('bocal::check_move_horizont(%1): calling set_recurrences(%2,%3)',true,$new_horizont,$event,$old_horizont); 482 } 483 // insert everything behind max(cal_start), which can be less then $old_horizont because of bugs in the past 484 $this->set_recurrences($event,$recuring[$cal_id]+1+$this->tz_offset_s); // set_recurences operates in user-time! 485 } 486 } 487 // update the horizont 488 $config =& CreateObject('phpgwapi.config','calendar'); 489 $config->save_value('horizont',$this->config['horizont'],'calendar'); 490 491 if ($this->debug == 'check_move_horizont') $this->debug_message('bocal::check_move_horizont(%1) new horizont=%2, exiting',true,$new_horizont,$this->config['horizont']); 492 } 493 494 /** 495 * set all recurances for an event til the defined horizont $this->config['horizont'] 496 * 497 * @param array $event 498 * @param mixed $start=0 minimum start-time for new recurances or !$start = since the start of the event 499 */ 500 function set_recurrences($event,$start=0) 501 { 502 if ($this->debug && ((int) $this->debug >= 2 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont')) 503 { 504 $this->debug_message('bocal::set_recurrences(%1,%2)',true,$event,$start); 505 } 506 // check if the caller gave the participants and if not read them from the DB 507 if (!isset($event['participants'])) 508 { 509 list(,$event_read) = each($this->so->read($event['id'])); 510 $event['participants'] = $event_read['participants']; 511 } 512 if (!$start) $start = $event['start']; 513 514 $events = array(); 515 $this->insert_all_repetitions($event,$start,$this->date2ts($this->config['horizont'],true),$events,null); 516 517 foreach($events as $event) 518 { 519 $this->so->recurrence($event['id'],$this->date2ts($event['start'],true),$this->date2ts($event['end'],true),$event['participants']); 520 } 521 } 522 523 /** 524 * convert data read from the db, eg. convert server to user-time 525 * 526 * @param array &$events array of event-arrays (reference) 527 * @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format 528 */ 529 function db2data(&$events,$date_format='ts') 530 { 531 if (!is_array($events)) echo "<p>bocal::db2data(\$events,$date_format) \$events is no array<br />\n".function_backtrace()."</p>\n"; 532 foreach($events as $id => $event) 533 { 534 // we convert here from the server-time timestamps to user-time and (optional) to a different date-format! 535 foreach(array('start','end','modified','recur_enddate') as $ts) 536 { 537 if (empty($event[$ts])) continue; 538 539 $events[$id][$ts] = $this->date2usertime($event[$ts],$date_format); 540 } 541 // same with the recur exceptions 542 if (isset($event['recur_exception']) && is_array($event['recur_exception'])) 543 { 544 foreach($event['recur_exception'] as $n => $date) 545 { 546 $events[$id]['recur_exception'][$n] = $this->date2usertime($date,$date_format); 547 } 548 } 549 // same with the alarms 550 if (isset($event['alarm']) && is_array($event['alarm'])) 551 { 552 foreach($event['alarm'] as $n => $alarm) 553 { 554 $events[$id]['alarm'][$n]['time'] = $this->date2usertime($alarm['time'],$date_format); 555 } 556 } 557 } 558 } 559 560 /** 561 * convert a date from server to user-time 562 * 563 * @param int $date timestamp in server-time 564 * @param $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in server-time, 'array'=array or string with date-format 565 */ 566 function date2usertime($ts,$date_format='ts') 567 { 568 if (empty($ts)) return $ts; 569 570 switch ($date_format) 571 { 572 case 'ts': 573 return $ts + $this->tz_offset_s; 574 575 case 'server': 576 return $ts; 577 578 case 'array': 579 return $this->date2array((int) $ts,true); 580 581 case 'string': 582 return $this->date2string($ts,true); 583 } 584 return $this->date2string($ts,true,$date_format); 585 } 586 587 /** 588 * Reads a calendar-entry 589 * 590 * @param int/array/string $ids id or array of id's of the entries to read, or string with a single uid 591 * @param mixed $date=null date to specify a single event of a series 592 * @param boolean $ignore_acl should we ignore the acl, default False for a single id, true for multiple id's 593 * @param string $date_format='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in servertime, 'array'=array, or string with date-format 594 * @return boolean/array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found 595 */ 596 function read($ids,$date=null,$ignore_acl=False,$date_format='ts') 597 { 598 if ($date) $date = $this->date2ts($date); 599 600 if ($ignore_acl || is_array($ids) || ($return = $this->check_perms(EGW_ACL_READ,$ids,0,$date_format))) 601 { 602 if (is_array($ids) || !isset($this->cached_event['id']) || $this->cached_event['id'] != $ids || 603 $this->cached_event_date_format != $date_format || 604 !is_null($date) && $this->cached_event['start'] < $date && $this->cached_event['recur_type'] != MCAL_RECUR_NONE) 605 { 606 $events = $this->so->read($ids,$date ? $this->date2ts($date,true) : 0); 607 608 if ($events) 609 { 610 $this->db2data($events,$date_format); 611 612 if (is_array($ids)) 613 { 614 $return =& $events; 615 } 616 else 617 { 618 $this->cached_event = array_shift($events); 619 $this->cached_event_date_format = $date_format; 620 $return =& $this->cached_event; 621 } 622 } 623 } 624 else 625 { 626 $return =& $this->cached_event; 627 } 628 } 629 if ($this->debug && ($this->debug > 1 || $this->debug == 'read')) 630 { 631 $this->debug_message('bocal::read(%1,%2,%3,%4)=%5',True,$ids,$date,$ignore_acl,$date_format,$return); 632 } 633 return $return; 634 } 635 636 /** 637 * Inserts all repetions of $event in the timespan between $start and $end into $events 638 * 639 * As events can have recur-exceptions, only those event-date not having one, should get inserted. 640 * The caller supplies an array with the already inserted exceptions. 641 * 642 * The new entries are just appended to $entries, so $events is no longer sorted by startdate !!! 643 * Unlike the old code the start- and end-date of the events should be adapted here !!! 644 * 645 * TODO: This code is mainly copied from bocalendar and need to be rewritten for the changed algorithm: 646 * We insert now all repetions of one event in one go. It should be possible to calculate the time-difference 647 * of the used recur-type and add all events in one simple for-loop. Daylightsaving changes need to be taken into Account. 648 * 649 * @param $event array repeating event whos repetions should be inserted 650 * @param $start mixed start-date 651 * @param $end mixed end-date 652 * @param $events array where the repetions get inserted 653 * @param $recur_exceptions array with date (in Ymd) as key (and True as values) 654 */ 655 function insert_all_repetitions($event,$start,$end,&$events,$recur_exceptions) 656 { 657 if ((int) $this->debug >= 3 || $this->debug == 'set_recurrences' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions') 658 { 659 $this->debug_message('bocal::insert_all_repitions(%1,%2,%3,&$event,%4)',true,$event,$start,$end,$recur_exceptions); 660 } 661 $start_in = $start; $end_in = $end; 662 663 $start = $this->date2ts($start); 664 $end = $this->date2ts($end); 665 $event_start_ts = $this->date2ts($event['start']); 666 $event_end_ts = $this->date2ts($event['end']); 667 668 if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions')) 669 { 670 $this->debug_message('bocal::insert_all_repetions(%1,start=%2,end=%3,,%4) starting...',True,$event,$start_in,$end_in,$recur_exceptions); 671 } 672 $id = $event['id']; 673 $event_start_arr = $this->date2array($event['start']); 674 // to be able to calculate the repetitions as difference to the start-date, 675 // both need to be calculated without daylight saving: mktime(,,,,,,0) 676 $event_start_daybegin_ts = adodb_mktime(0,0,0,$event_start_arr['month'],$event_start_arr['day'],$event_start_arr['year'],0); 677 678 if($event['recur_enddate']) 679 { 680 $recur_end_ymd = $this->date2string($event['recur_enddate']); 681 } 682 else 683 { 684 $recur_end_ymd = $this->date2string(adodb_mktime(0,0,0,1,1,5+adodb_date('Y'))); // go max. 5 years from now 685 } 686 687 // We only need to compute the intersection between our reported time-span and the live-time of the event 688 // To catch all multiday repeated events (eg. second days), we need to start the length of the even earlier 689 // then our original report-starttime 690 $event_length = $event_end_ts - $event_start_ts; 691 $start_ts = max($event_start_ts,$start-$event_length); 692 // we need to add 26*60*60-1 to the recur_enddate as its hour+minute are 0 693 $end_ts = $event['recur_enddate'] ? min($this->date2ts($event['recur_enddate'])+DAY_s-1,$end) : $end; 694 695 for($ts = $start_ts; $ts < $end_ts; $ts += DAY_s) 696 { 697 $search_date_ymd = (int)$this->date2string($ts); 698 699 $have_exception = !is_null($recur_exceptions) && isset($recur_exceptions[$search_date_ymd]); 700 701 if (!$have_exception) // no execption by an edited event => check the deleted ones 702 { 703 foreach((array)$event['recur_exception'] as $exception_ts) 704 { 705 if (($have_exception = $search_date_ymd == (int)$this->date2string($exception_ts))) break; 706 } 707 } 708 if ($this->debug && ((int) $this->debug > 3 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions')) 709 { 710 $this->debug_message('bocal::insert_all_repetions(...,%1) checking recur_exceptions[%2] and event[recur_exceptions]=%3 ==> %4',False, 711 $recur_exceptions,$search_date_ymd,$event['recur_exception'],$have_exception); 712 } 713 if ($have_exception) 714 { 715 continue; // we already have an exception for that date 716 } 717 $search_date_year = adodb_date('Y',$ts); 718 $search_date_month = adodb_date('m',$ts); 719 $search_date_day = adodb_date('d',$ts); 720 $search_date_dow = adodb_date('w',$ts); 721 // to be able to calculate the repetitions as difference to the start-date, 722 // both need to be calculated without daylight saving: mktime(,,,,,,0) 723 $search_beg_day = adodb_mktime(0,0,0,$search_date_month,$search_date_day,$search_date_year,0); 724 725 if ($search_date_ymd == $event_start_arr['full']) // first occurence 726 { 727 $this->add_adjusted_event($events,$event,$search_date_ymd); 728 continue; 729 } 730 $freq = $event['recur_interval']; 731 $type = $event['recur_type']; 732 switch($type) 733 { 734 case MCAL_RECUR_DAILY: 735 if($this->debug > 4) 736 { 737 echo '<!-- check_repeating_events - MCAL_RECUR_DAILY - '.$id.' -->'."\n"; 738 } 739 if ($freq == 1 && $event['recur_enddate'] && $search_date_ymd <= $recur_end_ymd) 740 { 741 $this->add_adjusted_event($events,$event,$search_date_ymd); 742 } 743 elseif (floor(($search_beg_day - $event_start_daybegin_ts)/DAY_s) % $freq) 744 { 745 continue; 746 } 747 else 748 { 749 $this->add_adjusted_event($events,$event,$search_date_ymd); 750 } 751 break; 752 case MCAL_RECUR_WEEKLY: 753 if (floor(($search_beg_day - $event_start_daybegin_ts)/WEEK_s) % $freq) 754 { 755 continue; 756 } 757 $check = 0; 758 switch($search_date_dow) 759 { 760 case 0: 761 $check = MCAL_M_SUNDAY; 762 break; 763 case 1: 764 $check = MCAL_M_MONDAY; 765 break; 766 case 2: 767 $check = MCAL_M_TUESDAY; 768 break; 769 case 3: 770 $check = MCAL_M_WEDNESDAY; 771 break; 772 case 4: 773 $check = MCAL_M_THURSDAY; 774 break; 775 case 5: 776 $check = MCAL_M_FRIDAY; 777 break; 778 case 6: 779 $check = MCAL_M_SATURDAY; 780 break; 781 } 782 if ($event['recur_data'] & $check) 783 { 784 $this->add_adjusted_event($events,$event,$search_date_ymd); 785 } 786 break; 787 case MCAL_RECUR_MONTHLY_WDAY: 788 if ((($search_date_year - $event_start_arr['year']) * 12 + $search_date_month - $event_start_arr['month']) % $freq) 789 { 790 continue; 791 } 792 793 if (($GLOBALS['egw']->datetime->day_of_week($event_start_arr['year'],$event_start_arr['month'],$event_start_arr['day']) == $GLOBALS['egw']->datetime->day_of_week($search_date_year,$search_date_month,$search_date_day)) && 794 (ceil($event_start_arr['day']/7) == ceil($search_date_day/7))) 795 { 796 $this->add_adjusted_event($events,$event,$search_date_ymd); 797 } 798 break; 799 case MCAL_RECUR_MONTHLY_MDAY: 800 if ((($search_date_year - $event_start_arr['year']) * 12 + $search_date_month - $event_start_arr['month']) % $freq) 801 { 802 continue; 803 } 804 if ($search_date_day == $event_start_arr['day']) 805 { 806 $this->add_adjusted_event($events,$event,$search_date_ymd); 807 } 808 break; 809 case MCAL_RECUR_YEARLY: 810 if (($search_date_year - $event_start_arr['year']) % $freq) 811 { 812 continue; 813 } 814 if (adodb_date('dm',$ts) == adodb_date('dm',$event_start_daybegin_ts)) 815 { 816 $this->add_adjusted_event($events,$event,$search_date_ymd); 817 } 818 break; 819 } // switch(recur-type) 820 } // for($date = ...) 821 if ($this->debug && ((int) $this->debug > 2 || $this->debug == 'insert_all_repetions' || $this->debug == 'check_move_horizont' || $this->debug == 'insert_all_repitions')) 822 { 823 $this->debug_message('bocal::insert_all_repetions(%1,start=%2,end=%3,events,exections=%4) events=%5',True,$event,$start_in,$end_in,$recur_exceptions,$events); 824 } 825 } 826 827 /** 828 * Adds one repetion of $event for $date_ymd to the $events array, after adjusting its start- and end-time 829 * 830 * @param $events array in which the event gets inserted 831 * @param $event array event to insert, it has start- and end-date of the first recurrence, not of $date_ymd 832 * @param $date_ymd int/string of the date of the event 833 */ 834 function add_adjusted_event(&$events,$event,$date_ymd) 835 { 836 $event_in = $event; 837 // calculate the new start- and end-time 838 $length_s = $this->date2ts($event['end']) - $this->date2ts($event['start']); 839 $event_start_arr = $this->date2array($event['start']); 840 841 $date_arr = $this->date2array((string) $date_ymd); 842 $date_arr['hour'] = $event_start_arr['hour']; 843 $date_arr['minute'] = $event_start_arr['minute']; 844 $date_arr['second'] = $event_start_arr['second']; 845 unset($date_arr['raw']); // else date2ts would use it 846 $event['start'] = $this->date2ts($date_arr); 847 $event['end'] = $event['start'] + $length_s; 848 849 $events[] = $event; 850 851 if ($this->debug && ($this->debug > 2 || $this->debug == 'add_adjust_event')) 852 { 853 $this->debug_message('bocal::add_adjust_event(,%1,%2) as %3',True,$event_in,$date_ymd,$event); 854 } 855 } 856 857 /** 858 * Fetch information about a resource 859 * 860 * We do some caching here, as the resource itself might not do it. 861 * 862 * @param string $uid string with one-letter resource-type and numerical resource-id, eg. "r19" 863 * @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 864 */ 865 function resource_info($uid) 866 { 867 static $res_info_cache = array(); 868 869 if (!isset($res_info_cache[$uid])) 870 { 871 list($res_info_cache[$uid]) = $this->resources[$uid{0}]['info'] ? ExecMethod($this->resources[$uid{0}]['info'],substr($uid,1)) : false; 872 } 873 if ($this->debug && ($this->debug > 2 || $this->debug == 'resource_info')) 874 { 875 $this->debug_message('bocal::resource_info(%1) = %2',True,$uid,$res_info_cache[$uid]); 876 } 877 return $res_info_cache[$uid]; 878 } 879 880 /** 881 * Checks if the current user has the necessary ACL rights 882 * 883 * The check is performed on an event or generally on the cal of an other user 884 * 885 * Note: Participating in an event is considered as haveing read-access on that event, 886 * even if you have no general read-grant from that user. 887 * 888 * @param int $needed necessary ACL right: EGW_ACL_{READ|EDIT|DELETE} 889 * @param mixed $event event as array or the event-id or 0 for a general check 890 * @param int $other uid to check (if event==0) or 0 to check against $this->user 891 * @param string $date_format='ts' date-formats: 'ts'=timestamp, 'array'=array, 'string'=iso8601 string for xmlrpc 892 * @return boolean true permission granted, false for permission denied or null if event not found 893 */ 894 function check_perms($needed,$event=0,$other=0,$date_format='ts') 895 { 896 $event_in = $event; 897 if ($other && !is_numeric($other)) 898 { 899 $resource = $this->resource_info($other); 900 901 return $needed & $resource['rights']; 902 } 903 if (is_int($event) && $event == 0) 904 { 905 $owner = $other ? $other : $this->user; 906 } 907 else 908 { 909 if (!is_array($event)) 910 { 911 $event = $this->read($event,null,True,$date_format); // = no ACL check !!! 912 } 913 if (!is_array($event)) 914 { 915 if ($this->xmlrpc) 916 { 917 $GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['not_exist'],$GLOBALS['xmlrpcstr']['not_exist']); 918 } 919 return null; // event not found 920 } 921 $owner = $event['owner']; 922 $private = !$event['public']; 923 } 924 $user = $GLOBALS['egw_info']['user']['account_id']; 925 $grants = $this->grants[$owner]; 926 927 if (is_array($event) && $needed == EGW_ACL_READ) 928 { 929 // Check if the $user is one of the participants or has a read-grant from one of them 930 // in that case he has an implicite READ grant for that event 931 // 932 foreach($event['participants'] as $uid => $accept) 933 { 934 if ($uid == $user || $uid < 0 && in_array($user,$this->accounts_members($uid,true))) 935 { 936 // if we are a participant, we have an implicite READ and PRIVAT grant 937 $grants |= EGW_ACL_READ | EGW_ACL_PRIVATE; 938 break; 939 } 940 elseif ($this->grants[$uid] & EGW_ACL_READ) 941 { 942 // if we have a READ grant from a participant, we dont give an implicit privat grant too 943 $grants |= EGW_ACL_READ; 944 // we cant break here, as we might be a participant too, and would miss the privat grant 945 } 946 elseif (!is_numeric($uid)) 947 { 948 // if we have a resource as participant 949 $resource = $this->resource_info($uid); 950 $grants |= $resource['rights']; 951 } 952 } 953 } 954 955 if ($GLOBALS['egw']->accounts->get_type($owner) == 'g' && $needed == EGW_ACL_ADD) 956 { 957 $access = False; // a group can't be the owner of an event 958 } 959 else 960 { 961 $access = $user == $owner || $grants & $needed && (!$private || $grants & EGW_ACL_PRIVATE); 962 } 963 if ($this->debug && ($this->debug > 2 || $this->debug == 'check_perms')) 964 { 965 $this->debug_message('bocal::check_perms(%1,%2,%3)=%4',True,ACL_TYPE_IDENTIFER.$needed,$event,$other,$access); 966 } 967 return $access; 968 } 969 970 /** 971 * From the accounts class in trunk: Get all members of the group $account_id 972 * 973 * @param int/string $accountid='' numeric account-id or alphanum. account-lid, 974 * default account of the user of this session 975 * @param boolean $just_id=false return just an array of id's and not id => lid pairs, default false 976 * @return array with account_id ($just_id) or account_id => account_lid pairs (!$just_id) 977 */ 978 function accounts_members($account_id,$just_id=false) 979 { 980 $members = array(); 981 foreach($GLOBALS['egw']->accounts->member($account_id) as $data) 982 { 983 $members[$data['account_id']] = $data['account_lid']; 984 } 985 return $just_id ? array_keys($members) : $members; 986 } 987 988 /** 989 * Converts several date-types to a timestamp and optionaly converts user- to server-time 990 * 991 * @param $date mixed date to convert, should be one of the following types 992 * string (!) in form YYYYMMDD or iso8601 YYYY-MM-DDThh:mm:ss or YYYYMMDDThhmmss 993 * int already a timestamp 994 * array with keys 'second', 'minute', 'hour', 'day' or 'mday' (depricated !), 'month' and 'year' 995 * @param $user2server_time boolean conversation between user- and server-time default False == Off 996 */ 997 function date2ts($date,$user2server=False) 998 { 999 $date_in = $date; 1000 1001 1002 switch(gettype($date)) 1003 { 1004 case 'string': // YYYYMMDD or iso8601 YYYY-MM-DDThh:mm:ss string 1005 if (is_numeric($date) && $date > 21000000) 1006 { 1007 $date = (int) $date; // this is already as timestamp 1008 break; 1009 } 1010 // ToDo: evaluate evtl. added timezone 1011 1012 // removing all non-nummerical chars, gives YYYYMMDDhhmmss, independent of the iso8601 format 1013 $date = str_replace(array('-',':','T','Z',' '),'',$date); 1014 $date = array( 1015 'year' => (int) substr($date,0,4), 1016 'month' => (int) substr($date,4,2), 1017 'day' => (int) substr($date,6,2), 1018 'hour' => (int) substr($date,8,2), 1019 'minute' => (int) substr($date,10,2), 1020 'second' => (int) substr($date,12,2), 1021 ); 1022 // fall-through 1023 case 'array': // day, month and year keys 1024 if (isset($date['raw']) && $date['raw']) // we already have a timestamp 1025 { 1026 $date = $date['raw']; 1027 break; 1028 } 1029 if (!isset($date['year']) && isset($date['full'])) 1030 { 1031 $date['year'] = (int) substr($date['full'],0,4); 1032 $date['month'] = (int) substr($date['full'],4,2); 1033 $date['day'] = (int) substr($date['full'],6,2); 1034 } 1035 $date = adodb_mktime((int)$date['hour'],(int)$date['minute'],(int)$date['second'],(int)$date['month'], 1036 (int) (isset($date['day']) ? $date['day'] : $date['mday']),(int)$date['year']); 1037 break; 1038 case 'integer': // already a timestamp 1039 break; 1040 default: // eg. boolean, means now in user-time (!) 1041 $date = $this->now_su; 1042 break; 1043 } 1044 if ($user2server) 1045 { 1046 $date -= $this->tz_offset_s; 1047 } 1048 if ($this->debug && ($this->debug > 3 || $this->debug == 'date2ts')) 1049 { 1050 $this->debug_message('bocal::date2ts(%1,user2server=%2)=%3)',False,$date_in,$user2server,$date); 1051 } 1052 return $date; 1053 } 1054 1055 /** 1056 * Converts a date to an array and optionaly converts server- to user-time 1057 * 1058 * @param $date mixed date to convert 1059 * @param $server2user_time boolean conversation between user- and server-time default False == Off 1060 * @return array with keys 'second', 'minute', 'hour', 'day', 'month', 'year', 'raw' (timestamp) and 'full' (Ymd-string) 1061 */ 1062 function date2array($date,$server2user=False) 1063 { 1064 $date_called = $date; 1065 1066 if (!is_array($date) || count($date) < 8 || $server2user) // do we need a conversation 1067 { 1068 if (!is_int($date)) 1069 { 1070 $date = $this->date2ts($date); 1071 } 1072 if ($server2user) 1073 { 1074 $date += $this->tz_offset_s; 1075 } 1076 $arr = array(); 1077 foreach(array('second'=>'s','minute'=>'i','hour'=>'H','day'=>'d','month'=>'m','year'=>'Y','full'=>'Ymd') as $key => $frmt) 1078 { 1079 $arr[$key] = (int) adodb_date($frmt,$date); 1080 } 1081 $arr['raw'] = $date; 1082 } 1083 if ($this->debug && ($this->debug > 3 || $this->debug == 'date2array')) 1084 { 1085 $this->debug_message('bocal::date2array(%1,server2user=%2)=%3)',False,$date_called,$server2user,$arr); 1086 } 1087 return $arr; 1088 } 1089 1090 /** 1091 * Converts a date as timestamp or array to a date-string and optionaly converts server- to user-time 1092 * 1093 * @param mixed $date integer timestamp or array with ('year','month',..,'second') to convert 1094 * @param boolean $server2user_time conversation between user- and server-time default False == Off, not used if $format ends with \Z 1095 * @param string $format='Ymd' format of the date to return, eg. 'Y-m-d\TH:i:sO' (2005-11-01T15:30:00+0100) 1096 * @return string date formatted according to $format 1097 */ 1098 function date2string($date,$server2user=False,$format='Ymd') 1099 { 1100 $date_in = $date; 1101 1102 if (!$format) $format = 'Ymd'; 1103 1104 if (is_array($date) && isset($date['full']) && !$server2user && $format == 'Ymd') 1105 { 1106 $date = $date['full']; 1107 } 1108 else 1109 { 1110 $date = $this->date2ts($date,False); 1111 1112 // if timezone is requested, we dont need to convert to user-time 1113 if (($tz_used = substr($format,-1)) == 'O' || $tz_used == 'Z') $server2user = false; 1114 1115 if ($server2user && substr($format,-1) ) 1116 { 1117 $date += $this->tz_offset_s; 1118 } 1119 if (substr($format,-2) == '\\Z') // GMT aka. Zulu time 1120 { 1121 $date = adodb_gmdate($format,$date); 1122 } 1123 else 1124 { 1125 $date = adodb_date($format,$date); 1126 } 1127 } 1128 if ($this->debug && ($this->debug > 3 || $this->debug == 'date2string')) 1129 { 1130 $this->debug_message('bocal::date2string(%1,server2user=%2,format=%3)=%4)',False,$date_in,$server2user,$format,$date); 1131 } 1132 return $date; 1133 } 1134 1135 /** 1136 * Formats a date given as timestamp or array 1137 * 1138 * @param mixed $date integer timestamp or array with ('year','month',..,'second') to convert 1139 * @param string/boolean $format='' default common_prefs[dateformat], common_prefs[timeformat], false=time only, true=date only 1140 * @return string the formated date (incl. time) 1141 */ 1142 function format_date($date,$format='') 1143 { 1144 $timeformat = $this->common_prefs['timeformat'] != '12' ? 'H:i' : 'h:i a'; 1145 if ($format === '') // date+time wanted 1146 { 1147 $format = $this->common_prefs['dateformat'].', '.$timeformat; 1148 } 1149 elseif ($format === false) // time wanted 1150 { 1151 $format = $timeformat; 1152 } 1153 elseif ($format === true) 1154 { 1155 $format = $this->common_prefs['dateformat']; 1156 } 1157 return adodb_date($format,$this->date2ts($date,False)); 1158 } 1159 1160 /** 1161 * Gives out a debug-message with certain parameters 1162 * 1163 * All permanent debug-messages in the calendar should be done by this function !!! 1164 * (In future they may be logged or sent as xmlrpc-faults back.) 1165 * 1166 * Permanent debug-message need to make sure NOT to give secret information like passwords !!! 1167 * 1168 * This function do NOT honor the setting of the debug variable, you may use it like 1169 * if ($this->debug > N) $this->debug_message('Error ;-)'); 1170 * 1171 * The parameters get formated depending on their type. ACL-values need a ACL_TYPE_IDENTIFER prefix. 1172 * 1173 * @param $msg string message with parameters/variables like lang(), eg. '%1' 1174 * @param $backtrace include a function-backtrace, default True=On 1175 * should only be set to False=Off, if your code ensures a call with backtrace=On was made before !!! 1176 * @param $param mixed a variable number of parameters, to be inserted in $msg 1177 * arrays get serialized with print_r() ! 1178 */ 1179 function debug_message($msg,$backtrace=True) 1180 { 1181 static $acl2string = array( 1182 0 => 'ACL-UNKNOWN', 1183 EGW_ACL_READ => 'ACL_READ', 1184 EGW_ACL_WRITE => 'ACL_WRITE', 1185 EGW_ACL_ADD => 'ACL_ADD', 1186 EGW_ACL_DELETE => 'ACL_DELETE', 1187 EGW_ACL_PRIVATE => 'ACL_PRIVATE', 1188 ); 1189 for($i = 2; $i < func_num_args(); ++$i) 1190 { 1191 $param = func_get_arg($i); 1192 1193 if (is_null($param)) 1194 { 1195 $param='NULL'; 1196 } 1197 else 1198 { 1199 switch(gettype($param)) 1200 { 1201 case 'string': 1202 if (substr($param,0,strlen(ACL_TYPE_IDENTIFER))== ACL_TYPE_IDENTIFER) 1203 { 1204 $param = (int) substr($param,strlen(ACL_TYPE_IDENTIFER)); 1205 $param = isset($acl2string[$param]) ? $acl2string[$param] : $acl2string[0]; 1206 } 1207 else 1208 { 1209 $param = "'$param'"; 1210 } 1211 break; 1212 case 'array': 1213 case 'object': 1214 list(,$content) = @each($param); 1215 $do_pre = is_array($param) ? count($param) > 6 || is_array($content)&&count($content) : True; 1216 $param = ($do_pre ? '<pre>' : '').print_r($param,True).($do_pre ? '</pre>' : ''); 1217 break; 1218 case 'boolean': 1219 $param = $param ? 'True' : 'False'; 1220 break; 1221 case 'integer': 1222 if ($param >= mktime(0,0,0,1,1,2000)) $param = adodb_date('Y-m-d H:i:s',$param)." ($param)"; 1223 break; 1224 } 1225 } 1226 $msg = str_replace('%'.($i-1),$param,$msg); 1227 } 1228 echo '<p>'.$msg."<br>\n".($backtrace ? 'Backtrace: '.function_backtrace(1)."</p>\n" : '').str_repeat(' ',4096); 1229 } 1230 1231 /** 1232 * Formats one or two dates (range) as long date (full monthname), optionaly with a time 1233 * 1234 * @param mixed $first first date 1235 * @param mixed $last=0 last date if != 0 (default) 1236 * @param boolean $display_time=false should a time be displayed too 1237 * @param boolean $display_day=false should a day-name prefix the date, eg. monday June 20, 2006 1238 * @return string with formated date 1239 */ 1240 function long_date($first,$last=0,$display_time=false,$display_day=false) 1241 { 1242 $first = $this->date2array($first); 1243 if ($last) 1244 { 1245 $last = $this->date2array($last); 1246 } 1247 $datefmt = $this->common_prefs['dateformat']; 1248 $timefmt = $this->common_prefs['timeformat'] == 12 ? 'h:i a' : 'H:i'; 1249 1250 $month_before_day = strtolower($datefmt[0]) == 'm' || 1251 strtolower($datefmt[2]) == 'm' && $datefmt[4] == 'd'; 1252 1253 if ($display_day) 1254 { 1255 $range = lang(adodb_date('l',$first['raw'])).($this->common_prefs['dateformat']{0} != 'd' ? ' ' : ', '); 1256 } 1257 for ($i = 0; $i < 5; $i += 2) 1258 { 1259 switch($datefmt[$i]) 1260 { 1261 case 'd': 1262 $range .= $first['day'] . ($datefmt[1] == '.' ? '.' : ''); 1263 if ($first['month'] != $last['month'] || $first['year'] != $last['year']) 1264 { 1265 if (!$month_before_day) 1266 { 1267 $range .= ' '.lang(strftime('%B',$first['raw'])); 1268 } 1269 if ($first['year'] != $last['year'] && $datefmt[0] != 'Y') 1270 { 1271 $range .= ($datefmt[0] != 'd' ? ', ' : ' ') . $first['year']; 1272 } 1273 if ($display_time) 1274 { 1275 $range .= ' '.adodb_date($timefmt,$first['raw']); 1276 } 1277 if (!$last) 1278 { 1279 return $range; 1280 } 1281 $range .= ' - '; 1282 1283 if ($first['year'] != $last['year'] && $datefmt[0] == 'Y') 1284 { 1285 $range .= $last['year'] . ', '; 1286 } 1287 1288 if ($month_before_day) 1289 { 1290 $range .= lang(strftime('%B',$last['raw'])); 1291 } 1292 } 1293 else 1294 { 1295 if ($display_time) 1296 { 1297 $range .= ' '.adodb_date($timefmt,$first['raw']); 1298 } 1299 $range .= ' - '; 1300 } 1301 $range .= ' ' . $last['day'] . ($datefmt[1] == '.' ? '.' : ''); 1302 break; 1303 case 'm': 1304 case 'M': 1305 $range .= ' '.lang(strftime('%B',$month_before_day ? $first['raw'] : $last['raw'])) . ' '; 1306 break; 1307 case 'Y': 1308 if ($datefmt[0] != 'm') 1309 { 1310 $range .= ' ' . ($datefmt[0] == 'Y' ? $first['year'].($datefmt[2] == 'd' ? ', ' : ' ') : $last['year'].' '); 1311 } 1312 break; 1313 } 1314 } 1315 if ($display_time && $last) 1316 { 1317 $range .= ' '.adodb_date($timefmt,$last['raw']); 1318 } 1319 if ($datefmt[4] == 'Y' && $datefmt[0] == 'm') 1320 { 1321 $range .= ', ' . $last['year']; 1322 } 1323 return $range; 1324 } 1325 1326 /** 1327 * Displays a timespan, eg. $both ? "10:00 - 13:00: 3h" (10:00 am - 1 pm: 3h) : "10:00 3h" (10:00 am 3h) 1328 * 1329 * @param int $start_m start time in minutes since 0h 1330 * @param int $end_m end time in minutes since 0h 1331 * @param boolean $both=false display the end-time too, duration is always displayed 1332 */ 1333 function timespan($start_m,$end_m,$both=false) 1334 { 1335 $duration = $end_m - $start_m; 1336 if ($end_m == 24*60-1) ++$duration; 1337 $duration = floor($duration/60).lang('h').($duration%60 ? $duration%60 : ''); 1338 1339 $timespan = $t = $GLOBALS['egw']->common->formattime(sprintf('%02d',$start_m/60),sprintf('%02d',$start_m%60)); 1340 1341 if ($both) // end-time too 1342 { 1343 $timespan .= ' - '.$GLOBALS['egw']->common->formattime(sprintf('%02d',$end_m/60),sprintf('%02d',$end_m%60)); 1344 // dont double am/pm if they are the same in both times 1345 if ($this->common_prefs['timeformat'] == 12 && substr($timespan,-2) == substr($t,-2)) 1346 { 1347 $timespan = str_replace($t,substr($t,0,-3),$timespan); 1348 } 1349 $timespan .= ':'; 1350 } 1351 return $timespan . ' ' . $duration; 1352 } 1353 1354 /** 1355 * Converts a participant into a (readable) user- or resource-name 1356 * 1357 * @param $id string/int id of user or resource 1358 * @return string with name 1359 */ 1360 function participant_name($id,$use_type=false) 1361 { 1362 static $id2lid = array(); 1363 1364 if ($use_type && $use_type != 'u') $id = $use_type.$id; 1365 1366 if (!isset($id2lid[$id])) 1367 { 1368 if (!is_numeric($id)) 1369 { 1370 $res_info = $this->resource_info($id); 1371 $id2lid[$id] = $res_info && isset($res_info['name']) ? $res_info['name'] : "resource($id)"; 1372 } 1373 else 1374 { 1375 $id2lid[$id] = $GLOBALS['egw']->common->grab_owner_name($id); 1376 } 1377 } 1378 return $id2lid[$id]; 1379 } 1380 1381 /** 1382 * Converts participants array of an event into array of (readable) participant-names with status 1383 * 1384 * @param array $event event-data 1385 * @param boolean $long_status=false should the long/verbose status or only the one letter shortcut be used 1386 * @param boolean $show_group_invitation=false show group-invitations (status == 'G') or not (default) 1387 * @return array with id / names with status pairs 1388 */ 1389 function participants($event,$long_status=False,$show_group_invitation=false) 1390 { 1391 //_debug_array($event); 1392 $names = array(); 1393 foreach($event['participants'] as $id => $status) 1394 { 1395 if ($status == 'G' && !$show_group_invitation) continue; // dont show group-invitation 1396 1397 $status = $this->verbose_status[$status]; 1398 1399 if (!$long_status) 1400 { 1401 $status = substr($status,0,1); 1402 } 1403 $names[$id] = $this->participant_name($id).' ('.$status.')'; 1404 } 1405 return $names; 1406 } 1407 1408 /** 1409 * Converts category string of an event into array of (readable) category-names 1410 * 1411 * @param $category string cat-id (multiple id's commaseparated) 1412 * @param $color int color of the category, if multiple cats, the color of the last one with color is returned 1413 * @return array with id / names 1414 */ 1415 function categories($category,&$color) 1416 { 1417 static $id2cat = array(); 1418 $cats = array(); 1419 $color = 0; 1420 if (!is_object($this->cats)) 1421 { 1422 $this->cats =& CreateObject('phpgwapi.categories','','calendar'); 1423 } 1424 foreach(explode(',',$category) as $cat_id) 1425 { 1426 if (!$cat_id) continue; 1427 1428 if (!isset($id2cat[$cat_id])) 1429 { 1430 list($id2cat[$cat_id]) = $this->cats->return_single($cat_id); 1431 $id2cat[$cat_id]['data'] = unserialize($id2cat[$cat_id]['data']); 1432 } 1433 $cat = $id2cat[$cat_id]; 1434 1435 if ($cat['data']['color'] || preg_match('/(#[0-9A-Fa-f]{6})/',$cat['description'],$parts)) 1436 { 1437 $color = $cat['data']['color'] ? $cat['data']['color'] : $parts[1]; 1438 } 1439 $cats[$cat_id] = stripslashes($cat['name']); 1440 } 1441 return $cats; 1442 } 1443 1444 /* This is called only by list_cals(). It was moved here to remove fatal error in php5 beta4 */ 1445 function _list_cals_add($id,&$users,&$groups) 1446 { 1447 $name = $GLOBALS['egw']->common->grab_owner_name($id); 1448 if (($type = $GLOBALS['egw']->accounts->get_type($id)) == 'g') 1449 { 1450 $arr = &$groups; 1451 } 1452 else 1453 { 1454 $arr = &$users; 1455 } 1456 $arr[$name] = Array( 1457 'grantor' => $id, 1458 'value' => ($type == 'g' ? 'g_' : '') . $id, 1459 'name' => $name 1460 ); 1461 } 1462 1463 /** 1464 * generate list of user- / group-calendars for the selectbox in the header 1465 * @return alphabeticaly sorted array with groups first and then users 1466 */ 1467 function list_cals() 1468 { 1469 $users = $groups = array(); 1470 foreach($this->grants as $id => $rights) 1471 { 1472 $this->_list_cals_add($id,$users,$groups); 1473 } 1474 if ($memberships = $GLOBALS['egw']->accounts->membership($GLOBALS['egw_info']['user']['account_id'])) 1475 { 1476 foreach($memberships as $group_info) 1477 { 1478 $this->_list_cals_add($group_info['account_id'],$users,$groups); 1479 1480 if ($account_perms = $GLOBALS['egw']->acl->get_ids_for_location($group_info['account_id'],EGW_ACL_READ,'calendar')) 1481 { 1482 foreach($account_perms as $id) 1483 { 1484 $this->_list_cals_add($id,$users,$groups); 1485 } 1486 } 1487 } 1488 } 1489 uksort($users,'strnatcasecmp'); 1490 uksort($groups,'strnatcasecmp'); 1491 1492 return $users + $groups; // users first and then groups, both alphabeticaly 1493 } 1494 1495 /** 1496 * Convert the recure-information of an event, into a human readable string 1497 * 1498 * @param array $event 1499 * @return string 1500 */ 1501 function recure2string($event) 1502 { 1503 $str = ''; 1504 // Repeated Events 1505 if($event['recur_type'] != MCAL_RECUR_NONE) 1506 { 1507 $str = lang($this->recur_types[$event['recur_type']]); 1508 1509 $str_extra = array(); 1510 if ($event['recur_enddate']) 1511 { 1512 $str_extra[] = lang('ends').': '.lang($this->format_date($event['recur_enddate'],'l')).', '.$this->long_date($event['recur_enddate']).' '; 1513 } 1514 // only weekly uses the recur-data (days) !!! 1515 if($event['recur_type'] == MCAL_RECUR_WEEKLY) 1516 { 1517 $repeat_days = array(); 1518 foreach ($this->recur_days as $mcal_mask => $dayname) 1519 { 1520 if ($event['recur_data'] & $mcal_mask) 1521 { 1522 $repeat_days[] = lang($dayname); 1523 } 1524 } 1525 if(count($repeat_days)) 1526 { 1527 $str_extra[] = lang('days repeated').': '.implode(', ',$repeat_days); 1528 } 1529 } 1530 if($event['recur_interval']) 1531 { 1532 $str_extra[] = lang('Interval').': '.$event['recur_interval']; 1533 } 1534 1535 if(count($str_extra)) 1536 { 1537 $str .= ' ('.implode(', ',$str_extra).')'; 1538 } 1539 } 1540 return $str; 1541 } 1542 1543 /** 1544 * Read the holidays for a given $year 1545 * 1546 * The holidays get cached in the session (performance), so changes in holidays or birthdays do NOT affect a current session!!! 1547 * 1548 * @param integer $year=0 year, defaults to 0 = current year 1549 * @return array indexed with Ymd of array of holidays. A holiday is an array with the following fields: 1550 * index: numerical unique id 1551 * locale: string, 2-char short for the nation 1552 * name: string 1553 * day: numerical day in month 1554 * month: numerical month 1555 * occurence: numerical year or 0 for every year 1556 * dow: day of week, 0=sunday, .., 6= saturday 1557 * observande_rule: boolean 1558 */ 1559 function read_holidays($year=0) 1560 { 1561 if (!$year) $year = (int) date('Y',$this->now_su); 1562 1563 if (!$this->cached_holidays) // try reading the holidays from the session 1564 { 1565 $this->cached_holidays = $GLOBALS['egw']->session->appsession('holidays','calendar'); 1566 } 1567 if (!isset($this->cached_holidays[$year])) 1568 { 1569 if (!is_object($this->holidays)) 1570 { 1571 $this->holidays =& CreateObject('calendar.boholiday'); 1572 } 1573 $this->holidays->prepare_read_holidays($year); 1574 $this->cached_holidays[$year] = $this->holidays->read_holiday(); 1575 1576 // search for birthdays 1577 $contacts =& CreateObject('phpgwapi.contacts'); 1578 $bdays =& $contacts->read(0,0,array('id','n_family','n_given','n_prefix','n_middle','bday'),'',"bday=!'',n_family=!''",'ASC','bday'); 1579 if ($bdays) 1580 { 1581 // sort by month and day only 1582 usort($bdays,create_function('$a,$b','return (int) $a[\'bday\'] == (int) $b[\'bday\'] ? strcmp($a[\'bday\'],$b[\'bday\']) : (int) $a[\'bday\'] - (int) $b[\'bday\'];')); 1583 foreach($bdays as $pers) 1584 { 1585 list($m,$d,$y) = explode('/',$pers['bday']); 1586 if ($y > $year) continue; // not yet born 1587 $this->cached_holidays[$year][sprintf('%04d%02d%02d',$year,$m,$d)][] = array( 1588 'day' => $d, 1589 'month' => $m, 1590 'occurence' => 0, 1591 'name' => lang('Birthday').' '.($pers['n_given'] ? $pers['n_given'] : $pers['n_prefix']).' '.$pers['n_middle'].' '. 1592 $pers['n_family'].($y ? ' ('.$y.')' : ''), 1593 'birthyear' => $y, // this can be used to identify birthdays from holidays 1594 ); 1595 } 1596 } 1597 // store holidays and birthdays in the session 1598 $this->cached_holidays = $GLOBALS['egw']->session->appsession('holidays','calendar',$this->cached_holidays); 1599 } 1600 if ((int) $this->debug >= 2 || $this->debug == 'read_holidays') 1601 { 1602 $this->debug_message('bocal::read_holidays(%1)=%2',true,$year,$this->cached_holidays[$year]); 1603 } 1604 return $this->cached_holidays[$year]; 1605 } 1606 1607 /** 1608 * get title for an event identified by $event 1609 * 1610 * Is called as hook to participate in the linking 1611 * 1612 * @param int/array $entry int cal_id or array with event 1613 * @param string/boolean string with title, null if not found or false if not read perms 1614 */ 1615 function link_title($event) 1616 { 1617 if (!is_array($event) && (int) $event > 0) 1618 { 1619 $event = $this->read($event); 1620 } 1621 if (!is_array($event)) 1622 { 1623 return $event; 1624 } 1625 return $this->format_date($event['start']) . ': ' . $event['title']; 1626 } 1627 1628 /** 1629 * query calendar for events matching $pattern 1630 * 1631 * Is called as hook to participate in the linking 1632 * 1633 * @param string $pattern pattern to search 1634 * @return array with pm_id - title pairs of the matching entries 1635 */ 1636 function link_query($pattern) 1637 { 1638 $result = array(); 1639 foreach((array) $this->search(array('query' => $pattern)) as $event) 1640 { 1641 $result[$event['id']] = $this->link_title($event); 1642 } 1643 return $result; 1644 } 1645 1646 /** 1647 * Hook called by link-class to include calendar in the appregistry of the linkage 1648 * 1649 * @param array/string $location location and other parameters (not used) 1650 * @return array with method-names 1651 */ 1652 function search_link($location) 1653 { 1654 return array( 1655 'query' => 'calendar.bocal.link_query', 1656 'title' => 'calendar.bocal.link_title', 1657 'view' => array( 1658 'menuaction' => 'calendar.uiforms.view', 1659 ), 1660 'view_id' => 'cal_id', 1661 'view_popup' => '750x400', 1662 'add' => array( 1663 'menuaction' => 'calendar.uiforms.edit', 1664 ), 1665 'add_app' => 'link_app', 1666 'add_id' => 'link_id', 1667 'add_popup' => '750x400', 1668 ); 1669 } 1670 1671 /** 1672 * sets the default prefs, if they are not already set (on a per pref. basis) 1673 * 1674 * It sets a flag in the app-session-data to be called only once per session 1675 */ 1676 function check_set_default_prefs() 1677 { 1678 if ($this->cal_prefs['interval'] && ($set = $GLOBALS['egw']->session->appsession('default_prefs_set','calendar'))) 1679 { 1680 return; 1681 } 1682 $GLOBALS['egw']->session->appsession('default_prefs_set','calendar','set'); 1683 1684 $default_prefs =& $GLOBALS['egw']->preferences->default['calendar']; 1685 1686 if (!($planner_start_with_group = $GLOBALS['egw']->accounts->name2id('Default'))) 1687 { 1688 $planner_start_with_group = '0'; 1689 } 1690 $subject = lang('Calendar Event') . ' - $$action$$: $$startdate$$ $$title$$'."\n"; 1691 $defaults = array( 1692 'defaultcalendar' => 'week', 1693 'mainscreen_showevents' => '0', 1694 'summary' => 'no', 1695 'receive_updates' => 'no', 1696 'update_format' => 'extended', 1697 'notifyAdded' => $subject . lang ('You have a meeting scheduled for %1','$$startdate$$'), 1698 'notifyCanceled' => $subject . lang ('Your meeting scheduled for %1 has been canceled','$$startdate$$'), 1699 'notifyModified' => $subject . lang ('Your meeting that had been scheduled for %1 has been rescheduled to %2','$$olddate$$','$$startdate$$'), 1700 'notifyDisinvited'=> $subject . lang ('You have been disinvited from the meeting at %1','$$startdate$$'), 1701 'notifyResponse' => $subject . lang ('On %1 %2 %3 your meeting request for %4','$$date$$','$$fullname$$','$$action$$','$$startdate$$'), 1702 'notifyAlarm' => lang('Alarm for %1 at %2 in %3','$$title$$','$$startdate$$','$$location$$')."\n".lang ('Here is your requested alarm.'), 1703 'show_rejected' => '0', 1704 'display_status' => '1', 1705 'weekdaystarts' => 'Monday', 1706 'workdaystarts' => '9', 1707 'workdayends' => '17', 1708 'interval' => '30', 1709 'defaultlength' => '60', 1710 'planner_start_with_group' => $planner_start_with_group, 1711 'defaultfilter' => 'all', 1712 'default_private' => '0', 1713 ); 1714 foreach($defaults as $var => $default) 1715 { 1716 if (!isset($default_prefs[$var]) || (string)$default_prefs[$var] == '') 1717 { 1718 $GLOBALS['egw']->preferences->add('calendar',$var,$default,'default'); 1719 $this->cal_prefs[$var] = $default; 1720 $need_save = True; 1721 } 1722 } 1723 if ($need_save) 1724 { 1725 $GLOBALS['egw']->preferences->save_repository(False,'default'); 1726 } 1727 } 1728 1729 /** 1730 * Get the freebusy URL of a user 1731 * 1732 * @param int/string $user account_id or account_lid 1733 * @param string $pw=null password 1734 */ 1735 function freebusy_url($user,$pw=null) 1736 { 1737 if (is_numeric($user)) $user = $GLOBALS['egw']->accounts->id2name($user); 1738 1739 return (!$GLOBALS['egw_info']['server']['webserver_url'] || $GLOBALS['egw_info']['server']['webserver_url']{0} == '/' ? 1740 ($_SERVER['HTTPS'] ? 'https://' : 'http://').$_SERVER['HTTP_HOST'] : ''). 1741 $GLOBALS['egw_info']['server']['webserver_url'].'/calendar/freebusy.php?user='.urlencode($user). 1742 ($pw ? '&password='.urlencode($pw) : ''); 1743 } 1744 } 1745 1746 if (!function_exists('array_intersect_key')) // php5.1 function 1747 { 1748 function array_intersect_key($array1,$array2) 1749 { 1750 $intersection = $keys = array(); 1751 foreach(func_get_args() as $arr) 1752 { 1753 $keys[] = array_keys((array)$arr); 1754 } 1755 foreach(call_user_func_array('array_intersect',$keys) as $key) 1756 { 1757 $intersection[$key] = $array1[$key]; 1758 } 1759 return $intersection; 1760 } 1761 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Sun Feb 25 17:20:01 2007 | par Balluche grâce à PHPXref 0.7 |