Overview

Packages

  • ActiveSync
  • None

Classes

  • Horde_ActiveSync
  • Horde_ActiveSync_Connector_Exporter
  • Horde_ActiveSync_Connector_Importer
  • Horde_ActiveSync_Driver_Base
  • Horde_ActiveSync_Exception
  • Horde_ActiveSync_Exception_InvalidRequest
  • Horde_ActiveSync_Exception_StateGone
  • Horde_ActiveSync_Message_Base
  • Horde_ActiveSync_Request_Base
  • Horde_ActiveSync_Request_FolderCreate
  • Horde_ActiveSync_Request_FolderSync
  • Horde_ActiveSync_Request_GetHierarchy
  • Horde_ActiveSync_Request_GetItemEstimate
  • Horde_ActiveSync_Request_MeetingResponse
  • Horde_ActiveSync_Request_MoveItems
  • Horde_ActiveSync_Request_Notify
  • Horde_ActiveSync_Request_Ping
  • Horde_ActiveSync_Request_Provision
  • Horde_ActiveSync_Request_Search
  • Horde_ActiveSync_Request_SendMail
  • Horde_ActiveSync_Request_SmartForward
  • Horde_ActiveSync_Request_SmartReply
  • Horde_ActiveSync_Request_Sync
  • Horde_ActiveSync_State_File
  • Horde_ActiveSync_Sync
  • Horde_ActiveSync_Wbxml
  • Horde_ActiveSync_Wbxml_Decoder
  • Horde_ActiveSync_Wbxml_Encoder
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * File based state management. Some code based on the Z-Push project's
  4:  * diff backend, original copyright notice appears below.
  5:  *
  6:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * @author Michael J. Rubinsky <mrubinsk@horde.org>
  9:  * @package ActiveSync
 10:  */
 11: /**
 12:  * File      :   statemachine.php
 13:  * Project   :   Z-Push
 14:  * Descr     :   This class handles state requests;
 15:  *               Each differential mechanism can
 16:  *               store its own state information,
 17:  *               which is stored through the
 18:  *               state machine. SyncKey's are
 19:  *               of the  form {UUID}N, in which
 20:  *               UUID is allocated during the
 21:  *               first sync, and N is incremented
 22:  *               for each request to 'getNewSyncKey'.
 23:  *               A sync state is simple an opaque
 24:  *               string value that can differ
 25:  *               for each backend used - normally
 26:  *               a list of items as the backend has
 27:  *               sent them to the PIM. The backend
 28:  *               can then use this backend
 29:  *               information to compute the increments
 30:  *               with current data.
 31:  *
 32:  *               Old sync states are not deleted
 33:  *               until a sync state is requested.
 34:  *               At that moment, the PIM is
 35:  *               apparently requesting an update
 36:  *               since sync key X, so any sync
 37:  *               states before X are already on
 38:  *               the PIM, and can therefore be
 39:  *               removed. This algorithm is
 40:  *                automatically enforced by the
 41:  *                StateMachine class.
 42:  *
 43:  *
 44:  * Created   :   01.10.2007
 45:  *
 46:  * � Zarafa Deutschland GmbH, www.zarafaserver.de
 47:  * This file is distributed under GPL-2.0.
 48:  * Consult COPYING file for details
 49:  */
 50: class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base
 51: {
 52:     /**
 53:      * Directory to store state files
 54:      *
 55:      * @var stirng
 56:      */
 57:     private $_stateDir;
 58: 
 59:     /**
 60:      * Cache results of file_exists for user's state directory
 61:      *
 62:      * @var boolean
 63:      */
 64:     private $_haveStateDirectory;
 65: 
 66:     /**
 67:      * Const'r
 68:      *
 69:      * @param array  $params   Must contain 'directory' entry
 70:      *
 71:      * @return Horde_ActiveSync_StateMachine_File
 72:      */
 73:     public function __construct($params = array())
 74:     {
 75:         parent::__construct($params);
 76: 
 77:         if (empty($this->_params['directory'])) {
 78:             throw new InvalidArgumentException('Missing required "stateDir" parameter.');
 79:         }
 80: 
 81:         $this->_stateDir = $this->_params['directory'];
 82:     }
 83: 
 84:     /**
 85:      * Load the sync state
 86:      *
 87:      * @param string $syncKey   The synckey
 88:      * @prarm string $type      Treat loaded state as this type of state.
 89:      *
 90:      * @return void
 91:      * @throws Horde_ActiveSync_Exception
 92:      */
 93:     public function loadState($syncKey, $type = null, $id = '')
 94:     {
 95:         /* Ensure state directory is present */
 96:         $this->_ensureUserDirectory();
 97: 
 98:         /* Prime the state cache for the first sync */
 99:         if (empty($syncKey)) {
100:             $this->_stateCache = array();
101:             return;
102:         }
103: 
104:         /* Check if synckey is allowed */
105:         if (!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) {
106:             throw new Horde_ActiveSync_Exception('Invalid sync key');
107:         }
108: 
109:         /* Cleanup all older syncstates */
110:         $this->_gc($syncKey);
111: 
112:         /* Read current sync state */
113:         $filename = $this->_stateDir . '/' . $this->_backend->getUser() . '/' . $syncKey;
114:         if (!file_exists($filename)) {
115:             throw new Horde_ActiveSync_Exception('Sync state not found');
116:         }
117:         $this->_stateCache = unserialize(file_get_contents($filename));
118:         $this->_syncKey = $syncKey;
119:     }
120: 
121:     /**
122:      * Determines if the server version of the message represented by $stat
123:      * conflicts with the PIM version of the message according to the current
124:      * state.
125:      *
126:      * @see Horde_ActiveSync_State_Base::isConflict()
127:      */
128:     public function isConflict($stat, $type)
129:     {
130:         foreach ($this->_stateCache as $state) {
131:             if ($state['id'] == $stat['id']) {
132:                 $oldstat = $state;
133:                 break;
134:             }
135:         }
136: 
137:         // New message on server - can never conflict, but we shouldn't be
138:         // here in this case anyway, right?
139:         if (!isset($oldstat)) {
140:             return false;
141:         }
142: 
143:         if ($stat['mod'] != $oldstat['mod']) {
144:             // Changed here
145:             if ($type == 'delete' || $type == 'change') {
146:                 // changed here, but deleted there -> conflict,
147:                 // or changed here and changed there -> conflict
148:                 return true;
149:             } else {
150:                 // changed here, and other remote changes (move or flags)
151:                 return false;
152:             }
153:         }
154:     }
155: 
156:     /**
157:      * Save the current state to storage
158:      *
159:      * @param string $syncKey  The sync key to save
160:      *
161:      * @return boolean
162:      */
163:     public function save()
164:     {
165:         return file_put_contents(
166:             $this->_stateDir . '/' . $this->_backend->getUser() . '/' . $this->_syncKey,
167:             !empty($this->_stateCache) ? serialize($this->_stateCache) : '');
168:     }
169: 
170:     /**
171:      * Update the state to reflect changes
172:      *
173:      * @param string $type       The type of change (change, delete, flags)
174:      * @param array $change      Array describing change
175:      *
176:      * @return void
177:      */
178:     public function updateState($type, array $change, $origin = Horde_ActiveSync::CHANGE_ORIGIN_NA, $user = null)
179:     {
180:         if (empty($this->_stateCache)) {
181:             $this->_stateCache = array();
182:         }
183: 
184:         // Change can be a change or an add
185:         if ($type == 'change') {
186:             /* If we are a change and don't already have a mod time, stat the
187:              * message. This would only happen when exporting a server side
188:              * change. We need the mod time to track the version of the message
189:              * on the PIM. (Folder changes will already have a mod value)
190:              */
191:             if (!isset($change['mod'])) {
192:                 $change = $this->_backend->statMessage($this->_collection['id'], $change['id']);
193:             }
194:             for($i = 0; $i < count($this->_stateCache); $i++) {
195:                 if($this->_stateCache[$i]['id'] == $change['id']) {
196:                     $this->_stateCache[$i] = $change;
197:                     /* If we have a pingState, keep it in sync */
198:                     if (!empty($this->_pingState['collections'])) {
199:                         $this->_pingState['collections'][$this->_collection['class']]['state'] = $this->_stateCache;
200:                     }
201:                     return;
202:                 }
203:             }
204:             // Not found, add as new
205:             $this->_stateCache[] = $change;
206:         } else {
207:             for ($i = 0; $i < count($this->_stateCache); $i++) {
208:                 // Search for the entry for this item
209:                 if ($this->_stateCache[$i]['id'] == $change['id']) {
210:                     if ($type == 'flags') {
211:                         // Update flags
212:                         $this->_stateCache[$i]['flags'] = $change['flags'];
213:                     } elseif ($type == 'delete') {
214:                         // Delete item
215:                         array_splice($this->_stateCache, $i, 1);
216:                     }
217:                     break;
218:                 }
219:             }
220:         }
221: 
222:         /* If we have a pingState, keep it in sync */
223:         if (!empty($this->_pingState['collections'])) {
224:             $this->_pingState['collections'][$this->_collection['class']]['state'] = $this->_stateCache;
225:         }
226:     }
227: 
228:     /**
229:      * Save folder data for a specific device. Used only for compatibility with
230:      * older (version 1) ActiveSync requests.
231:      *
232:      * @param string $devId     The device Id
233:      * @param array $folders    The folder data
234:      *
235:      * @return boolean
236:      * @throws Horde_ActiveSync_Exception
237:      */
238:     public function setFolderData($devId, $folders)
239:     {
240:         if (!is_array($folders) || empty ($folders)) {
241:             return false;
242:         }
243: 
244:         $unique_folders = array ();
245:         foreach ($folders as $folder) {
246:             // don't save folder-ids for emails
247:             if ($folder->type == Horde_ActiveSync::FOLDER_TYPE_INBOX) {
248:                 continue;
249:             }
250: 
251:             // no folder from that type or the default folder
252:             if (!array_key_exists($folder->type, $unique_folders) || $folder->parentid == 0) {
253:                 $unique_folders[$folder->type] = $folder->serverid;
254:             }
255:         }
256: 
257:         // Treo does initial sync for calendar and contacts too, so we need to fake
258:         // these folders if they are not supported by the backend
259:         if (!array_key_exists(Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT, $unique_folders)) {
260:             $unique_folders[Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT] = Horde_ActiveSync::FOLDER_TYPE_DUMMY;
261:         }
262:         if (!array_key_exists(Horde_ActiveSync::FOLDER_TYPE_CONTACT, $unique_folders)) {
263:             $unique_folders[Horde_ActiveSync::FOLDER_TYPE_CONTACT] = Horde_ActiveSync::FOLDER_TYPE_DUMMY;
264: 
265:         }
266:         if (!file_put_contents($this->_stateDir . '/' . $this->_backend->getUser() . '/compat-' . $devId, serialize($unique_folders))) {
267:             $this->_logger->err('_saveFolderData: Data could not be saved!');
268:             throw new Horde_ActiveSync_Exception('Folder data could not be saved');
269:         }
270:     }
271: 
272:     /**
273:      * Get the folder data for a specific collection for a specific device. Used
274:      * only with older (version 1) ActiveSync requests.
275:      *
276:      * @param string $devId  The device id
277:      * @param string $class  The folder class to fetch (Calendar, Contacts etc.)
278:      *
279:      * @return mixed  Either an array of folder data || false
280:      */
281:     public function getFolderData($devId, $class)
282:     {
283:         $filename = $this->_stateDir . '/' . $this->_backend->getUser() . '/compat-' . $devId;
284:         if (file_exists($filename)) {
285:             $arr = unserialize(file_get_contents($filename));
286:             if ($class == "Calendar") {
287:                 return $arr[Horde_ActiveSync::FOLDER_TYPE_APPOINTMENT];
288:             }
289:             if ($class == "Contacts") {
290:                 return $arr[Horde_ActiveSync::FOLDER_TYPE_CONTACT];
291:             }
292:         }
293: 
294:         return false;
295:     }
296: 
297:     /**
298:      * Return an array of known folders. This is essentially the state for a
299:      * FOLDERSYNC request. AS uses a seperate synckey for FOLDERSYNC requests
300:      * also, so need to treat it as any other collection.
301:      *
302:      * @return array
303:      */
304:     public function getKnownFolders()
305:     {
306:         if (!isset($this->_stateCache)) {
307:             throw new Horde_ActiveSync_Exception('Sync state not loaded');
308:         }
309:         $folders = array();
310:         foreach ($this->_stateCache as $folder) {
311:             $folders[] = $folder['id'];
312:         }
313:         return $folders;
314:     }
315: 
316:     /**
317:      * Perform any initialization needed to deal with pingStates
318:      * For this driver, it loads the device's state file.
319:      *
320:      * @param string $devId     The device id of the PIM to load PING state for
321:      *
322:      * @return array The $collection array
323:      */
324:     public function initPingState($devId)
325:     {
326:         $this->_devId = $devId;
327:         $file = $this->_stateDir . '/' . $this->_backend->getUser() . '/' . $devId;
328:         if (file_exists($file)) {
329:             $this->_pingState = unserialize(file_get_contents($file));
330:         } else {
331:             $this->resetPingState();
332:         }
333: 
334:         return $this->_pingState['collections'];
335:     }
336: 
337:     /**
338:      * Obtain the device object.
339:      *
340:      * @param string $devId  The device id to obtain
341:      * @param string $user   The user account to use
342:      *
343:      * @return object  The device info object
344:      * @throws Horde_ActiveSync_Exception
345:      */
346:     public function loadDeviceInfo($devId, $user)
347:     {
348:         $this->_devId = $devId;
349:         $file = $this->_stateDir . '/' . $user . '/info-' . $devId;
350:         if (file_exists($file)) {
351:             return unserialize(file_get_contents($file));
352:         } else {
353:             throw new Horde_ActiveSync_Exception('Device not found.');
354:         }
355:     }
356: 
357:     /**
358:      * Set new device info
359:      *
360:      * @param string $devId   The device id.
361:      * @param StdClass $data  The device information
362:      *
363:      * @return boolean
364:      */
365:     public function setDeviceInfo($data)
366:     {
367:         $this->_ensureUserDirectory();
368:         $this->_devId = $data->id;
369:         $file = $this->_stateDir . '/' . $this->_backend->getUser() . '/info-' . $this->_devId;
370:         return file_put_contents($file, serialize($data));
371:     }
372: 
373:     /**
374:      * Check that a given device id is known to the server. This is regardless
375:      * of Provisioning status.
376:      *
377:      * @param string $devId
378:      * @param string $user
379:      *
380:      * @return boolean
381:      */
382:     public function deviceExists($devId, $user = null)
383:     {
384:         if (empty($user)) {
385:             return count(glob($this->_stateDir . '/*/info-' . $devId)) > 0;
386:         }
387:         return file_exists($this->_stateDir . '/' . $user . '/info-' . $devId);
388:     }
389: 
390:     /**
391:      * Load a specific collection's ping state. Ping state must already have
392:      * been loaded.
393:      *
394:      * @param array $pingCollection  The collection array from the PIM request
395:      *
396:      * @return void
397:      * @throws Horde_ActiveSync_Exception
398:      */
399:     public function loadPingCollectionState($pingCollection)
400:     {
401:         if (empty($this->_pingState)) {
402:             throw new Horde_ActiveSync_Exception('PING state not initialized');
403:         }
404: 
405:         $haveState = false;
406: 
407:         /* Load any existing state */
408:         // @TODO: I'm almost positive we need to key these by 'id', not 'class'
409:         // but this is what z-push did so...
410:         if (!empty($this->_pingState['collections'][$pingCollection['class']])) {
411:             $this->_collection = $this->_pingState['collections'][$pingCollection['class']];
412:             $this->_collection['synckey'] = $this->_devId;
413:             $this->_stateCache = $this->_collection['state'];
414:             $haveState = true;
415:         }
416: 
417:         /* Initialize state for this collection */
418:         if (!$haveState) {
419:             $this->_logger->info('[' . $this->_devId . '] Empty state for '. $pingCollection['class']);
420: 
421:             /* Init members for the getChanges call */
422:             $this->_syncKey = $this->_devId;
423:             $this->_collection = $pingCollection;
424:             $this->_collection['synckey'] = $this->_devId;
425:             $this->_collection['state'] = array();
426: 
427:             /* If we are here, then the pingstate was empty, prime it */
428:             $this->_pingState['collections'][$this->_collection['class']] = $this->_collection;
429: 
430:             /* Need to load _stateCache so getChanges has it */
431:             $this->_stateCache = array();
432: 
433:             $changes = $this->getChanges();
434:             foreach ($changes as $change) {
435:                 switch ($change['type']) {
436:                 case 'change':
437:                     $stat = $this->_backend->statMessage($this->_collection['id'], $change['id']);
438:                     if (!$message = $this->_backend->getMessage($this->_collection['id'], $change['id'], 0)) {
439:                         continue;
440:                     }
441:                     if ($stat && $message) {
442:                         $this->updateState('change', $stat);
443:                     }
444:                     break;
445: 
446:                 default:
447:                     throw new Horde_ActiveSync_Exception('Unexpected change type in loadPingState');
448:                 }
449:             }
450: 
451:             $this->_pingState['collections'][$this->_collection['class']]['state'] = $this->_stateCache;
452:             $this->savePingState();
453:         }
454:     }
455: 
456:     /**
457:      * Save the current ping state to storage
458:      *
459:      * @return boolean
460:      * @throws Horde_ActiveSync_Exception
461:      */
462:     public function savePingState()
463:     {
464:         if (empty($this->_pingState)) {
465:             throw new Horde_ActiveSync_Exception('PING state not initialized');
466:         }
467:         $this->_ensureUserDirectory();
468:         $state = serialize(array('lifetime' => $this->_pingState['lifetime'],
469:                                  'collections' => $this->_pingState['collections']));
470: 
471:         $this->_logger->info('[' . $this->_devId . '] Saving new PING state.');
472:         return file_put_contents($this->_stateDir . '/' . $this->_backend->getUser() . '/' . $this->_devId, $state);
473:     }
474: 
475:     /**
476:      * Return the heartbeat interval, or zero if we have no existing state
477:      *
478:      * @return integer  The hearbeat interval, or zero if not found.
479:      * @throws Horde_ActiveSync_Exception
480:      */
481:     public function getHeartbeatInterval()
482:     {
483:         if (empty($this->_pingState)) {
484:             throw new Horde_ActiveSync_Exception('PING state not initialized');
485:         }
486: 
487:         return (!$this->_pingState) ? 0 : $this->_pingState['lifetime'];
488:     }
489: 
490:     /**
491:      * Set the device's heartbeat interval
492:      *
493:      * @param integer $lifetime
494:      */
495:     public function setHeartbeatInterval($lifetime)
496:     {
497:         $this->_pingState['lifetime'] = $lifetime;
498:     }
499: 
500:     /**
501:      * Save a new device policy key to storage.
502:      *
503:      * @param string $devId  The device id
504:      * @param integer $key   The new policy key
505:      */
506:     public function setPolicyKey($devId, $key)
507:     {
508:         $info = $this->loadDeviceInfo($devId);
509:         $info->policykey = $key;
510:         $this->setDeviceInfo($info);
511:         $this->_logger->info('[' . $devId . '] New policykey saved: ' . $key);
512:     }
513: 
514:     /**
515:      * Reset ALL device policy keys. Used when server policies have changed
516:      * and you want to force ALL devices to pick up the changes. This will
517:      * cause all devices that support provisioning to be reprovisioned.
518:      *
519:      * @throws Horde_ActiveSync_Exception
520:      *
521:      */
522:     public function resetAllPolicyKeys()
523:     {
524:         throw new Horde_ActiveSync_Exception('Not Implemented');
525:     }
526: 
527:     /**
528:      * Set a new remotewipe status for the device
529:      *
530:      * @param string $devId
531:      * @param integer $status
532:      *
533:      * @return boolean
534:      */
535:     public function setDeviceRWStatus($devId, $status)
536:     {
537:         $info = $this->loadDeviceInfo($devId);
538:         $info->rwstatus = $status;
539:         $this->setDeviceInfo($info);
540:         $this->_logger->info('[' . $devId . '] Setting DeviceRWStatus: ' . $status);
541:     }
542: 
543:     /**
544:      * Get list of server changes
545:      *
546:      * @param integer $flags
547:      *
548:      * @return array
549:      */
550:     public function getChanges($flags = 0)
551:     {
552:         $syncState = empty($this->_stateCache) ? array() : $this->_stateCache;
553:         $cutoffdate = self::_getCutOffDate(!empty($this->_collection['filtertype']) ? $this->_collection['filtertype'] : 0);
554: 
555:         if (!empty($this->_collection['id'])) {
556:             $folderId = $this->_collection['id'];
557:             $this->_logger->info('[' . $this->_devId . '] Initializing message diff engine.');
558:             if (!$syncState) {
559:                 $syncState = array();
560:             }
561:             $this->_logger->debug('[' . $this->_devId . ']' . count($syncState) . ' messages in state.');
562: 
563:             /* do nothing if it is a dummy folder */
564:             if ($folderId != Horde_ActiveSync::FOLDER_TYPE_DUMMY) {
565:                 // on ping: check if backend supports alternative PING mechanism & use it
566:                 if ($this->_collection['class'] === false && $flags == Horde_ActiveSync::BACKEND_DISCARD_DATA && $this->_backend->alterPing()) {
567:                     //@TODO - look at the passing of syncstate here - should probably pass self??
568:                     $this->_changes = $this->_backend->alterPingChanges($folderId, $syncState);
569:                 } else {
570:                     /* Get our lists - syncstate (old)  and msglist (new) */
571:                     $msglist = $this->_backend->getMessageList($this->_collection['id'], $cutoffdate);
572:                     if ($msglist === false) {
573:                         return false;
574:                     }
575:                     $this->_changes = $this->_getDiff($syncState, $msglist);
576:                 }
577:             }
578:             $this->_logger->info('[' . $this->_devId . '] Found ' . count($this->_changes) . ' message changes.');
579: 
580:         } else {
581: 
582:             $this->_logger->info('[' . $this->_devId . '] Initializing folder diff engine.');
583:             $folderlist = $this->_backend->getFolderList();
584:             if ($folderlist === false) {
585:                 return false;
586:             }
587: 
588:             $this->_changes = $this->_getDiff($syncState, $folderlist);
589:             $this->_logger->info('[' . $this->_devId . '] Found ' . count($this->_changes) . ' folder changes.');
590:         }
591: 
592:         return $this->_changes;
593:     }
594: 
595:     /**
596:      * Explicitly remove a specific state. Normally used if a request results in
597:      * a synckey mismatch. This isn't strictly needed, but helps keep the state
598:      * storage clean.
599:      *
600:      */
601:     public function removeState($syncKey = null, $devId = null)
602:     {
603:         if ($devId) {
604:             throw new Horde_ActiveSync_Exception('Not implemented.');
605:         }
606:         $this->_gc($syncKey, true);
607:     }
608: 
609:     public function listDevices()
610:     {
611:        throw new Horde_ActiveSync_Exception('Not Implemented');
612:     }
613: 
614:     /**
615:      * Get the last time a particular device issued a SYNC request.
616:      *
617:      * @param string $devId  The device id
618:      *
619:      * @return integer  The timestamp of the last sync, regardless of collection
620:      * @throws Horde_ActiveSync_Exception
621:      */
622:     public function getLastSyncTimestamp()
623:     {
624:         throw new Horde_ActiveSync_Exception('Not Implemented');
625:     }
626: 
627:     /**
628:      * Garbage collector - clean up from previous sync
629:      * requests.
630:      *
631:      * @params string $syncKey  The sync key
632:      *
633:      * @return boolean
634:      * @throws Horde_ActiveSync_Exception
635:      */
636:     private function _gc($syncKey, $all = false)
637:     {
638:         if (!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) {
639:             return false;
640:         }
641:         $guid = $matches[1];
642:         $n = $matches[2];
643: 
644:         $dir = @opendir($this->_stateDir . '/' . $this->_backend->getUser());
645:         if (!$dir) {
646:             return false;
647:         }
648:         while ($entry = readdir($dir)) {
649:             if (preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $entry, $matches)) {
650:                 if ($matches[1] == $guid && ((!$all && $matches[2] < $n) || $all)) {
651:                     unlink($this->_stateDir . '/' . $this->_backend->getUser() . '/' . $entry);
652:                 }
653:             }
654:         }
655: 
656:         return true;
657:     }
658: 
659:     /**
660:      * Ensure that the user's state directory is present.
661:      *
662:      * @return void
663:      */
664:     private function _ensureUserDirectory()
665:     {
666:         /* Make sure this user's state directory exists */
667:         if ($this->_haveStateDirectory) {
668:             return true;
669:         }
670: 
671:         $dir = $this->_stateDir . '/' . $this->_backend->getUser();
672:         if (!file_exists($dir)) {
673:             if (!mkdir($dir)) {
674:                 throw new Horde_ActiveSync_Exception('Failed to create user state storage');
675:             }
676:         }
677: 
678:         $this->_haveStateDirectory = true;
679:     }
680: 
681: }
682: 
API documentation generated by ApiGen