Files
server/usr/share/psa-pear/pear/php/Horde/ActiveSync/Device.php
2026-01-07 20:52:11 +01:00

854 lines
32 KiB
PHP

<?php
/**
* Horde_ActiveSync_Device::
*
* @license http://www.horde.org/licenses/gpl GPLv2
*
* @copyright 2010-2020 Horde LLC (http://www.horde.org)
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @package ActiveSync
*/
/**
* Horde_ActiveSync_Device:: Wraps all functionality related to device data.
*
* @license http://www.horde.org/licenses/gpl GPLv2
*
* @copyright 2010-2020 Horde LLC (http://www.horde.org)
* @author Michael J Rubinsky <mrubinsk@horde.org>
* @package ActiveSync
*
* @property string $id The device id.
* @property string $deviceType The device type string.
* @property string $clientType The client name, if available.
* @property integer $rwstatus The RemoteWipe status - a
* Horde_ActiveSync::RWSTATUS_* constant.
* @property string $userAgent The device's user agent string.
* @property string $user The userid for the current device
* account.
* @property array $supported The SUPPORTED data sent from this device.
* @property string $policykey The current policykey, if provisioned.
* @property array $properties The device properties, sent in
* DEVICEINFO, along with any custom
* properties set.
* @property string $announcedVersion The most last EAS supported versions
* announced to the device.
* @property integer $multiplex Bitmask describing collections that this
* device does not support user created
* folders for, therefore all sources must
* be multiplexed together. Masks are
* the MULTIPLEX_* constants.
* @property boolean $blocked True if device has been marked as
* blocked.
*
*/
class Horde_ActiveSync_Device
{
const MODEL = 'Settings:Model';
const IMEI = 'Settings:IMEI';
const NAME = 'Settings:FriendlyName';
const OS = 'Settings:OS';
const OS_LANGUAGE = 'Settings:OSLanguage';
const PHONE_NUMBER = 'Settings:PhoneNumber';
const OPERATOR = 'Settings:MobileOperator';
const VERSION = 'version';
const MULTIPLEX = 'multiplex';
const ANNOUNCED_VERSION = 'announcedVersion';
const BLOCKED = 'blocked';
// Bitwise constants for flagging device must use multiplexed collections.
// @since 2.9.0
const MULTIPLEX_CONTACTS = 1;
const MULTIPLEX_CALENDAR = 2;
const MULTIPLEX_TASKS = 4;
const MULTIPLEX_NOTES = 8;
const TYPE_IPHONE = 'iphone';
const TYPE_IPOD = 'ipod';
const TYPE_IPAD = 'ipad';
const TYPE_WEBOS = 'webos';
const TYPE_ANDROID = 'android';
const TYPE_BLACKBERRY = 'blackberry';
const TYPE_WP = 'windowsphone';
const TYPE_TOUCHDOWN = 'touchdown';
const TYPE_UNKNOWN = 'unknown';
const TYPE_NINE = 'nine';
const TYPE_GMAIL = 'gmail';
/**
* Quirk to specify if the client fails to property ghost the
* POOMCONTACTS:Picture field. If this quirk is present, it means we should
* add the POOMCONTACTS:Picture field to the SUPPORTED array for this client.
*/
const QUIRK_NEEDS_SUPPORTED_PICTURE_TAG = 1;
/**
* iOS sends an empty picture tag on every edit of contacts whose pictures
* did not originate on the client itself.
*/
const QUIRK_INCORRECTLY_SENDS_EMPTY_PICTURE_TAG = 2;
/**
* These client can support TNEF data without decoding it first.
*
* @since 2.40.0
*/
const QUIRK_SUPPORTS_TNEF = 3;
/**
* Device properties.
*
* @var array
*/
protected $_properties = array();
/**
* State handler
*
* @var Horde_ActiveSync_State_Base
*/
protected $_state;
/**
* Dirty flag
*
* @var array
*/
protected $_dirty = array();
/**
* Flag to indicate self::multiplex was set externally.
*
* @var boolean
*/
protected $_multiplexSet = false;
/**
* Local override/cache of detected clientType.
*
* @var string
*/
protected $_clientType;
/**
* Cache of OS version.
*
* @var string
*/
protected $_iOSVersion;
/**
* Const'r
*
* @param Horde_ActiveSync_State_Base $state The state driver.
* @param array $data The current device data.
*/
public function __construct(Horde_ActiveSync_State_Base $state, array $data = array())
{
$this->_state = $state;
$this->_properties = $data;
}
/**
* Getter
*/
public function &__get($property)
{
switch ($property) {
case self::MULTIPLEX:
if (!$this->_multiplexSet && empty($this->_properties['properties'][self::MULTIPLEX])) {
$this->_sniffMultiplex();
$this->multiplex = $this->_properties['properties'][self::MULTIPLEX];
$this->save();
}
case self::ANNOUNCED_VERSION:
case self::BLOCKED:
return $this->_properties['properties'][$property];
case 'clientType':
if (!isset($this->_clientType)) {
$this->_clientType = $this->_getClientType();
}
return $this->_clientType;
case self::VERSION:
if (isset($this->_properties['properties'][self::VERSION])) {
return $this->_properties['properties'][self::VERSION];
}
break;
case self::OS:
if (isset($this->_properties['properties'][self::OS])) {
return $this->_properties['properties'][self::OS];
}
break;
case 'properties':
if (!isset($this->_properties['properties'])) {
$return = array();
return $return;
}
// Fall through.
default:
if (isset($this->_properties[$property])) {
return $this->_properties[$property];
}
}
$return = null;
return $return;
}
/**
* Setter
*/
public function __set($property, $value)
{
switch ($property) {
case self::MULTIPLEX:
$this->_multiplexSet = true;
// fallthrough
case self::ANNOUNCED_VERSION:
case self::BLOCKED:
case self::VERSION:
case self::OS:
$properties = $this->properties;
if (empty($properties)) {
$properties = array();
}
$properties[$property] = $value;
$this->setDeviceProperties($properties);
break;
case 'clientType':
$this->_clientType = $value;
break;
default:
if (!isset($this->_properties[$property]) || $value != $this->_properties[$property]) {
$this->_dirty[$property] = true;
$this->_properties[$property] = $value;
}
}
}
/**
* Magic isset
*/
public function __isset($property)
{
return !empty($this->_properties[$property]);
}
/**
* Indicates if we need to announce new EAS version string to the client.
* If the property is empty, we don't send it since we are sending the
* EAS-Version header anyway and this is a new device.
*
* @param string $supported The current EAS-Version header.
*
* @return boolean True if we need to send the MS-RP header, otherwise false.
*/
public function needsVersionUpdate($supported)
{
if (empty($this->properties[self::ANNOUNCED_VERSION])) {
$properties = $this->properties;
$properties[self::ANNOUNCED_VERSION] = $supported;
$this->setDeviceProperties($properties);
return false;
}
if ($this->properties[self::ANNOUNCED_VERSION] != $supported) {
$properties = $this->properties;
$properties[self::ANNOUNCED_VERSION] = $supported;
$this->setDeviceProperties($properties);
return true;
}
return false;
}
/**
* Returns if the current device is an expected non-provisionable device.
* I.e., the client does not support provisioning at all, but should still
* be allowed to connect to a server that has provisioning set to Force.
* Currently, this only applies to Windows Communication Apps (Outlook 2013).
*
* @return boolean True if the device should be allowed to connect to a
* Forced provision server. False if not.
*/
public function isNonProvisionable()
{
// Outlook? The specs say that "Windows Communication Apps" should
// provide the 'OS' parameter of the ITEMSETTINGS data equal to 'Windows',
// but Outlook 2013 doesn't even send the ITEMSETTINGS command, so we
// need to check the userAgent header. Early versions used Microsoft.Outlook,
// but after some update it was changed to 'Outlook/15.0'
if (strpos($this->deviceType, 'MicrosoftOutlook') !== false ||
strpos($this->userAgent, 'Outlook') !== false) {
return true;
}
// Don't attempt to provision the Microsoft Connectivity Analyser
if ($this->deviceType === 'TestActiveSyncConnectivity') {
return true;
}
return false;
}
/**
* Check if we should enforce provisioning on this device.
*
* @return boolean
*/
public function enforceProvisioning()
{
if (strpos($this->userAgent, 'Android') !== false) {
if (preg_match('@EAS[/-]{0,1}([.0-9]{2,})@', $this->userAgent, $matches)) {
return $matches[1] > 1.2;
}
return false;
}
return !$this->isNonProvisionable();
}
/**
* Set the device's DEVICEINFO data.
*
* @param array $data The data array sent from the device.
*/
public function setDeviceProperties(array $data)
{
$data = array_merge($this->properties, $data);
if (empty($data['userAgent']) && !empty($this->_properties['userAgent'])) {
$data['userAgent'] = $this->_properties['userAgent'];
}
$this->properties = $data;
$this->_dirty['properties'] = true;
}
/**
* Return an array of DEVICEINFO data, with keys suitable for displaying.
*
* @return array
*/
public function getFormattedDeviceProperties()
{
$data = array(
_("Id") => $this->id,
_("Policy Key") => $this->policykey,
_("User Agent") => $this->userAgent
);
if (!empty($this->properties[self::MODEL])) {
$data[_("Model")] = $this->properties[self::MODEL];
}
if (!empty($this->properties[self::IMEI])) {
$data[_("IMEI")] = $this->properties[self::IMEI];
}
if (!empty($this->properties[self::NAME])) {
$data[_("Common Name")] = $this->properties[self::NAME];
}
if (!empty($this->properties[self::OPERATOR])) {
$data[_("Mobile Operator")] = $this->properties[self::OPERATOR];
}
if (!empty($this->properties[self::OS])) {
$data[_("OS")] = $this->properties[self::OS];
}
if (!empty($this->properties[self::OS_LANGUAGE])) {
$data[_("OS Language")] = $this->properties[self::OS_LANGUAGE];
}
if (!empty($this->properties[self::PHONE_NUMBER])) {
$data[_("Phone Number")] = $this->properties[self::PHONE_NUMBER];
}
if (!empty($this->properties[self::VERSION])) {
$data[_("EAS Version")] = $this->properties[self::VERSION];
}
if (!empty($this->properties[self::MULTIPLEX])) {
$data[_("Forced Multiplexed Bitmask")] = $this->properties[self::MULTIPLEX];
}
return $data;
}
/**
* Return the last time the device issued a SYNC request.
*
* @return integer The timestamp.
*/
public function getLastSyncTimestamp()
{
return $this->_state->getLastSyncTimestamp($this->id, $this->user);
}
/**
* Save the dirty device info data.
*
* @param boolean $all If true, save all properties (deviceInfo and
* deviceProperties). Otherwise, just save dirty
* deviceProperties. @since 2.16.0
* @todo For 3.0, make it clearer that deviceInfo is per-user and
* deviceProperties is per-device.
*/
public function save($all = true)
{
if ($all) {
$this->_state->setDeviceInfo($this, $this->_dirty);
}
if (!empty($this->_dirty['properties'])) {
$this->_state->setDeviceProperties($this->properties, $this->id);
}
$this->_dirty = array();
}
/**
* Return the major version number of the OS (or client app) as reported
* by the client.
*
* @return integer The version number.
*/
public function getMajorVersion()
{
switch (Horde_String::lower($this->clientType)) {
case self::TYPE_BLACKBERRY:
if (preg_match('/(.+)\/(.+)/', $this->userAgent, $matches)) {
return $matches[2];
}
break;
case self::TYPE_IPOD:
case self::TYPE_IPAD:
case self::TYPE_IPHONE:
if (empty($this->_iOSVersion)) {
$this->_getIosVersion();
}
if (preg_match('/(\d+)\.(\d+)/', $this->_iOSVersion, $matches)) {
return $matches[1];
}
break;
case self::TYPE_ANDROID:
case self::TYPE_NINE:
// Most newer Android clients send self::OS, so check that first
if (!empty($this->properties[self::OS]) && preg_match('/(\d+)\.(\d+)/', $this->properties[self::OS], $matches)) {
return $matches[1];
}
// Some newer devices send userAgent like Android/4.3.3-EAS-1.3
if (preg_match('/Android\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[1];
}
// Older Android/0.3 type userAgent strings.
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[2];
}
break;
case self::TYPE_TOUCHDOWN:
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[2];
}
break;
}
return 0;
}
/**
* Detects the iOS version in M.m format and caches locally.
*/
protected function _getIosVersion()
{
// First see if we have a newer client that sends the OS version
// Newer iOS sends e.g., "iOS 8.2.2" in OS field.
if (!empty($this->properties[self::OS]) &&
preg_match('/\d+\.\d+\.?\d?/', $this->properties[self::OS], $matches)) {
if (!empty($matches[0])) {
$this->_iOSVersion = $matches[0];
return;
}
}
// Match to a known UserAgent string version.
foreach (Horde_ActiveSync_Device_Ios::$VERSION_MAP as $userAgent => $version) {
if (preg_match('/\w+\/(' . $userAgent . ')$/', $this->userAgent, $matches)) {
$this->_iOSVersion = $version;
return;
}
}
}
/**
* Return the minor version number of the OS (or client app) as reported
* by the client.
*
* @return integer The version number.
*/
public function getMinorVersion()
{
switch (Horde_String::lower($this->clientType)) {
case self::TYPE_BLACKBERRY:
if (preg_match('/(.+)\/(.+)/', $this->userAgent, $matches)) {
return $matches[2];
}
break;
case self::TYPE_IPOD:
case self::TYPE_IPAD:
case self::TYPE_IPHONE:
if (empty($this->_iOSVersion)) {
$this->_getIosVersion();
}
if (preg_match('/(\d+)\.(\d+)/', $this->_iOSVersion, $matches)) {
return $matches[2];
}
break;
case self::TYPE_ANDROID:
// Most newer Android clients send self::OS, so check that first
if (!empty($this->properties[self::OS]) && preg_match('/(\d+)\.(\d+)/', $this->properties[self::OS], $matches)) {
return $matches[2];
}
// Some newer devices send userAgent like Android/4.3.3-EAS-1.3
if (preg_match('/Android\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[2];
}
// Older Android/0.3 type userAgent strings.
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[3];
}
break;
case self::TYPE_TOUCHDOWN:
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->userAgent, $matches)) {
return $matches[3];
}
break;
}
return 0;
}
/**
* Return the number of hours to offset a POOMCONTACTS:BIRTHDAY
* or ANNIVERSARY field in an attempt to work around a bug in the
* protocol - the protocol states that "the time portion SHOULD be ignored,
* so that synchronizing between different timezones does not change the
* date" (MS-ASCNTC 2.2.2.6). However, it appears that next to zero clients
* actually honor this.
*
*
* WP:
* Devices seem to send the birthdays at the entered date, with
* a time of 00:00:00 UTC during standard time and with 01:00:00 UTC
* during DST if the client's configured timezone observes it. No idea
* what purpose this serves since no timezone data is transmitted for
* birthday values.
*
* iOS:
* Seems different based on version. iOS 5+, at least seems to send
* the birthday as midnight at the entered date in the device's timezone
* then converted to UTC. Some minor issues with offsets being off an
* hour or two for some timezones though.
*
* iOS < 5 sends the birthday time part as the time the birthday
* was entered/edited on the device, converted to UTC, so it can't be
* trusted at all. The best we can do here is transform the date to
* midnight on date_default_timezone() converted to UTC.
*
* Android:
* Lollipop and newer got rid of the stock "Email" app in favor of
* GMail. Gmail seems to send birthdays up at 12:00:00 UTC, but doesn't
* expect them at that time. It *seems* that sending them down at
* 00:00:00 UTC works, but Unfortunately I don't know the version
* history here as to if they always did this, so will rely on bug
* reports if this is not correct :)
*
* For legacy android:
*
* For contacts originating on the SERVER, the following is true:
*
* Stock 4.3 Takes the down-synched bday value which is assumed to be
* UTC, does some magic to it (converts to milliseconds, creates a
* gregorian calendar object, then converts to YYYY-MM-DD). When
* sending the bday value up, it sends it up as-is. No conversion
* to/from UTC or local is done.
*
* Stock 4.4.x does the above, but before sending the bday value,
* validates that it's in a correct format for sending to the server.
* This really only affects date data originally entered on the device
* for non-stock android clients.
*
* There is some strange bit of code in Android that adds 1 to the
* DAY_OF_MONTH when HOUR_OF_DAY >= 12 in an attempt to "fix"
* birthday handling for GMT+n users. See:
* https://android.googlesource.com/platform/packages/apps/Exchange/+/32daacdd71b9de8fd5e3f59c37934e3e4a9fa972%5E!/exchange2/src/com/android/exchange/adapter/ContactsSyncAdapter.java
* Not sure what to make of it, or why it's not just converted to
* local tz when displaying but this probably breaks birthday handling
* for people in a few timezones.
*
* For contacts originating on the CLIENT, the datetime is sent as
* 08:00:00 UTC, and this seems to be regardless of the timezone set
* in the Android system.
*
* Given all of this, it makes sense to me to ALWAYS send birthday
* data as occuring at 08:00:00 UTC for *native* Android clients.
*
* BB 10+ expects it at 11:00:00 UTC
*
* @param Horde_Date $date The date. This should normally be in the local
* timezone if encoding the date for the client.
* If decoding the date from the client, it will
* normally be in UTC.
* @param boolean $toEas Convert from local to device if true.
* DEFAULT: false
*
* @return Horde_Date The date of the birthday/anniversary, with
* any fixes applied for the current device. The
* timezone set in the object will depend on the
* client detected, and whether the date is being
* encoding or decoding.
*/
public function normalizePoomContactsDates($date, $toEas = false)
{
switch (Horde_String::lower($this->clientType)) {
case self::TYPE_WP:
case 'wp8': // Legacy. Remove in H6.
case 'wp': // Legacy. Remove in H6.
if ($toEas) {
return new Horde_Date($date->format('Y-m-d'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
case self::TYPE_GMAIL:
if ($toEas) {
return new Horde_Date($date->format('Y-m-d'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
case self::TYPE_ANDROID:
// Need to protect against clients that don't send the actual Android
// version in the OS field.
if (stripos($this->deviceType, 'samsung') === 0) {
// Samsung's native Contacts app works differently than stock
// Android, always sending as 00:00:00
if ($toEas) {
return new Horde_Date($date->format('Y-m-d'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
}
if ($this->getMajorVersion() >= 4 && $this->getMajorVersion() <= 10) {
if ($toEas) {
return new Horde_Date($date->format('Y-m-d 08:00:00'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
}
// POOMCONTACTS:BIRTHDAY not really supported in early Android
// versions. Return as is.
return $date;
case self::TYPE_IPAD:
case self::TYPE_IPHONE:
case self::TYPE_IPOD:
if ($this->getMajorVersion() >= 5) {
// iOS >= 5 handles it correctly more or less.
if ($toEas) {
return new Horde_Date($date->format('Y-m-d 00:00:00'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
}
if ($toEas) {
return new Horde_Date($date->format('Y-m-d'), 'UTC');
}
return new Horde_Date($date->format('Y-m-d'));
case self::TYPE_NINE:
if ($toEas) {
return new Horde_Date($date->format('Y-m-d 00:00:00'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
case self::TYPE_BLACKBERRY:
if ($toEas) {
return new Horde_Date($date->format('Y-m-d 11:00:00'), 'UTC');
}
$date = new Horde_Date($date->format('Y-m-d'));
return $date->setTimezone('UTC');
case self::TYPE_TOUCHDOWN:
case self::TYPE_UNKNOWN:
default:
return $date;
}
}
/**
* Return if this client has the described quirk.
*
* @param integer $quirk The specified quirk to check for.
*
* @return boolean True if quirk is present.
*/
public function hasQuirk($quirk)
{
switch ($quirk) {
case self::QUIRK_NEEDS_SUPPORTED_PICTURE_TAG:
return ($this->_isIos() && $this->getMajorVersion() == 4);
case self::QUIRK_INCORRECTLY_SENDS_EMPTY_PICTURE_TAG:
return ($this->_isIos() && $this->getMajorVersion() >= 8 && $this->getMinorVersion() > 1);
case self::QUIRK_SUPPORTS_TNEF:
// These clients can support TNEF data naitively.
return (strpos($this->deviceType, 'MicrosoftOutlook') !== false ||
strpos($this->deviceType, 'WindowsOutlook') != false ||
strpos($this->userAgent, 'Outlook') !== false);
default:
return false;
}
}
/**
* Attempt to determine the *client* application as opposed to the device,
* which may or may not be the client.
*
* @return string The client name.
*/
protected function _getClientType()
{
// Differentiate between the deviceType and the client app.
if ((!empty($this->properties[self::OS]) &&
stripos($this->properties[self::OS], 'Android') !== false) ||
Horde_String::lower($this->deviceType) == self::TYPE_ANDROID) {
// We can detect native Android, TouchDown, and Nine.
// Moxier does not distinguish itself, so we can't sniff it.
if (strpos($this->userAgent, 'TouchDown') !== false) {
return self::TYPE_TOUCHDOWN;
} else if ($this->_isNine()) {
return self::TYPE_NINE;
} else if (stripos($this->userAgent, 'Android-Mail') !== false) {
// Modern android OS uses GMAIL as the stock client.
return self::TYPE_GMAIL;
} else if (stripos($this->userAgent, 'Android') !== false) {
return $this->deviceType;
} else {
return self::TYPE_ANDROID;
}
} else {
return $this->deviceType;
}
}
/**
* Helper method to sniff out the 9Folders client, "Nine".
* @see https://ninefolders.plan.io/track/7048/46b213 for the discussion on
* how to sniff out the Nine client. Not the best solution, but it's the one
* they decided to use.
*
* @return boolean True if client is thought to be "Nine".
*/
protected function _isNine()
{
if (!ctype_xdigit($this->id)) {
return false;
}
return stripos(pack('H*', $this->id), 'nine') === 0;
}
/**
* Basic sniffing for determining if devices can support non-multiplexed
* collections.
*/
protected function _sniffMultiplex()
{
$clientType = Horde_String::lower($this->clientType);
if ($this->_isIos()) {
// iOS seems to support multiple collections for everything except Notes.
$this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_NOTES;
} else if ($clientType == self::TYPE_ANDROID) {
// Special cases: These clients don't support non-multiplexed
// collections. Samsung's native client and HTCOnemini2.
if (stripos($this->deviceType, 'samsung') === 0 ||
stripos($this->model, 'HTCOnemini2') === 0 ||
$this->deviceType == 'HTCOnemini2') {
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS |
Horde_ActiveSync_Device::MULTIPLEX_CALENDAR |
Horde_ActiveSync_Device::MULTIPLEX_NOTES |
Horde_ActiveSync_Device::MULTIPLEX_TASKS;
return;
}
// Newer GMAIL applications may not send the self::OS value.
if (strpos($this->userAgent, 'Android-Mail/7.5') !== false) {
$this->_properties['properties'][SELF::MULTIPLEX] = 0;
return;
}
// All android before 4.4 KitKat requires multiplex. KitKat and
// Android 5 native supports non-multiplexed calendars only.
if (!empty($this->properties[self::OS]) &&
preg_match('/(\d+\.\d+(\.\d+)*)/', $this->properties[self::OS], $matches) &&
version_compare($matches[0], '4.4.0') >= 0) {
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_NOTES |
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS |
Horde_ActiveSync_Device::MULTIPLEX_TASKS;
} else {
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS |
Horde_ActiveSync_Device::MULTIPLEX_CALENDAR |
Horde_ActiveSync_Device::MULTIPLEX_NOTES |
Horde_ActiveSync_Device::MULTIPLEX_TASKS;
}
} else if (strpos($this->userAgent, 'MSFT-WP/8.10') !== false) {
// Windows Phone 8.10 supports multiple calendars and tasks, but
// no contacts.
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS;
} else if (strpos($this->userAgent, 'MSFT-WP/8.0') !== false || $this->deviceType == 'WP8') {
// Windows Phone 8.0 seems that only multiple tasklists are
// supported. The rest must be multiplexed.
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS |
Horde_ActiveSync_Device::MULTIPLEX_CALENDAR;
} else if (strpos($this->userAgent, 'MSFT-PPC') !== false || $this->deviceType == 'PocketPC') {
// PocketPC versions seem to not support any user defined
// collections at all, though I've only tested on a single HTC device.
$this->_properties['properties'][self::MULTIPLEX] =
Horde_ActiveSync_Device::MULTIPLEX_CONTACTS |
Horde_ActiveSync_Device::MULTIPLEX_CALENDAR |
Horde_ActiveSync_Device::MULTIPLEX_NOTES |
Horde_ActiveSync_Device::MULTIPLEX_TASKS;
} else if (strpos($this->userAgent, 'Outlook/15.0') !== false ||
strpos($this->userAgent, 'Outlook/16.0') !== false) {
// OL2013 and OL2016 do not support multiple contact lists.
$this->_properties['properties'][self::MULTIPLEX] = Horde_ActiveSync_Device::MULTIPLEX_CONTACTS;
} else {
$this->_properties['properties'][self::MULTIPLEX] = 0;
}
}
/**
* Return if this client is an iOS device. Different versions require
* different checks.
*
* @return boolean [description]
*/
protected function _isIos()
{
// Compare in order of likelyhood / most recent to least recent versions.
if (strpos($this->{self::OS}, 'iOS') === 0 ||
strpos($this->userAgent, 'iOS') === 0 ||
in_array(Horde_String::lower($this->clientType), array(self::TYPE_IPAD, self::TYPE_IPOD, self::TYPE_IPHONE)) ||
strpos($this->userAgent, 'Apple-') === 0) {
return true;
}
return false;
}
public function __toString()
{
return serialize($this->_properties);
}
}