source: trunk/zpush/lib/core/asdevice.php @ 7589

Revision 7589, 23.8 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/***********************************************
3* File      :   asdevice.php
4* Project   :   Z-Push
5* Descr     :   The ASDevice holds basic data about a device,
6*               its users and the linked states
7*
8* Created   :   11.04.2011
9*
10* Copyright 2007 - 2012 Zarafa Deutschland GmbH
11*
12* This program is free software: you can redistribute it and/or modify
13* it under the terms of the GNU Affero General Public License, version 3,
14* as published by the Free Software Foundation with the following additional
15* term according to sec. 7:
16*
17* According to sec. 7 of the GNU Affero General Public License, version 3,
18* the terms of the AGPL are supplemented with the following terms:
19*
20* "Zarafa" is a registered trademark of Zarafa B.V.
21* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
22* The licensing of the Program under the AGPL does not imply a trademark license.
23* Therefore any rights, title and interest in our trademarks remain entirely with us.
24*
25* However, if you propagate an unmodified version of the Program you are
26* allowed to use the term "Z-Push" to indicate that you distribute the Program.
27* Furthermore you may use our trademarks where it is necessary to indicate
28* the intended purpose of a product or service provided you use it in accordance
29* with honest practices in industrial or commercial matters.
30* If you want to propagate modified versions of the Program under the name "Z-Push",
31* you may only do so if you have a written permission by Zarafa Deutschland GmbH
32* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
33*
34* This program is distributed in the hope that it will be useful,
35* but WITHOUT ANY WARRANTY; without even the implied warranty of
36* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37* GNU Affero General Public License for more details.
38*
39* You should have received a copy of the GNU Affero General Public License
40* along with this program.  If not, see <http://www.gnu.org/licenses/>.
41*
42* Consult LICENSE file for details
43************************************************/
44
45class ASDevice extends StateObject {
46    const UNDEFINED = -1;
47    // content data
48    const FOLDERUUID = 1;
49    const FOLDERTYPE = 2;
50    const FOLDERSUPPORTEDFIELDS = 3;
51
52    // expected values for not set member variables
53    protected $unsetdata = array(
54                                    'useragenthistory' => array(),
55                                    'hierarchyuuid' => false,
56                                    'contentdata' => array(),
57                                    'wipestatus' => SYNC_PROVISION_RWSTATUS_NA,
58                                    'wiperequestedby' => false,
59                                    'wiperequestedon' => false,
60                                    'wipeactionon' => false,
61                                    'lastupdatetime' => 0,
62                                    'conversationmode' => false,
63                                    'policies' => array(),
64                                    'policykey' => self::UNDEFINED,
65                                    'forcesave' => false,
66                                    'asversion' => false,
67                                    'ignoredmessages' => array(),
68                                    'announcedASversion' => false,
69                                );
70
71    static private $loadedData;
72    protected $newdevice;
73    protected $hierarchyCache;
74    protected $ignoredMessageIds;
75
76    /**
77     * AS Device constructor
78     *
79     * @param string        $devid
80     * @param string        $devicetype
81     * @param string        $getuser
82     * @param string        $useragent
83     *
84     * @access public
85     * @return
86     */
87    public function ASDevice($devid, $devicetype, $getuser, $useragent) {
88        $this->deviceid = $devid;
89        $this->devicetype = $devicetype;
90        list ($this->deviceuser, $this->domain) =  Utils::SplitDomainUser($getuser);
91        $this->useragent = $useragent;
92        $this->firstsynctime = time();
93        $this->newdevice = true;
94        $this->ignoredMessageIds = array();
95    }
96
97    /**
98     * initializes the ASDevice with previousily saved data
99     *
100     * @param mixed     $stateObject        the StateObject containing the device data
101     * @param boolean   $semanticUpdate     indicates if data relevant for all users should be cross checked (e.g. wipe requests)
102     *
103     * @access public
104     * @return
105     */
106    public function SetData($stateObject, $semanticUpdate = true) {
107        if (!($stateObject instanceof StateObject) || !isset($stateObject->devices) || !is_array($stateObject->devices)) return;
108
109        // is information about this device & user available?
110        if (isset($stateObject->devices[$this->deviceuser]) && $stateObject->devices[$this->deviceuser] instanceof ASDevice) {
111            // overwrite local data with data from the saved object
112            $this->SetDataArray($stateObject->devices[$this->deviceuser]->GetDataArray());
113            $this->newdevice = false;
114            ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice data loaded for user: '%s'", $this->deviceuser));
115        }
116
117        // check if RWStatus from another user on same device may require action
118        if ($semanticUpdate && count($stateObject->devices) > 1) {
119            foreach ($stateObject->devices as $user=>$asuserdata) {
120                if ($user == $this->user) continue;
121
122                // another user has a required action on this device
123                if (isset($asuserdata->wipeStatus) && $asuserdata->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) {
124                    ZLog::Write(LOGLEVEL_INFO, sprintf("User '%s' has requested a remote wipe for this device on '%s'", $asuserdata->wipeRequestBy, strftime("%Y-%m-%d %H:%M", $asuserdata->wipeRequstOn)));
125
126                    // reset status to PENDING if wipe was executed before
127                    $this->wipeStatus =  ($asuserdata->wipeStatus & SYNC_PROVISION_RWSTATUS_WIPED)?SYNC_PROVISION_RWSTATUS_PENDING:$asuserdata->wipeStatus;
128                    $this->wipeRequestBy =  $asuserdata->wipeRequestBy;
129                    $this->wipeRequestOn =  $asuserdata->wipeRequestOn;
130                    $this->wipeActionOn = $asuserdata->wipeActionOn;
131                    break;
132                }
133            }
134        }
135
136        self::$loadedData = $stateObject;
137        $this->changed = false;
138    }
139
140    /**
141     * Returns the current AS Device in it's StateObject
142     * If the data was not changed, it returns false (no need to update any data)
143     *
144     * @access public
145     * @return array/boolean
146     */
147    public function GetData() {
148        if (! $this->changed)
149            return false;
150
151        // device was updated
152        $this->lastupdatetime = time();
153        unset($this->ignoredMessageIds);
154
155        if (!isset(self::$loadedData) || !isset(self::$loadedData->devices) || !is_array(self::$loadedData->devices)) {
156            self::$loadedData = new StateObject();
157            $devices = array();
158        }
159        else
160            $devices = self::$loadedData->devices;
161
162        $devices[$this->deviceuser] = $this;
163
164        // check if RWStatus has to be updated so it can be updated for other users on same device
165        if (isset($this->wipeStatus) && $this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) {
166            foreach ($devices as $user=>$asuserdata) {
167                if ($user == $this->deviceuser) continue;
168                if (isset($this->wipeStatus))       $asuserdata->wipeStatus     = $this->wipeStatus;
169                if (isset($this->wipeRequestBy))    $asuserdata->wipeRequestBy  = $this->wipeRequestBy;
170                if (isset($this->wipeRequestOn))    $asuserdata->wipeRequestOn  = $this->wipeRequestOn;
171                if (isset($this->wipeActionOn))     $asuserdata->wipeActionOn   = $this->wipeActionOn;
172                $devices[$user] = $asuserdata;
173
174                ZLog::Write(LOGLEVEL_DEBUG, sprintf("Updated remote wipe status for user '%s' on the same device", $user));
175            }
176        }
177        self::$loadedData->devices = $devices;
178        return self::$loadedData;
179    }
180
181   /**
182     * Removes internal data from the object, so this data can not be exposed
183     *
184     * @access public
185     * @return boolean
186     */
187    public function StripData() {
188        unset($this->changed);
189        unset($this->unsetdata);
190        unset($this->hierarchyCache);
191        unset($this->forceSave);
192        unset($this->newdevice);
193        unset($this->ignoredMessageIds);
194
195        if (isset($this->ignoredmessages) && is_array($this->ignoredmessages)) {
196            $imessages = $this->ignoredmessages;
197            $unserializedMessage = array();
198            foreach ($imessages as $im) {
199                $im->asobject = unserialize($im->asobject);
200                $im->asobject->StripData();
201                $unserializedMessage[] = $im;
202            }
203            $this->ignoredmessages = $unserializedMessage;
204        }
205
206        return true;
207    }
208
209   /**
210     * Indicates if the object was just created
211     *
212     * @access public
213     * @return boolean
214     */
215    public function IsNewDevice() {
216        return (isset($this->newdevice) && $this->newdevice === true);
217    }
218
219
220    /**----------------------------------------------------------------------------------------------------------
221     * Non-standard Getter and Setter
222     */
223
224    /**
225     * Returns the user agent of this device
226     *
227     * @access public
228     * @return string
229     */
230    public function GetDeviceUserAgent() {
231        if (!isset($this->useragent) || !$this->useragent)
232            return "unknown";
233
234        return $this->useragent;
235    }
236
237    /**
238     * Returns the user agent history of this device
239     *
240     * @access public
241     * @return string
242     */
243    public function GetDeviceUserAgentHistory() {
244        return $this->useragentHistory;
245    }
246
247    /**
248     * Sets the useragent of the current request
249     * If this value is alreay available, no update is done
250     *
251     * @param string    $useragent
252     *
253     * @access public
254     * @return boolean
255     */
256    public function SetUserAgent($useragent) {
257        if ($useragent == $this->useragent || $useragent === false || $useragent === Request::UNKNOWN)
258            return true;
259
260        // save the old user agent, if available
261        if ($this->useragent != "") {
262            // [] = changedate, previous user agent
263            $a = $this->useragentHistory;
264            $a[] = array(time(), $this->useragent);
265            $this->useragentHistory = $a;
266        }
267        $this->useragent = $useragent;
268        return true;
269    }
270
271   /**
272     * Sets the current remote wipe status
273     *
274     * @param int       $status
275     * @param string    $requestedBy
276     * @access public
277     * @return int
278     */
279    public function SetWipeStatus($status, $requestedBy = false) {
280        // force saving the updated information if there was a transition between the wiping status
281        if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK && $status > SYNC_PROVISION_RWSTATUS_OK)
282            $this->forceSave = true;
283
284        if ($requestedBy != false) {
285            $this->wipeRequestedBy = $requestedBy;
286            $this->wipeRequestedOn = time();
287        }
288        else {
289            $this->wipeActionOn = time();
290        }
291
292        $this->wipeStatus = $status;
293
294        if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_PENDING)
295            ZLog::Write(LOGLEVEL_INFO, sprintf("ASDevice id '%s' was %s remote wiped on %s. Action requested by user '%s' on %s",
296                                        $this->deviceid, ($this->wipeStatus == SYNC_PROVISION_RWSTATUS_REQUESTED ? "requested to be": "sucessfully"),
297                                        strftime("%Y-%m-%d %H:%M", $this->wipeActionOn), $this->wipeRequestedBy, strftime("%Y-%m-%d %H:%M", $this->wipeRequestedOn)));
298    }
299
300   /**
301     * Sets the deployed policy key
302     *
303     * @param int       $policykey
304     *
305     * @access public
306     * @return
307     */
308    public function SetPolicyKey($policykey) {
309        $this->policykey = $policykey;
310        if ($this->GetWipeStatus() == SYNC_PROVISION_RWSTATUS_NA)
311            $this->wipeStatus = SYNC_PROVISION_RWSTATUS_OK;
312    }
313
314    /**
315     * Adds a messages which was ignored to the device data
316     *
317     * @param StateObject   $ignoredMessage
318     *
319     * @access public
320     * @return boolean
321     */
322    public function AddIgnoredMessage($ignoredMessage) {
323        // we should have all previousily ignored messages in an id array
324        if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
325            foreach($this->ignoredMessages as $oldMessage) {
326                if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
327                    $this->ignoredMessageIds[$oldMessage->folderid] = array();
328                $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
329            }
330        }
331
332        // serialize the AS object - if available
333        if (isset($ignoredMessage->asobject))
334            $ignoredMessage->asobject = serialize($ignoredMessage->asobject);
335
336        // try not to add the same message several times
337        if (isset($ignoredMessage->folderid) && isset($ignoredMessage->id)) {
338            if (!isset($this->ignoredMessageIds[$ignoredMessage->folderid]))
339                $this->ignoredMessageIds[$ignoredMessage->folderid] = array();
340
341            if (in_array($ignoredMessage->id, $this->ignoredMessageIds[$ignoredMessage->folderid]))
342                $this->RemoveIgnoredMessage($ignoredMessage->folderid, $ignoredMessage->id);
343
344            $this->ignoredMessageIds[$ignoredMessage->folderid][] = $ignoredMessage->id;
345            $msges = $this->ignoredMessages;
346            $msges[] = $ignoredMessage;
347            $this->ignoredMessages = $msges;
348
349            return true;
350        }
351        else {
352            $msges = $this->ignoredMessages;
353            $msges[] = $ignoredMessage;
354            $this->ignoredMessages = $msges;
355            ZLog::Write(LOGLEVEL_WARN, "ASDevice->AddIgnoredMessage(): added message has no folder/id");
356            return true;
357        }
358    }
359
360    /**
361     * Removes message in the list of ignored messages
362     *
363     * @param string    $folderid       parent folder id of the message
364     * @param string    $id             message id
365     *
366     * @access public
367     * @return boolean
368     */
369    public function RemoveIgnoredMessage($folderid, $id) {
370        // we should have all previousily ignored messages in an id array
371        if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
372            foreach($this->ignoredMessages as $oldMessage) {
373                if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
374                    $this->ignoredMessageIds[$oldMessage->folderid] = array();
375                $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
376            }
377        }
378
379        $foundMessage = false;
380        // there are ignored messages in that folder
381        if (isset($this->ignoredMessageIds[$folderid])) {
382            // resync of a folder.. we should remove all previousily ignored messages
383            if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
384                $ignored = $this->ignoredMessages;
385                $newMessages = array();
386                foreach ($ignored as $im) {
387                    if ($im->folderid == $folderid) {
388                        if ($id === false || $im->id === $id) {
389                            $foundMessage = true;
390                            if (count($this->ignoredMessageIds[$folderid]) == 1) {
391                                unset($this->ignoredMessageIds[$folderid]);
392                            }
393                            else {
394                                unset($this->ignoredMessageIds[$folderid][array_search($id, $this->ignoredMessageIds[$folderid])]);
395                            }
396                            continue;
397                        }
398                        else
399                            $newMessages[] = $im;
400                    }
401                }
402                $this->ignoredMessages = $newMessages;
403            }
404        }
405
406        return $foundMessage;
407    }
408
409    /**
410     * Indicates if a message is in the list of ignored messages
411     *
412     * @param string    $folderid       parent folder id of the message
413     * @param string    $id             message id
414     *
415     * @access public
416     * @return boolean
417     */
418    public function HasIgnoredMessage($folderid, $id) {
419        // we should have all previousily ignored messages in an id array
420        if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
421            foreach($this->ignoredMessages as $oldMessage) {
422                if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
423                    $this->ignoredMessageIds[$oldMessage->folderid] = array();
424                $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
425            }
426        }
427
428        $foundMessage = false;
429        // there are ignored messages in that folder
430        if (isset($this->ignoredMessageIds[$folderid])) {
431            // resync of a folder.. we should remove all previousily ignored messages
432            if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
433                $foundMessage = true;
434            }
435        }
436
437        return $foundMessage;
438    }
439
440    /**----------------------------------------------------------------------------------------------------------
441     * HierarchyCache and ContentData operations
442     */
443
444    /**
445     * Sets the HierarchyCache
446     * The hierarchydata, can be:
447     *  - false     a new HierarchyCache is initialized
448     *  - array()   new HierarchyCache is initialized and data from GetHierarchy is loaded
449     *  - string    previousely serialized data is loaded
450     *
451     * @param string    $hierarchydata      (opt)
452     *
453     * @access public
454     * @return boolean
455     */
456    public function SetHierarchyCache($hierarchydata = false) {
457        if ($hierarchydata !== false && $hierarchydata instanceof ChangesMemoryWrapper) {
458            $this->hierarchyCache = $hierarchydata;
459            $this->hierarchyCache->CopyOldState();
460        }
461        else
462            $this->hierarchyCache = new ChangesMemoryWrapper();
463
464        if (is_array($hierarchydata))
465            return $this->hierarchyCache->ImportFolders($hierarchydata);
466        return true;
467    }
468
469    /**
470     * Returns serialized data of the HierarchyCache
471     *
472     * @access public
473     * @return string
474     */
475    public function GetHierarchyCacheData() {
476        if (isset($this->hierarchyCache))
477            return $this->hierarchyCache;
478
479        ZLog::Write(LOGLEVEL_WARN, "ASDevice->GetHierarchyCacheData() has no data! HierarchyCache probably never initialized.");
480        return false;
481    }
482
483   /**
484     * Returns the HierarchyCache Object
485     *
486     * @access public
487     * @return object   HierarchyCache
488     */
489    public function GetHierarchyCache() {
490        if (!isset($this->hierarchyCache))
491            $this->SetHierarchyCache();
492
493        ZLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetHierarchyCache(): ". $this->hierarchyCache->GetStat());
494        return $this->hierarchyCache;
495    }
496
497   /**
498     * Returns all known folderids
499     *
500     * @access public
501     * @return array
502     */
503    public function GetAllFolderIds() {
504        if (isset($this->contentData) && is_array($this->contentData))
505            return array_keys($this->contentData);
506        return array();
507    }
508
509   /**
510     * Returns a linked UUID for a folder id
511     *
512     * @param string        $folderid       (opt) if not set, Hierarchy UUID is returned
513     *
514     * @access public
515     * @return string
516     */
517    public function GetFolderUUID($folderid = false) {
518        if ($folderid === false)
519            return (isset($this->hierarchyUuid) && $this->hierarchyUuid !== self::UNDEFINED) ? $this->hierarchyUuid : false;
520        else if (isset($this->contentData) && isset($this->contentData[$folderid]) && isset($this->contentData[$folderid][self::FOLDERUUID]))
521            return $this->contentData[$folderid][self::FOLDERUUID];
522        return false;
523    }
524
525   /**
526     * Link a UUID to a folder id
527     * If a boolean false UUID is sent, the relation is removed
528     *
529     * @param string        $uuid
530     * @param string        $folderid       (opt) if not set Hierarchy UUID is linked
531     *
532     * @access public
533     * @return boolean
534     */
535    public function SetFolderUUID($uuid, $folderid = false) {
536        if ($folderid === false) {
537            $this->hierarchyUuid = $uuid;
538            // when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages
539            if ($folderid === false) {
540                $this->contentData = array();
541                $this->ignoredMessageIds = array();
542                $this->ignoredMessages = array();
543            }
544        }
545        else {
546
547            $contentData = $this->contentData;
548            if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
549                $contentData[$folderid] = array();
550
551            // check if the foldertype is set. This has to be available at this point, as generated during the first HierarchySync
552            if (!isset($contentData[$folderid][self::FOLDERTYPE]))
553                return false;
554
555            if ($uuid)
556                $contentData[$folderid][self::FOLDERUUID] = $uuid;
557            else
558                $contentData[$folderid][self::FOLDERUUID] = false;
559
560            $this->contentData = $contentData;
561        }
562    }
563
564   /**
565     * Returns a foldertype for a folder already known to the mobile
566     *
567     * @param string        $folderid
568     *
569     * @access public
570     * @return int/boolean  returns false if the type is not set
571     */
572    public function GetFolderType($folderid) {
573        if (isset($this->contentData) && isset($this->contentData[$folderid]) &&
574            isset($this->contentData[$folderid][self::FOLDERTYPE]) )
575
576            return $this->contentData[$folderid][self::FOLDERTYPE];
577        return false;
578    }
579
580   /**
581     * Sets the foldertype of a folder id
582     *
583     * @param string        $uuid
584     * @param string        $folderid       (opt) if not set Hierarchy UUID is linked
585     *
586     * @access public
587     * @return boolean      true if the type was set or updated
588     */
589    public function SetFolderType($folderid, $foldertype) {
590        $contentData = $this->contentData;
591
592        if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
593            $contentData[$folderid] = array();
594        if (!isset($contentData[$folderid][self::FOLDERTYPE]) || $contentData[$folderid][self::FOLDERTYPE] != $foldertype ) {
595            $contentData[$folderid][self::FOLDERTYPE] = $foldertype;
596            $this->contentData = $contentData;
597            return true;
598        }
599        return false;
600    }
601
602    /**
603     * Gets the supported fields transmitted previousely by the device
604     * for a certain folder
605     *
606     * @param string    $folderid
607     *
608     * @access public
609     * @return array/boolean        false means no supportedFields are available
610     */
611    public function GetSupportedFields($folderid) {
612        if (isset($this->contentData) && isset($this->contentData[$folderid]) &&
613            isset($this->contentData[$folderid][self::FOLDERUUID]) && $this->contentData[$folderid][self::FOLDERUUID] !== false &&
614            isset($this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS]) )
615
616            return $this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS];
617
618        return false;
619    }
620
621    /**
622     * Sets the set of supported fields transmitted by the device for a certain folder
623     *
624     * @param string    $folderid
625     * @param array     $fieldlist          supported fields
626     *
627     * @access public
628     * @return boolean
629     */
630    public function SetSupportedFields($folderid, $fieldlist) {
631        $contentData = $this->contentData;
632        if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
633            $contentData[$folderid] = array();
634
635        $contentData[$folderid][self::FOLDERSUPPORTEDFIELDS] = $fieldlist;
636        $this->contentData = $contentData;
637        return true;
638    }
639}
640
641?>
Note: See TracBrowser for help on using the repository browser.