| 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 |