grommunio /
grommunio-web
| 1 | <?php |
||
| 2 | require_once(BASE_PATH . 'server/includes/mapi/class.recurrence.php'); |
||
| 3 | |||
| 4 | /** |
||
| 5 | * Appointment Module |
||
| 6 | */ |
||
| 7 | class AppointmentListModule extends ListModule |
||
| 8 | { |
||
| 9 | /** |
||
| 10 | * @var date start interval of view visible |
||
| 11 | */ |
||
| 12 | private $startdate; |
||
| 13 | |||
| 14 | /** |
||
| 15 | * @var date end interval of view visible |
||
| 16 | */ |
||
| 17 | private $enddate; |
||
| 18 | |||
| 19 | /** |
||
| 20 | * Constructor |
||
| 21 | * @param int $id unique id. |
||
| 22 | * @param array $data list of all actions. |
||
| 23 | */ |
||
| 24 | function __construct($id, $data) |
||
| 25 | { |
||
| 26 | parent::__construct($id, $data); |
||
| 27 | |||
| 28 | $this->properties = $GLOBALS["properties"]->getAppointmentListProperties(); |
||
| 29 | |||
| 30 | $this->startdate = false; |
||
| 31 | $this->enddate = false; |
||
| 32 | } |
||
| 33 | |||
| 34 | /** |
||
| 35 | * Creates the notifiers for this module, |
||
| 36 | * and register them to the Bus. |
||
| 37 | */ |
||
| 38 | function createNotifiers() |
||
| 39 | { |
||
| 40 | $entryid = $this->getEntryID(); |
||
| 41 | $GLOBALS["bus"]->registerNotifier('appointmentlistnotifier', $entryid); |
||
| 42 | } |
||
| 43 | |||
| 44 | /** |
||
| 45 | * Executes all the actions in the $data variable. |
||
| 46 | * @return boolean true on success of false on fialure. |
||
| 47 | */ |
||
| 48 | function execute() |
||
| 49 | { |
||
| 50 | foreach($this->data as $actionType => $action) |
||
| 51 | { |
||
| 52 | if(isset($actionType)) { |
||
| 53 | try { |
||
| 54 | $store = $this->getActionStore($action); |
||
| 55 | $entryid = $this->getActionEntryID($action); |
||
| 56 | |||
| 57 | switch($actionType) |
||
| 58 | { |
||
| 59 | case "list": |
||
| 60 | $this->messageList($store, $entryid, $action, $actionType); |
||
| 61 | break; |
||
| 62 | case "search": |
||
| 63 | // @FIXME add functionality to handle private items |
||
| 64 | $this->search($store, $entryid, $action, $actionType); |
||
| 65 | break; |
||
| 66 | case "updatesearch": |
||
| 67 | $this->updatesearch($store, $entryid, $action); |
||
| 68 | break; |
||
| 69 | case "stopsearch": |
||
| 70 | $this->stopSearch($store, $entryid, $action); |
||
| 71 | break; |
||
| 72 | default: |
||
| 73 | $this->handleUnknownActionType($actionType); |
||
| 74 | } |
||
| 75 | } catch (MAPIException $e) { |
||
| 76 | if (isset($action['suppress_exception']) && $action['suppress_exception'] === true) { |
||
| 77 | $e->setNotificationType('console'); |
||
| 78 | } |
||
| 79 | $this->processException($e, $actionType); |
||
| 80 | } |
||
| 81 | } |
||
| 82 | } |
||
| 83 | } |
||
| 84 | |||
| 85 | /** |
||
| 86 | * Function which retrieves a list of calendar items in a calendar folder |
||
| 87 | * @param object $store MAPI Message Store Object |
||
| 88 | * @param string $entryid entryid of the folder |
||
| 89 | * @param array $action the action data, sent by the client |
||
| 90 | * @param string $actionType the action type, sent by the client |
||
| 91 | * @return boolean true on success or false on failure |
||
| 92 | */ |
||
| 93 | function messageList($store, $entryid, $action, $actionType) |
||
| 94 | { |
||
| 95 | if($store && $entryid) { |
||
| 96 | // initialize start and due date with false value so it will not take values from previous request |
||
| 97 | $this->startdate = false; |
||
| 98 | $this->enddate = false; |
||
| 99 | |||
| 100 | if(isset($action["restriction"])) { |
||
| 101 | if(isset($action["restriction"]["startdate"])) { |
||
| 102 | $this->startdate = $action["restriction"]["startdate"]; |
||
| 103 | } |
||
| 104 | |||
| 105 | if(isset($action["restriction"]["duedate"])) { |
||
| 106 | $this->enddate = $action["restriction"]["duedate"]; |
||
| 107 | } |
||
| 108 | } |
||
| 109 | |||
| 110 | if($this->startdate && $this->enddate) { |
||
| 111 | $data = array(); |
||
| 112 | |||
| 113 | if(is_array($entryid) && !empty($entryid)) { |
||
| 114 | $data["item"] = array(); |
||
| 115 | for($index = 0, $index2 = count($entryid); $index < $index2; $index++) { |
||
| 116 | $this->getDelegateFolderInfo($store[$index]); |
||
| 117 | |||
| 118 | // Set the active store in properties class and get the props based on active store. |
||
| 119 | // we need to do this because of multi server env where shared store belongs to the different server. |
||
| 120 | // Here name space is different per server. e.g. There is user A and user B and both are belongs to |
||
| 121 | // different server and user B is shared store of user A because of that user A has 'categories' => -2062020578 |
||
| 122 | // and user B 'categories' => -2062610402, |
||
| 123 | $GLOBALS["properties"]->setActiveStore($store[$index]); |
||
| 124 | $this->properties = $GLOBALS["properties"]->getAppointmentListProperties(); |
||
| 125 | |||
| 126 | $data["item"] = array_merge($data["item"], $this->getCalendarItems($store[$index], $entryid[$index], $this->startdate, $this->enddate)); |
||
| 127 | } |
||
| 128 | } else { |
||
| 129 | $this->getDelegateFolderInfo($store); |
||
| 130 | $data["item"] = $this->getCalendarItems($store, $entryid, $this->startdate, $this->enddate); |
||
| 131 | } |
||
| 132 | |||
| 133 | $this->addActionData("list", $data); |
||
| 134 | $GLOBALS["bus"]->addData($this->getResponseData()); |
||
| 135 | } else { |
||
| 136 | // for list view in calendar as startdate and enddate is passed as false |
||
| 137 | // this will set sorting and paging for items in listview. |
||
| 138 | |||
| 139 | $this->getDelegateFolderInfo($store); |
||
| 140 | |||
| 141 | |||
| 142 | /* This is an override for parent::messageList(), which ignores an array of entryids / stores. |
||
| 143 | * The following block considers this possibly and merges the data of several folders / stores. |
||
| 144 | */ |
||
| 145 | |||
| 146 | $this->searchFolderList = false; // Set to indicate this is not the search result, but a normal folder content |
||
| 147 | |||
| 148 | if($store && $entryid) { |
||
| 149 | // Restriction |
||
| 150 | $this->parseRestriction($action); |
||
| 151 | |||
| 152 | // Sort |
||
| 153 | $this->parseSortOrder($action, null, true); |
||
| 154 | |||
| 155 | $limit = false; |
||
| 156 | if(isset($action['restriction']['limit'])){ |
||
| 157 | $limit = $action['restriction']['limit']; |
||
| 158 | } else { |
||
| 159 | $limit = $GLOBALS['settings']->get('zarafa/v1/main/page_size', 50); |
||
| 160 | } |
||
| 161 | |||
| 162 | $isSearchFolder = isset($action['search_folder_entryid']); |
||
| 163 | $entryid = $isSearchFolder ? hex2bin($action['search_folder_entryid']) : $entryid; |
||
| 164 | |||
| 165 | if(!is_array($entryid) && !is_array($store)) { |
||
| 166 | $entryid = [ $entryid ]; |
||
| 167 | $store = [ $store ]; |
||
| 168 | } |
||
| 169 | |||
| 170 | // Get the table and merge the arrays |
||
| 171 | $data = array(); |
||
| 172 | $items = array(); |
||
| 173 | for($i = 0, $c = count($entryid); $i < $c; $i++) { |
||
| 174 | $newItems = $GLOBALS["operations"]->getTable($store[$i], $entryid[$i], $this->properties, $this->sort, $this->start, $limit, $this->restriction); |
||
| 175 | $items = array_merge($items, $newItems['item']); |
||
| 176 | } |
||
| 177 | |||
| 178 | // If the request come from search folder then no need to send folder information |
||
| 179 | if (!$isSearchFolder) { |
||
| 180 | $contentCount = 0; |
||
| 181 | $contentUnread = 0; |
||
| 182 | |||
| 183 | // For each folder |
||
| 184 | for($i = 0, $c = count($entryid); $i < $c; $i++) { |
||
| 185 | //Open folder |
||
| 186 | $folder = mapi_msgstore_openentry($store[$i], $entryid[$i]); |
||
| 187 | // Obtain some statistics from the folder contents |
||
| 188 | $content = mapi_getprops($folder, array(PR_CONTENT_COUNT, PR_CONTENT_UNREAD)); |
||
| 189 | if (isset($content[PR_CONTENT_COUNT])) { |
||
| 190 | $contentCount += $content[PR_CONTENT_COUNT]; |
||
| 191 | } |
||
| 192 | |||
| 193 | if (isset($content[PR_CONTENT_UNREAD])) { |
||
| 194 | $contentUnread += $content[PR_CONTENT_UNREAD]; |
||
| 195 | } |
||
| 196 | } |
||
| 197 | |||
| 198 | $data["folder"] = array(); |
||
| 199 | $data["folder"]["content_count"] = $contentCount; |
||
| 200 | $data["folder"]["content_unread"] = $contentUnread; |
||
| 201 | } |
||
| 202 | |||
| 203 | $items = $this->filterPrivateItems($items); |
||
| 204 | // unset will remove the value but will not regenerate array keys, so we need to |
||
| 205 | // do it here |
||
| 206 | $data["item"] = $items; |
||
| 207 | |||
| 208 | for($i = 0, $c = count($entryid); $i < $c; $i++) { |
||
| 209 | // Allowing to hook in just before the data sent away to be sent to the client |
||
| 210 | $GLOBALS['PluginManager']->triggerHook('server.module.listmodule.list.after', array( |
||
| 211 | 'moduleObject' =>& $this, |
||
| 212 | 'store' => $store[$i], |
||
| 213 | 'entryid' => $entryid[$i], |
||
| 214 | 'action' => $action, |
||
| 215 | 'data' =>& $data |
||
| 216 | )); |
||
| 217 | } |
||
| 218 | |||
| 219 | $this->addActionData($actionType, $data); |
||
| 220 | $GLOBALS["bus"]->addData($this->getResponseData()); |
||
| 221 | } |
||
| 222 | } |
||
| 223 | } |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * Function to return all Calendar items in a given timeframe. This |
||
| 228 | * function also takes recurring items into account. |
||
| 229 | * @param object $store message store |
||
| 230 | * @param object $calendar folder |
||
| 231 | * @param date $start startdate of the interval |
||
| 232 | * @param date $end enddate of the interval |
||
| 233 | */ |
||
| 234 | function getCalendarItems($store, $entryid, $start, $end) |
||
| 235 | { |
||
| 236 | // Create mapping for restriction used properties which should not be send to the client. |
||
| 237 | $properties = Array( |
||
| 238 | "clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235", |
||
| 239 | "clipend" => "PT_SYSTIME:PSETID_Appointment:0x8236", |
||
| 240 | ); |
||
| 241 | $properties = getPropIdsFromStrings($store, $properties); |
||
| 242 | |||
| 243 | $restriction = |
||
| 244 | // OR |
||
| 245 | // - Either we want all appointments which fall within the given range |
||
| 246 | // - Or we want all recurring items which we manually check if an occurrence |
||
| 247 | // exists which will fall inside the range |
||
| 248 | Array(RES_OR, |
||
| 249 | Array( |
||
| 250 | // OR |
||
| 251 | // - Either we want all properties which fall inside the range (or overlap the range somewhere) |
||
| 252 | // (start < appointmentEnd && due > appointmentStart) |
||
| 253 | // - Or we want all zero-minute appointments which fall at the start of the restriction. |
||
| 254 | // Note that this will effectively exclude any appointments which have an enddate on the restriction |
||
| 255 | // start date, as those are not useful for us. Secondly, we exclude all zero-minute appointments |
||
| 256 | // which fall on the end of the restriction as the restriction is <start, end]. |
||
| 257 | array(RES_OR, |
||
| 258 | array( |
||
| 259 | // AND |
||
| 260 | // - The AppointmentEnd must fall after the start of the range |
||
| 261 | // - The AppointmentStart must fall before the end of the range |
||
| 262 | Array(RES_AND, |
||
| 263 | Array( |
||
| 264 | // start < appointmentEnd |
||
| 265 | Array(RES_PROPERTY, |
||
| 266 | Array(RELOP => RELOP_GT, |
||
| 267 | ULPROPTAG => $this->properties["duedate"], |
||
| 268 | VALUE => $start |
||
| 269 | ) |
||
| 270 | ), |
||
| 271 | // due > appointmentStart |
||
| 272 | Array(RES_PROPERTY, |
||
| 273 | Array(RELOP => RELOP_LT, |
||
| 274 | ULPROPTAG => $this->properties["startdate"], |
||
| 275 | VALUE => $end |
||
| 276 | ) |
||
| 277 | ) |
||
| 278 | ) |
||
| 279 | ), |
||
| 280 | // AND |
||
| 281 | // - The AppointmentStart equals the start of the range |
||
| 282 | // - The AppointmentEnd equals the start of the range |
||
| 283 | // In other words the zero-minute appointments on the start of the range |
||
| 284 | array(RES_AND, |
||
| 285 | array( |
||
| 286 | // appointmentStart == start |
||
| 287 | array(RES_PROPERTY, |
||
| 288 | Array(RELOP => RELOP_EQ, |
||
| 289 | ULPROPTAG => $this->properties["startdate"], |
||
| 290 | VALUE => $start |
||
| 291 | ) |
||
| 292 | ), |
||
| 293 | // appointmentEnd == start |
||
| 294 | array(RES_PROPERTY, |
||
| 295 | Array(RELOP => RELOP_EQ, |
||
| 296 | ULPROPTAG => $this->properties["duedate"], |
||
| 297 | VALUE => $start |
||
| 298 | ) |
||
| 299 | ) |
||
| 300 | ) |
||
| 301 | ), |
||
| 302 | ) |
||
| 303 | ), |
||
| 304 | //OR |
||
| 305 | //(item[isRecurring] == true) |
||
| 306 | Array(RES_AND, |
||
| 307 | array( |
||
| 308 | Array(RES_PROPERTY, |
||
| 309 | Array(RELOP => RELOP_EQ, |
||
| 310 | ULPROPTAG => $this->properties["recurring"], |
||
| 311 | VALUE => true |
||
| 312 | ), |
||
| 313 | ), |
||
| 314 | array(RES_AND, |
||
| 315 | array( |
||
| 316 | array(RES_PROPERTY, |
||
| 317 | Array(RELOP => RELOP_GT, |
||
| 318 | ULPROPTAG => $properties["clipend"], |
||
| 319 | VALUE => $start |
||
| 320 | ) |
||
| 321 | ), |
||
| 322 | array(RES_PROPERTY, |
||
| 323 | Array(RELOP => RELOP_LT, |
||
| 324 | ULPROPTAG => $properties["clipstart"], |
||
| 325 | VALUE => $end |
||
| 326 | ) |
||
| 327 | ) |
||
| 328 | ) |
||
| 329 | ), |
||
| 330 | ) |
||
| 331 | ) |
||
| 332 | ) |
||
| 333 | );// global OR |
||
| 334 | |||
| 335 | $folder = mapi_msgstore_openentry($store, $entryid); |
||
| 336 | $table = mapi_folder_getcontentstable($folder, MAPI_DEFERRED_ERRORS); |
||
| 337 | $calendaritems = mapi_table_queryallrows($table, $this->properties, $restriction); |
||
| 338 | |||
| 339 | return $this->processItems($calendaritems, $store, $entryid, $start, $end); |
||
| 340 | } |
||
| 341 | |||
| 342 | /** |
||
| 343 | * Process calendar items to prepare them for being sent back to the client |
||
| 344 | * @param array $calendaritems array of appointments retrieved from the mapi tablwe |
||
| 345 | * @param object $store message store |
||
| 346 | * @param object $calendar folder |
||
| 347 | * @param date $start startdate of the interval |
||
| 348 | * @param date $end enddate of the interval |
||
| 349 | * @return array $items processed items |
||
| 350 | */ |
||
| 351 | function processItems($calendaritems, $store, $entryid, $start, $end) |
||
| 352 | { |
||
| 353 | $items = Array(); |
||
| 354 | $openedMessages = Array(); |
||
| 355 | $proptags = $GLOBALS["properties"]->getRecurrenceProperties(); |
||
| 356 | |||
| 357 | foreach($calendaritems as $calendaritem) |
||
| 358 | { |
||
| 359 | $item = null; |
||
| 360 | if (isset($calendaritem[$this->properties["recurring"]]) && $calendaritem[$this->properties["recurring"]]) { |
||
| 361 | $recurrence = new Recurrence($store, $calendaritem, $proptags); |
||
| 362 | $recuritems = $recurrence->getItems($start, $end); |
||
| 363 | |||
| 364 | foreach($recuritems as $recuritem) |
||
| 365 | { |
||
| 366 | $item = Conversion::mapMAPI2XML($this->properties, $recuritem); |
||
| 367 | |||
| 368 | // Single occurrences are never recurring |
||
| 369 | $item['props']['recurring'] = false; |
||
| 370 | |||
| 371 | if(isset($recuritem["exception"])) { |
||
| 372 | $item["props"]["exception"] = true; |
||
| 373 | } |
||
| 374 | |||
| 375 | if(isset($recuritem["basedate"])) { |
||
| 376 | $item["props"]["basedate"] = $recuritem["basedate"]; |
||
| 377 | } |
||
| 378 | |||
| 379 | if ( isset($recuritem["exception"]) ){ |
||
| 380 | // Add categories if they are set on the exception |
||
| 381 | // We will create a new Recurrence object with the opened message, |
||
| 382 | // so we can open the attachments. The attachments for this exception |
||
| 383 | // contains the categories property (if changed) |
||
| 384 | $msgEntryid = bin2hex($calendaritem[$this->properties["entryid"]]); |
||
| 385 | if ( !isset($openedMessages[$msgEntryid]) ){ |
||
| 386 | // Open the message and add it to the openedMessages property |
||
| 387 | $message = mapi_msgstore_openentry($store, $calendaritem[$this->properties["entryid"]]); |
||
| 388 | $openedMessages[$msgEntryid] = $message; |
||
| 389 | } else { |
||
| 390 | // This message was already opened |
||
| 391 | $message = $openedMessages[$msgEntryid]; |
||
| 392 | } |
||
| 393 | // Now create a Recurrence object with the mapi message (instead of the message props) |
||
| 394 | // so we can open the attachments |
||
| 395 | $recurrence = new Recurrence($store, $message, $proptags); |
||
| 396 | $exceptionatt = $recurrence->getExceptionAttachment($recuritem["basedate"]); |
||
| 397 | if($exceptionatt) { |
||
| 398 | // Existing exception (open existing item, which includes basedate) |
||
| 399 | $exception = mapi_attach_openobj($exceptionatt, 0); |
||
| 400 | $exceptionProps = $GLOBALS['operations']->getMessageProps($store, $exception, array('categories'=>$this->properties["categories"])); |
||
| 401 | |||
| 402 | if ( isset($exceptionProps['props']['categories']) ){ |
||
| 403 | $item["props"]["categories"] = $exceptionProps['props']['categories']; |
||
| 404 | } |
||
| 405 | } |
||
| 406 | } |
||
| 407 | |||
| 408 | $item = $this->processPrivateItem($item); |
||
| 409 | |||
| 410 | // only add it in response if its not removed by above function |
||
| 411 | if(!empty($item)) { |
||
| 412 | if (empty($item["props"]["commonstart"]) && isset($item["props"]["startdate"])) { |
||
| 413 | $item["props"]["commonstart"] = $item["props"]["startdate"]; |
||
| 414 | } |
||
| 415 | if (empty($item["props"]["commonend"]) && isset($item["props"]["duedate"])) { |
||
| 416 | $item["props"]["commonend"] = $item["props"]["duedate"]; |
||
| 417 | } |
||
| 418 | array_push($items, $item); |
||
| 419 | } |
||
| 420 | } |
||
| 421 | } else { |
||
| 422 | $item = Conversion::mapMAPI2XML($this->properties, $calendaritem); |
||
| 423 | |||
| 424 | $item = $this->processPrivateItem($item); |
||
| 425 | |||
| 426 | // only add it in response if its not removed by above function |
||
| 427 | if(!empty($item)) { |
||
| 428 | if (empty($item["props"]["commonstart"]) && isset($item["props"]["startdate"])) { |
||
| 429 | $item["props"]["commonstart"] = $item["props"]["startdate"]; |
||
| 430 | } |
||
| 431 | if (empty($item["props"]["commonend"]) && isset($item["props"]["duedate"])) { |
||
| 432 | $item["props"]["commonend"] = $item["props"]["duedate"]; |
||
| 433 | } |
||
| 434 | array_push($items,$item); |
||
| 435 | } |
||
| 436 | } |
||
| 437 | } |
||
| 438 | |||
| 439 | usort($items, array("AppointmentListModule", "compareCalendarItems")); |
||
| 440 | |||
| 441 | return $items; |
||
| 442 | } |
||
| 443 | |||
| 444 | /** |
||
| 445 | * Function will be used to process private items in a list response, modules can |
||
| 446 | * can decide what to do with the private items, remove the entire row or just |
||
| 447 | * hide the data. This function will only hide the data of the private appointments. |
||
| 448 | * @param {Object} $item item properties |
||
| 449 | * @return {Object} item properties after processing private items |
||
| 450 | */ |
||
| 451 | function processPrivateItem($item) |
||
| 452 | { |
||
| 453 | if($this->startdate && $this->enddate) { |
||
| 454 | if($this->checkPrivateItem($item)) { |
||
| 455 | $item['props']['subject'] = _('Private Appointment'); |
||
| 456 | $item['props']['location'] = ''; |
||
| 457 | $item['props']['reminder'] = 0; |
||
| 458 | $item['props']['access'] = 0; |
||
| 459 | $item['props']['sent_representing_name'] = ''; |
||
| 460 | $item['props']['sender_name'] = ''; |
||
| 461 | |||
| 462 | return $item; |
||
| 463 | } |
||
| 464 | |||
| 465 | return $item; |
||
| 466 | } else { |
||
| 467 | // if we are in list view then we need to follow normal procedure of other listviews |
||
| 468 | return parent::processPrivateItem($item); |
||
| 469 | } |
||
| 470 | } |
||
| 471 | |||
| 472 | /** |
||
| 473 | * Function will sort items for the month view |
||
| 474 | * small startdate on top. |
||
| 475 | */ |
||
| 476 | public static function compareCalendarItems($a, $b) |
||
| 477 | { |
||
| 478 | $start_a = $a["props"]["startdate"]; |
||
| 479 | $start_b = $b["props"]["startdate"]; |
||
| 480 | |||
| 481 | if ($start_a == $start_b) { |
||
| 482 | return 0; |
||
| 483 | } |
||
| 484 | return ($start_a < $start_b) ? -1 : 1; |
||
| 485 | } |
||
| 486 | } |
||
| 487 | ?> |
||
|
0 ignored issues
–
show
|
|||
| 488 |
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.
A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.