1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/z-push_ynh.git synced 2024-09-03 18:05:58 +02:00

first commit

This commit is contained in:
root 2014-12-17 15:40:48 +00:00
commit 011be933a0
164 changed files with 56935 additions and 0 deletions

36
manifest.json Normal file
View file

@ -0,0 +1,36 @@
{
"name": "Z-push",
"id": "z-push",
"description": {
"en": "A self hostable read-it-later app",
"fr": "Une application de lecture-plus-tard auto-hébergeable"
},
"licence": "WTFPL-2",
"developer": {
"name": "beudbeud",
"email": "beudbeud@beudibox.fr",
"url": "http://www.z-push.org"
},
"multi_instance": "true",
"arguments": {
"install" : [
{
"name": "domain",
"ask": {
"en": "Choose a domain for Z-push",
"fr": "Choisissez un domaine pour Z-push"
},
"example": "domain.org"
},
{
"name": "path",
"ask": {
"en": "Choose a path for Z-push",
"fr": "Choisissez un chemin pour Z-push"
},
"example": "/z-push",
"default": "/z-push"
}
]
}
}

35
scripts/install Normal file
View file

@ -0,0 +1,35 @@
#!/bin/bash
# Retrieve arguments
domain=$1
path=$2
# Check domain/path availability
sudo yunohost app checkurl $domain$path -a z-push
if [[ ! $? -eq 0 ]]; then
exit 1
fi
# Copy files to the right place
final_path=/var/www/z-push
sudo mkdir -p $final_path
sudo cp -a ../sources/* $final_path
# Set permissions to roundcube directory
sudo chown -R www-data: $final_path
# Configuration
#sudo cp ../conf/config.inc.php $final_path/inc/poche/
# Modify Nginx configuration file and copy it to Nginx conf directory
#sed -i "s@PATHTOCHANGE@$path@g" ../conf/nginx.conf*
#sed -i "s@ALIASTOCHANGE@$final_path/@g" ../conf/nginx.conf*
#sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/z-push.conf
# Enable api for client
sudo yunohost app setting z-push skipped_uris -v "/"
# Reload Nginx and regenerate SSOwat conf
sudo service nginx reload
sudo yunohost app ssowatconf

4
scripts/remove Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash
sudo rm -rf /var/www/z-push
#sudo rm -f /etc/nginx/conf.d/$domain.d/z-push.conf

288
sources/INSTALL Normal file
View file

@ -0,0 +1,288 @@
Installing Z-Push
======================
Requirements
------------
Z-Push 2 runs only on PHP 5.1 or later
A PEAR dependency as in previous versions does not exist in Z-Push 2.
The PHP version requirement is met in these distributions and versions (or later).
Debian 4.0 (etch)
Ubuntu 8.04 (hardy heron)
RHEL/CentOS 5.5
Fedora 5 (bordeaux)
OpenSuse 10.1
Slackware 12.0
Gentoo 2006.1
FreeBSD 6.1
OpenBSD 4.0
Mandriva 2007
If your distribution is not listed here, you can check which PHP version
is default for it at http://distrowatch.com/.
Additional informations can be found in the Zarafa Administrator Manual:
http://doc.zarafa.com/trunk/Administrator_Manual/en-US/html/_zpush.html
Additional php packages
----------------------
To use the full featureset of Z-Push 2 and the z-push-top command line utility,
additional php packages are required. These provide SOAP support, access to
process control and shared memory.
These packages vary in names between the distributions.
- Generally install the packages: php-cli php-soap
- On Suse (SLES & OpenSuse) install the packages: php53 php53-soap php53-pcntl php53-sysvshm php53-sysvsem php53-posix
- On RHEL based systems install the package: php-cli php-soap php-process
In order to install these packages you need to add an extra channel subscription
from the RHEL Server Optional channel.
How to install
--------------
To install Z-Push, simply untar the z-push archive, e.g. with:
tar -xzvf z-push-[version]-{buildnr}.tar.gz
The tar contains a folder which has the following structure:
z-push-[version]-{buildnr}
The contents of this folder should be copied to /usr/share/z-push.
In a case that /usr/share/z-push does not exist yet, create it with:
mkdir -p /usr/share/z-push
cp -R z-push-[version]-{buildnr}/* /usr/share/z-push/
Edit the config.php file in the Z-Push directory to fit your needs.
If you intend to use Z-Push with Zarafa backend and Zarafa is installed
on the same server, it should work out of the box without changing anything.
Please also set your timezone in the config.php file.
The parameters and their roles are also explained in the config.php file.
By default the state directory is /var/lib/z-push, the log directory /var/log/z-push.
Make sure that these directories exist and are writeable for your webserver
process, so either change the owner of these directories to the UID of
your apache process or make the directories world writeable:
chmod 755 /var/lib/z-push /var/log/z-push
chown apache:apache /var/lib/z-push /var/log/z-push
For the default webserver user please refer to your distribution's manual.
Now, you must configure Apache to redirect the URL
'Microsoft-Server-ActiveSync' to the index.php file in the Z-Push
directory. This can be done by adding the line:
Alias /Microsoft-Server-ActiveSync /usr/share/z-push/index.php
to your httpd.conf file. Make sure that you are adding the line to the
correct part of your Apache configuration, taking care of virtual hosts and
other Apache configurations.
Another possibility is to add this line to z-push.conf file inside the directory
which contents are automatically processed during the webserver start (by
default it is conf.d inside the /etc/apache2 or /etc/httpd depending on your
distribution).
You have to reload your webserver after making these configurations.
*WARNING* You CANNOT simply rename the z-push directory to
Microsoft-Server-ActiveSync. This will cause Apache to send redirects to the
mobile device, which will definitely break your mobile device synchronisation.
Lastly, make sure that PHP has the following settings:
php_flag magic_quotes_gpc off
php_flag register_globals off
php_flag magic_quotes_runtime off
php_flag short_open_tag on
You can set this in the httpd.conf, in php.ini or in an .htaccess file in
the root of z-push.
If you have several php applications on the same system, you could specify the
z-push directory so these settings are considered only there.
<Directory /usr/share/z-push>
php_flag magic_quotes_gpc off
php_flag register_globals off
php_flag magic_quotes_runtime off
php_flag short_open_tag on
</Directory>
If you don't set this up correctly, you will not be
able to login correctly via z-push.
Please also set a memory_limit for php to 128M in php.ini.
Z-Push writes files to your file system like logs or data from the
FileStateMachine (which is default). In order to make this possible,
you either need to disable the php-safe-mode in php.ini or .htaccess with
php_admin_flag safe_mode off
or configure it accordingly, so Z-Push is allowed to write to the
log and state directories.
After doing this, you should be able to synchronize with your mobile device.
To use the command line tools, access the installation directory
(usually /usr/share/z-push) and execute:
./z-push-top.php and/or
./z-push-admin.php
To facilitate the access symbolic links can be created, by executing:
ln -s /usr/share/z-push/z-push-admin.php /usr/sbin/z-push-admin
ln -s /usr/share/z-push/z-push-top.php /usr/sbin/z-push-top
With these symlinks in place the cli tools can be accessed from any
directory and without the php file extension.
Upgrade
-------
Upgrading to a newer Z-Push version follows the same path as the
initial installation.
When upgrading to a new minor version e.g. from Z-Push 1.4 to
Z-Push 1.4.1, the existing Z-Push directory can be overwritten
when extracting the archive. When installing a new major version
it is recommended to extract the tarball to another directory and
to copy the state from the existing installation.
*Important*
It is crucial to always keep the data of the state directory in order
to ensure data consistency on already synchronized mobiles.
Without the state information mobile devices, which already have an
ActiveSync profile, will receive duplicate items or the synchronization
will break completely.
*Important*
Upgrading to Z-Push 2.X from 1.X it is not necessary to copy the state
directory because states are not compatible. However Z-Push 2 implements
a fully automatic resynchronizing of devices in the case states are
missing or faulty.
*Important*
Downgrading from Z-Push 2.X to 1.X is not simple. As the states are not
compatible you would have to follow the procedure for a new installation
and re-create profiles on every device.
*Important*
States of Z-Push 2.0 and Z-Push 2.1 are not compatible. A state migration
script called migrate-2.0.x-2.1.0.php is available in the tools folder.
*Important*
When running Z-Push seperately from your Zarafa installation you had in
the past to configure MAPI_SERVER directly in the config.php of Z-Push.
This setting has now moved to the config.php file of the Zarafa backend
(backend/zarafa/config.php).
Please also observe the published release notes of the new Z-Push version.
For some releases it is necessary to e.g. resynchronize the mobile.
S/MIME
------
Z-Push supports signing and en-/decrypting of emails on mobile devices
since the version 2.0.7.
*Important*
Currently only Android 4.X and higher and iOS 5 and higher devices are
known to support encryption/signing of emails.
It might be possible that PHP functions require CA information in order
to validate certs. Therefore the CAINFO parameter in the config.php
must be configured properly.
The major part of S/MIME deployment is the PKI setup. It includes the
public-private key/certificate obtaining, their management in directory
service and roll-out to the mobile devices. Individual certificates can
either be obtained from a local (company intern) or a public CA. There
are various public CAs offering certificates: commercial ones e.g.
Symantec or Comodo or community-driven e.g. CAcert.org.
Both most popular directory services Microsoft Active Directory (MS AD)
and free open source solution OpenLDAP allow to save certificates. Private
keys/certificates reside in users directory or on a smartcard. Public
certificates are saved in directory. MS AD and OpenLDAP both use
serCertificate attribute to save it.
In Active Directory the public key for contacts from GAB is saved in
PR_EMS_AB_TAGGED_X509_CERT (0x8C6A1102) property and if you save a key
in a contact its PR_USER_X509_CERTIFICATE (0x3A701102).
In LDAP public key for contacts from GAB is saved in userCertificate
property. It should be mapped to 0x3A220102 in ldap.propmap.cfg
(0x3A220102 = userCertificate). Make sure it looks like this in LDAP:
userCertificate;binary
MIIFGjCCBAKgAwIBAgIQbRnqpxlPa…
*Important*
It is strongly recommended to use MS AD or LDAP to manage certificates.
Other user plugin options like db or unix might not work correctly and
are not supported.
For in-depth information please refer to:
http://www.zarafa.com/blog/post/2013/05/smime-z-push-signing-and-en-decrypting-emails-mobile-devices
Setting up your mobile device
-----------------------------
This is simply a case of adding an 'exchange server' to your activesync
server list, specifying the IP address of the Z-Push's apache server,
disabling SSL, unless you have already setup SSL on your Apache server,
setting the correct username and password (the domain is ignored, you can
simply specify 'domain' or some other random string), and then going through
the standard activesync settings.
Once you have done this, you should be able to synchronise your mobile
simply by clicking the 'Sync' button in ActiveSync on your mobile.
*NOTE* using the synchronisation without SSL is not recommended because
your private data is transmitted in clear text over the net. Configuring
SSL on Apache is beyond of the scope of this document. Please refer to
Apache documention available at http://httpd.apache.org/docs/
Troubleshooting
---------------
Most problems will be caused by incorrect Apache settings. To test whether
your Apache setup is working correctly, you can simply type the Z-Push URL
in your browser, to see if apache is correctly redirecting your request to
z-push. You can simply use:
http://<serverip>/Microsoft-Server-ActiveSync
If correctly configured, you should see a username/password request and
when you specify a valid username and password, you should see a Z-Push
information page, saying that this kind of requests is not supported.
Without authentication credentials Z-Push displays general information.
If not then check your PHP and Apache settings and Apache error logs.
If you have other synchronisation problems, you can increase the LOGLEVEL
parameter in the config e.g. to LOGLEVEL_DEBUG or LOGLEVEL_WBXML.
The z-push.log file will then collect detailed debug information from your
synchronisation.
*NOTE* This setting will set Z-Push to log the detailed information for
*every* user on the system. You can set a different log level for particular
users by adding them comma separated to $specialLogUsers in the config.php
e.g. $specialLogUsers = array("user1", "user2", "user3");
*NOTE* Be aware that if you are using LOGLEVEL_DEBUG and LOGLEVEL_WBXML
Z-Push will be quite talkative, so it is advisable to use log-rotate
on the log file.
*Repeated incorrect password messages*
If a password contains characters which are encoded differently in ISO-8859-1
and Windows-1252 encodings (e.g. "§") the login might fail with Z-Push but
it works fine with the WebApp/Webaccess. The solution is to add:
setlocale(LC_CTYPE, "en_US.UTF-8");
to the config.php file.

696
sources/LICENSE Normal file
View file

@ -0,0 +1,696 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.
---
Copyright 2007 - 2013 Zarafa Deutschland GmbH
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License, version 3,
as published by the Free Software Foundation with the following additional
term according to sec. 7:
According to sec. 7 of the GNU Affero General Public License, version 3,
the terms of the AGPL are supplemented with the following terms:
"Zarafa" is a registered trademark of Zarafa B.V.
"Z-Push" is a registered trademark of Zarafa Deutschland GmbH
The licensing of the Program under the AGPL does not imply a trademark license.
Therefore any rights, title and interest in our trademarks remain entirely with us.
However, if you propagate an unmodified version of the Program you are
allowed to use the term "Z-Push" to indicate that you distribute the Program.
Furthermore you may use our trademarks where it is necessary to indicate
the intended purpose of a product or service provided you use it in accordance
with honest practices in industrial or commercial matters.
If you want to propagate modified versions of the Program under the name "Z-Push",
you may only do so if you have a written permission by Zarafa Deutschland GmbH
(to acquire a permission please contact Zarafa at trademark@zarafa.com).
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View file

@ -0,0 +1,419 @@
<?php
/***********************************************
* File : backend/combined/combined.php
* Project : Z-Push
* Descr : Combines several backends. Each type of message
* (Emails, Contacts, Calendar, Tasks) can be handled by
* a separate backend.
* As the CombinedBackend is a subclass of the default Backend
* class, it returns by that the supported AS version is 2.5.
* The method GetSupportedASVersion() could be implemented
* here, checking the version with all backends.
* But still, the lowest version in common must be
* returned, even if some backends support a higher version.
*
* Created : 29.11.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// default backend
include_once('lib/default/backend.php');
//include the CombinedBackend's own config file
require_once("backend/combined/config.php");
require_once("backend/combined/importer.php");
require_once("backend/combined/exporter.php");
class BackendCombined extends Backend {
public $config;
public $backends;
private $activeBackend;
private $activeBackendID;
/**
* Constructor of the combined backend
*
* @access public
*/
public function BackendCombined() {
parent::Backend();
$this->config = BackendCombinedConfig::GetBackendCombinedConfig();
foreach ($this->config['backends'] as $i => $b){
// load and instatiate backend
ZPush::IncludeBackend($b['name']);
$this->backends[$i] = new $b['name']();
}
ZLog::Write(LOGLEVEL_INFO, sprintf("Combined %d backends loaded.", count($this->backends)));
}
/**
* Authenticates the user on each backend
*
* @param string $username
* @param string $domain
* @param string $password
*
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon('%s', '%s',***))", $username, $domain));
if(!is_array($this->backends)){
return false;
}
foreach ($this->backends as $i => $b){
$u = $username;
$d = $domain;
$p = $password;
if(isset($this->config['backends'][$i]['users'])){
if(!isset($this->config['backends'][$i]['users'][$username])){
unset($this->backends[$i]);
continue;
}
if(isset($this->config['backends'][$i]['users'][$username]['username']))
$u = $this->config['backends'][$i]['users'][$username]['username'];
if(isset($this->config['backends'][$i]['users'][$username]['password']))
$p = $this->config['backends'][$i]['users'][$username]['password'];
if(isset($this->config['backends'][$i]['users'][$username]['domain']))
$d = $this->config['backends'][$i]['users'][$username]['domain'];
}
if($this->backends[$i]->Logon($u, $d, $p) == false){
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon() failed on %s ", $this->config['backends'][$i]['name']));
return false;
}
}
ZLog::Write(LOGLEVEL_INFO, "Combined->Logon() success");
return true;
}
/**
* Setup the backend to work on a specific store or checks ACLs there.
* If only the $store is submitted, all Import/Export/Fetch/Etc operations should be
* performed on this store (switch operations store).
* If the ACL check is enabled, this operation should just indicate the ACL status on
* the submitted store, without changing the store for operations.
* For the ACL status, the currently logged on user MUST have access rights on
* - the entire store - admin access if no folderid is sent, or
* - on a specific folderid in the store (secretary/full access rights)
*
* The ACLcheck MUST fail if a folder of the authenticated user is checked!
*
* @param string $store target store, could contain a "domain\user" value
* @param boolean $checkACLonly if set to true, Setup() should just check ACLs
* @param string $folderid if set, only ACLs on this folderid are relevant
*
* @access public
* @return boolean
*/
public function Setup($store, $checkACLonly = false, $folderid = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Setup('%s', '%s', '%s')", $store, Utils::PrintAsString($checkACLonly), $folderid));
if(!is_array($this->backends)){
return false;
}
foreach ($this->backends as $i => $b){
$u = $store;
if(isset($this->config['backends'][$i]['users']) && isset($this->config['backends'][$i]['users'][$store]['username'])){
$u = $this->config['backends'][$i]['users'][$store]['username'];
}
if($this->backends[$i]->Setup($u, $checkACLonly, $folderid) == false){
ZLog::Write(LOGLEVEL_WARN, "Combined->Setup() failed");
return false;
}
}
ZLog::Write(LOGLEVEL_INFO, "Combined->Setup() success");
return true;
}
/**
* Logs off each backend
*
* @access public
* @return boolean
*/
public function Logoff() {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logoff()");
foreach ($this->backends as $i => $b){
$this->backends[$i]->Logoff();
}
ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logoff() success");
return true;
}
/**
* Returns an array of SyncFolder types with the entire folder hierarchy
* from all backends combined
*
* provides AS 1.0 compatibility
*
* @access public
* @return array SYNC_FOLDER
*/
public function GetHierarchy(){
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetHierarchy()");
$ha = array();
foreach ($this->backends as $i => $b){
if(!empty($this->config['backends'][$i]['subfolder'])){
$f = new SyncFolder();
$f->serverid = $i.$this->config['delimiter'].'0';
$f->parentid = '0';
$f->displayname = $this->config['backends'][$i]['subfolder'];
$f->type = SYNC_FOLDER_TYPE_OTHER;
$ha[] = $f;
}
$h = $this->backends[$i]->GetHierarchy();
if(is_array($h)){
foreach($h as $j => $f){
$h[$j]->serverid = $i.$this->config['delimiter'].$h[$j]->serverid;
if($h[$j]->parentid != '0' || !empty($this->config['backends'][$i]['subfolder'])){
$h[$j]->parentid = $i.$this->config['delimiter'].$h[$j]->parentid;
}
if(isset($this->config['folderbackend'][$h[$j]->type]) && $this->config['folderbackend'][$h[$j]->type] != $i){
$h[$j]->type = SYNC_FOLDER_TYPE_OTHER;
}
}
$ha = array_merge($ha, $h);
}
}
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetHierarchy() success");
return $ha;
}
/**
* Returns the importer to process changes from the mobile
*
* @param string $folderid (opt)
*
* @access public
* @return object(ImportChanges)
*/
public function GetImporter($folderid = false) {
if($folderid !== false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetImporter() Content: ImportChangesCombined:('%s')", $folderid));
// get the contents importer from the folder in a backend
// the importer is wrapped to check foldernames in the ImportMessageMove function
$backend = $this->GetBackend($folderid);
if($backend === false)
return false;
$importer = $backend->GetImporter($this->GetBackendFolder($folderid));
if($importer){
return new ImportChangesCombined($this, $folderid, $importer);
}
return false;
}
else {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetImporter() -> Hierarchy: ImportChangesCombined()");
//return our own hierarchy importer which send each change to the right backend
return new ImportChangesCombined($this);
}
}
/**
* Returns the exporter to send changes to the mobile
* the exporter from right backend for contents exporter and our own exporter for hierarchy exporter
*
* @param string $folderid (opt)
*
* @access public
* @return object(ExportChanges)
*/
public function GetExporter($folderid = false){
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetExporter('%s')", $folderid));
if($folderid){
$backend = $this->GetBackend($folderid);
if($backend == false)
return false;
return $backend->GetExporter($this->GetBackendFolder($folderid));
}
return new ExportChangesCombined($this);
}
/**
* Sends an e-mail
* This messages needs to be saved into the 'sent items' folder
*
* @param SyncSendMail $sm SyncSendMail object
*
* @access public
* @return boolean
* @throws StatusException
*/
public function SendMail($sm) {
ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()");
foreach ($this->backends as $i => $b){
if($this->backends[$i]->SendMail($sm) == true){
return true;
}
}
return false;
}
/**
* Returns all available data of a single message
*
* @param string $folderid
* @param string $id
* @param ContentParameters $contentparameters flag
*
* @access public
* @return object(SyncObject)
* @throws StatusException
*/
public function Fetch($folderid, $id, $contentparameters) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Fetch('%s', '%s', CPO)", $folderid, $id));
$backend = $this->GetBackend($folderid);
if($backend == false)
return false;
return $backend->Fetch($this->GetBackendFolder($folderid), $id, $contentparameters);
}
/**
* Returns the waste basket
* If the wastebasket is set to one backend, return the wastebasket of that backend
* else return the first waste basket we can find
*
* @access public
* @return string
*/
function GetWasteBasket(){
ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetWasteBasket()");
if (isset($this->activeBackend)) {
if (!$this->activeBackend->GetWasteBasket())
return false;
else
return $this->activeBackendID . $this->config['delimiter'] . $this->activeBackend->GetWasteBasket();
}
return false;
}
/**
* Returns the content of the named attachment as stream.
* There is no way to tell which backend the attachment is from, so we try them all
*
* @param string $attname
*
* @access public
* @return SyncItemOperationsAttachment
* @throws StatusException
*/
public function GetAttachmentData($attname) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetAttachmentData('%s')", $attname));
foreach ($this->backends as $i => $b) {
try {
$attachment = $this->backends[$i]->GetAttachmentData($attname);
if ($attachment instanceof SyncItemOperationsAttachment)
return $attachment;
}
catch (StatusException $s) {
// backends might throw StatusExceptions if it's not their attachment
}
}
throw new StatusException("Combined->GetAttachmentData(): no backend found", SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
}
/**
* Processes a response to a meeting request.
*
* @param string $requestid id of the object containing the request
* @param string $folderid id of the parent folder of $requestid
* @param string $response
*
* @access public
* @return string id of the created/updated calendar obj
* @throws StatusException
*/
public function MeetingResponse($requestid, $folderid, $error) {
$backend = $this->GetBackend($folderid);
if($backend === false)
return false;
return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $error);
}
/**
* Finds the correct backend for a folder
*
* @param string $folderid combinedid of the folder
*
* @access public
* @return object
*/
public function GetBackend($folderid){
$pos = strpos($folderid, $this->config['delimiter']);
if($pos === false)
return false;
$id = substr($folderid, 0, $pos);
if(!isset($this->backends[$id]))
return false;
$this->activeBackend = $this->backends[$id];
$this->activeBackendID = $id;
return $this->backends[$id];
}
/**
* Returns an understandable folderid for the backend
*
* @param string $folderid combinedid of the folder
*
* @access public
* @return string
*/
public function GetBackendFolder($folderid){
$pos = strpos($folderid, $this->config['delimiter']);
if($pos === false)
return false;
return substr($folderid,$pos + strlen($this->config['delimiter']));
}
/**
* Returns backend id for a folder
*
* @param string $folderid combinedid of the folder
*
* @access public
* @return object
*/
public function GetBackendId($folderid){
$pos = strpos($folderid, $this->config['delimiter']);
if($pos === false)
return false;
return substr($folderid,0,$pos);
}
}
?>

View file

@ -0,0 +1,106 @@
<?php
/***********************************************
* File : backend/combined/config.php
* Project : Z-Push
* Descr : configuration file for the
* combined backend.
*
* Created : 29.11.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class BackendCombinedConfig {
// *************************
// BackendCombined settings
// *************************
/**
* Returns the configuration of the combined backend
*
* @access public
* @return array
*
*/
public static function GetBackendCombinedConfig() {
//use a function for it because php does not allow
//assigning variables to the class members (expecting T_STRING)
return array(
//the order in which the backends are loaded.
//login only succeeds if all backend return true on login
//sending mail: the mail is sent with first backend that is able to send the mail
'backends' => array(
'i' => array(
'name' => 'BackendIMAP',
),
'z' => array(
'name' => 'BackendZarafa',
),
'm' => array(
'name' => 'BackendMaildir',
),
'v' => array(
'name' => 'BackendVCardDir',
),
),
'delimiter' => '/',
//force one type of folder to one backend
//it must match one of the above defined backends
'folderbackend' => array(
SYNC_FOLDER_TYPE_INBOX => 'i',
SYNC_FOLDER_TYPE_DRAFTS => 'i',
SYNC_FOLDER_TYPE_WASTEBASKET => 'i',
SYNC_FOLDER_TYPE_SENTMAIL => 'i',
SYNC_FOLDER_TYPE_OUTBOX => 'i',
SYNC_FOLDER_TYPE_TASK => 'z',
SYNC_FOLDER_TYPE_APPOINTMENT => 'z',
SYNC_FOLDER_TYPE_CONTACT => 'z',
SYNC_FOLDER_TYPE_NOTE => 'z',
SYNC_FOLDER_TYPE_JOURNAL => 'z',
SYNC_FOLDER_TYPE_OTHER => 'i',
SYNC_FOLDER_TYPE_USER_MAIL => 'i',
SYNC_FOLDER_TYPE_USER_APPOINTMENT => 'z',
SYNC_FOLDER_TYPE_USER_CONTACT => 'z',
SYNC_FOLDER_TYPE_USER_TASK => 'z',
SYNC_FOLDER_TYPE_USER_JOURNAL => 'z',
SYNC_FOLDER_TYPE_USER_NOTE => 'z',
SYNC_FOLDER_TYPE_UNKNOWN => 'z',
),
//creating a new folder in the root folder should create a folder in one backend
'rootcreatefolderbackend' => 'i',
);
}
}
?>

View file

@ -0,0 +1,184 @@
<?php
/***********************************************
* File : backend/combined/exporter.php
* Project : Z-Push
* Descr : Exporter class for the combined backend.
*
* Created : 11.05.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* the ExportChangesCombined class is returned from GetExporter for changes.
* It combines the changes from all backends and prepends all folderids with the backendid
*/
class ExportChangesCombined implements IExportChanges {
private $backend;
private $syncstates;
private $exporters;
private $importer;
private $importwraps;
public function ExportChangesCombined(&$backend) {
$this->backend =& $backend;
$this->exporters = array();
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined constructed");
}
/**
* Initializes the state and flags
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean status flag
*/
public function Config($syncstate, $flags = 0) {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Config(...)");
$this->syncstates = $syncstate;
if(!is_array($this->syncstates)){
$this->syncstates = array();
}
foreach($this->backend->backends as $i => $b){
if(isset($this->syncstates[$i])){
$state = $this->syncstates[$i];
} else {
$state = '';
}
$this->exporters[$i] = $this->backend->backends[$i]->GetExporter();
$this->exporters[$i]->Config($state, $flags);
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Config() success");
}
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount() {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetChangeCount()");
$c = 0;
foreach($this->exporters as $i => $e){
$c += $this->exporters[$i]->GetChangeCount();
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetChangeCount() success");
return $c;
}
/**
* Synchronizes a change to the configured importer
*
* @access public
* @return array with status information
*/
public function Synchronize() {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Synchronize()");
foreach($this->exporters as $i => $e){
if(!empty($this->backend->config['backends'][$i]['subfolder']) && !isset($this->syncstates[$i])){
// first sync and subfolder backend
$f = new SyncFolder();
$f->serverid = $i.$this->backend->config['delimiter'].'0';
$f->parentid = '0';
$f->displayname = $this->backend->config['backends'][$i]['subfolder'];
$f->type = SYNC_FOLDER_TYPE_OTHER;
$this->importer->ImportFolderChange($f);
}
while(is_array($this->exporters[$i]->Synchronize()));
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Synchronize() success");
return true;
}
/**
* Reads and returns the current state
*
* @access public
* @return string
*/
public function GetState() {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetState()");
foreach($this->exporters as $i => $e){
$this->syncstates[$i] = $this->exporters[$i]->GetState();
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetState() success");
return $this->syncstates;
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters) {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->ConfigContentParameters()");
foreach($this->exporters as $i => $e){
//call the ConfigContentParameters() of each exporter
$e->ConfigContentParameters($contentparameters);
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->ConfigContentParameters() success");
}
/**
* Sets the importer where the exporter will sent its changes to
* This exporter should also be ready to accept calls after this
*
* @param object &$importer Implementation of IImportChanges
*
* @access public
* @return boolean
*/
public function InitializeExporter(&$importer) {
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)");
foreach ($this->exporters as $i => $e) {
if(!isset($this->_importwraps[$i])){
$this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer);
}
$e->InitializeExporter($this->importwraps[$i]);
}
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...) success");
}
}
?>

View file

@ -0,0 +1,353 @@
<?php
/***********************************************
* File : backend/combined/importer.php
* Project : Z-Push
* Descr : Importer class for the combined backend.
*
* Created : 11.05.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ImportChangesCombined implements IImportChanges {
private $backend;
private $folderid;
private $icc;
/**
* Constructor of the ImportChangesCombined class
*
* @param object $backend
* @param string $folderid
* @param object $importer
*
* @access public
*/
public function ImportChangesCombined(&$backend, $folderid = false, $icc = false) {
$this->backend = $backend;
$this->folderid = $folderid;
$this->icc = &$icc;
}
/**
* Loads objects which are expected to be exported with the state
* Before importing/saving the actual message from the mobile, a conflict detection should be done
*
* @param ContentParameters $contentparameters class of objects
* @param string $state
*
* @access public
* @return boolean
* @throws StatusException
*/
public function LoadConflicts($contentparameters, $state) {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->LoadConflicts() icc not configured");
return false;
}
$this->icc->LoadConflicts($contentparameters, $state);
}
/**
* Imports a single message
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean/string failure / id of message
*/
public function ImportMessageChange($id, $message) {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageChange() icc not configured");
return false;
}
return $this->icc->ImportMessageChange($id, $message);
}
/**
* Imports a deletion. This may conflict if the local object has been modified
*
* @param string $id
*
* @access public
* @return boolean
*/
public function ImportMessageDeletion($id) {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageDeletion() icc not configured");
return false;
}
return $this->icc->ImportMessageDeletion($id);
}
/**
* Imports a change in 'read' flag
* This can never conflict
*
* @param string $id
* @param int $flags
*
* @access public
* @return boolean
*/
public function ImportMessageReadFlag($id, $flags) {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageReadFlag() icc not configured");
return false;
}
return $this->icc->ImportMessageReadFlag($id, $flags);
}
/**
* Imports a move of a message. This occurs when a user moves an item to another folder
*
* @param string $id
* @param string $newfolder
*
* @access public
* @return boolean
*/
public function ImportMessageMove($id, $newfolder) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportMessageMove('%s', '%s')", $id, $newfolder));
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageMove icc not configured");
return false;
}
if($this->backend->GetBackendId($this->folderid) != $this->backend->GetBackendId($newfolder)){
ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportMessageMove() cannot move message between two backends");
return false;
}
return $this->icc->ImportMessageMove($id, $this->backend->GetBackendFolder($newfolder));
}
/**----------------------------------------------------------------------------------------------------------
* Methods to import hierarchy
*/
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return boolean/string status/id of the folder
*/
public function ImportFolderChange($folder) {
$id = $folder->serverid;
$parent = $folder->parentid;
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ImportFolderChange() ".print_r($folder, 1));
if($parent == '0') {
if($id) {
$backendid = $this->backend->GetBackendId($id);
}
else {
$backendid = $this->backend->config['rootcreatefolderbackend'];
}
}
else {
$backendid = $this->backend->GetBackendId($parent);
$parent = $this->backend->GetBackendFolder($parent);
}
if(!empty($this->backend->config['backends'][$backendid]['subfolder']) && $id == $backendid.$this->backend->config['delimiter'].'0') {
ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderChange() cannot change static folder");
return false;
}
if($id != false) {
if($backendid != $this->backend->GetBackendId($id)) {
ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderChange() cannot move folder between two backends");
return false;
}
$id = $this->backend->GetBackendFolder($id);
}
$this->icc = $this->backend->getBackend($backendid)->GetImporter();
$res = $this->icc->ImportFolderChange($folder);
ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->ImportFolderChange() success');
return $backendid.$this->backend->config['delimiter'].$res;
}
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent id
*
* @access public
* @return boolean/int success/SYNC_FOLDERHIERARCHY_STATUS
*/
public function ImportFolderDeletion($id, $parent = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportFolderDeletion('%s', '%s'), $id, $parent"));
$backendid = $this->backend->GetBackendId($id);
if(!empty($this->backend->config['backends'][$backendid]['subfolder']) && $id == $backendid.$this->backend->config['delimiter'].'0') {
ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderDeletion() cannot change static folder");
return false; //we can not change a static subfolder
}
$backend = $this->backend->GetBackend($id);
$id = $this->backend->GetBackendFolder($id);
if($parent != '0')
$parent = $this->backend->GetBackendFolder($parent);
$this->icc = $backend->GetImporter();
$res = $this->icc->ImportFolderDeletion($id, $parent);
ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->ImportFolderDeletion() success');
return $res;
}
/**
* Initializes the state and flags
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean status flag
*/
public function Config($state, $flags = 0) {
ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->Config(...)');
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->Config() icc not configured");
return false;
}
$this->icc->Config($state, $flags);
ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->Config() success');
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters) {
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ConfigContentParameters()");
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ConfigContentParameters() icc not configured");
return false;
}
$this->icc->ConfigContentParameters($contentparameters);
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ConfigContentParameters() success");
}
/**
* Reads and returns the current state
*
* @access public
* @return string
*/
public function GetState() {
if (!$this->icc) {
ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->GetState() icc not configured");
return false;
}
return $this->icc->GetState();
}
}
/**
* The ImportHierarchyChangesCombinedWrap class wraps the importer given in ExportChangesCombined->Config.
* It prepends the backendid to all folderids and checks foldertypes.
*/
class ImportHierarchyChangesCombinedWrap {
private $ihc;
private $backend;
private $backendid;
/**
* Constructor of the ImportChangesCombined class
*
* @param string $backendid
* @param object $backend
* @param object $ihc
*
* @access public
*/
public function ImportHierarchyChangesCombinedWrap($backendid, &$backend, &$ihc) {
ZLog::Write(LOGLEVEL_DEBUG, "ImportHierarchyChangesCombinedWrap->ImportHierarchyChangesCombinedWrap('$backendid',...)");
$this->backendid = $backendid;
$this->backend =& $backend;
$this->ihc = &$ihc;
}
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return boolean/string status/id of the folder
*/
public function ImportFolderChange($folder) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportHierarchyChangesCombinedWrap->ImportFolderChange('%s')", $folder->serverid));
$folder->serverid = $this->backendid.$this->backend->config['delimiter'].$folder->serverid;
if($folder->parentid != '0' || !empty($this->backend->config['backends'][$this->backendid]['subfolder'])){
$folder->parentid = $this->backendid.$this->backend->config['delimiter'].$folder->parentid;
}
if(isset($this->backend->config['folderbackend'][$folder->type]) && $this->backend->config['folderbackend'][$folder->type] != $this->backendid){
ZLog::Write(LOGLEVEL_DEBUG, sprintf("not using folder: '%s' ('%s')", $folder->displayname, $folder->serverid));
return true;
}
ZLog::Write(LOGLEVEL_DEBUG, "ImportHierarchyChangesCombinedWrap->ImportFolderChange() success");
return $this->ihc->ImportFolderChange($folder);
}
/**
* Imports a folder deletion
*
* @param string $id
*
* @access public
*
* @return boolean/int success/SYNC_FOLDERHIERARCHY_STATUS
*/
public function ImportFolderDeletion($id) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportHierarchyChangesCombinedWrap->ImportFolderDeletion('%s')", $id));
return $this->ihc->ImportFolderDeletion($this->backendid.$this->backend->config['delimiter'].$id);
}
}
?>

View file

@ -0,0 +1,78 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : IMAP backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// ************************
// BackendIMAP settings
// ************************
// Defines the server to which we want to connect
define('IMAP_SERVER', 'localhost');
// connecting to default port (143)
define('IMAP_PORT', 143);
// best cross-platform compatibility (see http://php.net/imap_open for options)
define('IMAP_OPTIONS', '/notls/norsh');
// overwrite the "from" header if it isn't set when sending emails
// options: 'username' - the username will be set (usefull if your login is equal to your emailaddress)
// 'domain' - the value of the "domain" field is used
// '@mydomain.com' - the username is used and the given string will be appended
define('IMAP_DEFAULTFROM', '');
// copy outgoing mail to this folder. If not set z-push will try the default folders
define('IMAP_SENTFOLDER', '');
// forward messages inline (default false - as attachment)
define('IMAP_INLINE_FORWARD', false);
// use imap_mail() to send emails (default) - if false mail() is used
define('IMAP_USE_IMAPMAIL', true);
/* BEGIN fmbiete's contribution r1527, ZP-319 */
// list of folders we want to exclude from sync. Names, or part of it, separated by |
// example: dovecot.sieve|archive|spam
define('IMAP_EXCLUDED_FOLDERS', '');
/* END fmbiete's contribution r1527, ZP-319 */
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : Maildir backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// ************************
// BackendMaildir settings
// ************************
define('MAILDIR_BASE', '/tmp');
define('MAILDIR_SUBDIR', 'Maildir');
?>

View file

@ -0,0 +1,723 @@
<?php
/***********************************************
* File : maildir.php
* Project : Z-Push
* Descr : This backend is based on
* 'BackendDiff' which handles the
* intricacies of generating
* differentials from static
* snapshots. This means that the
* implementation here needs no
* state information, and can simply
* return the current state of the
* messages. The diffbackend will
* then compare the current state
* to the known last state of the PDA
* and generate change increments
* from that.
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// config file
require_once("backend/maildir/config.php");
include_once('lib/default/diffbackend/diffbackend.php');
include_once('include/mimeDecode.php');
require_once('include/z_RFC822.php');
class BackendMaildir extends BackendDiff {
/**----------------------------------------------------------------------------------------------------------
* default backend methods
*/
/**
* Authenticates the user - NOT EFFECTIVELY IMPLEMENTED
* Normally some kind of password check would be done here.
* Alternatively, the password could be ignored and an Apache
* authentication via mod_auth_* could be done
*
* @param string $username
* @param string $domain
* @param string $password
*
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
return true;
}
/**
* Logs off
*
* @access public
* @return boolean
*/
public function Logoff() {
return true;
}
/**
* Sends an e-mail
* Not implemented here
*
* @param SyncSendMail $sm SyncSendMail object
*
* @access public
* @return boolean
* @throws StatusException
*/
public function SendMail($sm) {
return false;
}
/**
* Returns the waste basket
*
* @access public
* @return string
*/
public function GetWasteBasket() {
return false;
}
/**
* Returns the content of the named attachment as stream. The passed attachment identifier is
* the exact string that is returned in the 'AttName' property of an SyncAttachment.
* Any information necessary to find the attachment must be encoded in that 'attname' property.
* Data is written directly (with print $data;)
*
* @param string $attname
*
* @access public
* @return SyncItemOperationsAttachment
* @throws StatusException
*/
public function GetAttachmentData($attname) {
list($id, $part) = explode(":", $attname);
$fn = $this->findMessage($id);
if ($fn == false)
throw new StatusException(sprintf("BackendMaildir->GetAttachmentData('%s'): Error, requested message/attachment can not be found", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT);
// Parse e-mail
$rfc822 = file_get_contents($this->getPath() . "/$fn");
$message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
include_once('include/stringstreamwrapper.php');
$attachment = new SyncItemOperationsAttachment();
$attachment->data = StringStreamWrapper::Open($message->parts[$part]->body);
if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary))
$attachment->contenttype = $message->parts[$part]->ctype_primary .'/'.$message->parts[$part]->ctype_secondary;
return $attachment;
}
/**----------------------------------------------------------------------------------------------------------
* implemented DiffBackend methods
*/
/**
* Returns a list (array) of folders.
* In simple implementations like this one, probably just one folder is returned.
*
* @access public
* @return array
*/
public function GetFolderList() {
$folders = array();
$inbox = array();
$inbox["id"] = "root";
$inbox["parent"] = "0";
$inbox["mod"] = "Inbox";
$folders[]=$inbox;
$sub = array();
$sub["id"] = "sub";
$sub["parent"] = "root";
$sub["mod"] = "Sub";
// $folders[]=$sub;
return $folders;
}
/**
* Returns an actual SyncFolder object
*
* @param string $id id of the folder
*
* @access public
* @return object SyncFolder with information
*/
public function GetFolder($id) {
if($id == "root") {
$inbox = new SyncFolder();
$inbox->serverid = $id;
$inbox->parentid = "0"; // Root
$inbox->displayname = "Inbox";
$inbox->type = SYNC_FOLDER_TYPE_INBOX;
return $inbox;
} else if($id == "sub") {
$inbox = new SyncFolder();
$inbox->serverid = $id;
$inbox->parentid = "root";
$inbox->displayname = "Sub";
$inbox->type = SYNC_FOLDER_TYPE_OTHER;
return $inbox;
} else {
return false;
}
}
/**
* Returns folder stats. An associative array with properties is expected.
*
* @param string $id id of the folder
*
* @access public
* @return array
*/
public function StatFolder($id) {
$folder = $this->GetFolder($id);
$stat = array();
$stat["id"] = $id;
$stat["parent"] = $folder->parentid;
$stat["mod"] = $folder->displayname;
return $stat;
}
/**
* Creates or modifies a folder
* not implemented
*
* @param string $folderid id of the parent folder
* @param string $oldid if empty -> new folder created, else folder is to be renamed
* @param string $displayname new folder name (to be created, or to be renamed to)
* @param int $type folder type
*
* @access public
* @return boolean status
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*
*/
public function ChangeFolder($folderid, $oldid, $displayname, $type){
return false;
}
/**
* Deletes a folder
*
* @param string $id
* @param string $parent is normally false
*
* @access public
* @return boolean status - false if e.g. does not exist
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*
*/
public function DeleteFolder($id, $parentid){
return false;
}
/**
* Returns a list (array) of messages
*
* @param string $folderid id of the parent folder
* @param long $cutoffdate timestamp in the past from which on messages should be returned
*
* @access public
* @return array/false array with messages or false if folder is not available
*/
public function GetMessageList($folderid, $cutoffdate) {
$this->moveNewToCur();
if($folderid != "root")
return false;
// return stats of all messages in a dir. We can do this faster than
// just calling statMessage() on each message; We still need fstat()
// information though, so listing 10000 messages is going to be
// rather slow (depending on filesystem, etc)
// we also have to filter by the specified cutoffdate so only the
// last X days are retrieved. Normally, this would mean that we'd
// have to open each message, get the Received: header, and check
// whether that is in the filter range. Because this is much too slow, we
// are depending on the creation date of the message instead, which should
// normally be just about the same, unless you just did some kind of import.
$messages = array();
$dirname = $this->getPath();
$dir = opendir($dirname);
if(!$dir)
return false;
while($entry = readdir($dir)) {
if($entry{0} == ".")
continue;
$message = array();
$stat = stat("$dirname/$entry");
if($stat["mtime"] < $cutoffdate) {
// message is out of range for curoffdate, ignore it
continue;
}
$message["mod"] = $stat["mtime"];
$matches = array();
// Flags according to http://cr.yp.to/proto/maildir.html (pretty authoritative - qmail author's website)
if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$entry,$matches))
continue;
$message["id"] = $matches[1];
$message["flags"] = 0;
if(strpos($matches[2],"S") !== false) {
$message["flags"] |= 1; // 'seen' aka 'read' is the only flag we want to know about
}
array_push($messages, $message);
}
return $messages;
}
/**
* Returns the actual SyncXXX object type.
*
* @param string $folderid id of the parent folder
* @param string $id id of the message
* @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc)
*
* @access public
* @return object/false false if the message could not be retrieved
*/
public function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) {
if($folderid != 'root')
return false;
$fn = $this->findMessage($id);
// Get flags, etc
$stat = $this->StatMessage($folderid, $id);
// Parse e-mail
$rfc822 = file_get_contents($this->getPath() . "/" . $fn);
$message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
$output = new SyncMail();
$output->body = str_replace("\n", "\r\n", $this->getBody($message));
$output->bodysize = strlen($output->body);
$output->bodytruncated = 0; // We don't implement truncation in this backend
$output->datereceived = $this->parseReceivedDate($message->headers["received"][0]);
$output->messageclass = "IPM.Note";
$output->subject = $message->headers["subject"];
$output->read = $stat["flags"];
$output->from = $message->headers["from"];
$Mail_RFC822 = new Mail_RFC822();
$toaddr = $ccaddr = $replytoaddr = array();
if(isset($message->headers["to"]))
$toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]);
if(isset($message->headers["cc"]))
$ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]);
if(isset($message->headers["reply_to"]))
$replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]);
$output->to = array();
$output->cc = array();
$output->reply_to = array();
foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) {
foreach($addrlist as $addr) {
$address = $addr->mailbox . "@" . $addr->host;
$name = $addr->personal;
if (!isset($output->displayto) && $name != "")
$output->displayto = $name;
if($name == "" || $name == $address)
$fulladdr = w2u($address);
else {
if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') {
$fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">";
}
else {
$fulladdr = w2u($name) ." <" . w2u($address) . ">";
}
}
array_push($output->$type, $fulladdr);
}
}
// convert mime-importance to AS-importance
if (isset($message->headers["x-priority"])) {
$mimeImportance = preg_replace("/\D+/", "", $message->headers["x-priority"]);
if ($mimeImportance > 3)
$output->importance = 0;
if ($mimeImportance == 3)
$output->importance = 1;
if ($mimeImportance < 3)
$output->importance = 2;
}
// Attachments are only searched in the top-level part
$n = 0;
if(isset($message->parts)) {
foreach($message->parts as $part) {
if($part->ctype_primary == "application") {
$attachment = new SyncAttachment();
$attachment->attsize = strlen($part->body);
if(isset($part->d_parameters['filename']))
$attname = $part->d_parameters['filename'];
else if(isset($part->ctype_parameters['name']))
$attname = $part->ctype_parameters['name'];
else if(isset($part->headers['content-description']))
$attname = $part->headers['content-description'];
else $attname = "unknown attachment";
$attachment->displayname = $attname;
$attachment->attname = $id . ":" . $n;
$attachment->attmethod = 1;
$attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : "";
array_push($output->attachments, $attachment);
}
$n++;
}
}
return $output;
}
/**
* Returns message stats, analogous to the folder stats from StatFolder().
*
* @param string $folderid id of the folder
* @param string $id id of the message
*
* @access public
* @return array
*/
public function StatMessage($folderid, $id) {
$dirname = $this->getPath();
$fn = $this->findMessage($id);
if(!$fn)
return false;
$stat = stat("$dirname/$fn");
$entry = array();
$entry["id"] = $id;
$entry["flags"] = 0;
if(strpos($fn,"S"))
$entry["flags"] |= 1;
$entry["mod"] = $stat["mtime"];
return $entry;
}
/**
* Called when a message has been changed on the mobile.
* This functionality is not available for emails.
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param SyncXXX $message the SyncObject containing a message
* @param ContentParameters $contentParameters
*
* @access public
* @return array same return value as StatMessage()
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function ChangeMessage($folderid, $id, $message, $contentParameters) {
// TODO SyncInterval check + ContentParameters
// see https://jira.zarafa.com/browse/ZP-258 for details
// before changing the message, it should be checked if the message is in the SyncInterval
// to determine the cutoffdate use Utils::GetCutOffDate($contentparameters->GetFilterType());
// if the message is not in the interval an StatusException with code SYNC_STATUS_SYNCCANNOTBECOMPLETED should be thrown
return false;
}
/**
* Changes the 'read' flag of a message on disk
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags read flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function SetReadFlag($folderid, $id, $flags, $contentParameters) {
if($folderid != 'root')
return false;
// TODO SyncInterval check + ContentParameters
// see https://jira.zarafa.com/browse/ZP-258 for details
// before setting the read flag, it should be checked if the message is in the SyncInterval
// to determine the cutoffdate use Utils::GetCutOffDate($contentparameters->GetFilterType());
// if the message is not in the interval an StatusException with code SYNC_STATUS_OBJECTNOTFOUND should be thrown
$fn = $this->findMessage($id);
if(!$fn)
return true; // message may have been deleted
if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$fn,$matches))
return false;
// remove 'seen' (S) flag
if(!$flags) {
$newflags = str_replace("S","",$matches[2]);
} else {
// make sure we don't double add the 'S' flag
$newflags = str_replace("S","",$matches[2]) . "S";
}
$newfn = $matches[1] . ":2," . $newflags;
// rename if required
if($fn != $newfn)
rename($this->getPath() ."/$fn", $this->getPath() . "/$newfn");
return true;
}
/**
* Called when the user has requested to delete (really delete) a message
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function DeleteMessage($folderid, $id, $contentParameters) {
if($folderid != 'root')
return false;
// TODO SyncInterval check + ContentParameters
// see https://jira.zarafa.com/browse/ZP-258 for details
// before deleting the message, it should be checked if the message is in the SyncInterval
// to determine the cutoffdate use Utils::GetCutOffDate($contentparameters->GetFilterType());
// if the message is not in the interval an StatusException with code SYNC_STATUS_OBJECTNOTFOUND should be thrown
$fn = $this->findMessage($id);
if(!$fn)
return true; // success because message has been deleted already
if(!unlink($this->getPath() . "/$fn")) {
return true; // success - message may have been deleted in the mean time (since findMessage)
}
return true;
}
/**
* Called when the user moves an item on the PDA from one folder to another
* not implemented
*
* @param string $folderid id of the source folder
* @param string $id id of the message
* @param string $newfolderid id of the destination folder
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
*/
public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) {
return false;
}
/**----------------------------------------------------------------------------------------------------------
* private maildir-specific internals
*/
/**
* Searches for the message
*
* @param string $id id of the message
*
* @access private
* @return string
*/
private function findMessage($id) {
// We could use 'this->folderid' for path info but we currently
// only support a single INBOX. We also have to use a glob '*'
// because we don't know the flags of the message we're looking for.
$dirname = $this->getPath();
$dir = opendir($dirname);
while($entry = readdir($dir)) {
if(strpos($entry,$id) === 0)
return $entry;
}
return false; // not found
}
/**
* Parses the message and return only the plaintext body
*
* @param string $message html message
*
* @access private
* @return string plaintext message
*/
private function getBody($message) {
$body = "";
$htmlbody = "";
$this->getBodyRecursive($message, "plain", $body);
if(!isset($body) || $body === "") {
$this->getBodyRecursive($message, "html", $body);
// remove css-style tags
$body = preg_replace("/<style.*?<\/style>/is", "", $body);
// remove all other html
$body = strip_tags($body);
}
return $body;
}
/**
* Get all parts in the message with specified type and concatenate them together, unless the
* Content-Disposition is 'attachment', in which case the text is apparently an attachment
*
* @param string $message mimedecode message(part)
* @param string $message message subtype
* @param string &$body body reference
*
* @access private
* @return
*/
private function getBodyRecursive($message, $subtype, &$body) {
if(!isset($message->ctype_primary)) return;
if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
$body .= $message->body;
if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) {
foreach($message->parts as $part) {
if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment")) {
$this->getBodyRecursive($part, $subtype, $body);
}
}
}
}
/**
* Parses the received date
*
* @param string $received received date string
*
* @access private
* @return long
*/
private function parseReceivedDate($received) {
$pos = strpos($received, ";");
if(!$pos)
return false;
$datestr = substr($received, $pos+1);
$datestr = ltrim($datestr);
return strtotime($datestr);
}
/**
* Moves everything in Maildir/new/* to Maildir/cur/
*
* @access private
* @return
*/
private function moveNewToCur() {
$newdirname = MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/new";
$newdir = opendir($newdirname);
while($newentry = readdir($newdir)) {
if($newentry{0} == ".")
continue;
// link/unlink == move. This is the way to move the message according to cr.yp.to
link($newdirname . "/" . $newentry, $this->getPath() . "/" . $newentry . ":2,");
unlink($newdirname . "/" . $newentry);
}
}
/**
* The path we're working on
*
* @access private
* @return string
*/
private function getPath() {
return MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/cur";
}
}
?>

View file

@ -0,0 +1,75 @@
<?php
/***********************************************
* File : searchldap/config.php
* Project : Z-Push
* Descr : configuration file for the
* BackendSearchLDAP backend.
*
* Created : 03.08.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// LDAP host and port
define("LDAP_HOST", "ldap://127.0.0.1/");
define("LDAP_PORT", "389");
// Set USER and PASSWORD if not using anonymous bind
define("ANONYMOUS_BIND", true);
define("LDAP_BIND_USER", "cn=searchuser,dc=test,dc=net");
define("LDAP_BIND_PASSWORD", "");
// Search base & filter
// the SEARCHVALUE string is substituded by the value inserted into the search field
define("LDAP_SEARCH_BASE", "ou=global,dc=test,dc=net");
define("LDAP_SEARCH_FILTER", "(|(cn=*SEARCHVALUE*)(mail=*SEARCHVALUE*))");
// LDAP field mapping.
// values correspond to an inetOrgPerson class
global $ldap_field_map;
$ldap_field_map = array(
SYNC_GAL_DISPLAYNAME => 'cn',
SYNC_GAL_PHONE => 'telephonenumber',
SYNC_GAL_OFFICE => '',
SYNC_GAL_TITLE => 'title',
SYNC_GAL_COMPANY => 'ou',
SYNC_GAL_ALIAS => 'uid',
SYNC_GAL_FIRSTNAME => 'givenname',
SYNC_GAL_LASTNAME => 'sn',
SYNC_GAL_HOMEPHONE => 'homephone',
SYNC_GAL_MOBILEPHONE => 'mobile',
SYNC_GAL_EMAILADDRESS => 'mail',
);
?>

View file

@ -0,0 +1,198 @@
<?php
/***********************************************
* File : searchLDAP.php
* Project : Z-Push
* Descr : A ISearchProvider implementation to
* query a ldap server for GAL
* information.
*
* Created : 03.08.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
require_once("backend/searchldap/config.php");
class BackendSearchLDAP implements ISearchProvider {
private $connection;
/**
* Initializes the backend to perform the search
* Connects to the LDAP server using the values from the configuration
*
*
* @access public
* @return
* @throws StatusException
*/
public function BackendSearchLDAP() {
if (!function_exists("ldap_connect"))
throw new StatusException("BackendSearchLDAP(): php-ldap is not installed. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL);
// connect to LDAP
$this->connection = @ldap_connect(LDAP_HOST, LDAP_PORT);
@ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3);
// Authenticate
if (constant('ANONYMOUS_BIND') === true) {
if(! @ldap_bind($this->connection)) {
$this->connection = false;
throw new StatusException("BackendSearchLDAP(): Could not bind anonymously to server! Search aborted.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR);
}
}
else if (constant('LDAP_BIND_USER') != "") {
if(! @ldap_bind($this->connection, LDAP_BIND_USER, LDAP_BIND_PASSWORD)) {
$this->connection = false;
throw new StatusException(sprintf("BackendSearchLDAP(): Could not bind to server with user '%s' and specified password! Search aborted.", LDAP_BIND_USER), SYNC_SEARCHSTATUS_STORE_ACCESSDENIED, null, LOGLEVEL_ERROR);
}
}
else {
// it would be possible to use the users login and password to authenticate on the LDAP server
// the main $backend has to keep these values so they could be used here
$this->connection = false;
throw new StatusException("BackendSearchLDAP(): neither anonymous nor default bind enabled. Other options not implemented.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR);
}
}
/**
* Indicates if a search type is supported by this SearchProvider
* Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented
*
* @param string $searchtype
*
* @access public
* @return boolean
*/
public function SupportsType($searchtype) {
return ($searchtype == ISearchProvider::SEARCH_GAL);
}
/**
* Queries the LDAP backend
*
* @param string $searchquery string to be searched for
* @param string $searchrange specified searchrange
*
* @access public
* @return array search results
*/
public function GetGALSearchResults($searchquery, $searchrange) {
global $ldap_field_map;
if (isset($this->connection) && $this->connection !== false) {
$searchfilter = str_replace("SEARCHVALUE", $searchquery, LDAP_SEARCH_FILTER);
$result = @ldap_search($this->connection, LDAP_SEARCH_BASE, $searchfilter);
if (!$result) {
ZLog::Write(LOGLEVEL_ERROR, "BackendSearchLDAP: Error in search query. Search aborted");
return false;
}
// get entry data as array
$searchresult = ldap_get_entries($this->connection, $result);
// range for the search results, default symbian range end is 50, wm 99,
// so we'll use that of nokia
$rangestart = 0;
$rangeend = 50;
if ($searchrange != '0') {
$pos = strpos($searchrange, '-');
$rangestart = substr($searchrange, 0, $pos);
$rangeend = substr($searchrange, ($pos + 1));
}
$items = array();
// TODO the limiting of the searchresults could be refactored into Utils as it's probably used more than once
$querycnt = $searchresult['count'];
//do not return more results as requested in range
$querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt;
$items['range'] = $rangestart.'-'.($querycnt-1);
$items['searchtotal'] = $querycnt;
$rc = 0;
for ($i = $rangestart; $i < $querylimit; $i++) {
foreach ($ldap_field_map as $key=>$value ) {
if (isset($searchresult[$i][$value])) {
if (is_array($searchresult[$i][$value]))
$items[$rc][$key] = $searchresult[$i][$value][0];
else
$items[$rc][$key] = $searchresult[$i][$value];
}
}
$rc++;
}
return $items;
}
else
return false;
}
/**
* Searches for the emails on the server
*
* @param ContentParameter $cpo
*
* @return array
*/
public function GetMailboxSearchResults($cpo) {
return array();
}
/**
* Terminates a search for a given PID
*
* @param int $pid
*
* @return boolean
*/
public function TerminateSearch($pid) {
return true;
}
/**
* Disconnects from LDAP
*
* @access public
* @return boolean
*/
public function Disconnect() {
if ($this->connection)
@ldap_close($this->connection);
return true;
}
}
?>

View file

@ -0,0 +1,50 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : VCardDir backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// **********************
// BackendVCardDir settings
// **********************
define('VCARDDIR_DIR', '/home/%u/.kde/share/apps/kabc/stdvcf');
?>

View file

@ -0,0 +1,681 @@
<?php
/***********************************************
* File : vcarddir.php
* Project : Z-Push
* Descr : This backend is for vcard directories.
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// config file
require_once("backend/vcarddir/config.php");
include_once('lib/default/diffbackend/diffbackend.php');
class BackendVCardDir extends BackendDiff {
/**----------------------------------------------------------------------------------------------------------
* default backend methods
*/
/**
* Authenticates the user - NOT EFFECTIVELY IMPLEMENTED
* Normally some kind of password check would be done here.
* Alternatively, the password could be ignored and an Apache
* authentication via mod_auth_* could be done
*
* @param string $username
* @param string $domain
* @param string $password
*
* @access public
* @return boolean
*/
public function Logon($username, $domain, $password) {
return true;
}
/**
* Logs off
*
* @access public
* @return boolean
*/
public function Logoff() {
return true;
}
/**
* Sends an e-mail
* Not implemented here
*
* @param SyncSendMail $sm SyncSendMail object
*
* @access public
* @return boolean
* @throws StatusException
*/
public function SendMail($sm) {
return false;
}
/**
* Returns the waste basket
*
* @access public
* @return string
*/
public function GetWasteBasket() {
return false;
}
/**
* Returns the content of the named attachment as stream
* not implemented
*
* @param string $attname
*
* @access public
* @return SyncItemOperationsAttachment
* @throws StatusException
*/
public function GetAttachmentData($attname) {
return false;
}
/**----------------------------------------------------------------------------------------------------------
* implemented DiffBackend methods
*/
/**
* Returns a list (array) of folders.
* In simple implementations like this one, probably just one folder is returned.
*
* @access public
* @return array
*/
public function GetFolderList() {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolderList()');
$contacts = array();
$folder = $this->StatFolder("root");
$contacts[] = $folder;
return $contacts;
}
/**
* Returns an actual SyncFolder object
*
* @param string $id id of the folder
*
* @access public
* @return object SyncFolder with information
*/
public function GetFolder($id) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolder('.$id.')');
if($id == "root") {
$folder = new SyncFolder();
$folder->serverid = $id;
$folder->parentid = "0";
$folder->displayname = "Contacts";
$folder->type = SYNC_FOLDER_TYPE_CONTACT;
return $folder;
} else return false;
}
/**
* Returns folder stats. An associative array with properties is expected.
*
* @param string $id id of the folder
*
* @access public
* @return array
*/
public function StatFolder($id) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatFolder('.$id.')');
$folder = $this->GetFolder($id);
$stat = array();
$stat["id"] = $id;
$stat["parent"] = $folder->parentid;
$stat["mod"] = $folder->displayname;
return $stat;
}
/**
* Creates or modifies a folder
* not implemented
*
* @param string $folderid id of the parent folder
* @param string $oldid if empty -> new folder created, else folder is to be renamed
* @param string $displayname new folder name (to be created, or to be renamed to)
* @param int $type folder type
*
* @access public
* @return boolean status
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*
*/
public function ChangeFolder($folderid, $oldid, $displayname, $type){
return false;
}
/**
* Deletes a folder
*
* @param string $id
* @param string $parent is normally false
*
* @access public
* @return boolean status - false if e.g. does not exist
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*
*/
public function DeleteFolder($id, $parentid){
return false;
}
/**
* Returns a list (array) of messages
*
* @param string $folderid id of the parent folder
* @param long $cutoffdate timestamp in the past from which on messages should be returned
*
* @access public
* @return array/false array with messages or false if folder is not available
*/
public function GetMessageList($folderid, $cutoffdate) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessageList('.$folderid.')');
$messages = array();
$dir = opendir($this->getPath());
if(!$dir)
return false;
while($entry = readdir($dir)) {
if(is_dir($this->getPath() .'/'.$entry))
continue;
$message = array();
$message["id"] = $entry;
$stat = stat($this->getPath() .'/'.$entry);
$message["mod"] = $stat["mtime"];
$message["flags"] = 1; // always 'read'
$messages[] = $message;
}
return $messages;
}
/**
* Returns the actual SyncXXX object type.
*
* @param string $folderid id of the parent folder
* @param string $id id of the message
* @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc)
*
* @access public
* @return object/false false if the message could not be retrieved
*/
public function GetMessage($folderid, $id, $contentparameters) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessage('.$folderid.', '.$id.', ..)');
if($folderid != "root")
return;
$types = array ('dom' => 'type', 'intl' => 'type', 'postal' => 'type', 'parcel' => 'type', 'home' => 'type', 'work' => 'type',
'pref' => 'type', 'voice' => 'type', 'fax' => 'type', 'msg' => 'type', 'cell' => 'type', 'pager' => 'type',
'bbs' => 'type', 'modem' => 'type', 'car' => 'type', 'isdn' => 'type', 'video' => 'type',
'aol' => 'type', 'applelink' => 'type', 'attmail' => 'type', 'cis' => 'type', 'eworld' => 'type',
'internet' => 'type', 'ibmmail' => 'type', 'mcimail' => 'type',
'powershare' => 'type', 'prodigy' => 'type', 'tlx' => 'type', 'x400' => 'type',
'gif' => 'type', 'cgm' => 'type', 'wmf' => 'type', 'bmp' => 'type', 'met' => 'type', 'pmb' => 'type', 'dib' => 'type',
'pict' => 'type', 'tiff' => 'type', 'pdf' => 'type', 'ps' => 'type', 'jpeg' => 'type', 'qtime' => 'type',
'mpeg' => 'type', 'mpeg2' => 'type', 'avi' => 'type',
'wave' => 'type', 'aiff' => 'type', 'pcm' => 'type',
'x509' => 'type', 'pgp' => 'type', 'text' => 'value', 'inline' => 'value', 'url' => 'value', 'cid' => 'value', 'content-id' => 'value',
'7bit' => 'encoding', '8bit' => 'encoding', 'quoted-printable' => 'encoding', 'base64' => 'encoding',
);
// Parse the vcard
$message = new SyncContact();
$data = file_get_contents($this->getPath() . "/" . $id);
$data = str_replace("\x00", '', $data);
$data = str_replace("\r\n", "\n", $data);
$data = str_replace("\r", "\n", $data);
$data = preg_replace('/(\n)([ \t])/i', '', $data);
$lines = explode("\n", $data);
$vcard = array();
foreach($lines as $line) {
if (trim($line) == '')
continue;
$pos = strpos($line, ':');
if ($pos === false)
continue;
$field = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos+1));
$fieldparts = preg_split('/(?<!\\\\)(\;)/i', $field, -1, PREG_SPLIT_NO_EMPTY);
$type = strtolower(array_shift($fieldparts));
$fieldvalue = array();
foreach ($fieldparts as $fieldpart) {
if(preg_match('/([^=]+)=(.+)/', $fieldpart, $matches)){
if(!in_array(strtolower($matches[1]),array('value','type','encoding','language')))
continue;
if(isset($fieldvalue[strtolower($matches[1])]) && is_array($fieldvalue[strtolower($matches[1])])){
$fieldvalue[strtolower($matches[1])] = array_merge($fieldvalue[strtolower($matches[1])], preg_split('/(?<!\\\\)(\,)/i', $matches[2], -1, PREG_SPLIT_NO_EMPTY));
}else{
$fieldvalue[strtolower($matches[1])] = preg_split('/(?<!\\\\)(\,)/i', $matches[2], -1, PREG_SPLIT_NO_EMPTY);
}
}else{
if(!isset($types[strtolower($fieldpart)]))
continue;
$fieldvalue[$types[strtolower($fieldpart)]][] = $fieldpart;
}
}
//
switch ($type) {
case 'categories':
//case 'nickname':
$val = preg_split('/(?<!\\\\)(\,)/i', $value);
$val = array_map("w2ui", $val);
break;
default:
$val = preg_split('/(?<!\\\\)(\;)/i', $value);
break;
}
if(isset($fieldvalue['encoding'][0])){
switch(strtolower($fieldvalue['encoding'][0])){
case 'q':
case 'quoted-printable':
foreach($val as $i => $v){
$val[$i] = quoted_printable_decode($v);
}
break;
case 'b':
case 'base64':
foreach($val as $i => $v){
$val[$i] = base64_decode($v);
}
break;
}
}else{
foreach($val as $i => $v){
$val[$i] = $this->unescape($v);
}
}
$fieldvalue['val'] = $val;
$vcard[$type][] = $fieldvalue;
}
if(isset($vcard['email'][0]['val'][0]))
$message->email1address = $vcard['email'][0]['val'][0];
if(isset($vcard['email'][1]['val'][0]))
$message->email2address = $vcard['email'][1]['val'][0];
if(isset($vcard['email'][2]['val'][0]))
$message->email3address = $vcard['email'][2]['val'][0];
if(isset($vcard['tel'])){
foreach($vcard['tel'] as $tel) {
if(!isset($tel['type'])){
$tel['type'] = array();
}
if(in_array('car', $tel['type'])){
$message->carphonenumber = $tel['val'][0];
}elseif(in_array('pager', $tel['type'])){
$message->pagernumber = $tel['val'][0];
}elseif(in_array('cell', $tel['type'])){
$message->mobilephonenumber = $tel['val'][0];
}elseif(in_array('home', $tel['type'])){
if(in_array('fax', $tel['type'])){
$message->homefaxnumber = $tel['val'][0];
}elseif(empty($message->homephonenumber)){
$message->homephonenumber = $tel['val'][0];
}else{
$message->home2phonenumber = $tel['val'][0];
}
}elseif(in_array('work', $tel['type'])){
if(in_array('fax', $tel['type'])){
$message->businessfaxnumber = $tel['val'][0];
}elseif(empty($message->businessphonenumber)){
$message->businessphonenumber = $tel['val'][0];
}else{
$message->business2phonenumber = $tel['val'][0];
}
}elseif(empty($message->homephonenumber)){
$message->homephonenumber = $tel['val'][0];
}elseif(empty($message->home2phonenumber)){
$message->home2phonenumber = $tel['val'][0];
}else{
$message->radiophonenumber = $tel['val'][0];
}
}
}
//;;street;city;state;postalcode;country
if(isset($vcard['adr'])){
foreach($vcard['adr'] as $adr) {
if(empty($adr['type'])){
$a = 'other';
}elseif(in_array('home', $adr['type'])){
$a = 'home';
}elseif(in_array('work', $adr['type'])){
$a = 'business';
}else{
$a = 'other';
}
if(!empty($adr['val'][2])){
$b=$a.'street';
$message->$b = w2ui($adr['val'][2]);
}
if(!empty($adr['val'][3])){
$b=$a.'city';
$message->$b = w2ui($adr['val'][3]);
}
if(!empty($adr['val'][4])){
$b=$a.'state';
$message->$b = w2ui($adr['val'][4]);
}
if(!empty($adr['val'][5])){
$b=$a.'postalcode';
$message->$b = w2ui($adr['val'][5]);
}
if(!empty($adr['val'][6])){
$b=$a.'country';
$message->$b = w2ui($adr['val'][6]);
}
}
}
if(!empty($vcard['fn'][0]['val'][0]))
$message->fileas = w2ui($vcard['fn'][0]['val'][0]);
if(!empty($vcard['n'][0]['val'][0]))
$message->lastname = w2ui($vcard['n'][0]['val'][0]);
if(!empty($vcard['n'][0]['val'][1]))
$message->firstname = w2ui($vcard['n'][0]['val'][1]);
if(!empty($vcard['n'][0]['val'][2]))
$message->middlename = w2ui($vcard['n'][0]['val'][2]);
if(!empty($vcard['n'][0]['val'][3]))
$message->title = w2ui($vcard['n'][0]['val'][3]);
if(!empty($vcard['n'][0]['val'][4]))
$message->suffix = w2ui($vcard['n'][0]['val'][4]);
if(!empty($vcard['bday'][0]['val'][0])){
$tz = date_default_timezone_get();
date_default_timezone_set('UTC');
$message->birthday = strtotime($vcard['bday'][0]['val'][0]);
date_default_timezone_set($tz);
}
if(!empty($vcard['org'][0]['val'][0]))
$message->companyname = w2ui($vcard['org'][0]['val'][0]);
if(!empty($vcard['note'][0]['val'][0])){
$message->body = w2ui($vcard['note'][0]['val'][0]);
$message->bodysize = strlen($vcard['note'][0]['val'][0]);
$message->bodytruncated = 0;
}
if(!empty($vcard['role'][0]['val'][0]))
$message->jobtitle = w2ui($vcard['role'][0]['val'][0]);//$vcard['title'][0]['val'][0]
if(!empty($vcard['url'][0]['val'][0]))
$message->webpage = w2ui($vcard['url'][0]['val'][0]);
if(!empty($vcard['categories'][0]['val']))
$message->categories = $vcard['categories'][0]['val'];
if(!empty($vcard['photo'][0]['val'][0]))
$message->picture = base64_encode($vcard['photo'][0]['val'][0]);
return $message;
}
/**
* Returns message stats, analogous to the folder stats from StatFolder().
*
* @param string $folderid id of the folder
* @param string $id id of the message
*
* @access public
* @return array
*/
public function StatMessage($folderid, $id) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatMessage('.$folderid.', '.$id.')');
if($folderid != "root")
return false;
$stat = stat($this->getPath() . "/" . $id);
$message = array();
$message["mod"] = $stat["mtime"];
$message["id"] = $id;
$message["flags"] = 1;
return $message;
}
/**
* Called when a message has been changed on the mobile.
* This functionality is not available for emails.
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param SyncXXX $message the SyncObject containing a message
* @param ContentParameters $contentParameters
*
* @access public
* @return array same return value as StatMessage()
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function ChangeMessage($folderid, $id, $message, $contentParameters) {
ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::ChangeMessage('.$folderid.', '.$id.', ..)');
$mapping = array(
'fileas' => 'FN',
'lastname;firstname;middlename;title;suffix' => 'N',
'email1address' => 'EMAIL;INTERNET',
'email2address' => 'EMAIL;INTERNET',
'email3address' => 'EMAIL;INTERNET',
'businessphonenumber' => 'TEL;WORK',
'business2phonenumber' => 'TEL;WORK',
'businessfaxnumber' => 'TEL;WORK;FAX',
'homephonenumber' => 'TEL;HOME',
'home2phonenumber' => 'TEL;HOME',
'homefaxnumber' => 'TEL;HOME;FAX',
'mobilephonenumber' => 'TEL;CELL',
'carphonenumber' => 'TEL;CAR',
'pagernumber' => 'TEL;PAGER',
';;businessstreet;businesscity;businessstate;businesspostalcode;businesscountry' => 'ADR;WORK',
';;homestreet;homecity;homestate;homepostalcode;homecountry' => 'ADR;HOME',
';;otherstreet;othercity;otherstate;otherpostalcode;othercountry' => 'ADR',
'companyname' => 'ORG',
'body' => 'NOTE',
'jobtitle' => 'ROLE',
'webpage' => 'URL',
);
$data = "BEGIN:VCARD\nVERSION:2.1\nPRODID:Z-Push\n";
foreach($mapping as $k => $v){
$val = '';
$ks = explode(';', $k);
foreach($ks as $i){
if(!empty($message->$i))
$val .= $this->escape($message->$i);
$val.=';';
}
if(empty($val))
continue;
$val = substr($val,0,-1);
if(strlen($val)>50){
$data .= $v.":\n\t".substr(chunk_split($val, 50, "\n\t"), 0, -1);
}else{
$data .= $v.':'.$val."\n";
}
}
if(!empty($message->categories))
$data .= 'CATEGORIES:'.implode(',', $this->escape($message->categories))."\n";
if(!empty($message->picture))
$data .= 'PHOTO;ENCODING=BASE64;TYPE=JPEG:'."\n\t".substr(chunk_split($message->picture, 50, "\n\t"), 0, -1);
if(isset($message->birthday))
$data .= 'BDAY:'.date('Y-m-d', $message->birthday)."\n";
$data .= "END:VCARD";
// not supported: anniversary, assistantname, assistnamephonenumber, children, department, officelocation, radiophonenumber, spouse, rtf
if(!$id){
if(!empty($message->fileas)){
$name = u2wi($message->fileas);
}elseif(!empty($message->lastname)){
$name = $name = u2wi($message->lastname);
}elseif(!empty($message->firstname)){
$name = $name = u2wi($message->firstname);
}elseif(!empty($message->companyname)){
$name = $name = u2wi($message->companyname);
}else{
$name = 'unknown';
}
$name = preg_replace('/[^a-z0-9 _-]/i', '', $name);
$id = $name.'.vcf';
$i = 0;
while(file_exists($this->getPath().'/'.$id)){
$i++;
$id = $name.$i.'.vcf';
}
}
file_put_contents($this->getPath().'/'.$id, $data);
return $this->StatMessage($folderid, $id);
}
/**
* Changes the 'read' flag of a message on disk
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags read flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function SetReadFlag($folderid, $id, $flags, $contentParameters) {
return false;
}
/**
* Called when the user has requested to delete (really delete) a message
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public function DeleteMessage($folderid, $id, $contentParameters) {
return unlink($this->getPath() . '/' . $id);
}
/**
* Called when the user moves an item on the PDA from one folder to another
* not implemented
*
* @param string $folderid id of the source folder
* @param string $id id of the message
* @param string $newfolderid id of the destination folder
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
*/
public function MoveMessage($folderid, $id, $newfolderid, $contentParameters) {
return false;
}
/**----------------------------------------------------------------------------------------------------------
* private vcard-specific internals
*/
/**
* The path we're working on
*
* @access private
* @return string
*/
private function getPath() {
return str_replace('%u', $this->store, VCARDDIR_DIR);
}
/**
* Escapes a string
*
* @param string $data string to be escaped
*
* @access private
* @return string
*/
function escape($data){
if (is_array($data)) {
foreach ($data as $key => $val) {
$data[$key] = $this->escape($val);
}
return $data;
}
$data = str_replace("\r\n", "\n", $data);
$data = str_replace("\r", "\n", $data);
$data = str_replace(array('\\', ';', ',', "\n"), array('\\\\', '\\;', '\\,', '\\n'), $data);
return u2wi($data);
}
/**
* Un-escapes a string
*
* @param string $data string to be un-escaped
*
* @access private
* @return string
*/
function unescape($data){
$data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data);
return $data;
}
};
?>

View file

@ -0,0 +1,51 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : VCardDir backend configuration file
*
* Created : 27.11.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// ************************
// BackendZarafa settings
// ************************
// Defines the server to which we want to connect
define('MAPI_SERVER', 'file:///var/run/zarafa');
?>

View file

@ -0,0 +1,298 @@
<?php
/***********************************************
* File : exporter.php
* Project : Z-Push
* Descr : This is a generic class that is
* used by both the proxy importer
* (for outgoing messages) and our
* local importer (for incoming
* messages). Basically all shared
* conversion data for converting
* to and from MAPI objects is in here.
*
* Created : 14.02.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* This is our ICS exporter which requests the actual exporter from ICS and makes sure
* that the ImportProxies are used.
*/
class ExportChangesICS implements IExportChanges{
private $folderid;
private $store;
private $session;
private $restriction;
private $contentparameters;
private $flags;
private $exporterflags;
private $exporter;
/**
* Constructor
*
* @param mapisession $session
* @param mapistore $store
* @param string (opt)
*
* @access public
* @throws StatusException
*/
public function ExportChangesICS($session, $store, $folderid = false) {
// Open a hierarchy or a contents exporter depending on whether a folderid was specified
$this->session = $session;
$this->folderid = $folderid;
$this->store = $store;
$this->restriction = false;
try {
if($folderid) {
$entryid = mapi_msgstore_entryidfromsourcekey($store, $folderid);
}
else {
$storeprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
$entryid = $storeprops[PR_IPM_SUBTREE_ENTRYID];
}
$folder = false;
if ($entryid)
$folder = mapi_msgstore_openentry($this->store, $entryid);
// Get the actual ICS exporter
if($folderid) {
if ($folder)
$this->exporter = mapi_openproperty($folder, PR_CONTENTS_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
else
$this->exporter = false;
}
else {
$this->exporter = mapi_openproperty($folder, PR_HIERARCHY_SYNCHRONIZER, IID_IExchangeExportChanges, 0 , 0);
}
}
catch (MAPIException $me) {
$this->exporter = false;
// We return the general error SYNC_FSSTATUS_CODEUNKNOWN (12) which is also SYNC_STATUS_FOLDERHIERARCHYCHANGED (12)
// if this happened while doing content sync, the mobile will try to resync the folderhierarchy
throw new StatusException(sprintf("ExportChangesICS('%s','%s','%s'): Error, unable to open folder: 0x%X", $session, $store, Utils::PrintAsString($folderid), mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN);
}
}
/**
* Configures the exporter
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function Config($state, $flags = 0) {
$this->exporterflags = 0;
$this->flags = $flags;
// this should never happen
if ($this->exporter === false || is_array($state))
throw new StatusException("ExportChangesICS->Config(): Error, exporter not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
// change exporterflags if we are doing a ContentExport
if($this->folderid) {
$this->exporterflags |= SYNC_NORMAL | SYNC_READ_STATE;
// Initial sync, we don't want deleted items. If the initial sync is chunked
// we check the change ID of the syncstate (0 at initial sync)
// On subsequent syncs, we do want to receive delete events.
if(strlen($state) == 0 || bin2hex(substr($state,4,4)) == "00000000") {
if (!($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesICS->Config(): synching inital data");
$this->exporterflags |= SYNC_NO_SOFT_DELETIONS | SYNC_NO_DELETIONS;
}
}
if($this->flags & BACKEND_DISCARD_DATA)
$this->exporterflags |= SYNC_CATCHUP;
// Put the state information in a stream that can be used by ICS
$stream = mapi_stream_create();
if(strlen($state) == 0)
$state = hex2bin("0000000000000000");
if (!($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->Config() initialized with state: 0x%s", bin2hex($state)));
mapi_stream_write($stream, $state);
$this->statestream = $stream;
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters){
$filtertype = $contentparameters->GetFilterType();
switch($contentparameters->GetContentClass()) {
case "Email":
$this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetEmailRestriction(Utils::GetCutOffDate($filtertype)) : false;
break;
case "Calendar":
$this->restriction = ($filtertype || !Utils::CheckMapiExtVersion('7')) ? MAPIUtils::GetCalendarRestriction($this->store, Utils::GetCutOffDate($filtertype)) : false;
break;
default:
case "Contacts":
case "Tasks":
$this->restriction = false;
break;
}
$this->contentParameters = $contentparameters;
}
/**
* Sets the importer the exporter will sent it's changes to
* and initializes the Exporter
*
* @param object &$importer Implementation of IImportChanges
*
* @access public
* @return boolean
* @throws StatusException
*/
public function InitializeExporter(&$importer) {
// Because we're using ICS, we need to wrap the given importer to make it suitable to pass
// to ICS. We do this in two steps: first, wrap the importer with our own PHP importer class
// which removes all MAPI dependency, and then wrap that class with a C++ wrapper so we can
// pass it to ICS
// this should never happen!
if($this->exporter === false || !isset($this->statestream) || !isset($this->flags) || !isset($this->exporterflags) ||
($this->folderid && !isset($this->contentParameters)) )
throw new StatusException("ExportChangesICS->InitializeExporter(): Error, exporter or essential data not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
// PHP wrapper
$phpwrapper = new PHPWrapper($this->session, $this->store, $importer);
// with a folderid we are going to get content
if($this->folderid) {
$phpwrapper->ConfigContentParameters($this->contentParameters);
// ICS c++ wrapper
$mapiimporter = mapi_wrap_importcontentschanges($phpwrapper);
$includeprops = false;
}
else {
$mapiimporter = mapi_wrap_importhierarchychanges($phpwrapper);
$includeprops = array(PR_SOURCE_KEY, PR_DISPLAY_NAME);
}
if (!$mapiimporter)
throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_wrap_import_*_changes() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
$ret = mapi_exportchanges_config($this->exporter, $this->statestream, $this->exporterflags, $mapiimporter, $this->restriction, $includeprops, false, 1);
if(!$ret)
throw new StatusException(sprintf("ExportChangesICS->InitializeExporter(): Error, mapi_exportchanges_config() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
$changes = mapi_exportchanges_getchangecount($this->exporter);
if($changes || !($this->flags & BACKEND_DISCARD_DATA))
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ExportChangesICS->InitializeExporter() successfully. %d changes ready to sync.", $changes));
return $ret;
}
/**
* Reads the current state from the Exporter
*
* @access public
* @return string
* @throws StatusException
*/
public function GetState() {
$error = false;
if(!isset($this->statestream) || $this->exporter === false)
$error = true;
if($error === true || mapi_exportchanges_updatestate($this->exporter, $this->statestream) != true )
throw new StatusException(sprintf("ExportChangesICS->GetState(): Error, state not available or unable to update: 0x%X", mapi_last_hresult()), (($this->folderid)?SYNC_STATUS_FOLDERHIERARCHYCHANGED:SYNC_FSSTATUS_CODEUNKNOWN), null, LOGLEVEL_WARN);
mapi_stream_seek($this->statestream, 0, STREAM_SEEK_SET);
$state = "";
while(true) {
$data = mapi_stream_read($this->statestream, 4096);
if(strlen($data))
$state .= $data;
else
break;
}
return $state;
}
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount() {
if ($this->exporter)
return mapi_exportchanges_getchangecount($this->exporter);
else
return 0;
}
/**
* Synchronizes a change
*
* @access public
* @return array
*/
public function Synchronize() {
if ($this->exporter) {
return mapi_exportchanges_synchronize($this->exporter);
}
return false;
}
}
?>

View file

@ -0,0 +1,201 @@
<?php
/***********************************************
* File : z_ical.php
* Project : Z-Push
* Descr : This is a very basic iCalendar parser
* used to process incoming meeting requests
* and responses.
*
* Created : 01.12.2008
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ICalParser{
private $props;
/**
* Constructor
*
* @param mapistore $store
* @param array &$props properties to be set
*
* @access public
*/
public function ICalParser(&$store, &$props){
$this->props = $props;
}
/**
* Function reads calendar part and puts mapi properties into an array.
*
* @param string $ical the ical data
* @param array &$mapiprops mapi properties
*
* @access public
*/
public function ExtractProps($ical, &$mapiprops) {
//mapping between partstat in ical and MAPI Meeting Response classes as well as icons
$aClassMap = array(
"ACCEPTED" => array("class" => "IPM.Schedule.Meeting.Resp.Pos", "icon" => 0x405),
"DECLINED" => array("class" => "IPM.Schedule.Meeting.Resp.Neg", "icon" => 0x406),
"TENTATIVE" => array("class" => "IPM.Schedule.Meeting.Resp.Tent", "icon" => 0x407),
"NEEDS-ACTION" => array("class" => "IPM.Schedule.Meeting.Request", "icon" => 0x404), //iphone
"REQ-PARTICIPANT" => array("class" => "IPM.Schedule.Meeting.Request", "icon" => 0x404), //nokia
);
$aical = preg_split("/[\n]/", $ical);
$elemcount = count($aical);
$i=0;
$nextline = $aical[0];
//last element is empty
while ($i < $elemcount - 1) {
$line = $nextline;
$nextline = $aical[$i+1];
//if a line starts with a space or a tab it belongs to the previous line
while (strlen($nextline) > 0 && ($nextline{0} == " " || $nextline{0} == "\t")) {
$line = rtrim($line) . substr($nextline, 1);
$nextline = $aical[++$i + 1];
}
$line = rtrim($line);
switch (strtoupper($line)) {
case "BEGIN:VCALENDAR":
case "BEGIN:VEVENT":
case "END:VEVENT":
case "END:VCALENDAR":
break;
default:
unset ($field, $data, $prop_pos, $property);
if (preg_match ("/([^:]+):(.*)/", $line, $line)){
$field = $line[1];
$data = $line[2];
$property = $field;
$prop_pos = strpos($property,';');
if ($prop_pos !== false) $property = substr($property, 0, $prop_pos);
$property = strtoupper($property);
switch ($property) {
case 'DTSTART':
$data = $this->getTimestampFromStreamerDate($data);
$mapiprops[$this->props["starttime"]] = $mapiprops[$this->props["commonstart"]] = $mapiprops[$this->props["clipstart"]] = $mapiprops[PR_START_DATE] = $data;
break;
case 'DTEND':
$data = $this->getTimestampFromStreamerDate($data);
$mapiprops[$this->props["endtime"]] = $mapiprops[$this->props["commonend"]] = $mapiprops[$this->props["recurrenceend"]] = $mapiprops[PR_END_DATE] = $data;
break;
case 'UID':
$mapiprops[$this->props["goidtag"]] = $mapiprops[$this->props["goid2tag"]] = Utils::GetOLUidFromICalUid($data);
break;
case 'ATTENDEE':
$fields = explode(";", $field);
foreach ($fields as $field) {
$prop_pos = strpos($field, '=');
if ($prop_pos !== false) {
switch (substr($field, 0, $prop_pos)) {
case 'PARTSTAT' : $partstat = substr($field, $prop_pos+1); break;
case 'CN' : $cn = substr($field, $prop_pos+1); break;
case 'ROLE' : $role = substr($field, $prop_pos+1); break;
case 'RSVP' : $rsvp = substr($field, $prop_pos+1); break;
}
}
}
if (isset($partstat) && isset($aClassMap[$partstat]) &&
(!isset($mapiprops[PR_MESSAGE_CLASS]) || $mapiprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request")) {
$mapiprops[PR_MESSAGE_CLASS] = $aClassMap[$partstat]['class'];
$mapiprops[PR_ICON_INDEX] = $aClassMap[$partstat]['icon'];
}
// START ADDED dw2412 to support meeting requests on HTC Android Mail App
elseif (isset($role) && isset($aClassMap[$role]) &&
(!isset($mapiprops[PR_MESSAGE_CLASS]) || $mapiprops[PR_MESSAGE_CLASS] == "IPM.Schedule.Meeting.Request")) {
$mapiprops[PR_MESSAGE_CLASS] = $aClassMap[$role]['class'];
$mapiprops[PR_ICON_INDEX] = $aClassMap[$role]['icon'];
}
// END ADDED dw2412 to support meeting requests on HTC Android Mail App
if (!isset($cn)) $cn = "";
$data = str_replace ("MAILTO:", "", $data);
$attendee[] = array ('name' => stripslashes($cn), 'email' => stripslashes($data));
break;
case 'ORGANIZER':
$field = str_replace("ORGANIZER;CN=", "", $field);
$data = str_replace ("MAILTO:", "", $data);
$organizer[] = array ('name' => stripslashes($field), 'email' => stripslashes($data));
break;
case 'LOCATION':
$data = str_replace("\\n", "<br />", $data);
$data = str_replace("\\t", "&nbsp;", $data);
$data = str_replace("\\r", "<br />", $data);
$data = stripslashes($data);
$mapiprops[$this->props["tneflocation"]] = $mapiprops[$this->props["location"]] = $data;
break;
}
}
break;
}
$i++;
}
$mapiprops[$this->props["usetnef"]] = true;
}
/**
* Converts an YYYYMMDDTHHMMSSZ kind of string into an unixtimestamp
*
* @param string $data
*
* @access private
* @return long
*/
private function getTimestampFromStreamerDate ($data) {
$data = str_replace('Z', '', $data);
$data = str_replace('T', '', $data);
preg_match ('/([0-9]{4})([0-9]{2})([0-9]{2})([0-9]{0,2})([0-9]{0,2})([0-9]{0,2})/', $data, $regs);
if ($regs[1] < 1970) {
$regs[1] = '1971';
}
return gmmktime($regs[4], $regs[5], $regs[6], $regs[2], $regs[3], $regs[1]);
}
}
?>

View file

@ -0,0 +1,691 @@
<?php
/***********************************************
* File : importer.php
* Project : Z-Push
* Descr : This is a generic class that is
* used by both the proxy importer
* (for outgoing messages) and our
* local importer (for incoming
* messages). Basically all shared
* conversion data for converting
* to and from MAPI objects is in here.
*
* Created : 14.02.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* This is our local importer. Tt receives data from the PDA, for contents and hierarchy changes.
* It must therefore receive the incoming data and convert it into MAPI objects, and then send
* them to the ICS importer to do the actual writing of the object.
* The creation of folders is fairly trivial, because folders that are created on
* the PDA are always e-mail folders.
*/
class ImportChangesICS implements IImportChanges {
private $folderid;
private $store;
private $session;
private $flags;
private $statestream;
private $importer;
private $memChanges;
private $mapiprovider;
private $conflictsLoaded;
private $conflictsContentParameters;
private $conflictsState;
private $cutoffdate;
private $contentClass;
/**
* Constructor
*
* @param mapisession $session
* @param mapistore $store
* @param string $folderid (opt)
*
* @access public
* @throws StatusException
*/
public function ImportChangesICS($session, $store, $folderid = false) {
$this->session = $session;
$this->store = $store;
$this->folderid = $folderid;
$this->conflictsLoaded = false;
$this->cutoffdate = false;
$this->contentClass = false;
if ($folderid) {
$entryid = mapi_msgstore_entryidfromsourcekey($store, $folderid);
}
else {
$storeprops = mapi_getprops($store, array(PR_IPM_SUBTREE_ENTRYID));
$entryid = $storeprops[PR_IPM_SUBTREE_ENTRYID];
}
$folder = false;
if ($entryid)
$folder = mapi_msgstore_openentry($store, $entryid);
if(!$folder) {
$this->importer = false;
// We throw an general error SYNC_FSSTATUS_CODEUNKNOWN (12) which is also SYNC_STATUS_FOLDERHIERARCHYCHANGED (12)
// if this happened while doing content sync, the mobile will try to resync the folderhierarchy
throw new StatusException(sprintf("ImportChangesICS('%s','%s','%s'): Error, unable to open folder: 0x%X", $session, $store, Utils::PrintAsString($folderid), mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN);
}
$this->mapiprovider = new MAPIProvider($this->session, $this->store);
if ($folderid)
$this->importer = mapi_openproperty($folder, PR_COLLECTOR, IID_IExchangeImportContentsChanges, 0 , 0);
else
$this->importer = mapi_openproperty($folder, PR_COLLECTOR, IID_IExchangeImportHierarchyChanges, 0 , 0);
}
/**
* Initializes the importer
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function Config($state, $flags = 0) {
$this->flags = $flags;
// this should never happen
if ($this->importer === false)
throw new StatusException("ImportChangesICS->Config(): Error, importer not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_ERROR);
// Put the state information in a stream that can be used by ICS
$stream = mapi_stream_create();
if(strlen($state) == 0)
$state = hex2bin("0000000000000000");
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->Config(): initializing importer with state: 0x%s", bin2hex($state)));
mapi_stream_write($stream, $state);
$this->statestream = $stream;
if ($this->folderid !== false) {
// possible conflicting messages will be cached here
$this->memChanges = new ChangesMemoryWrapper();
$stat = mapi_importcontentschanges_config($this->importer, $stream, $flags);
}
else
$stat = mapi_importhierarchychanges_config($this->importer, $stream, $flags);
if (!$stat)
throw new StatusException(sprintf("ImportChangesICS->Config(): Error, mapi_import_*_changes_config() failed: 0x%X", mapi_last_hresult()), SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
return $stat;
}
/**
* Configures additional parameters for content selection
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters) {
$filtertype = $contentparameters->GetFilterType();
switch($contentparameters->GetContentClass()) {
case "Email":
$this->cutoffdate = ($filtertype) ? Utils::GetCutOffDate($filtertype) : false;
break;
case "Calendar":
$this->cutoffdate = ($filtertype) ? Utils::GetCutOffDate($filtertype) : false;
break;
default:
case "Contacts":
case "Tasks":
$this->cutoffdate = false;
break;
}
$this->contentClass = $contentparameters->GetContentClass();
}
/**
* Reads state from the Importer
*
* @access public
* @return string
* @throws StatusException
*/
public function GetState() {
$error = false;
if(!isset($this->statestream) || $this->importer === false)
$error = true;
if ($error === false && $this->folderid !== false && function_exists("mapi_importcontentschanges_updatestate"))
if(mapi_importcontentschanges_updatestate($this->importer, $this->statestream) != true)
$error = true;
if ($error == true)
throw new StatusException(sprintf("ImportChangesICS->GetState(): Error, state not available or unable to update: 0x%X", mapi_last_hresult()), (($this->folderid)?SYNC_STATUS_FOLDERHIERARCHYCHANGED:SYNC_FSSTATUS_CODEUNKNOWN), null, LOGLEVEL_WARN);
mapi_stream_seek($this->statestream, 0, STREAM_SEEK_SET);
$state = "";
while(true) {
$data = mapi_stream_read($this->statestream, 4096);
if(strlen($data))
$state .= $data;
else
break;
}
return $state;
}
/**
* Checks if a message is in the synchronization interval (window)
* if a filter (e.g. Sync items two weeks back) or limits this synchronization.
* These checks only apply to Emails and Appointments only, Contacts, Tasks and Notes do not have time restrictions.
*
* @param string $messageid the message id to be checked
*
* @access private
* @return boolean
*/
private function isMessageInSyncInterval($messageid) {
// if there is no restriciton we do not need to check
if ($this->cutoffdate === false)
return true;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->isMessageInSyncInterval('%s'): cut off date is: %s", $messageid, $this->cutoffdate));
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($messageid));
if(!$entryid) {
ZLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isMessageInSyncInterval('%s'): Error, unable to resolve message id: 0x%X", $messageid, mapi_last_hresult()));
return false;
}
$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
if(!$mapimessage) {
ZLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->isMessageInSyncInterval('%s'): Error, unable to open entry id: 0x%X", $messageid, mapi_last_hresult()));
return false;
}
if ($this->contentClass == "Email")
return MAPIUtils::IsInEmailSyncInterval($this->store, $mapimessage, $this->cutoffdate);
elseif ($this->contentClass == "Calendar")
return MAPIUtils::IsInCalendarSyncInterval($this->store, $mapimessage, $this->cutoffdate);
return true;
}
/**----------------------------------------------------------------------------------------------------------
* Methods for ContentsExporter
*/
/**
* Loads objects which are expected to be exported with the state
* Before importing/saving the actual message from the mobile, a conflict detection should be done
*
* @param ContentParameters $contentparameters class of objects
* @param string $state
*
* @access public
* @return boolean
* @throws StatusException
*/
public function LoadConflicts($contentparameters, $state) {
if (!isset($this->session) || !isset($this->store) || !isset($this->folderid))
throw new StatusException("ImportChangesICS->LoadConflicts(): Error, can not load changes for conflict detection. Session, store or folder information not available", SYNC_STATUS_SERVERERROR);
// save data to load changes later if necessary
$this->conflictsLoaded = false;
$this->conflictsContentParameters = $contentparameters;
$this->conflictsState = $state;
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesICS->LoadConflicts(): will be loaded later if necessary");
return true;
}
/**
* Potential conflicts are only loaded when really necessary,
* e.g. on ADD or MODIFY
*
* @access private
* @return
*/
private function lazyLoadConflicts() {
if (!isset($this->session) || !isset($this->store) || !isset($this->folderid) ||
!isset($this->conflictsContentParameters) || $this->conflictsState === false) {
ZLog::Write(LOGLEVEL_WARN, "ImportChangesICS->lazyLoadConflicts(): can not load potential conflicting changes in lazymode for conflict detection. Missing information");
return false;
}
if (!$this->conflictsLoaded) {
ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesICS->lazyLoadConflicts(): loading..");
// configure an exporter so we can detect conflicts
$exporter = new ExportChangesICS($this->session, $this->store, $this->folderid);
$exporter->Config($this->conflictsState);
$exporter->ConfigContentParameters($this->conflictsContentParameters);
$exporter->InitializeExporter($this->memChanges);
// monitor how long it takes to export potential conflicts
// if this takes "too long" we cancel this operation!
$potConflicts = $exporter->GetChangeCount();
$started = time();
$exported = 0;
while(is_array($exporter->Synchronize())) {
$exported++;
// stop if this takes more than 15 seconds and there are more than 5 changes still to be exported
// within 20 seconds this should be finished or it will not be performed
if ((time() - $started) > 15 && ($potConflicts - $exported) > 5 ) {
ZLog::Write(LOGLEVEL_WARN, sprintf("ImportChangesICS->lazyLoadConflicts(): conflict detection cancelled as operation is too slow. In %d seconds only %d from %d changes were processed.",(time() - $started), $exported, $potConflicts));
$this->conflictsLoaded = true;
return;
}
}
$this->conflictsLoaded = true;
}
}
/**
* Imports a single message
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean/string - failure / id of message
* @throws StatusException
*/
public function ImportMessageChange($id, $message) {
$parentsourcekey = $this->folderid;
if($id)
$sourcekey = hex2bin($id);
$flags = 0;
$props = array();
$props[PR_PARENT_SOURCE_KEY] = $parentsourcekey;
// set the PR_SOURCE_KEY if available or mark it as new message
if($id) {
$props[PR_SOURCE_KEY] = $sourcekey;
// on editing an existing message, check if it is in the synchronization interval
if (!$this->isMessageInSyncInterval($id))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Message is outside the sync interval. Data not saved.", $id, get_class($message)), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
// check for conflicts
$this->lazyLoadConflicts();
if($this->memChanges->IsChanged($id)) {
if ($this->flags & SYNC_CONFLICT_OVERWRITE_PIM) {
// in these cases the status SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT should be returned, so the mobile client can inform the end user
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Server overwrites PIM. User is informed.", $id, get_class($message)), SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT, null, LOGLEVEL_INFO);
return false;
}
else
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from Server will be dropped! PIM overwrites server.", $id, get_class($message)));
}
if($this->memChanges->IsDeleted($id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Object was deleted on server.", $id, get_class($message)));
return false;
}
}
else
$flags = SYNC_NEW_MESSAGE;
if(mapi_importcontentschanges_importmessagechange($this->importer, $props, $flags, $mapimessage)) {
$this->mapiprovider->SetMessage($mapimessage, $message);
mapi_message_savechanges($mapimessage);
if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error, mapi_message_savechanges() failed: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
$sourcekeyprops = mapi_getprops($mapimessage, array (PR_SOURCE_KEY));
return bin2hex($sourcekeyprops[PR_SOURCE_KEY]);
}
else
throw new StatusException(sprintf("ImportChangesICS->ImportMessageChange('%s','%s'): Error updating object: 0x%X", $id, get_class($message), mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
}
/**
* Imports a deletion. This may conflict if the local object has been modified
*
* @param string $id
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageDeletion($id) {
// check if the message is in the current syncinterval
if (!$this->isMessageInSyncInterval($id))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageDeletion('%s'): Message is outside the sync interval and so far not deleted.", $id), SYNC_STATUS_OBJECTNOTFOUND);
// check for conflicts
$this->lazyLoadConflicts();
if($this->memChanges->IsChanged($id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageDeletion('%s'): Conflict detected. Data from Server will be dropped! PIM deleted object.", $id));
}
elseif($this->memChanges->IsDeleted($id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageDeletion('%s'): Conflict detected. Data is already deleted. Request will be ignored.", $id));
return true;
}
// do a 'soft' delete so people can un-delete if necessary
if(mapi_importcontentschanges_importmessagedeletion($this->importer, 1, array(hex2bin($id))))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageDeletion('%s'): Error updating object: 0x%X", $id, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
return true;
}
/**
* Imports a change in 'read' flag
* This can never conflict
*
* @param string $id
* @param int $flags - read/unread
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageReadFlag($id, $flags) {
// check if the message is in the current syncinterval
if (!$this->isMessageInSyncInterval($id))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): Message is outside the sync interval. Flags not updated.", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND);
// check for conflicts
/*
* Checking for conflicts is correct at this point, but is a very expensive operation.
* If the message was deleted, only an error will be shown.
*
$this->lazyLoadConflicts();
if($this->memChanges->IsDeleted($id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesICS->ImportMessageReadFlag('%s'): Conflict detected. Data is already deleted. Request will be ignored.", $id));
return true;
}
*/
$readstate = array ( "sourcekey" => hex2bin($id), "flags" => $flags);
if(!mapi_importcontentschanges_importperuserreadstatechange($this->importer, array($readstate) ))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageReadFlag('%s','%d'): Error setting read state: 0x%X", $id, $flags, mapi_last_hresult()), SYNC_STATUS_OBJECTNOTFOUND);
return true;
}
/**
* Imports a move of a message. This occurs when a user moves an item to another folder
*
* Normally, we would implement this via the 'offical' importmessagemove() function on the ICS importer,
* but the Zarafa importer does not support this. Therefore we currently implement it via a standard mapi
* call. This causes a mirror 'add/delete' to be sent to the PDA at the next sync.
* Manfred, 2010-10-21. For some mobiles import was causing duplicate messages in the destination folder
* (Mantis #202). Therefore we will create a new message in the destination folder, copy properties
* of the source message to the new one and then delete the source message.
*
* @param string $id
* @param string $newfolder destination folder
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageMove($id, $newfolder) {
if (strtolower($newfolder) == strtolower(bin2hex($this->folderid)) )
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, source and destination are equal", $id, $newfolder), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST);
// check if the source message is in the current syncinterval
if (!$this->isMessageInSyncInterval($id))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Source message is outside the sync interval. Move not performed.", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
// Get the entryid of the message we're moving
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($id));
if(!$entryid)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve source message id", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
//open the source message
$srcmessage = mapi_msgstore_openentry($this->store, $entryid);
if (!$srcmessage)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source message: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
// get correct mapi store for the destination folder
$dststore = ZPush::GetBackend()->GetMAPIStoreForFolderId(ZPush::GetAdditionalSyncFolderStore($newfolder), $newfolder);
if ($dststore === false)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open store of destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID);
$dstentryid = mapi_msgstore_entryidfromsourcekey($dststore, hex2bin($newfolder));
if(!$dstentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID);
$dstfolder = mapi_msgstore_openentry($dststore, $dstentryid);
if(!$dstfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID);
$newmessage = mapi_folder_createmessage($dstfolder);
if (!$newmessage)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to create message in destination folder: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID);
// Copy message
mapi_copyto($srcmessage, array(), array(), $newmessage);
if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, copy to destination message failed: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
$srcfolderentryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid);
if(!$srcfolderentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve source folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
$srcfolder = mapi_msgstore_openentry($this->store, $srcfolderentryid);
if (!$srcfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source folder: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID);
// Save changes
mapi_savechanges($newmessage);
if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, mapi_savechanges() failed: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
// Delete the old message
if (!mapi_folder_deletemessages($srcfolder, array($entryid)))
throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, delete of source message failed: 0x%X. Possible duplicates.", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED);
$sourcekeyprops = mapi_getprops($newmessage, array (PR_SOURCE_KEY));
if (isset($sourcekeyprops[PR_SOURCE_KEY]) && $sourcekeyprops[PR_SOURCE_KEY])
return bin2hex($sourcekeyprops[PR_SOURCE_KEY]);
return false;
}
/**----------------------------------------------------------------------------------------------------------
* Methods for HierarchyExporter
*/
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return string id of the folder
* @throws StatusException
*/
public function ImportFolderChange($folder) {
$id = isset($folder->serverid)?$folder->serverid:false;
$parent = $folder->parentid;
$displayname = u2wi($folder->displayname);
$type = $folder->type;
if (Utils::IsSystemFolder($type))
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, system folder can not be created/modified", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname), SYNC_FSSTATUS_SYSTEMFOLDER);
// create a new folder if $id is not set
if (!$id) {
// the root folder is "0" - get IPM_SUBTREE
if ($parent == "0") {
$parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
if (isset($parentprops[PR_IPM_SUBTREE_ENTRYID]))
$parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID];
}
else
$parentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($parent));
if (!$parentfentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (no entry id)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND);
$parentfolder = mapi_msgstore_openentry($this->store, $parentfentryid);
if (!$parentfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent folder (open entry)", Utils::PrintAsString(false), $folder->parentid, $displayname), SYNC_FSSTATUS_PARENTNOTFOUND);
// mapi_folder_createfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION
$newfolder = mapi_folder_createfolder($parentfolder, $displayname, "");
if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_folder_createfolder() failed: 0x%X", Utils::PrintAsString(false), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_FOLDEREXISTS);
mapi_setprops($newfolder, array(PR_CONTAINER_CLASS => MAPIUtils::GetContainerClassFromFolderType($type)));
$props = mapi_getprops($newfolder, array(PR_SOURCE_KEY));
if (isset($props[PR_SOURCE_KEY])) {
$sourcekey = bin2hex($props[PR_SOURCE_KEY]);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Created folder '%s' with id: '%s'", $displayname, $sourcekey));
return $sourcekey;
}
else
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder created but PR_SOURCE_KEY not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
return false;
}
// open folder for update
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($id));
if (!$entryid)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND);
// check if this is a MAPI default folder
if ($this->mapiprovider->IsMAPIDefaultFolder($entryid))
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, MAPI default folder can not be created/modified", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname), SYNC_FSSTATUS_SYSTEMFOLDER);
$mfolder = mapi_msgstore_openentry($this->store, $entryid);
if (!$mfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND);
$props = mapi_getprops($mfolder, array(PR_SOURCE_KEY, PR_PARENT_SOURCE_KEY, PR_DISPLAY_NAME, PR_CONTAINER_CLASS));
if (!isset($props[PR_SOURCE_KEY]) || !isset($props[PR_PARENT_SOURCE_KEY]) || !isset($props[PR_DISPLAY_NAME]) || !isset($props[PR_CONTAINER_CLASS]))
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, folder data not available: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
// get the real parent source key from mapi
if ($parent == "0") {
$parentprops = mapi_getprops($this->store, array(PR_IPM_SUBTREE_ENTRYID));
$parentfentryid = $parentprops[PR_IPM_SUBTREE_ENTRYID];
$mapifolder = mapi_msgstore_openentry($this->store, $parentfentryid);
$rootfolderprops = mapi_getprops($mapifolder, array(PR_SOURCE_KEY));
$parent = bin2hex($rootfolderprops[PR_SOURCE_KEY]);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderChange(): resolved AS parent '0' to sourcekey '%s'", $parent));
}
// a changed parent id means that the folder should be moved
if (bin2hex($props[PR_PARENT_SOURCE_KEY]) !== $parent) {
$sourceparentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, $props[PR_PARENT_SOURCE_KEY]);
if(!$sourceparentfentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent source folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND);
$sourceparentfolder = mapi_msgstore_openentry($this->store, $sourceparentfentryid);
if(!$sourceparentfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open parent source folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_PARENTNOTFOUND);
$destparentfentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($parent));
if(!$sourceparentfentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open destination folder (no entry id): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
$destfolder = mapi_msgstore_openentry($this->store, $destparentfentryid);
if(!$destfolder)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to open destination folder (open entry): 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
// mapi_folder_copyfolder() fails if a folder with this name already exists -> MAPI_E_COLLISION
if(! mapi_folder_copyfolder($sourceparentfolder, $entryid, $destfolder, $displayname, FOLDER_MOVE))
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, unable to move folder: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_FOLDEREXISTS);
$folderProps = mapi_getprops($mfolder, array(PR_SOURCE_KEY));
return $folderProps[PR_SOURCE_KEY];
}
// update the display name
$props = array(PR_DISPLAY_NAME => $displayname);
mapi_setprops($mfolder, $props);
mapi_savechanges($mfolder);
if (mapi_last_hresult())
throw new StatusException(sprintf("ImportChangesICS->ImportFolderChange('%s','%s','%s'): Error, mapi_savechanges() failed: 0x%X", Utils::PrintAsString($folder->serverid), $folder->parentid, $displayname, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
ZLog::Write(LOGLEVEL_DEBUG, "Imported changes for folder: $id");
return $id;
}
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent id is ignored in ICS
*
* @access public
* @return int SYNC_FOLDERHIERARCHY_STATUS
* @throws StatusException
*/
public function ImportFolderDeletion($id, $parent = false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesICS->ImportFolderDeletion('%s','%s'): importing folder deletetion", $id, $parent));
$folderentryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($id));
if(!$folderentryid)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderDeletion('%s','%s'): Error, unable to resolve folder", $id, $parent, mapi_last_hresult()), SYNC_FSSTATUS_FOLDERDOESNOTEXIST);
// get the folder type from the MAPIProvider
$type = $this->mapiprovider->GetFolderType($folderentryid);
if (Utils::IsSystemFolder($type) || $this->mapiprovider->IsMAPIDefaultFolder($folderentryid))
throw new StatusException(sprintf("ImportChangesICS->ImportFolderDeletion('%s','%s'): Error deleting system/default folder", $id, $parent), SYNC_FSSTATUS_SYSTEMFOLDER);
$ret = mapi_importhierarchychanges_importfolderdeletion ($this->importer, 0, array(PR_SOURCE_KEY => hex2bin($id)));
if (!$ret)
throw new StatusException(sprintf("ImportChangesICS->ImportFolderDeletion('%s','%s'): Error deleting folder: 0x%X", $id, $parent, mapi_last_hresult()), SYNC_FSSTATUS_SERVERERROR);
return $ret;
}
}
?>

View file

@ -0,0 +1,184 @@
#!/usr/bin/php
<?php
/***********************************************
* File : listfolders.php
* Project : Z-Push
* Descr : This is a small command line
* tool to list folders of a user
* store or public folder available
* for synchronization.
*
* Created : 06.05.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
define("PHP_MAPI_PATH", "/usr/share/php/mapi/");
define('MAPI_SERVER', 'file:///var/run/zarafa');
$supported_classes = array (
"IPF.Note" => "SYNC_FOLDER_TYPE_USER_MAIL",
"IPF.Task" => "SYNC_FOLDER_TYPE_USER_TASK",
"IPF.Appointment" => "SYNC_FOLDER_TYPE_USER_APPOINTMENT",
"IPF.Contact" => "SYNC_FOLDER_TYPE_USER_CONTACT",
"IPF.StickyNote" => "SYNC_FOLDER_TYPE_USER_NOTE"
);
main();
function main() {
listfolders_configure();
listfolders_handle();
}
function listfolders_configure() {
if (!isset($_SERVER["TERM"]) || !isset($_SERVER["LOGNAME"])) {
echo "This script should not be called in a browser.\n";
exit(1);
}
if (!function_exists("getopt")) {
echo "PHP Function 'getopt()' not found. Please check your PHP version and settings.\n";
exit(1);
}
require(PHP_MAPI_PATH.'mapi.util.php');
require(PHP_MAPI_PATH.'mapidefs.php');
require(PHP_MAPI_PATH.'mapicode.php');
require(PHP_MAPI_PATH.'mapitags.php');
require(PHP_MAPI_PATH.'mapiguid.php');
}
function listfolders_handle() {
$shortoptions = "l:h:u:p:";
$options = getopt($shortoptions);
$mapi = MAPI_SERVER;
$user = "SYSTEM";
$pass = "";
if (isset($options['h']))
$mapi = $options['h'];
if (isset($options['u']) && isset($options['p'])) {
$user = $options['u'];
$pass = $options['p'];
}
$zarafaAdmin = listfolders_zarafa_admin_setup($mapi, $user, $pass);
if (isset($zarafaAdmin['adminStore']) && isset($options['l'])) {
listfolders_getlist($zarafaAdmin['adminStore'], $zarafaAdmin['session'], trim($options['l']));
}
else {
echo "Usage:\nlistfolders.php [actions] [options]\n\nActions: [-l username]\n\t-l username\tlist folders of user, for public folder use 'SYSTEM'\n\nGlobal options: [-h path] [[-u remoteuser] [-p password]]\n\t-h path\t\tconnect through <path>, e.g. file:///var/run/socket\n\t-u authuser\tlogin as authenticated administration user\n\t-p authpassword\tpassword of the remoteuser\n\n";
}
}
function listfolders_zarafa_admin_setup ($mapi, $user, $pass) {
$session = @mapi_logon_zarafa($user, $pass, $mapi);
if (!$session) {
echo "User '$user' could not login. The script will exit. Errorcode: 0x". sprintf("%x", mapi_last_hresult()) . "\n";
exit(1);
}
$stores = @mapi_getmsgstorestable($session);
$storeslist = @mapi_table_queryallrows($stores);
$adminStore = @mapi_openmsgstore($session, $storeslist[0][PR_ENTRYID]);
$zarafauserinfo = @mapi_zarafa_getuser_by_name($adminStore, $user);
$admin = (isset($zarafauserinfo['admin']) && $zarafauserinfo['admin'])?true:false;
if (!$stores || !$storeslist || !$adminStore || !$admin) {
echo "There was error trying to log in as admin or retrieving admin info. The script will exit.\n";
exit(1);
}
return array("session" => $session, "adminStore" => $adminStore);
}
function listfolders_getlist ($adminStore, $session, $user) {
global $supported_classes;
if (strtoupper($user) == 'SYSTEM') {
// Find the public store store
$storestables = @mapi_getmsgstorestable($session);
$result = @mapi_last_hresult();
if ($result == NOERROR){
$rows = @mapi_table_queryallrows($storestables, array(PR_ENTRYID, PR_MDB_PROVIDER));
foreach($rows as $row) {
if (isset($row[PR_MDB_PROVIDER]) && $row[PR_MDB_PROVIDER] == ZARAFA_STORE_PUBLIC_GUID) {
if (!isset($row[PR_ENTRYID])) {
echo "Public folder are not available.\nIf this is a multi-tenancy system, use -u and -p and login with an admin user of the company.\nThe script will exit.\n";
exit (1);
}
$entryid = $row[PR_ENTRYID];
break;
}
}
}
}
else
$entryid = @mapi_msgstore_createentryid($adminStore, $user);
$userStore = @mapi_openmsgstore($session, $entryid);
$hresult = mapi_last_hresult();
// Cache the store for later use
if($hresult != NOERROR) {
echo "Could not open store for '$user'. The script will exit.\n";
exit (1);
}
$folder = @mapi_msgstore_openentry($userStore);
$h_table = @mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH);
$subfolders = @mapi_table_queryallrows($h_table, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_CONTAINER_CLASS, PR_SOURCE_KEY));
echo "Available folders in store '$user':\n" . str_repeat("-", 50) . "\n";
foreach($subfolders as $folder) {
if (isset($folder[PR_CONTAINER_CLASS]) && array_key_exists($folder[PR_CONTAINER_CLASS], $supported_classes)) {
echo "Folder name:\t". $folder[PR_DISPLAY_NAME] . "\n";
echo "Folder ID:\t". bin2hex($folder[PR_SOURCE_KEY]) . "\n";
echo "Type:\t\t". $supported_classes[$folder[PR_CONTAINER_CLASS]] . "\n";
echo "\n";
}
}
}
?>

View file

@ -0,0 +1,226 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Defines a base exception class for all custom exceptions, so every exceptions that
* is thrown/caught by this application should extend this base class and make use of it.
* it removes some peculiarities between different versions of PHP and exception handling.
*
* Some basic function of Exception class
* getMessage()- message of exception
* getCode() - code of exception
* getFile() - source filename
* getLine() - source line
* getTrace() - n array of the backtrace()
* getTraceAsString() - formated string of trace
*/
class BaseException extends Exception
{
/**
* Reference of previous exception, only used for PHP < 5.3
* can't use $previous here as its a private variable of parent class
*/
private $_previous = null;
/**
* Base name of the file, so we don't have to use static path of the file
*/
private $baseFile = null;
/**
* Flag to check if exception is already handled or not
*/
public $isHandled = false;
/**
* The exception message to show at client side.
*/
public $displayMessage = null;
/**
* Construct the exception
*
* @param string $errorMessage
* @param int $code
* @param Exception $previous
* @param string $displayMessage
* @return void
*/
public function __construct($errorMessage, $code = 0, Exception $previous = null, $displayMessage = null) {
// assign display message
$this->displayMessage = $displayMessage;
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
parent::__construct($errorMessage, (int) $code);
// set previous exception
if(!is_null($previous)) {
$this->_previous = $previous;
}
} else {
parent::__construct($errorMessage, (int) $code, $previous);
}
}
/**
* Overloading of final methods to get rid of incompatibilities between different PHP versions.
*
* @param string $method
* @param array $args
* @return mixed
*/
public function __call($method, array $args)
{
if ('getprevious' == strtolower($method)) {
return $this->_getPrevious();
}
return null;
}
/**
* @return string returns file name and line number combined where exception occured.
*/
public function getFileLine()
{
return $this->getBaseFile() . ':' . $this->getLine();
}
/**
* @return string returns message that should be sent to client to display
*/
public function getDisplayMessage()
{
if(!is_null($this->displayMessage)) {
return $this->displayMessage;
}
return $this->getMessage();
}
/**
* Function sets display message of an exception that will be sent to the client side
* to show it to user.
* @param string $message display message.
*/
public function setDisplayMessage($message)
{
$this->displayMessage = $message;
}
/**
* Function sets a flag in exception class to indicate that exception is already handled
* so if it is caught again in the top level of function stack then we have to silently
* ignore it.
*/
public function setHandled()
{
$this->isHandled = true;
}
/**
* @return string returns base path of the file where exception occured.
*/
public function getBaseFile()
{
if(is_null($this->baseFile)) {
$this->baseFile = basename(parent::getFile());
}
return $this->baseFile;
}
/**
* Function will check for PHP version if it is greater than 5.3 then we can use its default implementation
* otherwise we have to use our own implementation of chanining functionality.
*
* @return Exception returns previous exception
*/
public function _getPrevious()
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
return $this->_previous;
} else {
return parent::getPrevious();
}
}
/**
* String representation of the exception, also handles previous exception.
*
* @return string
*/
public function __toString()
{
if (version_compare(PHP_VERSION, '5.3.0', '<')) {
if (($e = $this->getPrevious()) !== null) {
return $e->__toString()
. "\n\nNext "
. parent::__toString();
}
}
return parent::__toString();
}
/**
* Name of the class of exception.
*
* @return string
*/
public function getName()
{
return get_class($this);
}
// @TODO getTrace and getTraceAsString
}
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,397 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
include_once('backend/zarafa/mapi/class.recurrence.php');
class FreeBusyPublish {
var $session;
var $calendar;
var $entryid;
var $starttime;
var $length;
var $store;
var $proptags;
/**
* Constuctor
*
* @param mapi_session $session MAPI Session
* @param mapi_folder $calendar Calendar to publish
* @param string $entryid AddressBook Entry ID for the user we're publishing for
*/
function FreeBusyPublish($session, $store, $calendar, $entryid)
{
$properties["entryid"] = PR_ENTRYID;
$properties["parent_entryid"] = PR_PARENT_ENTRYID;
$properties["message_class"] = PR_MESSAGE_CLASS;
$properties["icon_index"] = PR_ICON_INDEX;
$properties["subject"] = PR_SUBJECT;
$properties["display_to"] = PR_DISPLAY_TO;
$properties["importance"] = PR_IMPORTANCE;
$properties["sensitivity"] = PR_SENSITIVITY;
$properties["startdate"] = "PT_SYSTIME:PSETID_Appointment:0x820d";
$properties["duedate"] = "PT_SYSTIME:PSETID_Appointment:0x820e";
$properties["recurring"] = "PT_BOOLEAN:PSETID_Appointment:0x8223";
$properties["recurring_data"] = "PT_BINARY:PSETID_Appointment:0x8216";
$properties["busystatus"] = "PT_LONG:PSETID_Appointment:0x8205";
$properties["label"] = "PT_LONG:PSETID_Appointment:0x8214";
$properties["alldayevent"] = "PT_BOOLEAN:PSETID_Appointment:0x8215";
$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
$properties["meeting"] = "PT_LONG:PSETID_Appointment:0x8217";
$properties["startdate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8235";
$properties["enddate_recurring"] = "PT_SYSTIME:PSETID_Appointment:0x8236";
$properties["location"] = "PT_STRING8:PSETID_Appointment:0x8208";
$properties["duration"] = "PT_LONG:PSETID_Appointment:0x8213";
$properties["responsestatus"] = "PT_LONG:PSETID_Appointment:0x8218";
$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
$properties["basedate"] = "PT_SYSTIME:PSETID_Appointment:0x8228";
$properties["timezone_data"] = "PT_BINARY:PSETID_Appointment:0x8233";
$this->proptags = getPropIdsFromStrings($store, $properties);
$this->session = $session;
$this->calendar = $calendar;
$this->entryid = $entryid;
$this->store = $store;
}
/**
* Publishes the infomation
* @paam timestamp $starttime Time from which to publish data (usually now)
* @paam integer $length Amount of seconds from $starttime we should publish
*/
function publishFB($starttime, $length) {
$start = $starttime;
$end = $starttime + $length;
// Get all the items in the calendar that we need
$calendaritems = Array();
$restrict = Array(RES_OR,
Array(
// OR
// (item[start] >= start && item[start] <= end)
Array(RES_AND,
Array(
Array(RES_PROPERTY,
Array(RELOP => RELOP_GE,
ULPROPTAG => $this->proptags["startdate"],
VALUE => $start
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_LE,
ULPROPTAG => $this->proptags["startdate"],
VALUE => $end
)
)
)
),
// OR
// (item[end] >= start && item[end] <= end)
Array(RES_AND,
Array(
Array(RES_PROPERTY,
Array(RELOP => RELOP_GE,
ULPROPTAG => $this->proptags["duedate"],
VALUE => $start
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_LE,
ULPROPTAG => $this->proptags["duedate"],
VALUE => $end
)
)
)
),
// OR
// (item[start] < start && item[end] > end)
Array(RES_AND,
Array(
Array(RES_PROPERTY,
Array(RELOP => RELOP_LT,
ULPROPTAG => $this->proptags["startdate"],
VALUE => $start
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_GT,
ULPROPTAG => $this->proptags["duedate"],
VALUE => $end
)
)
)
),
// OR
Array(RES_OR,
Array(
// OR
// (EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[end] >= start)
Array(RES_AND,
Array(
Array(RES_EXIST,
Array(ULPROPTAG => $this->proptags["enddate_recurring"],
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => $this->proptags["recurring"],
VALUE => true
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_GE,
ULPROPTAG => $this->proptags["enddate_recurring"],
VALUE => $start
)
)
)
),
// OR
// (!EXIST(ecurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
Array(RES_AND,
Array(
Array(RES_NOT,
Array(
Array(RES_EXIST,
Array(ULPROPTAG => $this->proptags["enddate_recurring"]
)
)
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_LE,
ULPROPTAG => $this->proptags["startdate"],
VALUE => $end
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => $this->proptags["recurring"],
VALUE => true
)
)
)
)
)
) // EXISTS OR
)
); // global OR
$contents = mapi_folder_getcontentstable($this->calendar);
mapi_table_restrict($contents, $restrict);
while(1) {
$rows = mapi_table_queryrows($contents, array_values($this->proptags), 0, 50);
if(!is_array($rows))
break;
if(empty($rows))
break;
foreach ($rows as $row) {
$occurrences = Array();
if(isset($row[$this->proptags['recurring']]) && $row[$this->proptags['recurring']]) {
$recur = new Recurrence($this->store, $row);
$occurrences = $recur->getItems($starttime, $starttime + $length);
} else {
$occurrences[] = $row;
}
$calendaritems = array_merge($calendaritems, $occurrences);
}
}
// $calendaritems now contains all the calendar items in the specified time
// frame. We now need to merge these into a flat array of begin/end/status
// objects. This also filters out all the 'free' items (status 0)
$freebusy = $this->mergeItemsFB($calendaritems);
// $freebusy now contains the start, end and status of all items, merged.
// Get the FB interface
try {
$fbsupport = mapi_freebusysupport_open($this->session, $this->store);
} catch (MAPIException $e) {
if($e->getCode() == MAPI_E_NOT_FOUND) {
$e->setHandled();
if(function_exists("dump")) {
dump("Error in opening freebusysupport object.");
}
}
}
// Open updater for this user
if(isset($fbsupport) && $fbsupport) {
$updaters = mapi_freebusysupport_loadupdate($fbsupport, Array($this->entryid));
$updater = $updaters[0];
// Send the data
mapi_freebusyupdate_reset($updater);
mapi_freebusyupdate_publish($updater, $freebusy);
mapi_freebusyupdate_savechanges($updater, $start-24*60*60, $end);
// We're finished
mapi_freebusysupport_close($fbsupport);
}
else
ZLog::Write(LOGLEVEL_WARN, "FreeBusyPublish is not available");
}
/**
* Sorts by timestamp, if equal, then end before start
*/
function cmp($a, $b)
{
if ($a["time"] == $b["time"]) {
if($a["type"] < $b["type"])
return 1;
if($a["type"] > $b["type"])
return -1;
return 0;
}
return ($a["time"] > $b["time"] ? 1 : -1);
}
/**
* Function mergeItems
* @author Steve Hardy
*/
function mergeItemsFB($items)
{
$merged = Array();
$timestamps = Array();
$csubj = Array();
$cbusy = Array();
$level = 0;
$laststart = null;
foreach($items as $item)
{
$ts["type"] = 0;
$ts["time"] = $item[$this->proptags["startdate"]];
$ts["subject"] = $item[PR_SUBJECT];
$ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : 0; //ZP-197
$timestamps[] = $ts;
$ts["type"] = 1;
$ts["time"] = $item[$this->proptags["duedate"]];
$ts["subject"] = $item[PR_SUBJECT];
$ts["status"] = (isset($item[$this->proptags["busystatus"]])) ? $item[$this->proptags["busystatus"]] : 0; //ZP-197
$timestamps[] = $ts;
}
usort($timestamps, Array($this, "cmp"));
$laststart = 0; // seb added
foreach($timestamps as $ts)
{
switch ($ts["type"])
{
case 0: // Start
if ($level != 0 && $laststart != $ts["time"])
{
$newitem["start"] = $laststart;
$newitem["end"] = $ts["time"];
$newitem["subject"] = join(",", $csubj);
$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
if($newitem["status"] > 0)
$merged[] = $newitem;
}
$level++;
$csubj[] = $ts["subject"];
$cbusy[] = $ts["status"];
$laststart = $ts["time"];
break;
case 1: // End
if ($laststart != $ts["time"])
{
$newitem["start"] = $laststart;
$newitem["end"] = $ts["time"];
$newitem["subject"] = join(",", $csubj);
$newitem["status"] = !empty($cbusy) ? max($cbusy) : 0;
if($newitem["status"] > 0)
$merged[] = $newitem;
}
$level--;
array_splice($csubj, array_search($ts["subject"], $csubj, 1), 1);
array_splice($cbusy, array_search($ts["status"], $cbusy, 1), 1);
$laststart = $ts["time"];
break;
}
}
return $merged;
}
}
?>

View file

@ -0,0 +1,107 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* MAPIException
* if enabled using mapi_enable_exceptions then php-ext can throw exceptions when
* any error occurs in mapi calls. this exception will only be thrown when severity bit is set in
* error code that means it will be thrown only for mapi errors not for mapi warnings.
*/
// FatalException will trigger a HTTP return code 500 to the mobile
class MAPIException extends FatalException
{
/**
* Function will return display message of exception if its set by the calle.
* if it is not set then we are generating some default display messages based
* on mapi error code.
* @return string returns error-message that should be sent to client to display.
*/
public function getDisplayMessage()
{
if(!empty($this->displayMessage))
return $this->displayMessage;
switch($this->getCode())
{
case MAPI_E_NO_ACCESS:
return _("You have insufficient privileges to open this object.");
case MAPI_E_LOGON_FAILED:
case MAPI_E_UNCONFIGURED:
return _("Logon Failed. Please check your username/password.");
case MAPI_E_NETWORK_ERROR:
return _("Can not connect to Zarafa server.");
case MAPI_E_UNKNOWN_ENTRYID:
return _("Can not open object with provided id.");
case MAPI_E_NO_RECIPIENTS:
return _("There are no recipients in the message.");
case MAPI_E_NOT_FOUND:
return _("Can not find object.");
case MAPI_E_INTERFACE_NOT_SUPPORTED:
case MAPI_E_INVALID_PARAMETER:
case MAPI_E_INVALID_ENTRYID:
case MAPI_E_INVALID_OBJECT:
case MAPI_E_TOO_COMPLEX:
case MAPI_E_CORRUPT_DATA:
case MAPI_E_END_OF_SESSION:
case MAPI_E_AMBIGUOUS_RECIP:
case MAPI_E_COLLISION:
case MAPI_E_UNCONFIGURED:
default :
return sprintf(_("Unknown MAPI Error: %s"), get_mapi_error_name($this->getCode()));
}
}
}
// Tell the PHP extension which exception class to instantiate
if (function_exists('mapi_enable_exceptions')) {
//mapi_enable_exceptions("mapiexception");
}
?>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,464 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
require_once("backend/zarafa/mapi/class.baserecurrence.php");
class TaskRecurrence extends BaseRecurrence
{
/**
* Timezone info which is always false for task
*/
var $tz = false;
function TaskRecurrence($store, $message)
{
$this->store = $store;
$this->message = $message;
$properties = array();
$properties["entryid"] = PR_ENTRYID;
$properties["parent_entryid"] = PR_PARENT_ENTRYID;
$properties["icon_index"] = PR_ICON_INDEX;
$properties["message_class"] = PR_MESSAGE_CLASS;
$properties["message_flags"] = PR_MESSAGE_FLAGS;
$properties["subject"] = PR_SUBJECT;
$properties["importance"] = PR_IMPORTANCE;
$properties["sensitivity"] = PR_SENSITIVITY;
$properties["last_modification_time"] = PR_LAST_MODIFICATION_TIME;
$properties["status"] = "PT_LONG:PSETID_Task:0x8101";
$properties["percent_complete"] = "PT_DOUBLE:PSETID_Task:0x8102";
$properties["startdate"] = "PT_SYSTIME:PSETID_Task:0x8104";
$properties["duedate"] = "PT_SYSTIME:PSETID_Task:0x8105";
$properties["reset_reminder"] = "PT_BOOLEAN:PSETID_Task:0x8107";
$properties["dead_occurrence"] = "PT_BOOLEAN:PSETID_Task:0x8109";
$properties["datecompleted"] = "PT_SYSTIME:PSETID_Task:0x810f";
$properties["recurring_data"] = "PT_BINARY:PSETID_Task:0x8116";
$properties["actualwork"] = "PT_LONG:PSETID_Task:0x8110";
$properties["totalwork"] = "PT_LONG:PSETID_Task:0x8111";
$properties["complete"] = "PT_BOOLEAN:PSETID_Task:0x811c";
$properties["task_f_creator"] = "PT_BOOLEAN:PSETID_Task:0x811e";
$properties["owner"] = "PT_STRING8:PSETID_Task:0x811f";
$properties["recurring"] = "PT_BOOLEAN:PSETID_Task:0x8126";
$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
$properties["reminder_time"] = "PT_SYSTIME:PSETID_Common:0x8502";
$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["private"] = "PT_BOOLEAN:PSETID_Common:0x8506";
$properties["contacts"] = "PT_MV_STRING8:PSETID_Common:0x853a";
$properties["contacts_string"] = "PT_STRING8:PSETID_Common:0x8586";
$properties["categories"] = "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords";
$properties["commonstart"] = "PT_SYSTIME:PSETID_Common:0x8516";
$properties["commonend"] = "PT_SYSTIME:PSETID_Common:0x8517";
$properties["commonassign"] = "PT_LONG:PSETID_Common:0x8518";
$properties["flagdueby"] = "PT_SYSTIME:PSETID_Common:0x8560";
$properties["side_effects"] = "PT_LONG:PSETID_Common:0x8510";
$properties["reminder"] = "PT_BOOLEAN:PSETID_Common:0x8503";
$properties["reminder_minutes"] = "PT_LONG:PSETID_Common:0x8501";
$this->proptags = getPropIdsFromStrings($store, $properties);
parent::BaseRecurrence($store, $message, $properties);
}
/**
* Function which saves recurrence and also regenerates task if necessary.
*@param array $recur new recurrence properties
*@return array of properties of regenerated task else false
*/
function setRecurrence(&$recur)
{
$this->recur = $recur;
$this->action =& $recur;
if(!isset($this->recur["changed_occurences"]))
$this->recur["changed_occurences"] = Array();
if(!isset($this->recur["deleted_occurences"]))
$this->recur["deleted_occurences"] = Array();
if (!isset($this->recur['startocc'])) $this->recur['startocc'] = 0;
if (!isset($this->recur['endocc'])) $this->recur['endocc'] = 0;
// Save recurrence because we need proper startrecurrdate and endrecurrdate
$this->saveRecurrence();
// Update $this->recur with proper startrecurrdate and endrecurrdate updated after saveing recurrence
$msgProps = mapi_getprops($this->message, array($this->proptags['recurring_data']));
$recurring_data = $this->parseRecurrence($msgProps[$this->proptags['recurring_data']]);
foreach($recurring_data as $key => $value) {
$this->recur[$key] = $value;
}
$this->setFirstOccurrence();
// Let's see if next occurrence has to be generated
return $this->moveToNextOccurrence();
}
/**
* Sets task object to first occurrence if startdate/duedate of task object is different from first occurrence
*/
function setFirstOccurrence()
{
// Check if it is already the first occurrence
if($this->action['start'] == $this->recur["start"]){
return;
}else{
$items = $this->getNextOccurrence();
$props = array();
$props[$this->proptags['startdate']] = $items[$this->proptags['startdate']];
$props[$this->proptags['commonstart']] = $items[$this->proptags['startdate']];
$props[$this->proptags['duedate']] = $items[$this->proptags['duedate']];
$props[$this->proptags['commonend']] = $items[$this->proptags['duedate']];
mapi_setprops($this->message, $props);
}
}
/**
* Function which creates new task as current occurrence and moves the
* existing task to next occurrence.
*
*@param array $recur $action from client
*@return boolean if moving to next occurrence succeed then it returns
* properties of either newly created task or existing task ELSE
* false because that was last occurrence
*/
function moveToNextOccurrence()
{
$result = false;
/**
* Every recurring task should have a 'duedate'. If a recurring task is created with no start/end date
* then we create first two occurrence separately and for first occurrence recurrence has ended.
*/
if ((empty($this->action['startdate']) && empty($this->action['duedate']))
|| ($this->action['complete'] == 1) || (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])){
$nextOccurrence = $this->getNextOccurrence();
$result = mapi_getprops($this->message, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
$props = array();
if ($nextOccurrence) {
if (!isset($this->action['deleteOccurrence'])) {
// Create current occurrence as separate task
$result = $this->regenerateTask($this->action['complete']);
}
// Set reminder for next occurrence
$this->setReminder($nextOccurrence);
// Update properties for next occurrence
$this->action['duedate'] = $props[$this->proptags['duedate']] = $nextOccurrence[$this->proptags['duedate']];
$this->action['commonend'] = $props[$this->proptags['commonend']] = $nextOccurrence[$this->proptags['duedate']];
$this->action['startdate'] = $props[$this->proptags['startdate']] = $nextOccurrence[$this->proptags['startdate']];
$this->action['commonstart'] = $props[$this->proptags['commonstart']] = $nextOccurrence[$this->proptags['startdate']];
// If current task as been mark as 'Complete' then next occurrence should be uncomplete.
if (isset($this->action['complete']) && $this->action['complete'] == 1) {
$this->action['status'] = $props[$this->proptags["status"]] = olTaskNotStarted;
$this->action['complete'] = $props[$this->proptags["complete"]] = false;
$this->action['percent_complete'] = $props[$this->proptags["percent_complete"]] = 0;
}
$props[$this->proptags["dead_occurrence"]] = false;
} else {
if (isset($this->action['deleteOccurrence']) && $this->action['deleteOccurrence'])
return false;
// Didn't get next occurrence, probably this is the last one, so recurrence ends here
$props[$this->proptags["dead_occurrence"]] = true;
$props[$this->proptags["datecompleted"]] = $this->action['datecompleted'];
$props[$this->proptags["task_f_creator"]] = true;
//OL props
$props[$this->proptags["side_effects"]] = 1296;
$props[$this->proptags["icon_index"]] = 1280;
}
mapi_setprops($this->message, $props);
}
return $result;
}
/**
* Function which return properties of next occurrence
*@return array startdate/enddate of next occurrence
*/
function getNextOccurrence()
{
if ($this->recur) {
$items = array();
//@TODO: fix start of range
$start = isset($this->messageprops[$this->proptags["duedate"]]) ? $this->messageprops[$this->proptags["duedate"]] : $this->action['start'];
$dayend = ($this->recur['term'] == 0x23) ? 0x7fffffff : $this->dayStartOf($this->recur["end"]);
// Fix recur object
$this->recur['startocc'] = 0;
$this->recur['endocc'] = 0;
// Retrieve next occurrence
$items = $this->getItems($start, $dayend, 1);
return !empty($items) ? $items[0] : false;
}
}
/**
* Function which clones current occurrence and sets appropriate properties.
* The original recurring item is moved to next occurrence.
*@param boolean $markComplete true if existing occurrence has to be mark complete else false.
*/
function regenerateTask($markComplete)
{
// Get all properties
$taskItemProps = mapi_getprops($this->message);
if (isset($this->action["subject"])) $taskItemProps[$this->proptags["subject"]] = $this->action["subject"];
if (isset($this->action["importance"])) $taskItemProps[$this->proptags["importance"]] = $this->action["importance"];
if (isset($this->action["startdate"])) {
$taskItemProps[$this->proptags["startdate"]] = $this->action["startdate"];
$taskItemProps[$this->proptags["commonstart"]] = $this->action["startdate"];
}
if (isset($this->action["duedate"])) {
$taskItemProps[$this->proptags["duedate"]] = $this->action["duedate"];
$taskItemProps[$this->proptags["commonend"]] = $this->action["duedate"];
}
$folder = mapi_msgstore_openentry($this->store, $taskItemProps[PR_PARENT_ENTRYID]);
$newMessage = mapi_folder_createmessage($folder);
$taskItemProps[$this->proptags["status"]] = $markComplete ? olTaskComplete : olTaskNotStarted;
$taskItemProps[$this->proptags["complete"]] = $markComplete;
$taskItemProps[$this->proptags["percent_complete"]] = $markComplete ? 1 : 0;
// This occurrence has been marked as 'Complete' so disable reminder
if ($markComplete) {
$taskItemProps[$this->proptags["reset_reminder"]] = false;
$taskItemProps[$this->proptags["reminder"]] = false;
$taskItemProps[$this->proptags["datecompleted"]] = $this->action["datecompleted"];
unset($this->action[$this->proptags['datecompleted']]);
}
// Recurrence ends for this item
$taskItemProps[$this->proptags["dead_occurrence"]] = true;
$taskItemProps[$this->proptags["task_f_creator"]] = true;
//OL props
$taskItemProps[$this->proptags["side_effects"]] = 1296;
$taskItemProps[$this->proptags["icon_index"]] = 1280;
// Copy recipients
$recipienttable = mapi_message_getrecipienttable($this->message);
$recipients = mapi_table_queryallrows($recipienttable, array(PR_ENTRYID, PR_DISPLAY_NAME, PR_EMAIL_ADDRESS, PR_RECIPIENT_ENTRYID, PR_RECIPIENT_TYPE, PR_SEND_INTERNET_ENCODING, PR_SEND_RICH_INFO, PR_RECIPIENT_DISPLAY_NAME, PR_ADDRTYPE, PR_DISPLAY_TYPE, PR_RECIPIENT_TRACKSTATUS, PR_RECIPIENT_TRACKSTATUS_TIME, PR_RECIPIENT_FLAGS, PR_ROWID));
$copy_to_recipientTable = mapi_message_getrecipienttable($newMessage);
$copy_to_recipientRows = mapi_table_queryallrows($copy_to_recipientTable, array(PR_ROWID));
foreach($copy_to_recipientRows as $recipient) {
mapi_message_modifyrecipients($newMessage, MODRECIP_REMOVE, array($recipient));
}
mapi_message_modifyrecipients($newMessage, MODRECIP_ADD, $recipients);
// Copy attachments
$attachmentTable = mapi_message_getattachmenttable($this->message);
if($attachmentTable) {
$attachments = mapi_table_queryallrows($attachmentTable, array(PR_ATTACH_NUM, PR_ATTACH_SIZE, PR_ATTACH_LONG_FILENAME, PR_ATTACHMENT_HIDDEN, PR_DISPLAY_NAME, PR_ATTACH_METHOD));
foreach($attachments as $attach_props){
$attach_old = mapi_message_openattach($this->message, (int) $attach_props[PR_ATTACH_NUM]);
$attach_newResourceMsg = mapi_message_createattach($newMessage);
mapi_copyto($attach_old, array(), array(), $attach_newResourceMsg, 0);
mapi_savechanges($attach_newResourceMsg);
}
}
mapi_setprops($newMessage, $taskItemProps);
mapi_savechanges($newMessage);
// Update body of original message
$msgbody = mapi_message_openproperty($this->message, PR_BODY);
$msgbody = trim($this->windows1252_to_utf8($msgbody), "\0");
$separator = "------------\r\n";
if (!empty($msgbody) && strrpos($msgbody, $separator) === false) {
$msgbody = $separator . $msgbody;
$stream = mapi_openpropertytostream($this->message, PR_BODY, MAPI_CREATE | MAPI_MODIFY);
mapi_stream_setsize($stream, strlen($msgbody));
mapi_stream_write($stream, $msgbody);
mapi_stream_commit($stream);
}
// We need these properties to notify client
return mapi_getprops($newMessage, array(PR_ENTRYID, PR_PARENT_ENTRYID, PR_STORE_ENTRYID));
}
/**
* processOccurrenceItem, adds an item to a list of occurrences, but only if the
* resulting occurrence starts or ends in the interval <$start, $end>
* @param array $items reference to the array to be added to
* @param date $start start of timeframe in GMT TIME
* @param date $end end of timeframe in GMT TIME
* @param date $basedate (hour/sec/min assumed to be 00:00:00) in LOCAL TIME OF THE OCCURRENCE
*/
function processOccurrenceItem(&$items, $start, $end, $now)
{
if ($now > $start) {
$newItem = array();
$newItem[$this->proptags['startdate']] = $now;
// If startdate and enddate are set on task, then slide enddate according to duration
if (isset($this->messageprops[$this->proptags["startdate"]]) && isset($this->messageprops[$this->proptags["duedate"]])) {
$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']] + ($this->messageprops[$this->proptags["duedate"]] - $this->messageprops[$this->proptags["startdate"]]);
} else {
$newItem[$this->proptags['duedate']] = $newItem[$this->proptags['startdate']];
}
$items[] = $newItem;
}
}
/**
* Function which marks existing occurrence to 'Complete'
*@param array $recur array action from client
*@return array of properties of regenerated task else false
*/
function markOccurrenceComplete(&$recur)
{
// Fix timezone object
$this->tz = false;
$this->action =& $recur;
$dead_occurrence = isset($this->messageprops[$this->proptags['dead_occurrence']]) ? $this->messageprops[$this->proptags['dead_occurrence']] : false;
if (!$dead_occurrence) {
return $this->moveToNextOccurrence();
}
return false;
}
/**
* Function which sets reminder on recurring task after existing occurrence has been deleted or marked complete.
*@param array $nextOccurrence properties of next occurrence
*/
function setReminder($nextOccurrence)
{
$props = array();
if ($nextOccurrence) {
// Check if reminder is reset. Default is 'false'
$reset_reminder = isset($this->messageprops[$this->proptags['reset_reminder']]) ? $this->messageprops[$this->proptags['reset_reminder']] : false;
$reminder = $this->messageprops[$this->proptags['reminder']];
// Either reminder was already set OR reminder was set but was dismissed bty user
if ($reminder || $reset_reminder) {
// Reminder can be set at any time either before or after the duedate, so get duration between the reminder time and duedate
$reminder_time = isset($this->messageprops[$this->proptags['reminder_time']]) ? $this->messageprops[$this->proptags['reminder_time']] : 0;
$reminder_difference = isset($this->messageprops[$this->proptags['duedate']]) ? $this->messageprops[$this->proptags['duedate']] : 0;
$reminder_difference = $reminder_difference - $reminder_time;
// Apply duration to next calculated duedate
$next_reminder_time = $nextOccurrence[$this->proptags['duedate']] - $reminder_difference;
$props[$this->proptags['reminder_time']] = $next_reminder_time;
$props[$this->proptags['flagdueby']] = $next_reminder_time;
$this->action['reminder'] = $props[$this->proptags['reminder']] = true;
}
} else {
// Didn't get next occurrence, probably this is the last occurrence
$props[$this->proptags['reminder']] = false;
$props[$this->proptags['reset_reminder']] = false;
}
if (!empty($props))
mapi_setprops($this->message, $props);
}
/**
* Function which recurring task to next occurrence.
* It simply doesn't regenerate task
@param array $action
*/
function deleteOccurrence($action)
{
$this->tz = false;
$this->action = $action;
$result = $this->moveToNextOccurrence();
mapi_savechanges($this->message);
return $result;
}
/**
* Convert from windows-1252 encoded string to UTF-8 string
*
* The same conversion rules as utf8_to_windows1252 apply.
*
* @param string $string the Windows-1252 string to convert
* @return string UTF-8 representation of the string
*/
function windows1252_to_utf8($string)
{
if (function_exists("iconv")){
return iconv("Windows-1252", "UTF-8//TRANSLIT", $string);
}else{
return utf8_encode($string); // no euro support here
}
}
}
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,339 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Function to make a MAPIGUID from a php string.
* The C++ definition for the GUID is:
* typedef struct _GUID
* {
* unsigned long Data1;
* unsigned short Data2;
* unsigned short Data3;
* unsigned char Data4[8];
* } GUID;
*
* A GUID is normally represented in the following form:
* {00062008-0000-0000-C000-000000000046}
*
* @param String GUID
*/
function makeGuid($guid)
{
// remove the { and } from the string and explode it into an array
$guidArray = explode('-', substr($guid, 1,strlen($guid)-2));
// convert to hex!
$data1[0] = intval(substr($guidArray[0], 0, 4),16); // we need to split the unsigned long
$data1[1] = intval(substr($guidArray[0], 4, 4),16);
$data2 = intval($guidArray[1], 16);
$data3 = intval($guidArray[2], 16);
$data4[0] = intval(substr($guidArray[3], 0, 2),16);
$data4[1] = intval(substr($guidArray[3], 2, 2),16);
for($i=0; $i < 6; $i++)
{
$data4[] = intval(substr($guidArray[4], $i*2, 2),16);
}
return pack("vvvvCCCCCCCC", $data1[1], $data1[0], $data2, $data3, $data4[0],$data4[1],$data4[2],$data4[3],$data4[4],$data4[5],$data4[6],$data4[7]);
}
/**
* Function to get a human readable string from a MAPI error code
*
*@param int $errcode the MAPI error code, if not given, we use mapi_last_hresult
*@return string The defined name for the MAPI error code
*/
function get_mapi_error_name($errcode=null)
{
if ($errcode === null){
$errcode = mapi_last_hresult();
}
if ($errcode !== 0) {
// get_defined_constants(true) is preferred, but crashes PHP
// https://bugs.php.net/bug.php?id=61156
$allConstants = get_defined_constants();
foreach ($allConstants as $key => $value) {
/**
* If PHP encounters a number beyond the bounds of the integer type,
* it will be interpreted as a float instead, so when comparing these error codes
* we have to manually typecast value to integer, so float will be converted in integer,
* but still its out of bound for integer limit so it will be auto adjusted to minus value
*/
if ($errcode == (int) $value) {
// Check that we have an actual MAPI error or warning definition
$prefix = substr($key, 0, 7);
if ($prefix == "MAPI_E_" || $prefix == "MAPI_W_") {
return $key;
}
}
}
} else {
return "NOERROR";
}
// error code not found, return hex value (this is a fix for 64-bit systems, we can't use the dechex() function for this)
$result = unpack("H*", pack("N", $errcode));
return "0x" . $result[1];
}
/**
* Parses properties from an array of strings. Each "string" may be either an ULONG, which is a direct property ID,
* or a string with format "PT_TYPE:{GUID}:StringId" or "PT_TYPE:{GUID}:0xXXXX" for named
* properties.
*
* @returns array of properties
*/
function getPropIdsFromStrings($store, $mapping)
{
$props = array();
$ids = array("name"=>array(), "id"=>array(), "guid"=>array(), "type"=>array()); // this array stores all the information needed to retrieve a named property
$num = 0;
// caching
$guids = array();
foreach($mapping as $name=>$val){
if(is_string($val)) {
$split = explode(":", $val);
if(count($split) != 3){ // invalid string, ignore
trigger_error(sprintf("Invalid property: %s \"%s\"",$name,$val), E_USER_NOTICE);
continue;
}
if(substr($split[2], 0, 2) == "0x") {
$id = hexdec(substr($split[2], 2));
} else {
$id = $split[2];
}
// have we used this guid before?
if (!defined($split[1])){
if (!array_key_exists($split[1], $guids)){
$guids[$split[1]] = makeguid($split[1]);
}
$guid = $guids[$split[1]];
}else{
$guid = constant($split[1]);
}
// temp store info about named prop, so we have to call mapi_getidsfromnames just one time
$ids["name"][$num] = $name;
$ids["id"][$num] = $id;
$ids["guid"][$num] = $guid;
$ids["type"][$num] = $split[0];
$num++;
}else{
// not a named property
$props[$name] = $val;
}
}
if (empty($ids["id"])){
return $props;
}
// get the ids
$named = mapi_getidsfromnames($store, $ids["id"], $ids["guid"]);
foreach($named as $num=>$prop){
$props[$ids["name"][$num]] = mapi_prop_tag(constant($ids["type"][$num]), mapi_prop_id($prop));
}
return $props;
}
/**
* Check wether a call to mapi_getprops returned errors for some properties.
* mapi_getprops function tries to get values of properties requested but somehow if
* if a property value can not be fetched then it changes type of property tag as PT_ERROR
* and returns error for that particular property, probable errors
* that can be returned as value can be MAPI_E_NOT_FOUND, MAPI_E_NOT_ENOUGH_MEMORY
*
* @param long $property Property to check for error
* @param Array $propArray An array of properties
* @return mixed Gives back false when there is no error, if there is, gives the error
*/
function propIsError($property, $propArray)
{
if (array_key_exists(mapi_prop_tag(PT_ERROR, mapi_prop_id($property)), $propArray)) {
return $propArray[mapi_prop_tag(PT_ERROR, mapi_prop_id($property))];
} else {
return false;
}
}
/******** Macro Functions for PR_DISPLAY_TYPE_EX values *********/
/**
* check addressbook object is a remote mailuser
*/
function DTE_IS_REMOTE_VALID($value) {
return !!($value & DTE_FLAG_REMOTE_VALID);
}
/**
* check addressbook object is able to receive permissions
*/
function DTE_IS_ACL_CAPABLE($value) {
return !!($value & DTE_FLAG_ACL_CAPABLE);
}
function DTE_REMOTE($value) {
return (($value & DTE_MASK_REMOTE) >> 8);
}
function DTE_LOCAL($value) {
return ($value & DTE_MASK_LOCAL);
}
/**
* Note: Static function, more like a utility function.
*
* Gets all the items (including recurring items) in the specified calendar in the given timeframe. Items are
* included as a whole if they overlap the interval <$start, $end> (non-inclusive). This means that if the interval
* is <08:00 - 14:00>, the item [6:00 - 8:00> is NOT included, nor is the item [14:00 - 16:00>. However, the item
* [7:00 - 9:00> is included as a whole, and is NOT capped to [8:00 - 9:00>.
*
* @param $store resource The store in which the calendar resides
* @param $calendar resource The calendar to get the items from
* @param $viewstart int Timestamp of beginning of view window
* @param $viewend int Timestamp of end of view window
* @param $propsrequested array Array of properties to return
* @param $rows array Array of rowdata as if they were returned directly from mapi_table_queryrows. Each recurring item is
* expanded so that it seems that there are only many single appointments in the table.
*/
function getCalendarItems($store, $calendar, $viewstart, $viewend, $propsrequested){
$result = array();
$properties = getPropIdsFromStrings($store, Array( "duedate" => "PT_SYSTIME:PSETID_Appointment:0x820e",
"startdate" => "PT_SYSTIME:PSETID_Appointment:0x820d",
"enddate_recurring" => "PT_SYSTIME:PSETID_Appointment:0x8236",
"recurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
"recurring_data" => "PT_BINARY:PSETID_Appointment:0x8216",
"timezone_data" => "PT_BINARY:PSETID_Appointment:0x8233",
"label" => "PT_LONG:PSETID_Appointment:0x8214"
));
// Create a restriction that will discard rows of appointments that are definitely not in our
// requested time frame
$table = mapi_folder_getcontentstable($calendar);
$restriction =
// OR
Array(RES_OR,
Array(
Array(RES_AND, // Normal items: itemEnd must be after viewStart, itemStart must be before viewEnd
Array(
Array(RES_PROPERTY,
Array(RELOP => RELOP_GT,
ULPROPTAG => $properties["duedate"],
VALUE => $viewstart
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_LT,
ULPROPTAG => $properties["startdate"],
VALUE => $viewend
)
)
)
),
// OR
Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => $properties["recurring"],
VALUE => true
)
)
) // EXISTS OR
); // global OR
// Get requested properties, plus whatever we need
$proplist = array(PR_ENTRYID, $properties["recurring"], $properties["recurring_data"], $properties["timezone_data"]);
$proplist = array_merge($proplist, $propsrequested);
$propslist = array_unique($proplist);
$rows = mapi_table_queryallrows($table, $proplist, $restriction);
// $rows now contains all the items that MAY be in the window; a recurring item needs expansion before including in the output.
foreach($rows as $row) {
$items = array();
if(isset($row[$properties["recurring"]]) && $row[$properties["recurring"]]) {
// Recurring item
$rec = new Recurrence($store, $row);
// GetItems guarantees that the item overlaps the interval <$viewstart, $viewend>
$occurrences = $rec->getItems($viewstart, $viewend);
foreach($occurrences as $occurrence) {
// The occurrence takes all properties from the main row, but overrides some properties (like start and end obviously)
$item = $occurrence + $row;
array_push($items, $item);
}
} else {
// Normal item, it matched the search criteria and therefore overlaps the interval <$viewstart, $viewend>
array_push($items, $row);
}
$result = array_merge($result,$items);
}
// All items are guaranteed to overlap the interval <$viewstart, $viewend>. Note that we may be returning a few extra
// properties that the caller did not request (recurring, etc). This shouldn't be a problem though.
return $result;
}
?>

View file

@ -0,0 +1,250 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/**
* Status codes returned by MAPI functions
*
*
*/
/* From winerror.h */
//
// Success codes
//
define('S_OK', 0x00000000);
define('S_FALSE', 0x00000001);
define('SEVERITY_ERROR', 1);
/* from winerror.h */
/**
* Function to make a error
*/
function make_mapi_e($code)
{
return (int) mapi_make_scode(1, $code);
}
/**
* Function to make an warning
*/
function make_mapi_s($code)
{
return (int) mapi_make_scode(0, $code);
}
/* From mapicode.h */
/*
* On Windows NT 3.5 and Windows 95, scodes are 32-bit values
* laid out as follows:
*
* 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* +-+-+-+-+-+---------------------+-------------------------------+
* |S|R|C|N|r| Facility | Code |
* +-+-+-+-+-+---------------------+-------------------------------+
*
* where
*
* S - Severity - indicates success/fail
*
* 0 - Success
* 1 - Fail (COERROR)
*
* R - reserved portion of the facility code, corresponds to NT's
* second severity bit.
*
* C - reserved portion of the facility code, corresponds to NT's
* C field.
*
* N - reserved portion of the facility code. Used to indicate a
* mapped NT status value.
*
* r - reserved portion of the facility code. Reserved for internal
* use. Used to indicate HRESULT values that are not status
* values, but are instead message ids for display strings.
*
* Facility - is the facility code
* FACILITY_NULL 0x0
* FACILITY_RPC 0x1
* FACILITY_DISPATCH 0x2
* FACILITY_STORAGE 0x3
* FACILITY_ITF 0x4
* FACILITY_WIN32 0x7
* FACILITY_WINDOWS 0x8
*
* Code - is the facility's status code
*
*/
define('NOERROR' ,0);
define('MAPI_E_CALL_FAILED' ,(int) 0x80004005);
define('MAPI_E_NOT_ENOUGH_MEMORY' ,(int) 0x8007000E);
define('MAPI_E_INVALID_PARAMETER' ,(int) 0x80070057);
define('MAPI_E_INTERFACE_NOT_SUPPORTED' ,(int) 0x80004002);
define('MAPI_E_NO_ACCESS' ,(int) 0x80070005);
define('MAPI_E_NO_SUPPORT' ,make_mapi_e(0x102));
define('MAPI_E_BAD_CHARWIDTH' ,make_mapi_e(0x103));
define('MAPI_E_STRING_TOO_LONG' ,make_mapi_e(0x105));
define('MAPI_E_UNKNOWN_FLAGS' ,make_mapi_e(0x106));
define('MAPI_E_INVALID_ENTRYID' ,make_mapi_e(0x107));
define('MAPI_E_INVALID_OBJECT' ,make_mapi_e(0x108));
define('MAPI_E_OBJECT_CHANGED' ,make_mapi_e(0x109));
define('MAPI_E_OBJECT_DELETED' ,make_mapi_e(0x10A));
define('MAPI_E_BUSY' ,make_mapi_e(0x10B));
define('MAPI_E_NOT_ENOUGH_DISK' ,make_mapi_e(0x10D));
define('MAPI_E_NOT_ENOUGH_RESOURCES' ,make_mapi_e(0x10E));
define('MAPI_E_NOT_FOUND' ,make_mapi_e(0x10F));
define('MAPI_E_VERSION' ,make_mapi_e(0x110));
define('MAPI_E_LOGON_FAILED' ,make_mapi_e(0x111));
define('MAPI_E_SESSION_LIMIT' ,make_mapi_e(0x112));
define('MAPI_E_USER_CANCEL' ,make_mapi_e(0x113));
define('MAPI_E_UNABLE_TO_ABORT' ,make_mapi_e(0x114));
define('MAPI_E_NETWORK_ERROR' ,make_mapi_e(0x115));
define('MAPI_E_DISK_ERROR' ,make_mapi_e(0x116));
define('MAPI_E_TOO_COMPLEX' ,make_mapi_e(0x117));
define('MAPI_E_BAD_COLUMN' ,make_mapi_e(0x118));
define('MAPI_E_EXTENDED_ERROR' ,make_mapi_e(0x119));
define('MAPI_E_COMPUTED' ,make_mapi_e(0x11A));
define('MAPI_E_CORRUPT_DATA' ,make_mapi_e(0x11B));
define('MAPI_E_UNCONFIGURED' ,make_mapi_e(0x11C));
define('MAPI_E_FAILONEPROVIDER' ,make_mapi_e(0x11D));
define('MAPI_E_UNKNOWN_CPID' ,make_mapi_e(0x11E));
define('MAPI_E_UNKNOWN_LCID' ,make_mapi_e(0x11F));
/* Flavors of E_ACCESSDENIED, used at logon */
define('MAPI_E_PASSWORD_CHANGE_REQUIRED' ,make_mapi_e(0x120));
define('MAPI_E_PASSWORD_EXPIRED' ,make_mapi_e(0x121));
define('MAPI_E_INVALID_WORKSTATION_ACCOUNT' ,make_mapi_e(0x122));
define('MAPI_E_INVALID_ACCESS_TIME' ,make_mapi_e(0x123));
define('MAPI_E_ACCOUNT_DISABLED' ,make_mapi_e(0x124));
/* MAPI base function and status object specific errors and warnings */
define('MAPI_E_END_OF_SESSION' ,make_mapi_e(0x200));
define('MAPI_E_UNKNOWN_ENTRYID' ,make_mapi_e(0x201));
define('MAPI_E_MISSING_REQUIRED_COLUMN' ,make_mapi_e(0x202));
define('MAPI_W_NO_SERVICE' ,make_mapi_s(0x203));
/* Property specific errors and warnings */
define('MAPI_E_BAD_VALUE' ,make_mapi_e(0x301));
define('MAPI_E_INVALID_TYPE' ,make_mapi_e(0x302));
define('MAPI_E_TYPE_NO_SUPPORT' ,make_mapi_e(0x303));
define('MAPI_E_UNEXPECTED_TYPE' ,make_mapi_e(0x304));
define('MAPI_E_TOO_BIG' ,make_mapi_e(0x305));
define('MAPI_E_DECLINE_COPY' ,make_mapi_e(0x306));
define('MAPI_E_UNEXPECTED_ID' ,make_mapi_e(0x307));
define('MAPI_W_ERRORS_RETURNED' ,make_mapi_s(0x380));
/* Table specific errors and warnings */
define('MAPI_E_UNABLE_TO_COMPLETE' ,make_mapi_e(0x400));
define('MAPI_E_TIMEOUT' ,make_mapi_e(0x401));
define('MAPI_E_TABLE_EMPTY' ,make_mapi_e(0x402));
define('MAPI_E_TABLE_TOO_BIG' ,make_mapi_e(0x403));
define('MAPI_E_INVALID_BOOKMARK' ,make_mapi_e(0x405));
define('MAPI_W_POSITION_CHANGED' ,make_mapi_s(0x481));
define('MAPI_W_APPROX_COUNT' ,make_mapi_s(0x482));
/* Transport specific errors and warnings */
define('MAPI_E_WAIT' ,make_mapi_e(0x500));
define('MAPI_E_CANCEL' ,make_mapi_e(0x501));
define('MAPI_E_NOT_ME' ,make_mapi_e(0x502));
define('MAPI_W_CANCEL_MESSAGE' ,make_mapi_s(0x580));
/* Message Store, Folder, and Message specific errors and warnings */
define('MAPI_E_CORRUPT_STORE' ,make_mapi_e(0x600));
define('MAPI_E_NOT_IN_QUEUE' ,make_mapi_e(0x601));
define('MAPI_E_NO_SUPPRESS' ,make_mapi_e(0x602));
define('MAPI_E_COLLISION' ,make_mapi_e(0x604));
define('MAPI_E_NOT_INITIALIZED' ,make_mapi_e(0x605));
define('MAPI_E_NON_STANDARD' ,make_mapi_e(0x606));
define('MAPI_E_NO_RECIPIENTS' ,make_mapi_e(0x607));
define('MAPI_E_SUBMITTED' ,make_mapi_e(0x608));
define('MAPI_E_HAS_FOLDERS' ,make_mapi_e(0x609));
define('MAPI_E_HAS_MESSAGES' ,make_mapi_e(0x60A));
define('MAPI_E_FOLDER_CYCLE' ,make_mapi_e(0x60B));
define('MAPI_E_STORE_FULL' ,make_mapi_e(0x60C));
define('MAPI_W_PARTIAL_COMPLETION' ,make_mapi_s(0x680));
/* Address Book specific errors and warnings */
define('MAPI_E_AMBIGUOUS_RECIP' ,make_mapi_e(0x700));
/* ICS errors and warnings */
define('SYNC_E_UNKNOWN_FLAGS', MAPI_E_UNKNOWN_FLAGS);
define('SYNC_E_INVALID_PARAMETER', MAPI_E_INVALID_PARAMETER);
define('SYNC_E_ERROR', MAPI_E_CALL_FAILED);
define('SYNC_E_OBJECT_DELETED', make_mapi_e(0x800));
define('SYNC_E_IGNORE', make_mapi_e(0x801));
define('SYNC_E_CONFLICT', make_mapi_e(0x802));
define('SYNC_E_NO_PARENT', make_mapi_e(0x803));
define('SYNC_E_INCEST', make_mapi_e(0x804));
define('SYNC_E_UNSYNCHRONIZED', make_mapi_e(0x805));
define('SYNC_W_PROGRESS', make_mapi_s(0x820));
define('SYNC_W_CLIENT_CHANGE_NEWER', make_mapi_s(0x821));
?>

View file

@ -0,0 +1,667 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/* Resource types as defined in main.h of the mapi extension */
define('RESOURCE_SESSION' ,'MAPI Session');
define('RESOURCE_TABLE' ,'MAPI Table');
define('RESOURCE_ROWSET' ,'MAPI Rowset');
define('RESOURCE_MSGSTORE' ,'MAPI Message Store');
define('RESOURCE_FOLDER' ,'MAPI Folder');
define('RESOURCE_MESSAGE' ,'MAPI Message');
define('RESOURCE_ATTACHMENT' ,'MAPI Attachment');
/* Object type */
define('MAPI_STORE' ,0x00000001); /* Message Store */
define('MAPI_ADDRBOOK' ,0x00000002); /* Address Book */
define('MAPI_FOLDER' ,0x00000003); /* Folder */
define('MAPI_ABCONT' ,0x00000004); /* Address Book Container */
define('MAPI_MESSAGE' ,0x00000005); /* Message */
define('MAPI_MAILUSER' ,0x00000006); /* Individual Recipient */
define('MAPI_ATTACH' ,0x00000007); /* Attachment */
define('MAPI_DISTLIST' ,0x00000008); /* Distribution List Recipient */
define('MAPI_PROFSECT' ,0x00000009); /* Profile Section */
define('MAPI_STATUS' ,0x0000000A); /* Status Object */
define('MAPI_SESSION' ,0x0000000B); /* Session */
define('MAPI_FORMINFO' ,0x0000000C); /* Form Information */
define('MV_FLAG' ,0x1000);
define('MV_INSTANCE' ,0x2000);
define('MVI_FLAG' ,MV_FLAG | MV_INSTANCE);
define('PT_UNSPECIFIED' , 0); /* (Reserved for interface use) type doesn't matter to caller */
define('PT_NULL' , 1); /* NULL property value */
define('PT_I2' , 2); /* Signed 16-bit value */
define('PT_LONG' , 3); /* Signed 32-bit value */
define('PT_R4' , 4); /* 4-byte floating point */
define('PT_DOUBLE' , 5); /* Floating point double */
define('PT_CURRENCY' , 6); /* Signed 64-bit int (decimal w/ 4 digits right of decimal pt) */
define('PT_APPTIME' , 7); /* Application time */
define('PT_ERROR' , 10); /* 32-bit error value */
define('PT_BOOLEAN' , 11); /* 16-bit boolean (non-zero true) */
define('PT_OBJECT' , 13); /* Embedded object in a property */
define('PT_I8' , 20); /* 8-byte signed integer */
define('PT_STRING8' , 30); /* Null terminated 8-bit character string */
define('PT_UNICODE' , 31); /* Null terminated Unicode string */
define('PT_SYSTIME' , 64); /* FILETIME 64-bit int w/ number of 100ns periods since Jan 1,1601 */
define('PT_CLSID' , 72); /* OLE GUID */
define('PT_BINARY' ,258); /* Uninterpreted (counted byte array) */
/* Changes are likely to these numbers, and to their structures. */
/* Alternate property type names for ease of use */
define('PT_SHORT' ,PT_I2);
define('PT_I4' ,PT_LONG);
define('PT_FLOAT' ,PT_R4);
define('PT_R8' ,PT_DOUBLE);
define('PT_LONGLONG' ,PT_I8);
define('PT_TSTRING' ,PT_STRING8);
define('PT_MV_I2' ,(MV_FLAG | PT_I2));
define('PT_MV_LONG' ,(MV_FLAG | PT_LONG));
define('PT_MV_R4' ,(MV_FLAG | PT_R4));
define('PT_MV_DOUBLE' ,(MV_FLAG | PT_DOUBLE));
define('PT_MV_CURRENCY' ,(MV_FLAG | PT_CURRENCY));
define('PT_MV_APPTIME' ,(MV_FLAG | PT_APPTIME));
define('PT_MV_SYSTIME' ,(MV_FLAG | PT_SYSTIME));
define('PT_MV_STRING8' ,(MV_FLAG | PT_STRING8));
define('PT_MV_BINARY' ,(MV_FLAG | PT_BINARY));
define('PT_MV_UNICODE' ,(MV_FLAG | PT_UNICODE));
define('PT_MV_CLSID' ,(MV_FLAG | PT_CLSID));
define('PT_MV_I8' ,(MV_FLAG | PT_I8));
define('PT_MV_TSTRING' ,PT_MV_STRING8);
/* bit 0: set if descending, clear if ascending */
define('TABLE_SORT_ASCEND' ,(0x00000000));
define('TABLE_SORT_DESCEND' ,(0x00000001));
define('TABLE_SORT_COMBINE' ,(0x00000002));
define('MAPI_UNICODE' ,0x80000000);
/* IMAPIFolder Interface --------------------------------------------------- */
define('CONVENIENT_DEPTH' ,0x00000001);
define('SEARCH_RUNNING' ,0x00000001);
define('SEARCH_REBUILD' ,0x00000002);
define('SEARCH_RECURSIVE' ,0x00000004);
define('SEARCH_FOREGROUND' ,0x00000008);
define('STOP_SEARCH' ,0x00000001);
define('RESTART_SEARCH' ,0x00000002);
define('RECURSIVE_SEARCH' ,0x00000004);
define('SHALLOW_SEARCH' ,0x00000008);
define('FOREGROUND_SEARCH' ,0x00000010);
define('BACKGROUND_SEARCH' ,0x00000020);
/* IMAPIFolder folder type (enum) */
define('FOLDER_ROOT' ,0x00000000);
define('FOLDER_GENERIC' ,0x00000001);
define('FOLDER_SEARCH' ,0x00000002);
/* CreateMessage */
/****** MAPI_DEFERRED_ERRORS ((ULONG) 0x00000008) below */
/****** MAPI_ASSOCIATED ((ULONG) 0x00000040) below */
/* CopyMessages */
define('MESSAGE_MOVE' ,0x00000001);
define('MESSAGE_DIALOG' ,0x00000002);
/****** MAPI_DECLINE_OK ((ULONG) 0x00000004) above */
/* CreateFolder */
define('OPEN_IF_EXISTS' ,0x00000001);
/****** MAPI_DEFERRED_ERRORS ((ULONG) 0x00000008) below */
/****** MAPI_UNICODE ((ULONG) 0x80000000) above */
/* DeleteFolder */
define('DEL_MESSAGES' ,0x00000001);
define('FOLDER_DIALOG' ,0x00000002);
define('DEL_FOLDERS' ,0x00000004);
/* EmptyFolder */
define('DEL_ASSOCIATED' ,0x00000008);
/* CopyFolder */
define('FOLDER_MOVE' ,0x00000001);
/****** FOLDER_DIALOG ((ULONG) 0x00000002) above */
/****** MAPI_DECLINE_OK ((ULONG) 0x00000004) above */
define('COPY_SUBFOLDERS' ,0x00000010);
/****** MAPI_UNICODE ((ULONG) 0x80000000) above */
/* SetReadFlags */
define('SUPPRESS_RECEIPT' ,0x00000001);
/****** FOLDER_DIALOG ((ULONG) 0x00000002) above */
define('CLEAR_READ_FLAG' ,0x00000004);
/****** MAPI_DEFERRED_ERRORS ((ULONG) 0x00000008) below */
define('GENERATE_RECEIPT_ONLY' ,0x00000010);
define('CLEAR_RN_PENDING' ,0x00000020);
define('CLEAR_NRN_PENDING' ,0x00000040);
/* Flags defined in PR_MESSAGE_FLAGS */
define('MSGFLAG_READ' ,0x00000001);
define('MSGFLAG_UNMODIFIED' ,0x00000002);
define('MSGFLAG_SUBMIT' ,0x00000004);
define('MSGFLAG_UNSENT' ,0x00000008);
define('MSGFLAG_HASATTACH' ,0x00000010);
define('MSGFLAG_FROMME' ,0x00000020);
define('MSGFLAG_ASSOCIATED' ,0x00000040);
define('MSGFLAG_RESEND' ,0x00000080);
define('MSGFLAG_RN_PENDING' ,0x00000100);
define('MSGFLAG_NRN_PENDING' ,0x00000200);
/* GetMessageStatus */
define('MSGSTATUS_HIGHLIGHTED' ,0x00000001);
define('MSGSTATUS_TAGGED' ,0x00000002);
define('MSGSTATUS_HIDDEN' ,0x00000004);
define('MSGSTATUS_DELMARKED' ,0x00000008);
/* Bits for remote message status */
define('MSGSTATUS_REMOTE_DOWNLOAD' ,0x00001000);
define('MSGSTATUS_REMOTE_DELETE' ,0x00002000);
/* SaveContentsSort */
define('RECURSIVE_SORT' ,0x00000002);
/* PR_STATUS property */
define('FLDSTATUS_HIGHLIGHTED' ,0x00000001);
define('FLDSTATUS_TAGGED' ,0x00000002);
define('FLDSTATUS_HIDDEN' ,0x00000004);
define('FLDSTATUS_DELMARKED' ,0x00000008);
/* IMAPIStatus Interface --------------------------------------------------- */
/* Values for PR_RESOURCE_TYPE, _METHODS, _FLAGS */
define('MAPI_STORE_PROVIDER' , 33); /* Message Store */
define('MAPI_AB' , 34); /* Address Book */
define('MAPI_AB_PROVIDER' , 35); /* Address Book Provider */
define('MAPI_TRANSPORT_PROVIDER' , 36); /* Transport Provider */
define('MAPI_SPOOLER' , 37); /* Message Spooler */
define('MAPI_PROFILE_PROVIDER' , 38); /* Profile Provider */
define('MAPI_SUBSYSTEM' , 39); /* Overall Subsystem Status */
define('MAPI_HOOK_PROVIDER' , 40); /* Spooler Hook */
define('STATUS_VALIDATE_STATE' ,0x00000001);
define('STATUS_SETTINGS_DIALOG' ,0x00000002);
define('STATUS_CHANGE_PASSWORD' ,0x00000004);
define('STATUS_FLUSH_QUEUES' ,0x00000008);
define('STATUS_DEFAULT_OUTBOUND' ,0x00000001);
define('STATUS_DEFAULT_STORE' ,0x00000002);
define('STATUS_PRIMARY_IDENTITY' ,0x00000004);
define('STATUS_SIMPLE_STORE' ,0x00000008);
define('STATUS_XP_PREFER_LAST' ,0x00000010);
define('STATUS_NO_PRIMARY_IDENTITY' ,0x00000020);
define('STATUS_NO_DEFAULT_STORE' ,0x00000040);
define('STATUS_TEMP_SECTION' ,0x00000080);
define('STATUS_OWN_STORE' ,0x00000100);
define('STATUS_NEED_IPM_TREE' ,0x00000800);
define('STATUS_PRIMARY_STORE' ,0x00001000);
define('STATUS_SECONDARY_STORE' ,0x00002000);
/* ------------ */
/* Random flags */
/* Flag for deferred error */
define('MAPI_DEFERRED_ERRORS' ,0x00000008);
/* Flag for creating and using Folder Associated Information Messages */
define('MAPI_ASSOCIATED' ,0x00000040);
/* Flags for OpenMessageStore() */
define('MDB_NO_DIALOG' ,0x00000001);
define('MDB_WRITE' ,0x00000004);
/****** MAPI_DEFERRED_ERRORS ((ULONG) 0x00000008) above */
/****** MAPI_BEST_ACCESS ((ULONG) 0x00000010) above */
define('MDB_TEMPORARY' ,0x00000020);
define('MDB_NO_MAIL' ,0x00000080);
/* Flags for OpenAddressBook */
define('AB_NO_DIALOG' ,0x00000001);
/* ((ULONG) 0x00000001 is not a valid flag on ModifyRecipients. */
define('MODRECIP_ADD' ,0x00000002);
define('MODRECIP_MODIFY' ,0x00000004);
define('MODRECIP_REMOVE' ,0x00000008);
define('MAPI_ORIG' ,0); /* Recipient is message originator */
define('MAPI_TO' ,1); /* Recipient is a primary recipient */
define('MAPI_CC' ,2); /* Recipient is a copy recipient */
define('MAPI_BCC' ,3); /* Recipient is blind copy recipient */
/* IAttach Interface ------------------------------------------------------- */
/* IAttach attachment methods: PR_ATTACH_METHOD values */
define('NO_ATTACHMENT' ,0x00000000);
define('ATTACH_BY_VALUE' ,0x00000001);
define('ATTACH_BY_REFERENCE' ,0x00000002);
define('ATTACH_BY_REF_RESOLVE' ,0x00000003);
define('ATTACH_BY_REF_ONLY' ,0x00000004);
define('ATTACH_EMBEDDED_MSG' ,0x00000005);
define('ATTACH_OLE' ,0x00000006);
/* OpenProperty - ulFlags */
define('MAPI_MODIFY' ,0x00000001);
define('MAPI_CREATE' ,0x00000002);
define('STREAM_APPEND' ,0x00000004);
/****** MAPI_DEFERRED_ERRORS ((ULONG) 0x00000008) below */
/* PR_PRIORITY values */
define('PRIO_URGENT' , 1);
define('PRIO_NORMAL' , 0);
define('PRIO_NONURGENT' ,-1);
/* PR_SENSITIVITY values */
define('SENSITIVITY_NONE' ,0x00000000);
define('SENSITIVITY_PERSONAL' ,0x00000001);
define('SENSITIVITY_PRIVATE' ,0x00000002);
define('SENSITIVITY_COMPANY_CONFIDENTIAL' ,0x00000003);
/* PR_IMPORTANCE values */
define('IMPORTANCE_LOW' ,0);
define('IMPORTANCE_NORMAL' ,1);
define('IMPORTANCE_HIGH' ,2);
/* Stream interace values */
define('STREAM_SEEK_SET' ,0);
define('STREAM_SEEK_CUR' ,1);
define('STREAM_SEEK_END' ,2);
define('SHOW_SOFT_DELETES' ,0x00000002);
define('DELETE_HARD_DELETE' ,0x00000010);
/*
* The following flags are used to indicate to the client what access
* level is permissible in the object. They appear in PR_ACCESS in
* message and folder objects as well as in contents and associated
* contents tables
*/
define('MAPI_ACCESS_MODIFY' ,0x00000001);
define('MAPI_ACCESS_READ' ,0x00000002);
define('MAPI_ACCESS_DELETE' ,0x00000004);
define('MAPI_ACCESS_CREATE_HIERARCHY' ,0x00000008);
define('MAPI_ACCESS_CREATE_CONTENTS' ,0x00000010);
define('MAPI_ACCESS_CREATE_ASSOCIATED' ,0x00000020);
define('MAPI_SEND_NO_RICH_INFO' ,0x00010000);
/* flags for PR_STORE_SUPPORT_MASK */
define('STORE_ANSI_OK' ,0x00020000); // The message store supports properties containing ANSI (8-bit) characters.
define('STORE_ATTACH_OK' ,0x00000020); // The message store supports attachments (OLE or non-OLE) to messages.
define('STORE_CATEGORIZE_OK' ,0x00000400); // The message store supports categorized views of tables.
define('STORE_CREATE_OK' ,0x00000010); // The message store supports creation of new messages.
define('STORE_ENTRYID_UNIQUE' ,0x00000001); // Entry identifiers for the objects in the message store are unique, that is, never reused during the life of the store.
define('STORE_HTML_OK' ,0x00010000); // The message store supports Hypertext Markup Language (HTML) messages, stored in the PR_BODY_HTML property. Note that STORE_HTML_OK is not defined in versions of MAPIDEFS.H included with Microsoft<66> Exchange 2000 Server and earlier. If your development environment uses a MAPIDEFS.H file that does not include STORE_HTML_OK, use the value 0x00010000 instead.
define('STORE_LOCALSTORE' ,0x00080000); // This flag is reserved and should not be used.
define('STORE_MODIFY_OK' ,0x00000008); // The message store supports modification of its existing messages.
define('STORE_MV_PROPS_OK' ,0x00000200); // The message store supports multivalued properties, guarantees the stability of value order in a multivalued property throughout a save operation, and supports instantiation of multivalued properties in tables.
define('STORE_NOTIFY_OK' ,0x00000100); // The message store supports notifications.
define('STORE_OLE_OK' ,0x00000040); // The message store supports OLE attachments. The OLE data is accessible through an IStorage interface, such as that available through the PR_ATTACH_DATA_OBJ property.
define('STORE_PUBLIC_FOLDERS' ,0x00004000); // The folders in this store are public (multi-user), not private (possibly multi-instance but not multi-user).
define('STORE_READONLY' ,0x00000002); // All interfaces for the message store have a read-only access level.
define('STORE_RESTRICTION_OK' ,0x00001000); // The message store supports restrictions.
define('STORE_RTF_OK' ,0x00000800); // The message store supports Rich Text Format (RTF) messages, usually stored compressed, and the store itself keeps PR_BODY and PR_RTF_COMPRESSED synchronized.
define('STORE_SEARCH_OK' ,0x00000004); // The message store supports search-results folders.
define('STORE_SORT_OK' ,0x00002000); // The message store supports sorting views of tables.
define('STORE_SUBMIT_OK' ,0x00000080); // The message store supports marking a message for submission.
define('STORE_UNCOMPRESSED_RTF' ,0x00008000); // The message store supports storage of Rich Text Format (RTF) messages in uncompressed form. An uncompressed RTF stream is identified by the value dwMagicUncompressedRTF in the stream header. The dwMagicUncompressedRTF value is defined in the RTFLIB.H file.
define('STORE_UNICODE_OK' ,0x00040000); // The message store supports properties containing Unicode characters.
/* PR_DISPLAY_TYPEs */
/* For address book contents tables */
define('DT_MAILUSER' ,0x00000000);
define('DT_DISTLIST' ,0x00000001);
define('DT_FORUM' ,0x00000002);
define('DT_AGENT' ,0x00000003);
define('DT_ORGANIZATION' ,0x00000004);
define('DT_PRIVATE_DISTLIST' ,0x00000005);
define('DT_REMOTE_MAILUSER' ,0x00000006);
/* For address book hierarchy tables */
define('DT_MODIFIABLE' ,0x00010000);
define('DT_GLOBAL' ,0x00020000);
define('DT_LOCAL' ,0x00030000);
define('DT_WAN' ,0x00040000);
define('DT_NOT_SPECIFIC' ,0x00050000);
/* For folder hierarchy tables */
define('DT_FOLDER' ,0x01000000);
define('DT_FOLDER_LINK' ,0x02000000);
define('DT_FOLDER_SPECIAL' ,0x04000000);
/* PR_DISPLAY_TYPE_EX values */
define('DT_ROOM' ,0x00000007);
define('DT_EQUIPMENT' ,0x00000008);
define('DT_SEC_DISTLIST' ,0x00000009);
/* PR_DISPLAY_TYPE_EX flags */
define('DTE_FLAG_REMOTE_VALID' ,0x80000000);
define('DTE_FLAG_ACL_CAPABLE' ,0x40000000); /* on for DT_MAILUSER and DT_SEC_DISTLIST */
define('DTE_MASK_REMOTE' ,0x0000FF00);
define('DTE_MASK_LOCAL' ,0x000000FF);
/* OlResponseStatus */
define('olResponseNone' ,0);
define('olResponseOrganized' ,1);
define('olResponseTentative' ,2);
define('olResponseAccepted' ,3);
define('olResponseDeclined' ,4);
define('olResponseNotResponded' ,5);
/* OlRecipientTrackStatus to set PR_RECIPIENT_TRACKSTATUS in recipient table
* Value of the recipient trackstatus are same as OlResponseStatus but
* recipient trackstatus doesn't have olResponseOrganized and olResponseNotResponded
* and olResponseNone has different interpretation with PR_RECIPIENT_TRACKSTATUS
* so to avoid confusions we have defined new constants.
*/
define('olRecipientTrackStatusNone' ,0);
define('olRecipientTrackStatusTentative' ,2);
define('olRecipientTrackStatusAccepted' ,3);
define('olRecipientTrackStatusDeclined' ,4);
/* OlMeetingStatus */
define('olNonMeeting' ,0);
define('olMeeting' ,1);
define('olMeetingReceived' ,3);
define('olMeetingCanceled' ,5);
define('olMeetingReceivedAndCanceled' ,7);
/* OlMeetingResponse */
define('olMeetingTentative' ,2);
define('olMeetingAccepted' ,3);
define('olMeetingDeclined' ,4);
/* OL Attendee type */
define('olAttendeeRequired' ,1);
define('olAttendeeOptional' ,2);
define('olAttendeeResource' ,3);
/* task status */
define('olTaskNotStarted' ,0);
define('olTaskInProgress' ,1);
define('olTaskComplete' ,2);
define('olTaskWaiting' ,3);
define('olTaskDeferred' ,4);
/* restrictions */
define('RES_AND' ,0);
define('RES_OR' ,1);
define('RES_NOT' ,2);
define('RES_CONTENT' ,3);
define('RES_PROPERTY' ,4);
define('RES_COMPAREPROPS' ,5);
define('RES_BITMASK' ,6);
define('RES_SIZE' ,7);
define('RES_EXIST' ,8);
define('RES_SUBRESTRICTION' ,9);
define('RES_COMMENT' ,10);
/* restriction compares */
define('RELOP_LT' ,0);
define('RELOP_LE' ,1);
define('RELOP_GT' ,2);
define('RELOP_GE' ,3);
define('RELOP_EQ' ,4);
define('RELOP_NE' ,5);
define('RELOP_RE' ,6);
/* string 'fuzzylevel' */
define('FL_FULLSTRING' ,0x00000000);
define('FL_SUBSTRING' ,0x00000001);
define('FL_PREFIX' ,0x00000002);
define('FL_IGNORECASE' ,0x00010000);
define('FL_IGNORENONSPACE' ,0x00020000);
define('FL_LOOSE' ,0x00040000);
/* bitmask restriction types */
define('BMR_EQZ' ,0x00000000);
define('BMR_NEZ' ,0x00000001);
/* array index values of restrictions -- same values are used in php-ext/main.cpp::PHPArraytoSRestriction() */
define('VALUE' ,0); // propval
define('RELOP' ,1); // compare method
define('FUZZYLEVEL' ,2); // string search flags
define('CB' ,3); // size restriction
define('ULTYPE' ,4); // bit mask restriction type BMR_xxx
define('ULMASK' ,5); // bitmask
define('ULPROPTAG' ,6); // property
define('ULPROPTAG1' ,7); // RES_COMPAREPROPS 1st property
define('ULPROPTAG2' ,8); // RES_COMPAREPROPS 2nd property
define('PROPS' ,9); // RES_COMMENT properties
define('RESTRICTION' ,10); // RES_COMMENT and RES_SUBRESTRICTION restriction
/* GUID's for PR_MDB_PROVIDER */
define("ZARAFA_SERVICE_GUID" ,makeGuid("{3C253DCA-D227-443C-94FE-425FAB958C19}")); // default store
define("ZARAFA_STORE_PUBLIC_GUID" ,makeGuid("{D47F4A09-D3BD-493C-B2FC-3C90BBCB48D4}")); // public store
define("ZARAFA_STORE_DELEGATE_GUID" ,makeGuid("{7C7C1085-BC6D-4E53-9DAB-8A53F8DEF808}")); // other store
define('ZARAFA_STORE_ARCHIVER_GUID' ,makeGuid("{BC8953AD-2E3F-4172-9404-896FF459870F}")); // archive store
/* global profile section guid */
define('pbGlobalProfileSectionGuid' ,makeGuid("{C8B0DB13-05AA-1A10-9BB0-00AA002FC45A}"));
/* Zarafa Contacts provider GUID */
define('ZARAFA_CONTACTS_GUID' ,makeGuid("{30047F72-92E3-DA4F-B86A-E52A7FE46571}"));
/* Permissions */
// Get permission type
define('ACCESS_TYPE_DENIED' ,1);
define('ACCESS_TYPE_GRANT' ,2);
define('ACCESS_TYPE_BOTH' ,3);
define('ecRightsNone' ,0x00000000);
define('ecRightsReadAny' ,0x00000001);
define('ecRightsCreate' ,0x00000002);
define('ecRightsEditOwned' ,0x00000008);
define('ecRightsDeleteOwned' ,0x00000010);
define('ecRightsEditAny' ,0x00000020);
define('ecRightsDeleteAny' ,0x00000040);
define('ecRightsCreateSubfolder' ,0x00000080);
define('ecRightsFolderAccess' ,0x00000100);
//define('ecrightsContact' ,0x00000200);
define('ecRightsFolderVisible' ,0x00000400);
define('ecRightsAll' ,ecRightsReadAny | ecRightsCreate | ecRightsEditOwned | ecRightsDeleteOwned | ecRightsEditAny | ecRightsDeleteAny | ecRightsCreateSubfolder | ecRightsFolderAccess | ecRightsFolderVisible);
define('ecRightsFullControl' ,ecRightsReadAny | ecRightsCreate | ecRightsEditOwned | ecRightsDeleteOwned | ecRightsEditAny | ecRightsDeleteAny | ecRightsCreateSubfolder | ecRightsFolderVisible);
define('ecRightsDefault' ,ecRightsNone | ecRightsFolderVisible);
define('ecRightsDefaultPublic' ,ecRightsReadAny | ecRightsFolderVisible);
define('ecRightsAdmin' ,0x00001000);
define('ecRightsAllMask' ,0x000015FB);
// Right change indication
define('RIGHT_NORMAL' ,0x00);
define('RIGHT_NEW' ,0x01);
define('RIGHT_MODIFY' ,0x02);
define('RIGHT_DELETED' ,0x04);
define('RIGHT_AUTOUPDATE_DENIED' ,0x08);
// IExchangeModifyTable: defines for rules
define('ROWLIST_REPLACE' ,0x0001);
define('ROW_ADD' ,0x0001);
define('ROW_MODIFY' ,0x0002);
define('ROW_REMOVE' ,0x0004);
define('ROW_EMPTY' ,(ROW_ADD|ROW_REMOVE));
// new property types
define('PT_SRESTRICTION' ,0x00FD);
define('PT_ACTIONS' ,0x00FE);
// unused, I believe
define('PT_FILE_HANDLE' ,0x0103);
define('PT_FILE_EA' ,0x0104);
define('PT_VIRTUAL' ,0x0105);
// rules state
define('ST_DISABLED' ,0x0000);
define('ST_ENABLED' ,0x0001);
define('ST_ERROR' ,0x0002);
define('ST_ONLY_WHEN_OOF' ,0x0004);
define('ST_KEEP_OOF_HIST' ,0x0008);
define('ST_EXIT_LEVEL' ,0x0010);
define('ST_SKIP_IF_SCL_IS_SAFE' ,0x0020);
define('ST_RULE_PARSE_ERROR' ,0x0040);
define('ST_CLEAR_OOF_HIST' ,0x80000000);
// action types
define('OP_MOVE' ,1);
define('OP_COPY' ,2);
define('OP_REPLY' ,3);
define('OP_OOF_REPLY' ,4);
define('OP_DEFER_ACTION' ,5);
define('OP_BOUNCE' ,6);
define('OP_FORWARD' ,7);
define('OP_DELEGATE' ,8);
define('OP_TAG' ,9);
define('OP_DELETE' ,10);
define('OP_MARK_AS_READ' ,11);
// for OP_REPLY
define('DO_NOT_SEND_TO_ORIGINATOR' ,1);
define('STOCK_REPLY_TEMPLATE' ,2);
// for OP_FORWARD
define('FWD_PRESERVE_SENDER' ,1);
define('FWD_DO_NOT_MUNGE_MSG' ,2);
define('FWD_AS_ATTACHMENT' ,4);
// scBounceCodevalues
define('BOUNCE_MESSAGE_SIZE_TOO_LARGE' ,13);
define('BOUNCE_FORMS_MISMATCH' ,31);
define('BOUNCE_ACCESS_DENIED' ,38);
// Free/busystatus
define('fbFree' ,0);
define('fbTentative' ,1);
define('fbBusy' ,2);
define('fbOutOfOffice' ,3);
/* ICS flags */
// For Synchronize()
define('SYNC_UNICODE' ,0x01);
define('SYNC_NO_DELETIONS' ,0x02);
define('SYNC_NO_SOFT_DELETIONS' ,0x04);
define('SYNC_READ_STATE' ,0x08);
define('SYNC_ASSOCIATED' ,0x10);
define('SYNC_NORMAL' ,0x20);
define('SYNC_NO_CONFLICTS' ,0x40);
define('SYNC_ONLY_SPECIFIED_PROPS' ,0x80);
define('SYNC_NO_FOREIGN_KEYS' ,0x100);
define('SYNC_LIMITED_IMESSAGE' ,0x200);
define('SYNC_CATCHUP' ,0x400);
define('SYNC_NEW_MESSAGE' ,0x800); // only applicable to ImportMessageChange()
define('SYNC_MSG_SELECTIVE' ,0x1000); // Used internally. Will reject if used by clients.
define('SYNC_BEST_BODY' ,0x2000);
define('SYNC_IGNORE_SPECIFIED_ON_ASSOCIATED' ,0x4000);
define('SYNC_PROGRESS_MODE' ,0x8000); // AirMapi progress mode
define('SYNC_FXRECOVERMODE' ,0x10000);
define('SYNC_DEFER_CONFIG' ,0x20000);
define('SYNC_FORCE_UNICODE' ,0x40000); // Forces server to return Unicode properties
define('EMS_AB_ADDRESS_LOOKUP' ,0x00000001); // Flag for resolvename to resolve only exact matches
define('TBL_BATCH' ,0x00000002); // Batch multiple table commands
/* Flags for recipients in exceptions */
define('recipSendable' ,0x00000001); // sendable attendee.
define('recipOrganizer' ,0x00000002); // meeting organizer
define('recipExceptionalResponse' ,0x00000010); // attendee gave a response for the exception
define('recipExceptionalDeleted' ,0x00000020); // recipientRow exists, but it is treated as if the corresponding recipient is deleted from meeting
define('recipOriginal' ,0x00000100); // recipient is an original Attendee
define('recipReserved' ,0x00000200);
/* Flags which indicates type of Meeting Object */
define('mtgEmpty' ,0x00000000); // Unspecified.
define('mtgRequest' ,0x00000001); // Initial meeting request.
define('mtgFull' ,0x00010000); // Full update.
define('mtgInfo' ,0x00020000); // Informational update.
define('mtgOutOfDate' ,0x00080000); // A newer Meeting Request object or Meeting Update object was received after this one.
define('mtgDelegatorCopy' ,0x00100000); // This is set on the delegator's copy when a delegate will handle meeting-related objects.
define('MAPI_ONE_OFF_UNICODE' ,0x8000); // the flag that defines whether the embedded strings are Unicode in one off entryids.
define('MAPI_ONE_OFF_NO_RICH_INFO' ,0x0001); // the flag that specifies whether the recipient gets TNEF or not.
/* Mask flags for mapi_msgstore_advise */
define('fnevCriticalError' ,0x00000001);
define('fnevNewMail' ,0x00000002);
define('fnevObjectCreated' ,0x00000004);
define('fnevObjectDeleted' ,0x00000008);
define('fnevObjectModified' ,0x00000010);
define('fnevObjectMoved' ,0x00000020);
define('fnevObjectCopied' ,0x00000040);
define('fnevSearchComplete' ,0x00000080);
define('fnevTableModified' ,0x00000100);
define('fnevStatusObjectModified' ,0x00000200);
define('fnevReservedForMapi' ,0x40000000);
define('fnevExtended' ,0x80000000);
?>

View file

@ -0,0 +1,74 @@
<?php
/*
* Copyright 2005 - 2013 Zarafa B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version
* 3, the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V. The licensing of
* the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain
* entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Zarafa" to indicate that you distribute the
* Program. Furthermore you may use our trademarks where it is necessary
* to indicate the intended purpose of a product or service provided you
* use it in accordance with honest practices in industrial or commercial
* matters. If you want to propagate modified versions of the Program
* under the name "Zarafa" or "Zarafa Server", you may only do so if you
* have a written permission by Zarafa B.V. (to acquire a permission
* please contact Zarafa at trademark@zarafa.com).
*
* The interactive user interface of the software displays an attribution
* notice containing the term "Zarafa" and/or the logo of Zarafa.
* Interactive user interfaces of unmodified and modified versions must
* display Appropriate Legal Notices according to sec. 5 of the GNU
* Affero General Public License, version 3, when you propagate
* unmodified or modified versions of the Program. In accordance with
* sec. 7 b) of the GNU Affero General Public License, version 3, these
* Appropriate Legal Notices must retain the logo of Zarafa or display
* the words "Initial Development by Zarafa" if the display of the logo
* is not reasonably feasible for technical reasons. The use of the logo
* of Zarafa in Legal Notices is allowed for unmodified and modified
* versions of the software.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
define('IID_IStream', makeguid("{0000000c-0000-0000-c000-000000000046}"));
define('IID_IMAPITable', makeguid("{00020301-0000-0000-c000-000000000046}"));
define('IID_IMessage', makeguid("{00020307-0000-0000-c000-000000000046}"));
define('IID_IExchangeExportChanges', makeguid("{a3ea9cc0-d1b2-11cd-80fc-00aa004bba0b}"));
define('IID_IExchangeImportContentsChanges', makeguid("{f75abfa0-d0e0-11cd-80fc-00aa004bba0b}"));
define('IID_IExchangeImportHierarchyChanges', makeguid("{85a66cf0-d0e0-11cd-80fc-00aa004bba0b}"));
define('PSETID_Appointment', makeguid("{00062002-0000-0000-C000-000000000046}"));
define('PSETID_Task', makeguid("{00062003-0000-0000-C000-000000000046}"));
define('PSETID_Address', makeguid("{00062004-0000-0000-C000-000000000046}"));
define('PSETID_Common', makeguid("{00062008-0000-0000-C000-000000000046}"));
define('PSETID_Log', makeguid("{0006200A-0000-0000-C000-000000000046}"));
define('PSETID_Note', makeguid("{0006200E-0000-0000-C000-000000000046}"));
define('PSETID_Meeting', makeguid("{6ED8DA90-450B-101B-98DA-00AA003F1305}"));
define('PSETID_Archive', makeguid("{72E98EBC-57D2-4AB5-B0AA-D50A7B531CB9}"));
define('PS_MAPI', makeguid("{00020328-0000-0000-C000-000000000046}"));
define('PS_PUBLIC_STRINGS', makeguid("{00020329-0000-0000-C000-000000000046}"));
define('PS_INTERNET_HEADERS', makeguid("{00020386-0000-0000-c000-000000000046}"));
// sk added for Z-Push
define ('PSETID_AirSync', makeguid("{71035549-0739-4DCB-9163-00F0580DBBDF}"));
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,524 @@
<?php
/***********************************************
* File : mapimapping.php
* Project : Z-Push
* Descr :
*
* Created : 29.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
*
* MAPI to AS mapping class
*
*
*/
class MAPIMapping {
/**
* Returns the MAPI to AS mapping for contacts
*
* @return array
*/
public static function GetContactMapping() {
return array (
"anniversary" => PR_WEDDING_ANNIVERSARY,
"assistantname" => PR_ASSISTANT,
"assistnamephonenumber" => PR_ASSISTANT_TELEPHONE_NUMBER,
"birthday" => PR_BIRTHDAY,
"body" => PR_BODY,
"business2phonenumber" => PR_BUSINESS2_TELEPHONE_NUMBER,
"businesscity" => "PT_STRING8:PSETID_Address:0x8046",
"businesscountry" => "PT_STRING8:PSETID_Address:0x8049",
"businesspostalcode" => "PT_STRING8:PSETID_Address:0x8048",
"businessstate" => "PT_STRING8:PSETID_Address:0x8047",
"businessstreet" => "PT_STRING8:PSETID_Address:0x8045",
"businessfaxnumber" => PR_BUSINESS_FAX_NUMBER,
"businessphonenumber" => PR_OFFICE_TELEPHONE_NUMBER,
"carphonenumber" => PR_CAR_TELEPHONE_NUMBER,
"categories" => "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords",
"children" => PR_CHILDRENS_NAMES,
"companyname" => PR_COMPANY_NAME,
"department" => PR_DEPARTMENT_NAME,
"email1address" => "PT_STRING8:PSETID_Address:0x8083",
"email2address" => "PT_STRING8:PSETID_Address:0x8093",
"email3address" => "PT_STRING8:PSETID_Address:0x80A3",
"fileas" => "PT_STRING8:PSETID_Address:0x8005",
"firstname" => PR_GIVEN_NAME,
"home2phonenumber" => PR_HOME2_TELEPHONE_NUMBER,
"homecity" => PR_HOME_ADDRESS_CITY,
"homecountry" => PR_HOME_ADDRESS_COUNTRY,
"homepostalcode" => PR_HOME_ADDRESS_POSTAL_CODE,
"homestate" => PR_HOME_ADDRESS_STATE_OR_PROVINCE,
"homestreet" => PR_HOME_ADDRESS_STREET,
"homefaxnumber" => PR_HOME_FAX_NUMBER,
"homephonenumber" => PR_HOME_TELEPHONE_NUMBER,
"jobtitle" => PR_TITLE,
"lastname" => PR_SURNAME,
"middlename" => PR_MIDDLE_NAME,
"mobilephonenumber" => PR_CELLULAR_TELEPHONE_NUMBER,
"officelocation" => PR_OFFICE_LOCATION,
"othercity" => PR_OTHER_ADDRESS_CITY,
"othercountry" => PR_OTHER_ADDRESS_COUNTRY,
"otherpostalcode" => PR_OTHER_ADDRESS_POSTAL_CODE,
"otherstate" => PR_OTHER_ADDRESS_STATE_OR_PROVINCE,
"otherstreet" => PR_OTHER_ADDRESS_STREET,
"pagernumber" => PR_PAGER_TELEPHONE_NUMBER,
"radiophonenumber" => PR_RADIO_TELEPHONE_NUMBER,
"spouse" => PR_SPOUSE_NAME,
"suffix" => PR_GENERATION,
"title" => PR_DISPLAY_NAME_PREFIX,
"webpage" => "PT_STRING8:PSETID_Address:0x802b",
"yomicompanyname" => "PT_STRING8:PSETID_Address:0x802e",
"yomifirstname" => "PT_STRING8:PSETID_Address:0x802c",
"yomilastname" => "PT_STRING8:PSETID_Address:0x802d",
"rtf" => PR_RTF_COMPRESSED,
// picture
"customerid" => PR_CUSTOMER_ID,
"governmentid" => PR_GOVERNMENT_ID_NUMBER,
"imaddress" => "PT_STRING8:PSETID_Address:0x8062",
"imaddress2" => "PT_STRING8:PSETID_AirSync:IMAddress2",
"imaddress3" => "PT_STRING8:PSETID_AirSync:IMAddress3",
"managername" => PR_MANAGER_NAME,
"companymainphone" => PR_COMPANY_MAIN_PHONE_NUMBER,
"accountname" => PR_ACCOUNT,
"nickname" => PR_NICKNAME,
// mms
);
}
/**
*
* Returns contact specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetContactProperties() {
return array (
"haspic" => "PT_BOOLEAN:PSETID_Address:0x8015",
"emailaddress1" => "PT_STRING8:PSETID_Address:0x8083",
"emailaddressdname1" => "PT_STRING8:PSETID_Address:0x8080",
"emailaddressdemail1" => "PT_STRING8:PSETID_Address:0x8084",
"emailaddresstype1" => "PT_STRING8:PSETID_Address:0x8082",
"emailaddressentryid1" => "PT_BINARY:PSETID_Address:0x8085",
"emailaddress2" => "PT_STRING8:PSETID_Address:0x8093",
"emailaddressdname2" => "PT_STRING8:PSETID_Address:0x8090",
"emailaddressdemail2" => "PT_STRING8:PSETID_Address:0x8094",
"emailaddresstype2" => "PT_STRING8:PSETID_Address:0x8092",
"emailaddressentryid2" => "PT_BINARY:PSETID_Address:0x8095",
"emailaddress3" => "PT_STRING8:PSETID_Address:0x80a3",
"emailaddressdname3" => "PT_STRING8:PSETID_Address:0x80a0",
"emailaddressdemail3" => "PT_STRING8:PSETID_Address:0x80a4",
"emailaddresstype3" => "PT_STRING8:PSETID_Address:0x80a2",
"emailaddressentryid3" => "PT_BINARY:PSETID_Address:0x80a5",
"addressbookmv" => "PT_MV_LONG:PSETID_Address:0x8028",
"addressbooklong" => "PT_LONG:PSETID_Address:0x8029",
"displayname" => PR_DISPLAY_NAME,
"subject" => PR_SUBJECT,
"country" => PR_COUNTRY,
"city" => PR_LOCALITY,
"postaladdress" => PR_POSTAL_ADDRESS,
"postalcode" => PR_POSTAL_CODE,
"state" => PR_STATE_OR_PROVINCE,
"street" => PR_STREET_ADDRESS,
"homeaddress" => "PT_STRING8:PSETID_Address:0x801a",
"businessaddress" => "PT_STRING8:PSETID_Address:0x801b",
"otheraddress" => "PT_STRING8:PSETID_Address:0x801c",
"mailingaddress" => "PT_LONG:PSETID_Address:0x8022",
);
}
/**
* Returns the MAPI to AS mapping for emails
*
* @return array
*/
public static function GetEmailMapping() {
return array (
// from
"datereceived" => PR_MESSAGE_DELIVERY_TIME,
"displayname" => PR_SUBJECT,
"displayto" => PR_DISPLAY_TO,
"importance" => PR_IMPORTANCE,
"messageclass" => PR_MESSAGE_CLASS,
"subject" => PR_SUBJECT,
"read" => PR_MESSAGE_FLAGS,
// "to" // need to be generated with SMTP addresses
// "cc"
// "threadtopic" => PR_CONVERSATION_TOPIC,
"internetcpid" => PR_INTERNET_CPID,
"nativebodytype" => PR_NATIVE_BODY_INFO,
"lastverbexecuted" => PR_LAST_VERB_EXECUTED,
"lastverbexectime" => PR_LAST_VERB_EXECUTION_TIME,
);
}
/**
*
* Returns email specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetEmailProperties() {
return array (
// Override 'From' to show "Full Name <user@domain.com>"
"representingname" => PR_SENT_REPRESENTING_NAME,
"representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
"sourcekey" => PR_SOURCE_KEY,
"entryid" => PR_ENTRYID,
"body" => PR_BODY,
"rtfcompressed" => PR_RTF_COMPRESSED,
"html" => PR_HTML,
"rtfinsync" => PR_RTF_IN_SYNC,
"processed" => PR_PROCESSED,
);
}
/**
* Returns the MAPI to AS mapping for meeting requests
*
* @return array
*/
public static function GetMeetingRequestMapping() {
return array (
"responserequested" => PR_RESPONSE_REQUESTED,
// timezone
"alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215",
"busystatus" => "PT_LONG:PSETID_Appointment:0x8205",
"rtf" => PR_RTF_COMPRESSED,
"dtstamp" => PR_LAST_MODIFICATION_TIME,
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",
"location" => "PT_STRING8:PSETID_Appointment:0x8208",
// recurrences
"reminder" => "PT_LONG:PSETID_Common:0x8501",
"starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d",
"sensitivity" => PR_SENSITIVITY,
);
}
public static function GetMeetingRequestProperties() {
return array (
"goidtag" => "PT_BINARY:PSETID_Meeting:0x3",
"timezonetag" => "PT_BINARY:PSETID_Appointment:0x8233",
"recReplTime" => "PT_SYSTIME:PSETID_Appointment:0x8228",
"isrecurringtag" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
"recurringstate" => "PT_BINARY:PSETID_Appointment:0x8216",
"appSeqNr" => "PT_LONG:PSETID_Appointment:0x8201",
"lidIsException" => "PT_BOOLEAN:PSETID_Appointment:0xA",
"recurStartTime" => "PT_LONG:PSETID_Meeting:0xE",
"reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
"remindertime" => "PT_LONG:PSETID_Common:0x8501",
"recurrenceend" => "PT_SYSTIME:PSETID_Appointment:0x8236",
);
}
public static function GetTnefAndIcalProperties() {
return array(
"starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d",
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",
"commonstart" => "PT_SYSTIME:PSETID_Common:0x8516",
"commonend" => "PT_SYSTIME:PSETID_Common:0x8517",
"clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235", //ical only
"recurrenceend" => "PT_SYSTIME:PSETID_Appointment:0x8236", //ical only
"isrecurringtag" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
"goidtag" => "PT_BINARY:PSETID_Meeting:0x3",
"goid2tag" => "PT_BINARY:PSETID_Meeting:0x23",
"usetnef" => "PT_LONG:PSETID_Meeting:0x8582",
"tneflocation" => "PT_STRING8:PSETID_Meeting:0x2", //ical only
"location" => "PT_STRING8:PSETID_Appointment:0x8208",
"tnefrecurr" => "PT_BOOLEAN:PSETID_Meeting:0x5",
"sideeffects" => "PT_LONG:PSETID_Common:0x8510",
"type" => "PT_STRING8:PSETID_Meeting:0x24",
"busystatus" => "PT_LONG:PSETID_Appointment:0x8205",
"meetingstatus" => "PT_LONG:PSETID_Appointment:0x8217",
"responsestatus" => "PT_LONG:PSETID_Meeting:0x8218",
//the properties below are currently not used
"dayinterval" => "PT_I2:PSETID_Meeting:0x11",
"weekinterval" => "PT_I2:PSETID_Meeting:0x12",
"monthinterval" => "PT_I2:PSETID_Meeting:0x13",
"yearinterval" => "PT_I2:PSETID_Meeting:0x14",
);
}
/**
* Returns the MAPI to AS mapping for appointments
*
* @return array
*/
public static function GetAppointmentMapping() {
return array (
"alldayevent" => "PT_BOOLEAN:PSETID_Appointment:0x8215",
"body" => PR_BODY,
"busystatus" => "PT_LONG:PSETID_Appointment:0x8205",
"categories" => "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords",
"rtf" => PR_RTF_COMPRESSED,
"dtstamp" => PR_LAST_MODIFICATION_TIME,
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",
"location" => "PT_STRING8:PSETID_Appointment:0x8208",
"meetingstatus" => "PT_LONG:PSETID_Appointment:0x8217",
"sensitivity" => PR_SENSITIVITY,
"subject" => PR_SUBJECT,
"starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d",
"uid" => "PT_BINARY:PSETID_Meeting:0x3",
"nativebodytype" => PR_NATIVE_BODY_INFO,
);
}
/**
*
* Returns appointment specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetAppointmentProperties() {
return array(
"sourcekey" => PR_SOURCE_KEY,
"representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
"representingname" => PR_SENT_REPRESENTING_NAME,
"sentrepresentingemail" => PR_SENT_REPRESENTING_EMAIL_ADDRESS,
"sentrepresentingaddt" => PR_SENT_REPRESENTING_ADDRTYPE,
"sentrepresentinsrchk" => PR_SENT_REPRESENTING_SEARCH_KEY,
"reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
"remindertime" => "PT_LONG:PSETID_Common:0x8501",
"meetingstatus" => "PT_LONG:PSETID_Appointment:0x8217",
"isrecurring" => "PT_BOOLEAN:PSETID_Appointment:0x8223",
"recurringstate" => "PT_BINARY:PSETID_Appointment:0x8216",
"timezonetag" => "PT_BINARY:PSETID_Appointment:0x8233",
"recurrenceend" => "PT_SYSTIME:PSETID_Appointment:0x8236",
"responsestatus" => "PT_LONG:PSETID_Appointment:0x8218",
"commonstart" => "PT_SYSTIME:PSETID_Common:0x8516",
"commonend" => "PT_SYSTIME:PSETID_Common:0x8517",
"reminderstart" => "PT_SYSTIME:PSETID_Common:0x8502",
"duration" => "PT_LONG:PSETID_Appointment:0x8213",
"private" => "PT_BOOLEAN:PSETID_Common:0x8506",
"uid" => "PT_BINARY:PSETID_Meeting:0x23",
"sideeffects" => "PT_LONG:PSETID_Common:0x8510",
"flagdueby" => "PT_SYSTIME:PSETID_Common:0x8560",
"icon" => PR_ICON_INDEX,
"mrwassent" => "PT_BOOLEAN:PSETID_Appointment:0x8229",
"endtime" => "PT_SYSTIME:PSETID_Appointment:0x820e",//this is here for calendar restriction, tnef and ical
"starttime" => "PT_SYSTIME:PSETID_Appointment:0x820d",//this is here for calendar restriction, tnef and ical
"clipstart" => "PT_SYSTIME:PSETID_Appointment:0x8235", //ical only
"recurrencetype" => "PT_LONG:PSETID_Appointment:0x8231",
"body" => PR_BODY,
"rtfcompressed" => PR_RTF_COMPRESSED,
"html" => PR_HTML,
"rtfinsync" => PR_RTF_IN_SYNC,
);
}
/**
* Returns the MAPI to AS mapping for tasks
*
* @return array
*/
public static function GetTaskMapping() {
return array (
"body" => PR_BODY,
"categories" => "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords",
"complete" => "PT_BOOLEAN:PSETID_Task:0x811C",
"datecompleted" => "PT_SYSTIME:PSETID_Task:0x810F",
"duedate" => "PT_SYSTIME:PSETID_Task:0x8105",
"utcduedate" => "PT_SYSTIME:PSETID_Common:0x8517",
"utcstartdate" => "PT_SYSTIME:PSETID_Common:0x8516",
"importance" => PR_IMPORTANCE,
// recurrence
// regenerate
// deadoccur
"reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
"remindertime" => "PT_SYSTIME:PSETID_Common:0x8502",
"sensitivity" => PR_SENSITIVITY,
"startdate" => "PT_SYSTIME:PSETID_Task:0x8104",
"subject" => PR_SUBJECT,
"rtf" => PR_RTF_COMPRESSED,
);
}
/**
* Returns task specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetTaskProperties() {
return array (
"isrecurringtag" => "PT_BOOLEAN:PSETID_Task:0x8126",
"recurringstate" => "PT_BINARY:PSETID_Task:0x8116",
"deadoccur" => "PT_BOOLEAN:PSETID_Task:0x8109",
"completion" => "PT_DOUBLE:PSETID_Task:0x8102",
"status" => "PT_LONG:PSETID_Task:0x8101",
"icon" => PR_ICON_INDEX,
"owner" => "PT_STRING8:PSETID_Task:0x811F",
);
}
/**
* Returns the MAPI to AS mapping for email todo flags
*
* @return array
*/
public static function GetMailFlagsMapping() {
return array (
"flagstatus" => PR_FLAG_STATUS,
"flagtype" => "PT_STRING8:PSETID_Common:0x8530",
"datecompleted" => "PT_SYSTIME:PSETID_Common:0x810F",
"completetime" => PR_FLAG_COMPLETE_TIME,
"startdate" => "PT_SYSTIME:PSETID_Task:0x8104",
"duedate" => "PT_SYSTIME:PSETID_Task:0x8105",
"utcstartdate" => "PT_SYSTIME:PSETID_Common:0x8516",
"utcduedate" => "PT_SYSTIME:PSETID_Common:0x8517",
"reminderset" => "PT_BOOLEAN:PSETID_Common:0x8503",
"remindertime" => "PT_SYSTIME:PSETID_Common:0x8502",
"ordinaldate" => "PT_SYSTIME:PSETID_Common:0x85A0",
"subordinaldate" => "PT_STRING8:PSETID_Common:0x85A1",
);
}
/**
* Returns email todo flags' specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetMailFlagsProperties() {
return array(
"todoitemsflags" => PR_TODO_ITEM_FLAGS,
"todotitle" => "PT_STRING8:PSETID_Common:0x85A4",
"flagicon" => PR_FLAG_ICON,
"replyrequested" => PR_REPLY_REQUESTED,
"responserequested" => PR_RESPONSE_REQUESTED,
"status" => "PT_LONG:PSETID_Task:0x8101",
"completion" => "PT_DOUBLE:PSETID_Task:0x8102",
"complete" => "PT_BOOLEAN:PSETID_Task:0x811C",
);
}
/**
* Returns the MAPI to AS mapping for notes
*
* @access public
*
* @return array
*/
public static function GetNoteMapping() {
return array(
"categories" => "PT_MV_STRING8:PS_PUBLIC_STRINGS:Keywords",
"lastmodificationtime" => PR_LAST_MODIFICATION_TIME,
"messageclass" => PR_MESSAGE_CLASS,
"subject" => PR_SUBJECT,
);
}
/**
* Returns note specific MAPI properties
*
* @access public
*
* @return array
*/
public static function GetNoteProperties() {
return array(
"body" => PR_BODY,
"messageclass" => PR_MESSAGE_CLASS,
"html" => PR_HTML,
"internetcpid" => PR_INTERNET_CPID,
);
}
/**
* Returns properties for sending an email
*
* @access public
*
* @return array
*/
public static function GetSendMailProperties() {
return array(
"outboxentryid" => PR_IPM_OUTBOX_ENTRYID,
"ipmsentmailentryid" => PR_IPM_SENTMAIL_ENTRYID,
"sentmailentryid" => PR_SENTMAIL_ENTRYID,
"subject" => PR_SUBJECT,
"messageclass" => PR_MESSAGE_CLASS,
"deliverytime" => PR_MESSAGE_DELIVERY_TIME,
"importance" => PR_IMPORTANCE,
"priority" => PR_PRIORITY,
"addrtype" => PR_ADDRTYPE,
"emailaddress" => PR_EMAIL_ADDRESS,
"displayname" => PR_DISPLAY_NAME,
"recipienttype" => PR_RECIPIENT_TYPE,
"entryid" => PR_ENTRYID,
"iconindex" => PR_ICON_INDEX,
"body" => PR_BODY,
"html" => PR_HTML,
"sentrepresentingname" => PR_SENT_REPRESENTING_NAME,
"sentrepresentingemail" => PR_SENT_REPRESENTING_EMAIL_ADDRESS,
"representingentryid" => PR_SENT_REPRESENTING_ENTRYID,
"sentrepresentingaddt" => PR_SENT_REPRESENTING_ADDRTYPE,
"sentrepresentinsrchk" => PR_SENT_REPRESENTING_SEARCH_KEY,
"displayto" => PR_DISPLAY_TO,
"displaycc" => PR_DISPLAY_CC,
"clientsubmittime" => PR_CLIENT_SUBMIT_TIME,
"attachnum" => PR_ATTACH_NUM,
"attachdatabin" => PR_ATTACH_DATA_BIN,
"internetcpid" => PR_INTERNET_CPID,
);
}
}
?>

View file

@ -0,0 +1,228 @@
<?php
/***********************************************
* File : mapiphpwrapper.php
* Project : Z-Push
* Descr : The ICS importer is very MAPI specific
* and needs to be wrapped, because we
* want all MAPI code to be separate from
* the rest of z-push. To do so all
* MAPI dependency are removed in this class.
* All the other importers are based on
* IChanges, not MAPI.
*
* Created : 14.02.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* This is the PHP wrapper which strips MAPI information from
* the import interface of ICS. We get all the information about messages
* from MAPI here which are sent to the next importer, which will
* convert the data into WBXML which is streamed to the PDA
*/
class PHPWrapper {
private $importer;
private $mapiprovider;
private $store;
private $contentparameters;
/**
* Constructor of the PHPWrapper
*
* @param ressource $session
* @param ressource $store
* @param IImportChanges $importer incoming changes from ICS are forwarded here
*
* @access public
* @return
*/
public function PHPWrapper($session, $store, $importer) {
$this->importer = &$importer;
$this->store = $store;
$this->mapiprovider = new MAPIProvider($session, $this->store);
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters) {
$this->contentparameters = $contentparameters;
}
/**
* Implement MAPI interface
*/
public function Config($stream, $flags = 0) {}
public function GetLastError($hresult, $ulflags, &$lpmapierror) {}
public function UpdateState($stream) { }
/**
* Imports a single message
*
* @param array $props
* @param long $flags
* @param object $retmapimessage
*
* @access public
* @return long
*/
public function ImportMessageChange($props, $flags, &$retmapimessage) {
$sourcekey = $props[PR_SOURCE_KEY];
$parentsourcekey = $props[PR_PARENT_SOURCE_KEY];
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey);
if(!$entryid)
return SYNC_E_IGNORE;
$mapimessage = mapi_msgstore_openentry($this->store, $entryid);
try {
$message = $this->mapiprovider->GetMessage($mapimessage, $this->contentparameters);
}
catch (SyncObjectBrokenException $mbe) {
$brokenSO = $mbe->GetSyncObject();
if (!$brokenSO) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but broken SyncObject available"));
}
else {
if (!isset($brokenSO->id)) {
$brokenSO->id = "Unknown ID";
ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but no ID of object set"));
}
ZPush::GetDeviceManager()->AnnounceIgnoredMessage(false, $brokenSO->id, $brokenSO);
}
// tell MAPI to ignore the message
return SYNC_E_IGNORE;
}
// substitute the MAPI SYNC_NEW_MESSAGE flag by a z-push proprietary flag
if ($flags == SYNC_NEW_MESSAGE) $message->flags = SYNC_NEWMESSAGE;
else $message->flags = $flags;
$this->importer->ImportMessageChange(bin2hex($sourcekey), $message);
// Tell MAPI it doesn't need to do anything itself, as we've done all the work already.
return SYNC_E_IGNORE;
}
/**
* Imports a list of messages to be deleted
*
* @param long $flags
* @param array $sourcekeys array with sourcekeys
*
* @access public
* @return
*/
public function ImportMessageDeletion($flags, $sourcekeys) {
foreach($sourcekeys as $sourcekey) {
$this->importer->ImportMessageDeletion(bin2hex($sourcekey));
}
}
/**
* Imports a list of messages to be deleted
*
* @param mixed $readstates sourcekeys and message flags
*
* @access public
* @return
*/
public function ImportPerUserReadStateChange($readstates) {
foreach($readstates as $readstate) {
$this->importer->ImportMessageReadFlag(bin2hex($readstate["sourcekey"]), $readstate["flags"] & MSGFLAG_READ);
}
}
/**
* Imports a message move
* this is never called by ICS
*
* @access public
* @return
*/
public function ImportMessageMove($sourcekeysrcfolder, $sourcekeysrcmessage, $message, $sourcekeydestmessage, $changenumdestmessage) {
// Never called
}
/**
* Imports a single folder change
*
* @param mixed $props sourcekey of the changed folder
*
* @access public
* @return
*/
function ImportFolderChange($props) {
$sourcekey = $props[PR_SOURCE_KEY];
$entryid = mapi_msgstore_entryidfromsourcekey($this->store, $sourcekey);
$mapifolder = mapi_msgstore_openentry($this->store, $entryid);
$folder = $this->mapiprovider->GetFolder($mapifolder);
// do not import folder if there is something "wrong" with it
if ($folder === false)
return 0;
$this->importer->ImportFolderChange($folder);
return 0;
}
/**
* Imports a list of folders which are to be deleted
*
* @param long $flags
* @param mixed $sourcekeys array with sourcekeys
*
* @access public
* @return
*/
function ImportFolderDeletion($flags, $sourcekeys) {
foreach ($sourcekeys as $sourcekey) {
$this->importer->ImportFolderDeletion(bin2hex($sourcekey));
}
return 0;
}
}
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,148 @@
<?php
/***********************************************
* File : mapistreamwrapper.php
* Project : Z-Push
* Descr : Wraps a mapi stream as a standard php stream
* The used method names are predefined and can not be altered.
*
* Created : 24.11.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class MAPIStreamWrapper {
const PROTOCOL = "mapistream";
private $mapistream;
private $position;
private $streamlength;
/**
* Opens the stream
* The mapistream reference is passed over the context
*
* @param string $path Specifies the URL that was passed to the original function
* @param string $mode The mode used to open the file, as detailed for fopen()
* @param int $options Holds additional flags set by the streams API
* @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
* opened_path should be set to the full path of the file/resource that was actually opened.
*
* @access public
* @return boolean
*/
public function stream_open($path, $mode, $options, &$opened_path) {
$contextOptions = stream_context_get_options($this->context);
if (!isset($contextOptions[self::PROTOCOL]['stream']))
return false;
$this->position = 0;
// this is our stream!
$this->mapistream = $contextOptions[self::PROTOCOL]['stream'];
// get the data length from mapi
$stat = mapi_stream_stat($this->mapistream);
$this->streamlength = $stat["cb"];
ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIStreamWrapper::stream_open(): initialized mapistream: %s streamlength: %d", $this->mapistream, $this->streamlength));
return true;
}
/**
* Reads from stream
*
* @param int $len amount of bytes to be read
*
* @access public
* @return string
*/
public function stream_read($len) {
$len = ($this->position + $len > $this->streamlength) ? ($this->streamlength - $this->position) : $len;
$data = mapi_stream_read($this->mapistream, $len);
$this->position += strlen($data);
return $data;
}
/**
* Returns the current position on stream
*
* @access public
* @return int
*/
public function stream_tell() {
return $this->position;
}
/**
* Indicates if 'end of file' is reached
*
* @access public
* @return boolean
*/
public function stream_eof() {
return ($this->position >= $this->streamlength);
}
/**
* Retrieves information about a stream
*
* @access public
* @return array
*/
public function stream_stat() {
return array(
7 => $this->streamlength,
'size' => $this->streamlength,
);
}
/**
* Instantiates a MAPIStreamWrapper
*
* @param mapistream $mapistream The stream to be wrapped
*
* @access public
* @return MAPIStreamWrapper
*/
static public function Open($mapistream) {
$context = stream_context_create(array(self::PROTOCOL => array('stream' => &$mapistream)));
return fopen(self::PROTOCOL . "://",'r', false, $context);
}
}
stream_wrapper_register(MAPIStreamWrapper::PROTOCOL, "MAPIStreamWrapper")
?>

View file

@ -0,0 +1,487 @@
<?php
/***********************************************
* File : mapiutils.php
* Project : Z-Push
* Descr :
*
* Created : 14.02.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
*
* MAPI to AS mapping class
*
*
*/
class MAPIUtils {
/**
* Create a MAPI restriction to use within an email folder which will
* return all messages since since $timestamp
*
* @param long $timestamp Timestamp since when to include messages
*
* @access public
* @return array
*/
public static function GetEmailRestriction($timestamp) {
// ATTENTION: ON CHANGING THIS RESTRICTION, MAPIUtils::IsInEmailSyncInterval() also needs to be changed
$restriction = array ( RES_PROPERTY,
array ( RELOP => RELOP_GE,
ULPROPTAG => PR_MESSAGE_DELIVERY_TIME,
VALUE => $timestamp
)
);
return $restriction;
}
/**
* Create a MAPI restriction to use in the calendar which will
* return all future calendar items, plus those since $timestamp
*
* @param MAPIStore $store the MAPI store
* @param long $timestamp Timestamp since when to include messages
*
* @access public
* @return array
*/
//TODO getting named properties
public static function GetCalendarRestriction($store, $timestamp) {
// This is our viewing window
$start = $timestamp;
$end = 0x7fffffff; // infinite end
$props = MAPIMapping::GetAppointmentProperties();
$props = getPropIdsFromStrings($store, $props);
// ATTENTION: ON CHANGING THIS RESTRICTION, MAPIUtils::IsInCalendarSyncInterval() also needs to be changed
$restriction = Array(RES_OR,
Array(
// OR
// item.end > window.start && item.start < window.end
Array(RES_AND,
Array(
Array(RES_PROPERTY,
Array(RELOP => RELOP_LE,
ULPROPTAG => $props["starttime"],
VALUE => $end
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_GE,
ULPROPTAG => $props["endtime"],
VALUE => $start
)
)
)
),
// OR
Array(RES_OR,
Array(
// OR
// (EXIST(recurrence_enddate_property) && item[isRecurring] == true && recurrence_enddate_property >= start)
Array(RES_AND,
Array(
Array(RES_EXIST,
Array(ULPROPTAG => $props["recurrenceend"],
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => $props["isrecurring"],
VALUE => true
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_GE,
ULPROPTAG => $props["recurrenceend"],
VALUE => $start
)
)
)
),
// OR
// (!EXIST(recurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
Array(RES_AND,
Array(
Array(RES_NOT,
Array(
Array(RES_EXIST,
Array(ULPROPTAG => $props["recurrenceend"]
)
)
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_LE,
ULPROPTAG => $props["starttime"],
VALUE => $end
)
),
Array(RES_PROPERTY,
Array(RELOP => RELOP_EQ,
ULPROPTAG => $props["isrecurring"],
VALUE => true
)
)
)
)
)
) // EXISTS OR
)
); // global OR
return $restriction;
}
/**
* Create a MAPI restriction in order to check if a contact has a picture
*
* @access public
* @return array
*/
public static function GetContactPicRestriction() {
return array ( RES_PROPERTY,
array (
RELOP => RELOP_EQ,
ULPROPTAG => mapi_prop_tag(PT_BOOLEAN, 0x7FFF),
VALUE => true
)
);
}
/**
* Create a MAPI restriction for search
*
* @access public
*
* @param string $query
* @return array
*/
public static function GetSearchRestriction($query) {
return array(RES_AND,
array(
array(RES_OR,
array(
array(RES_CONTENT, array(FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE, ULPROPTAG => PR_DISPLAY_NAME, VALUE => $query)),
array(RES_CONTENT, array(FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE, ULPROPTAG => PR_ACCOUNT, VALUE => $query)),
array(RES_CONTENT, array(FUZZYLEVEL => FL_SUBSTRING | FL_IGNORECASE, ULPROPTAG => PR_SMTP_ADDRESS, VALUE => $query)),
), // RES_OR
),
array(RES_OR,
array (
array(
RES_PROPERTY,
array(RELOP => RELOP_EQ, ULPROPTAG => PR_OBJECT_TYPE, VALUE => MAPI_MAILUSER)
),
array(
RES_PROPERTY,
array(RELOP => RELOP_EQ, ULPROPTAG => PR_OBJECT_TYPE, VALUE => MAPI_DISTLIST)
)
)
) // RES_OR
) // RES_AND
);
}
/**
* Create a MAPI restriction for a certain email address
*
* @access public
*
* @param MAPIStore $store the MAPI store
* @param string $query email address
*
* @return array
*/
public static function GetEmailAddressRestriction($store, $email) {
$props = MAPIMapping::GetContactProperties();
$props = getPropIdsFromStrings($store, $props);
return array(RES_OR,
array(
array( RES_PROPERTY,
array( RELOP => RELOP_EQ,
ULPROPTAG => $props['emailaddress1'],
VALUE => array($props['emailaddress1'] => $email),
),
),
array( RES_PROPERTY,
array( RELOP => RELOP_EQ,
ULPROPTAG => $props['emailaddress2'],
VALUE => array($props['emailaddress2'] => $email),
),
),
array( RES_PROPERTY,
array( RELOP => RELOP_EQ,
ULPROPTAG => $props['emailaddress3'],
VALUE => array($props['emailaddress3'] => $email),
),
),
),
);
}
/**
* Create a MAPI restriction for a certain folder type
*
* @access public
*
* @param string $foldertype folder type for restriction
* @return array
*/
public static function GetFolderTypeRestriction($foldertype) {
return array( RES_PROPERTY,
array( RELOP => RELOP_EQ,
ULPROPTAG => PR_CONTAINER_CLASS,
VALUE => array(PR_CONTAINER_CLASS => $foldertype)
),
);
}
/**
* Returns subfolders of given type for a folder or false if there are none.
*
* @access public
*
* @param MAPIFolder $folder
* @param string $type
*
* @return MAPITable|boolean
*/
public static function GetSubfoldersForType($folder, $type) {
$subfolders = mapi_folder_gethierarchytable($folder, CONVENIENT_DEPTH);
mapi_table_restrict($subfolders, MAPIUtils::GetFolderTypeRestriction($type));
if (mapi_table_getrowcount($subfolders) > 0) {
return mapi_table_queryallrows($subfolders, array(PR_ENTRYID));
}
return false;
}
/**
* Checks if mapimessage is inside the synchronization interval
* also defined by MAPIUtils::GetEmailRestriction()
*
* @param MAPIStore $store mapi store
* @param MAPIMessage $mapimessage the mapi message to be checked
* @param long $timestamp the lower time limit
*
* @access public
* @return boolean
*/
public static function IsInEmailSyncInterval($store, $mapimessage, $timestamp) {
$p = mapi_getprops($mapimessage, array(PR_MESSAGE_DELIVERY_TIME));
if (isset($p[PR_MESSAGE_DELIVERY_TIME]) && $p[PR_MESSAGE_DELIVERY_TIME] >= $timestamp) {
ZLog::Write(LOGLEVEL_DEBUG, "MAPIUtils->IsInEmailSyncInterval: Message is in the synchronization interval");
return true;
}
ZLog::Write(LOGLEVEL_WARN, "MAPIUtils->IsInEmailSyncInterval: Message is OUTSIDE the synchronization interval");
return false;
}
/**
* Checks if mapimessage is inside the synchronization interval
* also defined by MAPIUtils::GetCalendarRestriction()
*
* @param MAPIStore $store mapi store
* @param MAPIMessage $mapimessage the mapi message to be checked
* @param long $timestamp the lower time limit
*
* @access public
* @return boolean
*/
public static function IsInCalendarSyncInterval($store, $mapimessage, $timestamp) {
// This is our viewing window
$start = $timestamp;
$end = 0x7fffffff; // infinite end
$props = MAPIMapping::GetAppointmentProperties();
$props = getPropIdsFromStrings($store, $props);
$p = mapi_getprops($mapimessage, array($props["starttime"], $props["endtime"], $props["recurrenceend"], $props["isrecurring"], $props["recurrenceend"]));
if (
(
isset($p[$props["endtime"]]) && isset($p[$props["starttime"]]) &&
//item.end > window.start && item.start < window.end
$p[$props["endtime"]] > $start && $p[$props["starttime"]] < $end
)
||
(
isset($p[$props["isrecurring"]]) &&
//(EXIST(recurrence_enddate_property) && item[isRecurring] == true && recurrence_enddate_property >= start)
isset($p[$props["recurrenceend"]]) && $p[$props["isrecurring"]] == true && $p[$props["recurrenceend"]] >= $start
)
||
(
isset($p[$props["isrecurring"]]) && isset($p[$props["starttime"]]) &&
//(!EXIST(recurrence_enddate_property) && item[isRecurring] == true && item[start] <= end)
!isset($p[$props["recurrenceend"]]) && $p[$props["isrecurring"]] == true && $p[$props["starttime"]] <= $end
)
) {
ZLog::Write(LOGLEVEL_DEBUG, "MAPIUtils->IsInCalendarSyncInterval: Message is in the synchronization interval");
return true;
}
ZLog::Write(LOGLEVEL_WARN, "MAPIUtils->IsInCalendarSyncInterval: Message is OUTSIDE the synchronization interval");
return false;
}
/**
* Reads data of large properties from a stream
*
* @param MAPIMessage $message
* @param long $prop
*
* @access public
* @return string
*/
public static function readPropStream($message, $prop) {
$stream = mapi_openproperty($message, $prop, IID_IStream, 0, 0);
$ret = mapi_last_hresult();
if ($ret == MAPI_E_NOT_FOUND) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("MAPIUtils->readPropStream: property 0x%s not found. It is either empty or not set. It will be ignored.", str_pad(dechex($prop), 8, 0, STR_PAD_LEFT)));
return "";
}
elseif ($ret) {
ZLog::Write(LOGLEVEL_ERROR, "MAPIUtils->readPropStream error opening stream: 0X%X", $ret);
return "";
}
$data = "";
$string = "";
while(1) {
$data = mapi_stream_read($stream, 1024);
if(strlen($data) == 0)
break;
$string .= $data;
}
return $string;
}
/**
* Checks if a store supports properties containing unicode characters
*
* @param MAPIStore $store
*
* @access public
* @return
*/
public static function IsUnicodeStore($store) {
$supportmask = mapi_getprops($store, array(PR_STORE_SUPPORT_MASK));
if (isset($supportmask[PR_STORE_SUPPORT_MASK]) && ($supportmask[PR_STORE_SUPPORT_MASK] & STORE_UNICODE_OK)) {
ZLog::Write(LOGLEVEL_DEBUG, "Store supports properties containing Unicode characters.");
define('STORE_SUPPORTS_UNICODE', true);
//setlocale to UTF-8 in order to support properties containing Unicode characters
setlocale(LC_CTYPE, "en_US.UTF-8");
define('STORE_INTERNET_CPID', INTERNET_CPID_UTF8);
}
}
/**
* Returns the MAPI PR_CONTAINER_CLASS string for an ActiveSync Foldertype
*
* @param int $foldertype
*
* @access public
* @return string
*/
public static function GetContainerClassFromFolderType($foldertype) {
switch ($foldertype) {
case SYNC_FOLDER_TYPE_TASK:
case SYNC_FOLDER_TYPE_USER_TASK:
return "IPF.Task";
break;
case SYNC_FOLDER_TYPE_APPOINTMENT:
case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
return "IPF.Appointment";
break;
case SYNC_FOLDER_TYPE_CONTACT:
case SYNC_FOLDER_TYPE_USER_CONTACT:
return "IPF.Contact";
break;
case SYNC_FOLDER_TYPE_NOTE:
case SYNC_FOLDER_TYPE_USER_NOTE:
return "IPF.StickyNote";
break;
case SYNC_FOLDER_TYPE_JOURNAL:
case SYNC_FOLDER_TYPE_USER_JOURNAL:
return "IPF.Journal";
break;
case SYNC_FOLDER_TYPE_INBOX:
case SYNC_FOLDER_TYPE_DRAFTS:
case SYNC_FOLDER_TYPE_WASTEBASKET:
case SYNC_FOLDER_TYPE_SENTMAIL:
case SYNC_FOLDER_TYPE_OUTBOX:
case SYNC_FOLDER_TYPE_USER_MAIL:
case SYNC_FOLDER_TYPE_OTHER:
case SYNC_FOLDER_TYPE_UNKNOWN:
default:
return "IPF.Note";
break;
}
}
public static function GetSignedAttachmentRestriction() {
return array( RES_PROPERTY,
array( RELOP => RELOP_EQ,
ULPROPTAG => PR_ATTACH_MIME_TAG,
VALUE => array(PR_ATTACH_MIME_TAG => 'multipart/signed')
),
);
}
}
?>

View file

@ -0,0 +1,721 @@
<?php
/***********************************************
* File : tnefparser.php
* Project : Z-Push
* Descr : This is tnef implementation for z-push.
* It is based on Zarafa's tnef implementation.
* This class does only simple reading of a
* tnef stream. Most importantly, we currently
* only support properties within the message itself,
* and do not support recipient tables and
* attachment properties within the TNEF data.
* This class will accept TNEF streams with data about
* recipients and attachments, but the information
* will be ignored.
*
* Created : 21.06.2008
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**
* For more information on tnef refer to:
* http://msdn.microsoft.com/en-us/library/ms530652(EXCHG.10).aspx
* http://msdn.microsoft.com/en-us/library/cc425498(EXCHG.80).aspx
*
* The mapping between Microsoft Mail IPM classes and those used in
* MAPI see: http://msdn2.microsoft.com/en-us/library/ms527360.aspx
*/
class TNEFParser {
const TNEF_SIGNATURE = 0x223e9f78;
const TNEF_LVL_MESSAGE = 0x01;
const TNEF_LVL_ATTACHMENT = 0x02;
const DWORD = 32;
const WORD = 16;
const BYTE = 8;
/**
* Constructor
* We need a store in order to get the namedpropers
*
* @param mapistore $store
* @param array &$props properties to be set
*
* @access public
*/
public function TNEFParser(&$store, &$props) {
$this->store = $store;
$this->props = $props;
}
/**
* Function reads tnef stream and puts mapi properties into an array.
*
* @param string $tnefstream
* @param array &$mapiprops mapi properties
*
* @access public
* @return int
*/
public function ExtractProps($tnefstream, &$mapiprops) {
$hresult = NOERROR;
$signature = 0; //tnef signature - 32 Bit
$key = 0; //a nonzero 16-bit unsigned integer
$type = 0; // 32-bit value
$size = 0; // 32-bit value
$checksum = 0; //16-bit value
$component = 0; //8-bit value - either self::TNEF_LVL_MESSAGE or self::TNEF_LVL_ATTACHMENT
$buffer = "";
//mapping between Microsoft Mail IPM classes and those in MAPI
$aClassMap = array(
"IPM.Microsoft Schedule.MtgReq" => "IPM.Schedule.Meeting.Request",
"IPM.Microsoft Schedule.MtgRespP" => "IPM.Schedule.Meeting.Resp.Pos",
"IPM.Microsoft Schedule.MtgRespN" => "IPM.Schedule.Meeting.Resp.Neg",
"IPM.Microsoft Schedule.MtgRespA" => "IPM.Schedule.Meeting.Resp.Tent",
"IPM.Microsoft Schedule.MtgCncl" => "IPM.Schedule.Meeting.Canceled",
"IPM.Microsoft Mail.Non-Delivery" => "Report.IPM.Note.NDR",
"IPM.Microsoft Mail.Read Receipt" => "Report.IPM.Note.IPNRN",
"IPM.Microsoft Mail.Note" => "IPM.Note",
"IPM.Microsoft Mail.Note" => "IPM",
);
//read signature
$hresult = $this->readFromTnefStream($tnefstream, self::DWORD, $signature);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: STREAM:".bin2hex($tnefstream));
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading tnef signature");
return $hresult;
}
//check signature
if ($signature != self::TNEF_SIGNATURE) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: Corrupt signature.");
return MAPI_E_CORRUPT_DATA;
}
//read key
$hresult = $this->readFromTnefStream($tnefstream, self::WORD, $key);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading tnef key.");
return $hresult;
}
// File is made of blocks, with each a type and size. Component and Key are ignored.
while(1) {
//the stream is empty. exit
if (strlen($tnefstream) == 0) return NOERROR;
//read component - it is either self::TNEF_LVL_MESSAGE or self::TNEF_LVL_ATTACHMENT
$hresult = $this->readFromTnefStream($tnefstream, self::BYTE, $component);
if ($hresult !== NOERROR) {
$hresult = NOERROR; //EOF -> no error
return $hresult;
break;
}
//read type
$hresult = $this->readFromTnefStream($tnefstream, self::DWORD, $type);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property type");
return $hresult;
}
//read size
$hresult = $this->readFromTnefStream($tnefstream, self::DWORD, $size);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property size");
return $hresult;
}
if ($size == 0) {
// do not allocate 0 size data block
ZLog::Write(LOGLEVEL_WARN, "TNEF: Size is 0. Corrupt data.");
return MAPI_E_CORRUPT_DATA;
}
//read buffer
$hresult = $this->readBuffer($tnefstream, $size, $buffer);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
//read checksum
$hresult = $this->readFromTnefStream($tnefstream, self::WORD, $checksum);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property checksum.");
return $hresult;
}
// Loop through all the blocks of the TNEF data. We are only interested
// in the properties block for now (0x00069003)
switch ($type) {
case 0x00069003:
$hresult = $this->readMapiProps($buffer, $size, $mapiprops);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi properties' part.");
return $hresult;
}
break;
case 0x00078008: // PR_MESSAGE_CLASS
$msMailClass = trim($buffer);
if (array_key_exists($msMailClass, $aClassMap)) {
$messageClass = $aClassMap[$msMailClass];
}
else {
$messageClass = $msMailClass;
}
$mapiprops[PR_MESSAGE_CLASS] = $messageClass;
break;
case 0x00050008: // PR_OWNER_APPT_ID
$mapiprops[PR_OWNER_APPT_ID] = $buffer;
break;
case 0x00040009: // PR_RESPONSE_REQUESTED
$mapiprops[PR_RESPONSE_REQUESTED] = $buffer;
break;
// --- TNEF attachemnts ---
case 0x00069002:
break;
case 0x00018010: // PR_ATTACH_FILENAME
break;
case 0x00068011: // PR_ATTACH_RENDERING, extra icon information
break;
case 0x0006800f: // PR_ATTACH_DATA_BIN, will be set via OpenProperty() in ECTNEF::Finish()
break;
case 0x00069005: // Attachment property stream
break;
default:
// Ignore this block
break;
}
}
return NOERROR;
}
/**
* Reads a given number of bits from stream and converts them from little indian in a "normal" order. The function
* also cuts the tnef stream after reading.
*
* @param string &$tnefstream
* @param int $bits
* @param array &$element the read element
*
* @access private
* @return int
*/
private function readFromTnefStream(&$tnefstream, $bits, &$element) {
$bytes = $bits / 8;
$part = substr($tnefstream, 0, $bytes);
$packs = array();
switch ($bits) {
case self::DWORD:
$packs = unpack("V", $part);
break;
case self::WORD:
$packs = unpack("v", $part);
break;
case self::BYTE:
$packs[1] = ord($part[0]);
break;
default:
$packs = array();
break;
}
if (empty($packs) || !isset($packs[1])) return MAPI_E_CORRUPT_DATA;
$tnefstream = substr($tnefstream, $bytes);
$element = $packs[1];
return NOERROR;
}
/**
* Reads a given number of bytes from stream and puts them into $element. The function
* also cuts the tnef stream after reading.
*
* @param string &$tnefstream
* @param int $bytes
* @param array &$element the read element
*
* @access private
* @return int
*/
private function readBuffer(&$tnefstream, $bytes, &$element) {
$element = substr($tnefstream, 0, $bytes);
$tnefstream = substr($tnefstream, $bytes);
return NOERROR;
}
/**
* Reads mapi props from buffer into an anrray.
*
* @param string &$buffer
* @param int $size
* @param array &$mapiprops
*
* @access private
* @return int
*/
function readMapiProps(&$buffer, $size, &$mapiprops) {
$nrprops = 0;
//get number of mapi properties
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $nrprops);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error getting the number of mapi properties in stream.");
return $hresult;
}
$size -= 4;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: nrprops:$nrprops");
//loop through all the properties and add them to our internal list
while($nrprops) {
$hresult = $this->readSingleMapiProp($buffer, $size, $read, $mapiprops);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading a mapi property.");
ZLog::Write(LOGLEVEL_WARN, "TNEF: result: " . sprintf("0x%X", $hresult));
return $hresult;
}
$nrprops--;
}
return NOERROR;
}
/**
* Reads a single mapi prop.
*
* @param string &$buffer
* @param int $size
* @param mixed &$read
* @param array &$mapiprops
*
* @access private
* @return int
*/
private function readSingleMapiProp(&$buffer, &$size, &$read, &$mapiprops) {
$propTag = 0;
$len = 0;
$origSize = $size;
$isNamedId = 0;
$namedProp = 0;
$count = 0;
$mvProp = 0;
$guid = 0;
if($size < 8) {
return MAPI_E_NOT_FOUND;
}
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $propTag);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading a mapi property tag from the stream.");
return $hresult;
}
$size -= 4;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: mapi prop type:".dechex(mapi_prop_type($propTag)));
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: mapi prop tag: 0x".sprintf("%04x", mapi_prop_id($propTag)));
if (mapi_prop_id($propTag) >= 0x8000) {
// Named property, first read GUID, then name/id
if($size < 24) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: Corrupt guid size for named property:".dechex($propTag));
return MAPI_E_CORRUPT_DATA;
}
//strip GUID & name/id
$hresult = $this->readBuffer($buffer, 16, $guid);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
$size -= 16;
//it is not used and is here only for eventual debugging
$readableGuid = unpack("VV/v2v/n4n", $guid);
$readableGuid = sprintf("{%08x-%04x-%04x-%04x-%04x%04x%04x}",$readableGuid['V'], $readableGuid['v1'], $readableGuid['v2'],$readableGuid['n1'],$readableGuid['n2'],$readableGuid['n3'],$readableGuid['n4']);
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: guid:$readableGuid");
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $isNamedId);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property checksum.");
return $hresult;
}
$size -= 4;
if($isNamedId != 0) {
// A string name follows
//read length of the property
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $len);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi property's length");
return $hresult;
}
$size -= 4;
if ($size < $len) {
return MAPI_E_CORRUPT_DATA;
}
//read the name of the property, eg Keywords
$hresult = $this->readBuffer($buffer, $len, $namedProp);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
$size -= $len;
//Re-align
$buffer = substr($buffer, ($len & 3 ? 4 - ($len & 3) : 0));
$size -= $len & 3 ? 4 - ($len & 3) : 0;
}
else {
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $namedProp);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi property's length");
return $hresult;
}
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: named: 0x".sprintf("%04x", $namedProp));
$size -= 4;
}
if ($this->store !== false) {
$named = mapi_getidsfromnames($this->store, array($namedProp), array(makeguid($readableGuid)));
$propTag = mapi_prop_tag(mapi_prop_type($propTag), mapi_prop_id($named[0]));
}
else {
ZLog::Write(LOGLEVEL_WARN, "TNEF: Store not available. It is impossible to get named properties");
}
}
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: mapi prop tag: 0x".sprintf("%04x", mapi_prop_id($propTag))." ".sprintf("%04x", mapi_prop_type($propTag)));
if($propTag & MV_FLAG) {
if($size < 4) {
return MAPI_E_CORRUPT_DATA;
}
//read the number of properties
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $count);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading number of properties for:".dechex($propTag));
return $hresult;
}
$size -= 4;
}
else {
$count = 1;
}
for ($mvProp = 0; $mvProp < $count; $mvProp++) {
switch(mapi_prop_type($propTag) & ~MV_FLAG ) {
case PT_I2:
case PT_LONG:
$hresult = $this->readBuffer($buffer, 4, $value);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
$value = unpack("V", $value);
$value = intval($value[1], 16);
if($propTag & MV_FLAG) {
$mapiprops[$propTag][] = $value;
}
else {
$mapiprops[$propTag] = $value;
}
$size -= 4;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: int or long propvalue:".$value);
break;
case PT_R4:
if($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, 4, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, 4, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
$size -= 4;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_BOOLEAN:
$hresult = $this->readBuffer($buffer, 4, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
$size -= 4;
//reported by dw2412
//cast to integer as it evaluates to 1 or 0 because
//a non empty string evaluates to true :(
$mapiprops[$propTag] = (integer) bin2hex($mapiprops[$propTag]{0});
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_SYSTIME:
if($size < 8) {
return MAPI_E_CORRUPT_DATA;
}
if($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, 8, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, 8, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
//we have to convert the filetime to an unixtime timestamp
$filetime = unpack("V2v", $mapiprops[$propTag]);
//php on 64-bit systems converts unsigned values differently than on 32 bit systems
//we need this "fix" in order to get the same values on both types of systems
$filetime['v2'] = substr(sprintf("%08x",$filetime['v2']), -8);
$filetime['v1'] = substr(sprintf("%08x",$filetime['v1']), -8);
$filetime = hexdec($filetime['v2'].$filetime['v1']);
$filetime = ($filetime - 116444736000000000) / 10000000;
$mapiprops[$propTag] = $filetime;
// we have to set the start and end times separately because the standard PR_START_DATE and PR_END_DATE aren't enough
if ($propTag == PR_START_DATE) {
$mapiprops[$this->props["starttime"]] = $mapiprops[$this->props["commonstart"]] = $filetime;
}
if ($propTag == PR_END_DATE) {
$mapiprops[$this->props["endtime"]] = $mapiprops[$this->props["commonend"]] = $filetime;
}
$size -= 8;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_DOUBLE:
case PT_CURRENCY:
case PT_I8:
case PT_APPTIME:
if($size < 8) {
return MAPI_E_CORRUPT_DATA;
}
if($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, 8, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, 8, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
$size -= 8;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_STRING8:
if($size < 8) {
return MAPI_E_CORRUPT_DATA;
}
// Skip next 4 bytes, it's always '1' (ULONG)
$buffer = substr($buffer, 4);
$size -= 4;
//read length of the property
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $len);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi property's length");
return $hresult;
}
$size -= 4;
if ($size < $len) {
return MAPI_E_CORRUPT_DATA;
}
if ($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
//location fix. it looks like tnef uses this value for location
if (mapi_prop_id($propTag) == 0x8342) {
$mapiprops[$this->props["location"]] = $mapiprops[$propTag];
unset($mapiprops[$propTag]);
}
$size -= $len;
//Re-align
$buffer = substr($buffer, ($len & 3 ? 4 - ($len & 3) : 0));
$size -= $len & 3 ? 4 - ($len & 3) : 0;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_UNICODE:
if($size < 8) {
return MAPI_E_CORRUPT_DATA;
}
// Skip next 4 bytes, it's always '1' (ULONG)
$buffer = substr($buffer, 4);
$size -= 4;
//read length of the property
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $len);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi property's length");
return $hresult;
}
$size -= 4;
if ($size < $len) {
return MAPI_E_CORRUPT_DATA;
}
//currently unicode strings are not supported bz mapi_setprops, so we'll use PT_STRING8
$propTag = mapi_prop_tag(PT_STRING8, mapi_prop_id($propTag));
if ($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
//location fix. it looks like tnef uses this value for location
if (mapi_prop_id($propTag) == 0x8342) {
$mapiprops[$this->props["location"]] = iconv("UCS-2","windows-1252", $mapiprops[$propTag]);
unset($mapiprops[$propTag]);
}
//convert from unicode to windows encoding
if (isset($mapiprops[$propTag])) $mapiprops[$propTag] = iconv("UCS-2","windows-1252", $mapiprops[$propTag]);
$size -= $len;
//Re-align
$buffer = substr($buffer, ($len & 3 ? 4 - ($len & 3) : 0));
$size -= $len & 3 ? 4 - ($len & 3) : 0;
if (isset($mapiprops[$propTag])) ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".$mapiprops[$propTag]);
break;
case PT_OBJECT: // PST sends PT_OBJECT data. Treat as PT_BINARY
case PT_BINARY:
if($size < self::BYTE) {
return MAPI_E_CORRUPT_DATA;
}
// Skip next 4 bytes, it's always '1' (ULONG)
$buffer = substr($buffer, 4);
$size -= 4;
//read length of the property
$hresult = $this->readFromTnefStream($buffer, self::DWORD, $len);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading mapi property's length");
return $hresult;
}
$size -= 4;
if (mapi_prop_type($propTag) == PT_OBJECT) {
// IMessage guid [ 0x00020307 C000 0000 0000 0000 00 00 00 46 ]
$buffer = substr($buffer, 16);
$size -= 16;
$len -= 16;
}
if ($size < $len) {
return MAPI_E_CORRUPT_DATA;
}
if ($propTag & MV_FLAG) {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag][]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
else {
$hresult = $this->readBuffer($buffer, $len, $mapiprops[$propTag]);
if ($hresult !== NOERROR) {
ZLog::Write(LOGLEVEL_WARN, "TNEF: There was an error reading stream property buffer");
return $hresult;
}
}
$size -= $len;
//Re-align
$buffer = substr($buffer, ($len & 3 ? 4 - ($len & 3) : 0));
$size -= $len & 3 ? 4 - ($len & 3) : 0;
ZLog::Write(LOGLEVEL_DEBUG, "TNEF: propvalue:".bin2hex($mapiprops[$propTag]));
break;
default:
return MAPI_E_INVALID_PARAMETER;
break;
}
}
return NOERROR;
}
}
?>

File diff suppressed because it is too large Load diff

269
sources/config.php Normal file
View file

@ -0,0 +1,269 @@
<?php
/***********************************************
* File : config.php
* Project : Z-Push
* Descr : Main configuration file
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/**********************************************************************************
* Default settings
*/
// Defines the default time zone, change e.g. to "Europe/London" if necessary
define('TIMEZONE', '');
// Defines the base path on the server
define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/');
// Try to set unlimited timeout
define('SCRIPT_TIMEOUT', 0);
// When accessing through a proxy, the "X-Forwarded-For" header contains the original remote IP
define('USE_X_FORWARDED_FOR_HEADER', false);
// When using client certificates, we can check if the login sent matches the owner of the certificate.
// This setting specifies the owner parameter in the certificate to look at.
define("CERTIFICATE_OWNER_PARAMETER", "SSL_CLIENT_S_DN_CN");
/**********************************************************************************
* Default FileStateMachine settings
*/
define('STATE_DIR', '/var/lib/z-push/');
/**********************************************************************************
* Logging settings
* Possible LOGLEVEL and LOGUSERLEVEL values are:
* LOGLEVEL_OFF - no logging
* LOGLEVEL_FATAL - log only critical errors
* LOGLEVEL_ERROR - logs events which might require corrective actions
* LOGLEVEL_WARN - might lead to an error or require corrective actions in the future
* LOGLEVEL_INFO - usually completed actions
* LOGLEVEL_DEBUG - debugging information, typically only meaningful to developers
* LOGLEVEL_WBXML - also prints the WBXML sent to/from the device
* LOGLEVEL_DEVICEID - also prints the device id for every log entry
* LOGLEVEL_WBXMLSTACK - also prints the contents of WBXML stack
*
* The verbosity increases from top to bottom. More verbose levels include less verbose
* ones, e.g. setting to LOGLEVEL_DEBUG will also output LOGLEVEL_FATAL, LOGLEVEL_ERROR,
* LOGLEVEL_WARN and LOGLEVEL_INFO level entries.
*/
define('LOGFILEDIR', '/var/log/z-push/');
define('LOGFILE', LOGFILEDIR . 'z-push.log');
define('LOGERRORFILE', LOGFILEDIR . 'z-push-error.log');
define('LOGLEVEL', LOGLEVEL_INFO);
define('LOGAUTHFAIL', false);
// To save e.g. WBXML data only for selected users, add the usernames to the array
// The data will be saved into a dedicated file per user in the LOGFILEDIR
// Users have to be encapusulated in quotes, several users are comma separated, like:
// $specialLogUsers = array('info@domain.com', 'myusername');
define('LOGUSERLEVEL', LOGLEVEL_DEVICEID);
$specialLogUsers = array();
// Location of the trusted CA, e.g. '/etc/ssl/certs/EmailCA.pem'
// Uncomment and modify the following line if the validation of the certificates fails.
// define('CAINFO', '/etc/ssl/certs/EmailCA.pem');
/**********************************************************************************
* Mobile settings
*/
// Device Provisioning
define('PROVISIONING', true);
// This option allows the 'loose enforcement' of the provisioning policies for older
// devices which don't support provisioning (like WM 5 and HTC Android Mail) - dw2412 contribution
// false (default) - Enforce provisioning for all devices
// true - allow older devices, but enforce policies on devices which support it
define('LOOSE_PROVISIONING', false);
// Default conflict preference
// Some devices allow to set if the server or PIM (mobile)
// should win in case of a synchronization conflict
// SYNC_CONFLICT_OVERWRITE_SERVER - Server is overwritten, PIM wins
// SYNC_CONFLICT_OVERWRITE_PIM - PIM is overwritten, Server wins (default)
define('SYNC_CONFLICT_DEFAULT', SYNC_CONFLICT_OVERWRITE_PIM);
// Global limitation of items to be synchronized
// The mobile can define a sync back period for calendar and email items
// For large stores with many items the time period could be limited to a max value
// If the mobile transmits a wider time period, the defined max value is used
// Applicable values:
// SYNC_FILTERTYPE_ALL (default, no limitation)
// SYNC_FILTERTYPE_1DAY, SYNC_FILTERTYPE_3DAYS, SYNC_FILTERTYPE_1WEEK, SYNC_FILTERTYPE_2WEEKS,
// SYNC_FILTERTYPE_1MONTH, SYNC_FILTERTYPE_3MONTHS, SYNC_FILTERTYPE_6MONTHS
define('SYNC_FILTERTIME_MAX', SYNC_FILTERTYPE_ALL);
// Interval in seconds before checking if there are changes on the server when in Ping.
// It means the highest time span before a change is pushed to a mobile. Set it to
// a higher value if you have a high load on the server.
define('PING_INTERVAL', 30);
// Interval in seconds to force a re-check of potentially missed notifications when
// using a changes sink. Default are 300 seconds (every 5 min).
// This can also be disabled by setting it to false
define('SINK_FORCERECHECK', 300);
// Set the fileas (save as) order for contacts in the webaccess/webapp/outlook.
// It will only affect new/modified contacts on the mobile which then are synced to the server.
// Possible values are:
// SYNC_FILEAS_FIRSTLAST - fileas will be "Firstname Middlename Lastname"
// SYNC_FILEAS_LASTFIRST - fileas will be "Lastname, Firstname Middlename"
// SYNC_FILEAS_COMPANYONLY - fileas will be "Company"
// SYNC_FILEAS_COMPANYLAST - fileas will be "Company (Lastname, Firstname Middlename)"
// SYNC_FILEAS_COMPANYFIRST - fileas will be "Company (Firstname Middlename Lastname)"
// SYNC_FILEAS_LASTCOMPANY - fileas will be "Lastname, Firstname Middlename (Company)"
// SYNC_FILEAS_FIRSTCOMPANY - fileas will be "Firstname Middlename Lastname (Company)"
// The company-fileas will only be set if a contact has a company set. If one of
// company-fileas is selected and a contact doesn't have a company set, it will default
// to SYNC_FILEAS_FIRSTLAST or SYNC_FILEAS_LASTFIRST (depending on if last or first
// option is selected for company).
// If SYNC_FILEAS_COMPANYONLY is selected and company of the contact is not set
// SYNC_FILEAS_LASTFIRST will be used
define('FILEAS_ORDER', SYNC_FILEAS_LASTFIRST);
// Amount of items to be synchronized per request
// Normally this value is requested by the mobile. Common values are 5, 25, 50 or 100.
// Exporting too much items can cause mobile timeout on busy systems.
// Z-Push will use the lowest value, either set here or by the mobile.
// default: 100 - value used if mobile does not limit amount of items
define('SYNC_MAX_ITEMS', 100);
// The devices usually send a list of supported properties for calendar and contact
// items. If a device does not includes such a supported property in Sync request,
// it means the property's value will be deleted on the server.
// However some devices do not send a list of supported properties. It is then impossible
// to tell if a property was deleted or it was not set at all if it does not appear in Sync.
// This parameter defines Z-Push behaviour during Sync if a device does not issue a list with
// supported properties.
// See also https://jira.zarafa.com/browse/ZP-302.
// Possible values:
// false - do not unset properties which are not sent during Sync (default)
// true - unset properties which are not sent during Sync
define('UNSET_UNDEFINED_PROPERTIES', false);
// ActiveSync specifies that a contact photo may not exceed 48 KB. This value is checked
// in the semantic sanity checks and contacts with larger photos are not synchronized.
// This limitation is not being followed by the ActiveSync clients which set much bigger
// contact photos. You can override the default value of the max photo size.
// default: 49152 - 48 KB default max photo size in bytes
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
// Over the WebserviceUsers command it is possible to retrieve a list of all
// known devices and users on this Z-Push system. The authenticated user needs to have
// admin rights and a public folder must exist.
// In multicompany environments this enable an admin user of any company to retrieve
// this full list, so this feature is disabled by default. Enable with care.
define('ALLOW_WEBSERVICE_USERS_ACCESS', false);
/**********************************************************************************
* Backend settings
*/
// the backend data provider
define('BACKEND_PROVIDER', '');
/**********************************************************************************
* Search provider settings
*
* Alternative backend to perform SEARCH requests (GAL search)
* By default the main Backend defines the preferred search functionality.
* If set, the Search Provider will always be preferred.
* Use 'BackendSearchLDAP' to search in a LDAP directory (see backend/searchldap/config.php)
*/
define('SEARCH_PROVIDER', '');
// Time in seconds for the server search. Setting it too high might result in timeout.
// Setting it too low might not return all results. Default is 10.
define('SEARCH_WAIT', 10);
// The maximum number of results to send to the client. Setting it too high
// might result in timeout. Default is 10.
define('SEARCH_MAXRESULTS', 10);
/**********************************************************************************
* Synchronize additional folders to all mobiles
*
* With this feature, special folders can be synchronized to all mobiles.
* This is useful for e.g. global company contacts.
*
* This feature is supported only by certain devices, like iPhones.
* Check the compatibility list for supported devices:
* http://z-push.sf.net/compatibility
*
* To synchronize a folder, add a section setting all parameters as below:
* store: the ressource where the folder is located.
* Zarafa users use 'SYSTEM' for the 'Public Folder'
* folderid: folder id of the folder to be synchronized
* name: name to be displayed on the mobile device
* type: supported types are:
* SYNC_FOLDER_TYPE_USER_CONTACT
* SYNC_FOLDER_TYPE_USER_APPOINTMENT
* SYNC_FOLDER_TYPE_USER_TASK
* SYNC_FOLDER_TYPE_USER_MAIL
*
* Additional notes:
* - on Zarafa systems use backend/zarafa/listfolders.php script to get a list
* of available folders
*
* - all Z-Push users must have full writing permissions (secretary rights) so
* the configured folders can be synchronized to the mobile
*
* - this feature is only partly suitable for multi-tenancy environments,
* as ALL users from ALL tenents need access to the configured store & folder.
* When configuring a public folder, this will cause problems, as each user has
* a different public folder in his tenant, so the folder are not available.
* - changing this configuration could cause HIGH LOAD on the system, as all
* connected devices will be updated and load the data contained in the
* added/modified folders.
*/
$additionalFolders = array(
// demo entry for the synchronization of contacts from the public folder.
// uncomment (remove '/*' '*/') and fill in the folderid
/*
array(
'store' => "SYSTEM",
'folderid' => "",
'name' => "Public Contacts",
'type' => SYNC_FOLDER_TYPE_USER_CONTACT,
),
*/
);
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,144 @@
<?php
/***********************************************
* File : stringstreamwrapper.php
* Project : Z-Push
* Descr : Wraps a string as a standard php stream
* The used method names are predefined and can not be altered.
*
* Created : 24.11.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StringStreamWrapper {
const PROTOCOL = "stringstream";
private $stringstream;
private $position;
private $stringlength;
/**
* Opens the stream
* The string to be streamed is passed over the context
*
* @param string $path Specifies the URL that was passed to the original function
* @param string $mode The mode used to open the file, as detailed for fopen()
* @param int $options Holds additional flags set by the streams API
* @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
* opened_path should be set to the full path of the file/resource that was actually opened.
*
* @access public
* @return boolean
*/
public function stream_open($path, $mode, $options, &$opened_path) {
$contextOptions = stream_context_get_options($this->context);
if (!isset($contextOptions[self::PROTOCOL]['string']))
return false;
$this->position = 0;
// this is our stream!
$this->stringstream = $contextOptions[self::PROTOCOL]['string'];
$this->stringlength = strlen($this->stringstream);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StringStreamWrapper::stream_open(): initialized stream length: %d", $this->stringlength));
return true;
}
/**
* Reads from stream
*
* @param int $len amount of bytes to be read
*
* @access public
* @return string
*/
public function stream_read($len) {
$data = substr($this->stringstream, $this->position, $len);
$this->position += strlen($data);
return $data;
}
/**
* Returns the current position on stream
*
* @access public
* @return int
*/
public function stream_tell() {
return $this->position;
}
/**
* Indicates if 'end of file' is reached
*
* @access public
* @return boolean
*/
public function stream_eof() {
return ($this->position >= $this->stringlength);
}
/**
* Retrieves information about a stream
*
* @access public
* @return array
*/
public function stream_stat() {
return array(
7 => $this->stringlength,
'size' => $this->stringlength,
);
}
/**
* Instantiates a StringStreamWrapper
*
* @param string $string The string to be wrapped
*
* @access public
* @return StringStreamWrapper
*/
static public function Open($string) {
$context = stream_context_create(array(self::PROTOCOL => array('string' => &$string)));
return fopen(self::PROTOCOL . "://",'r', false, $context);
}
}
stream_wrapper_register(StringStreamWrapper::PROTOCOL, "StringStreamWrapper")
?>

View file

@ -0,0 +1,937 @@
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2001-2002, Richard Heyes |
// | All rights reserved. |
// | |
// | Redistribution and use in source and binary forms, with or without |
// | modification, are permitted provided that the following conditions |
// | are met: |
// | |
// | o Redistributions of source code must retain the above copyright |
// | notice, this list of conditions and the following disclaimer. |
// | o Redistributions in binary form must reproduce the above copyright |
// | notice, this list of conditions and the following disclaimer in the |
// | documentation and/or other materials provided with the distribution.|
// | o The names of the authors may not be used to endorse or promote |
// | products derived from this software without specific prior written |
// | permission. |
// | |
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
// | |
// +-----------------------------------------------------------------------+
// | Authors: Richard Heyes <richard@phpguru.org> |
// | Chuck Hagenbuch <chuck@horde.org> |
// +-----------------------------------------------------------------------+
/**
* RFC 822 Email address list validation Utility
*
* What is it?
*
* This class will take an address string, and parse it into it's consituent
* parts, be that either addresses, groups, or combinations. Nested groups
* are not supported. The structure it returns is pretty straight forward,
* and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
* print_r() to view the structure.
*
* How do I use it?
*
* $address_string = 'My Group: "Richard" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
* $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
* print_r($structure);
*
* @author Richard Heyes <richard@phpguru.org>
* @author Chuck Hagenbuch <chuck@horde.org>
* @version $Revision: 1.23 $
* @license BSD
* @package Mail
*/
class Mail_RFC822 {
/**
* The address being parsed by the RFC822 object.
* @var string $address
*/
var $address = '';
/**
* The default domain to use for unqualified addresses.
* @var string $default_domain
*/
var $default_domain = 'localhost';
/**
* Should we return a nested array showing groups, or flatten everything?
* @var boolean $nestGroups
*/
var $nestGroups = true;
/**
* Whether or not to validate atoms for non-ascii characters.
* @var boolean $validate
*/
var $validate = true;
/**
* The array of raw addresses built up as we parse.
* @var array $addresses
*/
var $addresses = array();
/**
* The final array of parsed address information that we build up.
* @var array $structure
*/
var $structure = array();
/**
* The current error message, if any.
* @var string $error
*/
var $error = null;
/**
* An internal counter/pointer.
* @var integer $index
*/
var $index = null;
/**
* The number of groups that have been found in the address list.
* @var integer $num_groups
* @access public
*/
var $num_groups = 0;
/**
* A variable so that we can tell whether or not we're inside a
* Mail_RFC822 object.
* @var boolean $mailRFC822
*/
var $mailRFC822 = true;
/**
* A limit after which processing stops
* @var int $limit
*/
var $limit = null;
/**
* Sets up the object. The address must either be set here or when
* calling parseAddressList(). One or the other.
*
* @access public
* @param string $address The address(es) to validate.
* @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
*
* @return object Mail_RFC822 A new Mail_RFC822 object.
*/
function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
{
if (isset($address)) $this->address = $address;
if (isset($default_domain)) $this->default_domain = $default_domain;
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
if (isset($validate)) $this->validate = $validate;
if (isset($limit)) $this->limit = $limit;
}
/**
* Starts the whole process. The address must either be set here
* or when creating the object. One or the other.
*
* @access public
* @param string $address The address(es) to validate.
* @param string $default_domain Default domain/host etc.
* @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
* @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
*
* @return array A structured array of addresses.
*/
function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
{
if (!isset($this) || !isset($this->mailRFC822)) {
$obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
return $obj->parseAddressList();
}
if (isset($address)) $this->address = $address;
if (strlen(trim($this->address)) == 0) return array();
if (isset($default_domain)) $this->default_domain = $default_domain;
if (isset($nest_groups)) $this->nestGroups = $nest_groups;
if (isset($validate)) $this->validate = $validate;
if (isset($limit)) $this->limit = $limit;
$this->structure = array();
$this->addresses = array();
$this->error = null;
$this->index = null;
// Unfold any long lines in $this->address.
$this->address = preg_replace('/\r?\n/', "\r\n", $this->address);
$this->address = preg_replace('/\r\n(\t| )+/', ' ', $this->address);
while ($this->address = $this->_splitAddresses($this->address));
if ($this->address === false || isset($this->error)) {
//require_once 'PEAR.php';
return $this->raiseError($this->error);
}
// Validate each address individually. If we encounter an invalid
// address, stop iterating and return an error immediately.
foreach ($this->addresses as $address) {
$valid = $this->_validateAddress($address);
if ($valid === false || isset($this->error)) {
//require_once 'PEAR.php';
return $this->raiseError($this->error);
}
if (!$this->nestGroups) {
$this->structure = array_merge($this->structure, $valid);
} else {
$this->structure[] = $valid;
}
}
return $this->structure;
}
/**
* Splits an address into separate addresses.
*
* @access private
* @param string $address The addresses to split.
* @return boolean Success or failure.
*/
function _splitAddresses($address)
{
if (!empty($this->limit) && count($this->addresses) == $this->limit) {
return '';
}
if ($this->_isGroup($address) && !isset($this->error)) {
$split_char = ';';
$is_group = true;
} elseif (!isset($this->error)) {
$split_char = ',';
$is_group = false;
} elseif (isset($this->error)) {
return false;
}
// Split the string based on the above ten or so lines.
$parts = explode($split_char, $address);
$string = $this->_splitCheck($parts, $split_char);
// If a group...
if ($is_group) {
// If $string does not contain a colon outside of
// brackets/quotes etc then something's fubar.
// First check there's a colon at all:
if (strpos($string, ':') === false) {
$this->error = 'Invalid address: ' . $string;
return false;
}
// Now check it's outside of brackets/quotes:
if (!$this->_splitCheck(explode(':', $string), ':')) {
return false;
}
// We must have a group at this point, so increase the counter:
$this->num_groups++;
}
// $string now contains the first full address/group.
// Add to the addresses array.
$this->addresses[] = array(
'address' => trim($string),
'group' => $is_group
);
// Remove the now stored address from the initial line, the +1
// is to account for the explode character.
$address = trim(substr($address, strlen($string) + 1));
// If the next char is a comma and this was a group, then
// there are more addresses, otherwise, if there are any more
// chars, then there is another address.
if ($is_group && substr($address, 0, 1) == ','){
$address = trim(substr($address, 1));
return $address;
} elseif (strlen($address) > 0) {
return $address;
} else {
return '';
}
// If you got here then something's off
return false;
}
/**
* Checks for a group at the start of the string.
*
* @access private
* @param string $address The address to check.
* @return boolean Whether or not there is a group at the start of the string.
*/
function _isGroup($address)
{
// First comma not in quotes, angles or escaped:
$parts = explode(',', $address);
$string = $this->_splitCheck($parts, ',');
// Now we have the first address, we can reliably check for a
// group by searching for a colon that's not escaped or in
// quotes or angle brackets.
if (count($parts = explode(':', $string)) > 1) {
$string2 = $this->_splitCheck($parts, ':');
return ($string2 !== $string);
} else {
return false;
}
}
/**
* A common function that will check an exploded string.
*
* @access private
* @param array $parts The exloded string.
* @param string $char The char that was exploded on.
* @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
*/
function _splitCheck($parts, $char)
{
$string = $parts[0];
for ($i = 0; $i < count($parts); $i++) {
if ($this->_hasUnclosedQuotes($string)
|| $this->_hasUnclosedBrackets($string, '<>')
|| $this->_hasUnclosedBrackets($string, '[]')
|| $this->_hasUnclosedBrackets($string, '()')
|| substr($string, -1) == '\\') {
if (isset($parts[$i + 1])) {
$string = $string . $char . $parts[$i + 1];
} else {
$this->error = 'Invalid address spec. Unclosed bracket or quotes';
return false;
}
} else {
$this->index = $i;
break;
}
}
return $string;
}
/**
* Checks if a string has an unclosed quotes or not.
*
* @access private
* @param string $string The string to check.
* @return boolean True if there are unclosed quotes inside the string, false otherwise.
*/
function _hasUnclosedQuotes($string)
{
$string = explode('"', $string);
$string_cnt = count($string);
for ($i = 0; $i < (count($string) - 1); $i++)
if (substr($string[$i], -1) == '\\')
$string_cnt--;
return ($string_cnt % 2 === 0);
}
/**
* Checks if a string has an unclosed brackets or not. IMPORTANT:
* This function handles both angle brackets and square brackets;
*
* @access private
* @param string $string The string to check.
* @param string $chars The characters to check for.
* @return boolean True if there are unclosed brackets inside the string, false otherwise.
*/
function _hasUnclosedBrackets($string, $chars)
{
$num_angle_start = substr_count($string, $chars[0]);
$num_angle_end = substr_count($string, $chars[1]);
$this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
$this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
if ($num_angle_start < $num_angle_end) {
$this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
return false;
} else {
return ($num_angle_start > $num_angle_end);
}
}
/**
* Sub function that is used only by hasUnclosedBrackets().
*
* @access private
* @param string $string The string to check.
* @param integer &$num The number of occurences.
* @param string $char The character to count.
* @return integer The number of occurences of $char in $string, adjusted for backslashes.
*/
function _hasUnclosedBracketsSub($string, &$num, $char)
{
$parts = explode($char, $string);
for ($i = 0; $i < count($parts); $i++){
if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
$num--;
if (isset($parts[$i + 1]))
$parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
}
return $num;
}
/**
* Function to begin checking the address.
*
* @access private
* @param string $address The address to validate.
* @return mixed False on failure, or a structured array of address information on success.
*/
function _validateAddress($address)
{
$is_group = false;
$addresses = array();
if ($address['group']) {
$is_group = true;
// Get the group part of the name
$parts = explode(':', $address['address']);
$groupname = $this->_splitCheck($parts, ':');
$structure = array();
// And validate the group part of the name.
if (!$this->_validatePhrase($groupname)){
$this->error = 'Group name did not validate.';
return false;
} else {
// Don't include groups if we are not nesting
// them. This avoids returning invalid addresses.
if ($this->nestGroups) {
$structure = new stdClass;
$structure->groupname = $groupname;
}
}
$address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
}
// If a group then split on comma and put into an array.
// Otherwise, Just put the whole address in an array.
if ($is_group) {
while (strlen($address['address']) > 0) {
$parts = explode(',', $address['address']);
$addresses[] = $this->_splitCheck($parts, ',');
$address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
}
} else {
$addresses[] = $address['address'];
}
// Check that $addresses is set, if address like this:
// Groupname:;
// Then errors were appearing.
if (!count($addresses)){
$this->error = 'Empty group.';
return false;
}
// Trim the whitespace from all of the address strings.
array_map('trim', $addresses);
// Validate each mailbox.
// Format could be one of: name <geezer@domain.com>
// geezer@domain.com
// geezer
// ... or any other format valid by RFC 822.
for ($i = 0; $i < count($addresses); $i++) {
if (!$this->validateMailbox($addresses[$i])) {
if (empty($this->error)) {
$this->error = 'Validation failed for: ' . $addresses[$i];
}
return false;
}
}
// Nested format
if ($this->nestGroups) {
if ($is_group) {
$structure->addresses = $addresses;
} else {
$structure = $addresses[0];
}
// Flat format
} else {
if ($is_group) {
$structure = array_merge($structure, $addresses);
} else {
$structure = $addresses;
}
}
return $structure;
}
/**
* Function to validate a phrase.
*
* @access private
* @param string $phrase The phrase to check.
* @return boolean Success or failure.
*/
function _validatePhrase($phrase)
{
// Splits on one or more Tab or space.
$parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
$phrase_parts = array();
while (count($parts) > 0){
$phrase_parts[] = $this->_splitCheck($parts, ' ');
for ($i = 0; $i < $this->index + 1; $i++)
array_shift($parts);
}
foreach ($phrase_parts as $part) {
// If quoted string:
if (substr($part, 0, 1) == '"') {
if (!$this->_validateQuotedString($part)) {
return false;
}
continue;
}
// Otherwise it's an atom:
if (!$this->_validateAtom($part)) return false;
}
return true;
}
/**
* Function to validate an atom which from rfc822 is:
* atom = 1*<any CHAR except specials, SPACE and CTLs>
*
* If validation ($this->validate) has been turned off, then
* validateAtom() doesn't actually check anything. This is so that you
* can split a list of addresses up before encoding personal names
* (umlauts, etc.), for example.
*
* @access private
* @param string $atom The string to check.
* @return boolean Success or failure.
*/
function _validateAtom($atom)
{
if (!$this->validate) {
// Validation has been turned off; assume the atom is okay.
return true;
}
// Check for any char from ASCII 0 - ASCII 127
if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
return false;
}
// Check for specials:
if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
return false;
}
// Check for control characters (ASCII 0-31):
if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
return false;
}
return true;
}
/**
* Function to validate quoted string, which is:
* quoted-string = <"> *(qtext/quoted-pair) <">
*
* @access private
* @param string $qstring The string to check
* @return boolean Success or failure.
*/
function _validateQuotedString($qstring)
{
// Leading and trailing "
$qstring = substr($qstring, 1, -1);
// Perform check, removing quoted characters first.
return !preg_match('/[\x0D\\\\"]/', preg_replace('/\\\\./', '', $qstring));
}
/**
* Function to validate a mailbox, which is:
* mailbox = addr-spec ; simple address
* / phrase route-addr ; name and route-addr
*
* @access public
* @param string &$mailbox The string to check.
* @return boolean Success or failure.
*/
function validateMailbox(&$mailbox)
{
// A couple of defaults.
$phrase = '';
$comment = '';
$comments = array();
// Catch any RFC822 comments and store them separately.
$_mailbox = $mailbox;
while (strlen(trim($_mailbox)) > 0) {
$parts = explode('(', $_mailbox);
$before_comment = $this->_splitCheck($parts, '(');
if ($before_comment != $_mailbox) {
// First char should be a (.
$comment = substr(str_replace($before_comment, '', $_mailbox), 1);
$parts = explode(')', $comment);
$comment = $this->_splitCheck($parts, ')');
$comments[] = $comment;
// +1 is for the trailing )
$_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
} else {
break;
}
}
foreach ($comments as $comment) {
$mailbox = str_replace("($comment)", '', $mailbox);
}
$mailbox = trim($mailbox);
// Check for name + route-addr
if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
$parts = explode('<', $mailbox);
$name = $this->_splitCheck($parts, '<');
$phrase = trim($name);
$route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
//z-push fix for umlauts and other special chars
if (substr($phrase, 0, 1) != '"' && substr($phrase, -1) != '"') {
$phrase = '"'.$phrase.'"';
}
if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false) {
return false;
}
// Only got addr-spec
} else {
// First snip angle brackets if present.
if (substr($mailbox, 0, 1) == '<' && substr($mailbox, -1) == '>') {
$addr_spec = substr($mailbox, 1, -1);
} else {
$addr_spec = $mailbox;
}
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
}
// Construct the object that will be returned.
$mbox = new stdClass();
// Add the phrase (even if empty) and comments
$mbox->personal = $phrase;
$mbox->comment = isset($comments) ? $comments : array();
if (isset($route_addr)) {
$mbox->mailbox = $route_addr['local_part'];
$mbox->host = $route_addr['domain'];
$route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
} else {
$mbox->mailbox = $addr_spec['local_part'];
$mbox->host = $addr_spec['domain'];
}
$mailbox = $mbox;
return true;
}
/**
* This function validates a route-addr which is:
* route-addr = "<" [route] addr-spec ">"
*
* Angle brackets have already been removed at the point of
* getting to this function.
*
* @access private
* @param string $route_addr The string to check.
* @return mixed False on failure, or an array containing validated address/route information on success.
*/
function _validateRouteAddr($route_addr)
{
// Check for colon.
if (strpos($route_addr, ':') !== false) {
$parts = explode(':', $route_addr);
$route = $this->_splitCheck($parts, ':');
} else {
$route = $route_addr;
}
// If $route is same as $route_addr then the colon was in
// quotes or brackets or, of course, non existent.
if ($route === $route_addr){
unset($route);
$addr_spec = $route_addr;
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
} else {
// Validate route part.
if (($route = $this->_validateRoute($route)) === false) {
return false;
}
$addr_spec = substr($route_addr, strlen($route . ':'));
// Validate addr-spec part.
if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
return false;
}
}
if (isset($route)) {
$return['adl'] = $route;
} else {
$return['adl'] = '';
}
$return = array_merge($return, $addr_spec);
return $return;
}
/**
* Function to validate a route, which is:
* route = 1#("@" domain) ":"
*
* @access private
* @param string $route The string to check.
* @return mixed False on failure, or the validated $route on success.
*/
function _validateRoute($route)
{
// Split on comma.
$domains = explode(',', trim($route));
foreach ($domains as $domain) {
$domain = str_replace('@', '', trim($domain));
if (!$this->_validateDomain($domain)) return false;
}
return $route;
}
/**
* Function to validate a domain, though this is not quite what
* you expect of a strict internet domain.
*
* domain = sub-domain *("." sub-domain)
*
* @access private
* @param string $domain The string to check.
* @return mixed False on failure, or the validated domain on success.
*/
function _validateDomain($domain)
{
// Note the different use of $subdomains and $sub_domains
$subdomains = explode('.', $domain);
while (count($subdomains) > 0) {
$sub_domains[] = $this->_splitCheck($subdomains, '.');
for ($i = 0; $i < $this->index + 1; $i++)
array_shift($subdomains);
}
foreach ($sub_domains as $sub_domain) {
if (!$this->_validateSubdomain(trim($sub_domain)))
return false;
}
// Managed to get here, so return input.
return $domain;
}
/**
* Function to validate a subdomain:
* subdomain = domain-ref / domain-literal
*
* @access private
* @param string $subdomain The string to check.
* @return boolean Success or failure.
*/
function _validateSubdomain($subdomain)
{
if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
if (!$this->_validateDliteral($arr[1])) return false;
} else {
if (!$this->_validateAtom($subdomain)) return false;
}
// Got here, so return successful.
return true;
}
/**
* Function to validate a domain literal:
* domain-literal = "[" *(dtext / quoted-pair) "]"
*
* @access private
* @param string $dliteral The string to check.
* @return boolean Success or failure.
*/
function _validateDliteral($dliteral)
{
return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
}
/**
* Function to validate an addr-spec.
*
* addr-spec = local-part "@" domain
*
* @access private
* @param string $addr_spec The string to check.
* @return mixed False on failure, or the validated addr-spec on success.
*/
function _validateAddrSpec($addr_spec)
{
$addr_spec = trim($addr_spec);
// Split on @ sign if there is one.
if (strpos($addr_spec, '@') !== false) {
$parts = explode('@', $addr_spec);
$local_part = $this->_splitCheck($parts, '@');
$domain = substr($addr_spec, strlen($local_part . '@'));
// No @ sign so assume the default domain.
} else {
$local_part = $addr_spec;
$domain = $this->default_domain;
}
if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
if (($domain = $this->_validateDomain($domain)) === false) return false;
// Got here so return successful.
return array('local_part' => $local_part, 'domain' => $domain);
}
/**
* Function to validate the local part of an address:
* local-part = word *("." word)
*
* @access private
* @param string $local_part
* @return mixed False on failure, or the validated local part on success.
*/
function _validateLocalPart($local_part)
{
$parts = explode('.', $local_part);
$words = array();
// Split the local_part into words.
while (count($parts) > 0){
$words[] = $this->_splitCheck($parts, '.');
for ($i = 0; $i < $this->index + 1; $i++) {
array_shift($parts);
}
}
// Validate each word.
foreach ($words as $word) {
// If this word contains an unquoted space, it is invalid. (6.2.4)
if (strpos($word, ' ') && $word[0] !== '"')
{
return false;
}
if ($this->_validatePhrase(trim($word)) === false) return false;
}
// Managed to get here, so return the input.
return $local_part;
}
/**
* Returns an approximate count of how many addresses are in the
* given string. This is APPROXIMATE as it only splits based on a
* comma which has no preceding backslash. Could be useful as
* large amounts of addresses will end up producing *large*
* structures when used with parseAddressList().
*
* @param string $data Addresses to count
* @return int Approximate count
*/
function approximateCount($data)
{
return count(preg_split('/(?<!\\\\),/', $data));
}
/**
* This is a email validating function separate to the rest of the
* class. It simply validates whether an email is of the common
* internet form: <user>@<domain>. This can be sufficient for most
* people. Optional stricter mode can be utilised which restricts
* mailbox characters allowed to alphanumeric, full stop, hyphen
* and underscore.
*
* @param string $data Address to check
* @param boolean $strict Optional stricter mode
* @return mixed False if it fails, an indexed array
* username/domain if it matches
*/
function isValidInetAddress($data, $strict = false)
{
$regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i';
if (preg_match($regex, trim($data), $matches)) {
return array($matches[1], $matches[2]);
} else {
return false;
}
}
/**
* Z-Push helper for error logging
* removing PEAR dependency
*
* @param string debug message
* @return boolean always false as there was an error
* @access private
*/
function raiseError($message) {
ZLog::Write(LOGLEVEL_ERROR, "z_RFC822 error: ". $message);
return false;
}
}

295
sources/index.php Normal file
View file

@ -0,0 +1,295 @@
<?php
/***********************************************
* File : index.php
* Project : Z-Push
* Descr : This is the entry point
* through which all requests
* are processed.
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
ob_start(null, 1048576);
// ignore user abortions because this can lead to weird errors - see ZP-239
ignore_user_abort(true);
include_once('lib/exceptions/exceptions.php');
include_once('lib/utils/utils.php');
include_once('lib/utils/compat.php');
include_once('lib/utils/timezoneutil.php');
include_once('lib/core/zpushdefs.php');
include_once('lib/core/stateobject.php');
include_once('lib/core/interprocessdata.php');
include_once('lib/core/pingtracking.php');
include_once('lib/core/topcollector.php');
include_once('lib/core/loopdetection.php');
include_once('lib/core/asdevice.php');
include_once('lib/core/statemanager.php');
include_once('lib/core/devicemanager.php');
include_once('lib/core/zpush.php');
include_once('lib/core/zlog.php');
include_once('lib/core/paddingfilter.php');
include_once('lib/interface/ibackend.php');
include_once('lib/interface/ichanges.php');
include_once('lib/interface/iexportchanges.php');
include_once('lib/interface/iimportchanges.php');
include_once('lib/interface/isearchprovider.php');
include_once('lib/interface/istatemachine.php');
include_once('lib/core/streamer.php');
include_once('lib/core/streamimporter.php');
include_once('lib/core/synccollections.php');
include_once('lib/core/hierarchycache.php');
include_once('lib/core/changesmemorywrapper.php');
include_once('lib/core/syncparameters.php');
include_once('lib/core/bodypreference.php');
include_once('lib/core/contentparameters.php');
include_once('lib/wbxml/wbxmldefs.php');
include_once('lib/wbxml/wbxmldecoder.php');
include_once('lib/wbxml/wbxmlencoder.php');
include_once('lib/syncobjects/syncobject.php');
include_once('lib/syncobjects/syncbasebody.php');
include_once('lib/syncobjects/syncbaseattachment.php');
include_once('lib/syncobjects/syncmailflags.php');
include_once('lib/syncobjects/syncrecurrence.php');
include_once('lib/syncobjects/syncappointment.php');
include_once('lib/syncobjects/syncappointmentexception.php');
include_once('lib/syncobjects/syncattachment.php');
include_once('lib/syncobjects/syncattendee.php');
include_once('lib/syncobjects/syncmeetingrequestrecurrence.php');
include_once('lib/syncobjects/syncmeetingrequest.php');
include_once('lib/syncobjects/syncmail.php');
include_once('lib/syncobjects/syncnote.php');
include_once('lib/syncobjects/synccontact.php');
include_once('lib/syncobjects/syncfolder.php');
include_once('lib/syncobjects/syncprovisioning.php');
include_once('lib/syncobjects/synctaskrecurrence.php');
include_once('lib/syncobjects/synctask.php');
include_once('lib/syncobjects/syncoofmessage.php');
include_once('lib/syncobjects/syncoof.php');
include_once('lib/syncobjects/syncuserinformation.php');
include_once('lib/syncobjects/syncdeviceinformation.php');
include_once('lib/syncobjects/syncdevicepassword.php');
include_once('lib/syncobjects/syncitemoperationsattachment.php');
include_once('lib/syncobjects/syncsendmail.php');
include_once('lib/syncobjects/syncsendmailsource.php');
include_once('lib/syncobjects/syncvalidatecert.php');
include_once('lib/syncobjects/syncresolverecipients.php');
include_once('lib/syncobjects/syncresolverecipient.php');
include_once('lib/syncobjects/syncresolverecipientsoptions.php');
include_once('lib/syncobjects/syncresolverecipientsavailability.php');
include_once('lib/syncobjects/syncresolverecipientscertificates.php');
include_once('lib/syncobjects/syncresolverecipientspicture.php');
include_once('lib/default/backend.php');
include_once('lib/default/searchprovider.php');
include_once('lib/request/request.php');
include_once('lib/request/requestprocessor.php');
include_once('config.php');
include_once('version.php');
// Attempt to set maximum execution time
ini_set('max_execution_time', SCRIPT_TIMEOUT);
set_time_limit(SCRIPT_TIMEOUT);
try {
// check config & initialize the basics
ZPush::CheckConfig();
Request::Initialize();
ZLog::Initialize();
ZLog::Write(LOGLEVEL_DEBUG,"-------- Start");
ZLog::Write(LOGLEVEL_INFO,
sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'",
@constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(),
Request::GetCommand(), Request::GetGETUser(), Request::GetDeviceID(), Request::GetDeviceType()));
// Stop here if this is an OPTIONS request
if (Request::IsMethodOPTIONS())
throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST);
ZPush::CheckAdvancedConfig();
// Process request headers and look for AS headers
Request::ProcessHeaders();
// Check required GET parameters
if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType()))
throw new FatalException("Requested the Z-Push URL without the required GET parameters");
// Load the backend
$backend = ZPush::GetBackend();
// always request the authorization header
if (! Request::AuthenticationInfo() || !Request::GetGETUser())
throw new AuthenticationRequiredException("Access denied. Please send authorisation information");
// check the provisioning information
if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) &&
((Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) || ZPush::GetDeviceManager()->ProvisioningRequired(Request::GetPolicyKey())) &&
(LOOSE_PROVISIONING === false ||
(LOOSE_PROVISIONING === true && Request::WasPolicyKeySent())))
//TODO for AS 14 send a wbxml response
throw new ProvisioningRequiredException();
// most commands require an authenticated user
if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode()))
RequestProcessor::Authenticate();
// Do the actual processing of the request
if (Request::IsMethodGET())
throw new NoPostRequestException("This is the Z-Push location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST);
// Do the actual request
header(ZPush::GetServerHeader());
// announce the supported AS versions (if not already sent to device)
if (ZPush::GetDeviceManager()->AnnounceASVersion()) {
$versions = ZPush::GetSupportedProtocolVersions(true);
ZLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions));
header("X-MS-RP: ". $versions);
}
RequestProcessor::Initialize();
if(!RequestProcessor::HandleRequest())
throw new WBXMLException(ZLog::GetWBXMLDebugInfo());
// eventually the RequestProcessor wants to send other headers to the mobile
foreach (RequestProcessor::GetSpecialHeaders() as $header)
header($header);
// stream the data
$len = ob_get_length();
$data = ob_get_contents();
ob_end_clean();
// log amount of data transferred
// TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked
ZPush::GetDeviceManager()->SentData($len);
// Unfortunately, even though Z-Push can stream the data to the client
// with a chunked encoding, using chunked encoding breaks the progress bar
// on the PDA. So the data is de-chunk here, written a content-length header and
// data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start)
// then it will be sent as a chunked packet anyway because PHP will have to flush
// the buffer.
if(!headers_sent())
header("Content-Length: $len");
// send vnd.ms-sync.wbxml content type header if there is no content
// otherwise text/html content type is added which might break some devices
if ($len == 0)
header("Content-Type: application/vnd.ms-sync.wbxml");
print $data;
// destruct backend after all data is on the stream
$backend->Logoff();
}
catch (NoPostRequestException $nopostex) {
if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) {
header(ZPush::GetServerHeader());
header(ZPush::GetSupportedProtocolVersions());
header(ZPush::GetSupportedCommands());
ZLog::Write(LOGLEVEL_INFO, $nopostex->getMessage());
}
else if ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) {
if (Request::GetUserAgent())
ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
if (!headers_sent() && $nopostex->showLegalNotice())
ZPush::PrintZPushLegal('GET not supported', $nopostex->getMessage());
}
}
catch (Exception $ex) {
if (Request::GetUserAgent())
ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent()));
$exclass = get_class($ex);
if(!headers_sent()) {
if ($ex instanceof ZPushException) {
header('HTTP/1.1 '. $ex->getHTTPCodeString());
foreach ($ex->getHTTPHeaders() as $h)
header($h);
}
// something really unexpected happened!
else
header('HTTP/1.1 500 Internal Server Error');
}
else
ZLog::Write(LOGLEVEL_FATAL, "Exception: ($exclass) - headers were already sent. Message: ". $ex->getMessage());
if ($ex instanceof AuthenticationRequiredException) {
ZPush::PrintZPushLegal($exclass, sprintf('<pre>%s</pre>',$ex->getMessage()));
// log the failed login attemt e.g. for fail2ban
if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false)
ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser()? Request::GetAuthUser(): Request::GetGETUser() ));
}
// This could be a WBXML problem.. try to get the complete request
else if ($ex instanceof WBXMLException) {
ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this.");
}
// Try to output some kind of error information. This is only possible if
// the output had not started yet. If it has started already, we can't show the user the error, and
// the device will give its own (useless) error message.
else if (!($ex instanceof ZPushException) || $ex->showLegalNotice()) {
$cmdinfo = (Request::GetCommand())? sprintf(" processing command <i>%s</i>", Request::GetCommand()): "";
$extrace = $ex->getTrace();
$trace = (!empty($extrace))? "\n\nTrace:\n". print_r($extrace,1):"";
ZPush::PrintZPushLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>',$ex->getMessage() . $trace));
}
// Announce exception to process loop detection
if (ZPush::GetDeviceManager(false))
ZPush::GetDeviceManager()->AnnounceProcessException($ex);
// Announce exception if the TopCollector if available
ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true);
}
// save device data if the DeviceManager is available
if (ZPush::GetDeviceManager(false))
ZPush::GetDeviceManager()->Save();
// end gracefully
ZLog::Write(LOGLEVEL_DEBUG, '-------- End');
?>

View file

@ -0,0 +1,690 @@
<?php
/***********************************************
* File : asdevice.php
* Project : Z-Push
* Descr : The ASDevice holds basic data about a device,
* its users and the linked states
*
* Created : 11.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ASDevice extends StateObject {
const UNDEFINED = -1;
// content data
const FOLDERUUID = 1;
const FOLDERTYPE = 2;
const FOLDERSUPPORTEDFIELDS = 3;
const FOLDERSYNCSTATUS = 4;
// expected values for not set member variables
protected $unsetdata = array(
'useragenthistory' => array(),
'hierarchyuuid' => false,
'contentdata' => array(),
'wipestatus' => SYNC_PROVISION_RWSTATUS_NA,
'wiperequestedby' => false,
'wiperequestedon' => false,
'wipeactionon' => false,
'lastupdatetime' => 0,
'conversationmode' => false,
'policies' => array(),
'policykey' => self::UNDEFINED,
'forcesave' => false,
'asversion' => false,
'ignoredmessages' => array(),
'announcedASversion' => false,
);
static private $loadedData;
protected $newdevice;
protected $hierarchyCache;
protected $ignoredMessageIds;
/**
* AS Device constructor
*
* @param string $devid
* @param string $devicetype
* @param string $getuser
* @param string $useragent
*
* @access public
* @return
*/
public function ASDevice($devid, $devicetype, $getuser, $useragent) {
$this->deviceid = $devid;
$this->devicetype = $devicetype;
list ($this->deviceuser, $this->domain) = Utils::SplitDomainUser($getuser);
$this->useragent = $useragent;
$this->firstsynctime = time();
$this->newdevice = true;
$this->ignoredMessageIds = array();
}
/**
* initializes the ASDevice with previousily saved data
*
* @param mixed $stateObject the StateObject containing the device data
* @param boolean $semanticUpdate indicates if data relevant for all users should be cross checked (e.g. wipe requests)
*
* @access public
* @return
*/
public function SetData($stateObject, $semanticUpdate = true) {
if (!($stateObject instanceof StateObject) || !isset($stateObject->devices) || !is_array($stateObject->devices)) return;
// is information about this device & user available?
if (isset($stateObject->devices[$this->deviceuser]) && $stateObject->devices[$this->deviceuser] instanceof ASDevice) {
// overwrite local data with data from the saved object
$this->SetDataArray($stateObject->devices[$this->deviceuser]->GetDataArray());
$this->newdevice = false;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice data loaded for user: '%s'", $this->deviceuser));
}
// check if RWStatus from another user on same device may require action
if ($semanticUpdate && count($stateObject->devices) > 1) {
foreach ($stateObject->devices as $user=>$asuserdata) {
if ($user == $this->user) continue;
// another user has a required action on this device
if (isset($asuserdata->wipeStatus) && $asuserdata->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) {
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)));
// reset status to PENDING if wipe was executed before
$this->wipeStatus = ($asuserdata->wipeStatus & SYNC_PROVISION_RWSTATUS_WIPED)?SYNC_PROVISION_RWSTATUS_PENDING:$asuserdata->wipeStatus;
$this->wipeRequestBy = $asuserdata->wipeRequestBy;
$this->wipeRequestOn = $asuserdata->wipeRequestOn;
$this->wipeActionOn = $asuserdata->wipeActionOn;
break;
}
}
}
self::$loadedData = $stateObject;
$this->changed = false;
}
/**
* Returns the current AS Device in it's StateObject
* If the data was not changed, it returns false (no need to update any data)
*
* @access public
* @return array/boolean
*/
public function GetData() {
if (! $this->changed)
return false;
// device was updated
$this->lastupdatetime = time();
unset($this->ignoredMessageIds);
if (!isset(self::$loadedData) || !isset(self::$loadedData->devices) || !is_array(self::$loadedData->devices)) {
self::$loadedData = new StateObject();
$devices = array();
}
else
$devices = self::$loadedData->devices;
$devices[$this->deviceuser] = $this;
// check if RWStatus has to be updated so it can be updated for other users on same device
if (isset($this->wipeStatus) && $this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) {
foreach ($devices as $user=>$asuserdata) {
if ($user == $this->deviceuser) continue;
if (isset($this->wipeStatus)) $asuserdata->wipeStatus = $this->wipeStatus;
if (isset($this->wipeRequestBy)) $asuserdata->wipeRequestBy = $this->wipeRequestBy;
if (isset($this->wipeRequestOn)) $asuserdata->wipeRequestOn = $this->wipeRequestOn;
if (isset($this->wipeActionOn)) $asuserdata->wipeActionOn = $this->wipeActionOn;
$devices[$user] = $asuserdata;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Updated remote wipe status for user '%s' on the same device", $user));
}
}
self::$loadedData->devices = $devices;
return self::$loadedData;
}
/**
* Removes internal data from the object, so this data can not be exposed
*
* @access public
* @return boolean
*/
public function StripData() {
unset($this->changed);
unset($this->unsetdata);
unset($this->hierarchyCache);
unset($this->forceSave);
unset($this->newdevice);
unset($this->ignoredMessageIds);
if (isset($this->ignoredmessages) && is_array($this->ignoredmessages)) {
$imessages = $this->ignoredmessages;
$unserializedMessage = array();
foreach ($imessages as $im) {
$im->asobject = unserialize($im->asobject);
$im->asobject->StripData();
$unserializedMessage[] = $im;
}
$this->ignoredmessages = $unserializedMessage;
}
return true;
}
/**
* Indicates if the object was just created
*
* @access public
* @return boolean
*/
public function IsNewDevice() {
return (isset($this->newdevice) && $this->newdevice === true);
}
/**----------------------------------------------------------------------------------------------------------
* Non-standard Getter and Setter
*/
/**
* Returns the user agent of this device
*
* @access public
* @return string
*/
public function GetDeviceUserAgent() {
if (!isset($this->useragent) || !$this->useragent)
return "unknown";
return $this->useragent;
}
/**
* Returns the user agent history of this device
*
* @access public
* @return string
*/
public function GetDeviceUserAgentHistory() {
return $this->useragentHistory;
}
/**
* Sets the useragent of the current request
* If this value is alreay available, no update is done
*
* @param string $useragent
*
* @access public
* @return boolean
*/
public function SetUserAgent($useragent) {
if ($useragent == $this->useragent || $useragent === false || $useragent === Request::UNKNOWN)
return true;
// save the old user agent, if available
if ($this->useragent != "") {
// [] = changedate, previous user agent
$a = $this->useragentHistory;
// only add if this agent was not seen before
if (! in_array(array(true, $this->useragent), $a)) {
$a[] = array(time(), $this->useragent);
$this->useragentHistory = $a;
}
}
$this->useragent = $useragent;
return true;
}
/**
* Sets the current remote wipe status
*
* @param int $status
* @param string $requestedBy
* @access public
* @return int
*/
public function SetWipeStatus($status, $requestedBy = false) {
// force saving the updated information if there was a transition between the wiping status
if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK && $status > SYNC_PROVISION_RWSTATUS_OK)
$this->forceSave = true;
if ($requestedBy != false) {
$this->wipeRequestedBy = $requestedBy;
$this->wipeRequestedOn = time();
}
else {
$this->wipeActionOn = time();
}
$this->wipeStatus = $status;
if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_PENDING)
ZLog::Write(LOGLEVEL_INFO, sprintf("ASDevice id '%s' was %s remote wiped on %s. Action requested by user '%s' on %s",
$this->deviceid, ($this->wipeStatus == SYNC_PROVISION_RWSTATUS_REQUESTED ? "requested to be": "sucessfully"),
strftime("%Y-%m-%d %H:%M", $this->wipeActionOn), $this->wipeRequestedBy, strftime("%Y-%m-%d %H:%M", $this->wipeRequestedOn)));
}
/**
* Sets the deployed policy key
*
* @param int $policykey
*
* @access public
* @return
*/
public function SetPolicyKey($policykey) {
$this->policykey = $policykey;
if ($this->GetWipeStatus() == SYNC_PROVISION_RWSTATUS_NA)
$this->wipeStatus = SYNC_PROVISION_RWSTATUS_OK;
}
/**
* Adds a messages which was ignored to the device data
*
* @param StateObject $ignoredMessage
*
* @access public
* @return boolean
*/
public function AddIgnoredMessage($ignoredMessage) {
// we should have all previousily ignored messages in an id array
if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
foreach($this->ignoredMessages as $oldMessage) {
if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
$this->ignoredMessageIds[$oldMessage->folderid] = array();
$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
}
}
// serialize the AS object - if available
if (isset($ignoredMessage->asobject))
$ignoredMessage->asobject = serialize($ignoredMessage->asobject);
// try not to add the same message several times
if (isset($ignoredMessage->folderid) && isset($ignoredMessage->id)) {
if (!isset($this->ignoredMessageIds[$ignoredMessage->folderid]))
$this->ignoredMessageIds[$ignoredMessage->folderid] = array();
if (in_array($ignoredMessage->id, $this->ignoredMessageIds[$ignoredMessage->folderid]))
$this->RemoveIgnoredMessage($ignoredMessage->folderid, $ignoredMessage->id);
$this->ignoredMessageIds[$ignoredMessage->folderid][] = $ignoredMessage->id;
$msges = $this->ignoredMessages;
$msges[] = $ignoredMessage;
$this->ignoredMessages = $msges;
return true;
}
else {
$msges = $this->ignoredMessages;
$msges[] = $ignoredMessage;
$this->ignoredMessages = $msges;
ZLog::Write(LOGLEVEL_WARN, "ASDevice->AddIgnoredMessage(): added message has no folder/id");
return true;
}
}
/**
* Removes message in the list of ignored messages
*
* @param string $folderid parent folder id of the message
* @param string $id message id
*
* @access public
* @return boolean
*/
public function RemoveIgnoredMessage($folderid, $id) {
// we should have all previousily ignored messages in an id array
if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
foreach($this->ignoredMessages as $oldMessage) {
if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
$this->ignoredMessageIds[$oldMessage->folderid] = array();
$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
}
}
$foundMessage = false;
// there are ignored messages in that folder
if (isset($this->ignoredMessageIds[$folderid])) {
// resync of a folder.. we should remove all previousily ignored messages
if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
$ignored = $this->ignoredMessages;
$newMessages = array();
foreach ($ignored as $im) {
if ($im->folderid == $folderid) {
if ($id === false || $im->id === $id) {
$foundMessage = true;
if (count($this->ignoredMessageIds[$folderid]) == 1) {
unset($this->ignoredMessageIds[$folderid]);
}
else {
unset($this->ignoredMessageIds[$folderid][array_search($id, $this->ignoredMessageIds[$folderid])]);
}
continue;
}
else
$newMessages[] = $im;
}
}
$this->ignoredMessages = $newMessages;
}
}
return $foundMessage;
}
/**
* Indicates if a message is in the list of ignored messages
*
* @param string $folderid parent folder id of the message
* @param string $id message id
*
* @access public
* @return boolean
*/
public function HasIgnoredMessage($folderid, $id) {
// we should have all previousily ignored messages in an id array
if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) {
foreach($this->ignoredMessages as $oldMessage) {
if (!isset($this->ignoredMessageIds[$oldMessage->folderid]))
$this->ignoredMessageIds[$oldMessage->folderid] = array();
$this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id;
}
}
$foundMessage = false;
// there are ignored messages in that folder
if (isset($this->ignoredMessageIds[$folderid])) {
// resync of a folder.. we should remove all previousily ignored messages
if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) {
$foundMessage = true;
}
}
return $foundMessage;
}
/**----------------------------------------------------------------------------------------------------------
* HierarchyCache and ContentData operations
*/
/**
* Sets the HierarchyCache
* The hierarchydata, can be:
* - false a new HierarchyCache is initialized
* - array() new HierarchyCache is initialized and data from GetHierarchy is loaded
* - string previousely serialized data is loaded
*
* @param string $hierarchydata (opt)
*
* @access public
* @return boolean
*/
public function SetHierarchyCache($hierarchydata = false) {
if ($hierarchydata !== false && $hierarchydata instanceof ChangesMemoryWrapper) {
$this->hierarchyCache = $hierarchydata;
$this->hierarchyCache->CopyOldState();
}
else
$this->hierarchyCache = new ChangesMemoryWrapper();
if (is_array($hierarchydata))
return $this->hierarchyCache->ImportFolders($hierarchydata);
return true;
}
/**
* Returns serialized data of the HierarchyCache
*
* @access public
* @return string
*/
public function GetHierarchyCacheData() {
if (isset($this->hierarchyCache))
return $this->hierarchyCache;
ZLog::Write(LOGLEVEL_WARN, "ASDevice->GetHierarchyCacheData() has no data! HierarchyCache probably never initialized.");
return false;
}
/**
* Returns the HierarchyCache Object
*
* @access public
* @return object HierarchyCache
*/
public function GetHierarchyCache() {
if (!isset($this->hierarchyCache))
$this->SetHierarchyCache();
ZLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetHierarchyCache(): ". $this->hierarchyCache->GetStat());
return $this->hierarchyCache;
}
/**
* Returns all known folderids
*
* @access public
* @return array
*/
public function GetAllFolderIds() {
if (isset($this->contentData) && is_array($this->contentData))
return array_keys($this->contentData);
return array();
}
/**
* Returns a linked UUID for a folder id
*
* @param string $folderid (opt) if not set, Hierarchy UUID is returned
*
* @access public
* @return string
*/
public function GetFolderUUID($folderid = false) {
if ($folderid === false)
return (isset($this->hierarchyUuid) && $this->hierarchyUuid !== self::UNDEFINED) ? $this->hierarchyUuid : false;
else if (isset($this->contentData) && isset($this->contentData[$folderid]) && isset($this->contentData[$folderid][self::FOLDERUUID]))
return $this->contentData[$folderid][self::FOLDERUUID];
return false;
}
/**
* Link a UUID to a folder id
* If a boolean false UUID is sent, the relation is removed
*
* @param string $uuid
* @param string $folderid (opt) if not set Hierarchy UUID is linked
*
* @access public
* @return boolean
*/
public function SetFolderUUID($uuid, $folderid = false) {
if ($folderid === false) {
$this->hierarchyUuid = $uuid;
// when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages
if ($folderid === false) {
$this->contentData = array();
$this->ignoredMessageIds = array();
$this->ignoredMessages = array();
}
}
else {
$contentData = $this->contentData;
if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
$contentData[$folderid] = array();
// check if the foldertype is set. This has to be available at this point, as generated during the first HierarchySync
if (!isset($contentData[$folderid][self::FOLDERTYPE]))
return false;
if ($uuid)
$contentData[$folderid][self::FOLDERUUID] = $uuid;
else
$contentData[$folderid][self::FOLDERUUID] = false;
$this->contentData = $contentData;
}
}
/**
* Returns a foldertype for a folder already known to the mobile
*
* @param string $folderid
*
* @access public
* @return int/boolean returns false if the type is not set
*/
public function GetFolderType($folderid) {
if (isset($this->contentData) && isset($this->contentData[$folderid]) &&
isset($this->contentData[$folderid][self::FOLDERTYPE]) )
return $this->contentData[$folderid][self::FOLDERTYPE];
return false;
}
/**
* Sets the foldertype of a folder id
*
* @param string $uuid
* @param string $folderid (opt) if not set Hierarchy UUID is linked
*
* @access public
* @return boolean true if the type was set or updated
*/
public function SetFolderType($folderid, $foldertype) {
$contentData = $this->contentData;
if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
$contentData[$folderid] = array();
if (!isset($contentData[$folderid][self::FOLDERTYPE]) || $contentData[$folderid][self::FOLDERTYPE] != $foldertype ) {
$contentData[$folderid][self::FOLDERTYPE] = $foldertype;
$this->contentData = $contentData;
return true;
}
return false;
}
/**
* Gets the supported fields transmitted previousely by the device
* for a certain folder
*
* @param string $folderid
*
* @access public
* @return array/boolean false means no supportedFields are available
*/
public function GetSupportedFields($folderid) {
if (isset($this->contentData) && isset($this->contentData[$folderid]) &&
isset($this->contentData[$folderid][self::FOLDERUUID]) && $this->contentData[$folderid][self::FOLDERUUID] !== false &&
isset($this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS]) )
return $this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS];
return false;
}
/**
* Sets the set of supported fields transmitted by the device for a certain folder
*
* @param string $folderid
* @param array $fieldlist supported fields
*
* @access public
* @return boolean
*/
public function SetSupportedFields($folderid, $fieldlist) {
$contentData = $this->contentData;
if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
$contentData[$folderid] = array();
$contentData[$folderid][self::FOLDERSUPPORTEDFIELDS] = $fieldlist;
$this->contentData = $contentData;
return true;
}
/**
* Gets the current sync status of a certain folder
*
* @param string $folderid
*
* @access public
* @return mixed/boolean false means the status is not available
*/
public function GetFolderSyncStatus($folderid) {
if (isset($this->contentData) && isset($this->contentData[$folderid]) &&
isset($this->contentData[$folderid][self::FOLDERUUID]) && $this->contentData[$folderid][self::FOLDERUUID] !== false &&
isset($this->contentData[$folderid][self::FOLDERSYNCSTATUS]) )
return $this->contentData[$folderid][self::FOLDERSYNCSTATUS];
return false;
}
/**
* Sets the current sync status of a certain folder
*
* @param string $folderid
* @param mixed $status if set to false the current status is deleted
*
* @access public
* @return boolean
*/
public function SetFolderSyncStatus($folderid, $status) {
$contentData = $this->contentData;
if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid]))
$contentData[$folderid] = array();
if ($status !== false) {
$contentData[$folderid][self::FOLDERSYNCSTATUS] = $status;
}
else if (isset($contentData[$folderid][self::FOLDERSYNCSTATUS])) {
unset($contentData[$folderid][self::FOLDERSYNCSTATUS]);
}
$this->contentData = $contentData;
return true;
}
}
?>

View file

@ -0,0 +1,68 @@
<?php
/***********************************************
* File : bodypreference.php
* Project : Z-Push
* Descr : Holds body preference data
*
* Created : 18.04.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class BodyPreference extends StateObject {
protected $unsetdata = array( 'truncationsize' => false,
'allornone' => false,
'preview' => false,
);
/**
* expected magic getters and setters
*
* GetTruncationSize() + SetTruncationSize()
* GetAllOrNone() + SetAllOrNone()
* GetPreview() + SetPreview()
*/
/**
* Indicates if this object has values
*
* @access public
* @return boolean
*/
public function HasValues() {
return (count($this->data) > 0);
}
}
?>

View file

@ -0,0 +1,349 @@
<?php
/***********************************************
* File : changesmemorywrapper.php
* Project : Z-Push
* Descr : Class that collect changes in memory
*
* Created : 18.08.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ChangesMemoryWrapper extends HierarchyCache implements IImportChanges, IExportChanges {
const CHANGE = 1;
const DELETION = 2;
private $changes;
private $step;
private $destinationImporter;
private $exportImporter;
/**
* Constructor
*
* @access public
* @return
*/
public function ChangesMemoryWrapper() {
$this->changes = array();
$this->step = 0;
parent::HierarchyCache();
}
/**
* Only used to load additional folder sync information for hierarchy changes
*
* @param array $state current state of additional hierarchy folders
*
* @access public
* @return boolean
*/
public function Config($state, $flags = 0) {
// we should never forward this changes to a backend
if (!isset($this->destinationImporter)) {
foreach($state as $addKey => $addFolder) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->Config(AdditionalFolders) : process folder '%s'", $addFolder->displayname));
if (isset($addFolder->NoBackendFolder) && $addFolder->NoBackendFolder == true) {
$hasRights = ZPush::GetBackend()->Setup($addFolder->Store, true, $addFolder->serverid);
// delete the folder on the device
if (! $hasRights) {
// delete the folder only if it was an additional folder before, else ignore it
$synchedfolder = $this->GetFolder($addFolder->serverid);
if (isset($synchedfolder->NoBackendFolder) && $synchedfolder->NoBackendFolder == true)
$this->ImportFolderDeletion($addFolder->serverid, $addFolder->parentid);
continue;
}
}
// add folder to the device - if folder is already on the device, nothing will happen
$this->ImportFolderChange($addFolder);
}
// look for folders which are currently on the device if there are now not to be synched anymore
$alreadyDeleted = $this->GetDeletedFolders();
foreach ($this->ExportFolders(true) as $sid => $folder) {
// we are only looking at additional folders
if (isset($folder->NoBackendFolder)) {
// look if this folder is still in the list of additional folders and was not already deleted (e.g. missing permissions)
if (!array_key_exists($sid, $state) && !array_key_exists($sid, $alreadyDeleted)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ChangesMemoryWrapper->Config(AdditionalFolders) : previously synchronized folder '%s' is not to be synched anymore. Sending delete to mobile.", $folder->displayname));
$this->ImportFolderDeletion($folder->serverid, $folder->parentid);
}
}
}
}
return true;
}
/**
* Implement interfaces which are never used
*/
public function GetState() { return false;}
public function LoadConflicts($contentparameters, $state) { return true; }
public function ConfigContentParameters($contentparameters) { return true; }
public function ImportMessageReadFlag($id, $flags) { return true; }
public function ImportMessageMove($id, $newfolder) { return true; }
/**----------------------------------------------------------------------------------------------------------
* IImportChanges & destination importer
*/
/**
* Sets an importer where incoming changes should be sent to
*
* @param IImportChanges $importer message to be changed
*
* @access public
* @return boolean
*/
public function SetDestinationImporter(&$importer) {
$this->destinationImporter = $importer;
}
/**
* Imports a message change, which is imported into memory
*
* @param string $id id of message which is changed
* @param SyncObject $message message to be changed
*
* @access public
* @return boolean
*/
public function ImportMessageChange($id, $message) {
$this->changes[] = array(self::CHANGE, $id);
return true;
}
/**
* Imports a message deletion, which is imported into memory
*
* @param string $id id of message which is deleted
*
* @access public
* @return boolean
*/
public function ImportMessageDeletion($id) {
$this->changes[] = array(self::DELETION, $id);
return true;
}
/**
* Checks if a message id is flagged as changed
*
* @param string $id message id
*
* @access public
* @return boolean
*/
public function IsChanged($id) {
return (array_search(array(self::CHANGE, $id), $this->changes) === false) ? false:true;
}
/**
* Checks if a message id is flagged as deleted
*
* @param string $id message id
*
* @access public
* @return boolean
*/
public function IsDeleted($id) {
return (array_search(array(self::DELETION, $id), $this->changes) === false) ? false:true;
}
/**
* Imports a folder change
*
* @param SyncFolder $folder folder to be changed
*
* @access public
* @return boolean
*/
public function ImportFolderChange($folder) {
// if the destinationImporter is set, then this folder should be processed by another importer
// instead of being loaded in memory.
if (isset($this->destinationImporter)) {
// normally the $folder->type is not set, but we need this value to check if the change operation is permitted
// e.g. system folders can normally not be changed - set the type from cache and let the destinationImporter decide
if (!isset($folder->type)) {
$cacheFolder = $this->GetFolder($folder->serverid);
$folder->type = $cacheFolder->type;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Set foldertype for folder '%s' from cache as it was not sent: '%s'", $folder->displayname, $folder->type));
}
$ret = $this->destinationImporter->ImportFolderChange($folder);
// if the operation was sucessfull, update the HierarchyCache
if ($ret) {
// for folder creation, the serverid is not set and has to be updated before
if (!isset($folder->serverid) || $folder->serverid == "")
$folder->serverid = $ret;
$this->AddFolder($folder);
}
return $ret;
}
// load into memory
else {
if (isset($folder->serverid)) {
// The Zarafa HierarchyExporter exports all kinds of changes for folders (e.g. update no. of unread messages in a folder).
// These changes are not relevant for the mobiles, as something changes but the relevant displayname and parentid
// stay the same. These changes will be dropped and are not sent!
$cacheFolder = $this->GetFolder($folder->serverid);
if ($folder->equals($this->GetFolder($folder->serverid))) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Change for folder '%s' will not be sent as modification is not relevant.", $folder->displayname));
return false;
}
// load this change into memory
$this->changes[] = array(self::CHANGE, $folder);
// HierarchyCache: already add/update the folder so changes are not sent twice (if exported twice)
$this->AddFolder($folder);
return true;
}
return false;
}
}
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent (opt) the parent id of the folders
*
* @access public
* @return boolean
*/
public function ImportFolderDeletion($id, $parent = false) {
// if the forwarder is set, then this folder should be processed by another importer
// instead of being loaded in mem.
if (isset($this->destinationImporter)) {
$ret = $this->destinationImporter->ImportFolderDeletion($id, $parent);
// if the operation was sucessfull, update the HierarchyCache
if ($ret)
$this->DelFolder($id);
return $ret;
}
else {
// if this folder is not in the cache, the change does not need to be streamed to the mobile
if ($this->GetFolder($id)) {
// load this change into memory
$this->changes[] = array(self::DELETION, $id, $parent);
// HierarchyCache: delete the folder so changes are not sent twice (if exported twice)
$this->DelFolder($id);
return true;
}
}
}
/**----------------------------------------------------------------------------------------------------------
* IExportChanges & destination importer
*/
/**
* Initializes the Exporter where changes are synchronized to
*
* @param IImportChanges $importer
*
* @access public
* @return boolean
*/
public function InitializeExporter(&$importer) {
$this->exportImporter = $importer;
$this->step = 0;
return true;
}
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount() {
return count($this->changes);
}
/**
* Synchronizes a change. Only HierarchyChanges will be Synchronized()
*
* @access public
* @return array
*/
public function Synchronize() {
if($this->step < count($this->changes) && isset($this->exportImporter)) {
$change = $this->changes[$this->step];
if ($change[0] == self::CHANGE) {
if (! $this->GetFolder($change[1]->serverid, true))
$change[1]->flags = SYNC_NEWMESSAGE;
$this->exportImporter->ImportFolderChange($change[1]);
}
// deletion
else {
$this->exportImporter->ImportFolderDeletion($change[1], $change[2]);
}
$this->step++;
// return progress array
return array("steps" => count($this->changes), "progress" => $this->step);
}
else
return false;
}
/**
* Initializes a few instance variables
* called after unserialization
*
* @access public
* @return array
*/
public function __wakeup() {
$this->changes = array();
$this->step = 0;
}
}
?>

View file

@ -0,0 +1,141 @@
<?php
/***********************************************
* File : contentparameters.php
* Project : Z-Push
* Descr : Simple transportation class for
* requested content parameter options
*
* Created : 11.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ContentParameters extends StateObject {
protected $unsetdata = array( 'contentclass' => false,
'foldertype' => '',
'conflict' => false,
'deletesasmoves' => true,
'filtertype' => false,
'truncation' => false,
'rtftruncation' => false,
'mimesupport' => false,
'conversationmode' => false,
);
private $synckeyChanged = false;
/**
* Expected magic getters and setters
*
* GetContentClass() + SetContentClass()
* GetConflict() + SetConflict()
* GetDeletesAsMoves() + SetDeletesAsMoves()
* GetFilterType() + SetFilterType()
* GetTruncation() + SetTruncation
* GetRTFTruncation() + SetRTFTruncation()
* GetMimeSupport () + SetMimeSupport()
* GetMimeTruncation() + SetMimeTruncation()
* GetConversationMode() + SetConversationMode()
*/
/**
* Overwrite StateObject->__call so we are able to handle ContentParameters->BodyPreference()
*
* @access public
* @return mixed
*/
public function __call($name, $arguments) {
if ($name === "BodyPreference")
return $this->BodyPreference($arguments[0]);
return parent::__call($name, $arguments);
}
/**
* Instantiates/returns the bodypreference object for a type
*
* @param int $type
*
* @access public
* @return int/boolean returns false if value is not defined
*/
public function BodyPreference($type) {
if (!isset($this->bodypref))
$this->bodypref = array();
if (isset($this->bodypref[$type]))
return $this->bodypref[$type];
else {
$asb = new BodyPreference();
$arr = (array)$this->bodypref;
$arr[$type] = $asb;
$this->bodypref = $arr;
return $asb;
}
}
/**
* Returns available body preference objects
*
* @access public
* @return array/boolean returns false if the client's body preference is not available
*/
public function GetBodyPreference() {
if (!isset($this->bodypref) || !(is_array($this->bodypref) || empty($this->bodypref))) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ContentParameters->GetBodyPreference(): bodypref is empty or not set"));
return false;
}
return array_keys($this->bodypref);
}
/**
* Called before the StateObject is serialized
*
* @access protected
* @return boolean
*/
protected function preSerialize() {
parent::preSerialize();
if ($this->changed === true && $this->synckeyChanged)
$this->lastsynctime = time();
return true;
}
}
?>

View file

@ -0,0 +1,841 @@
<?php
/***********************************************
* File : devicemanager.php
* Project : Z-Push
* Descr : Manages device relevant data, provisioning,
* loop detection and device states.
* The DeviceManager uses a IStateMachine
* implementation with IStateMachine::DEVICEDATA
* to save device relevant data.
*
* Created : 11.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class DeviceManager {
// broken message indicators
const MSG_BROKEN_UNKNOWN = 1;
const MSG_BROKEN_CAUSINGLOOP = 2;
const MSG_BROKEN_SEMANTICERR = 4;
const FLD_SYNC_INITIALIZED = 1;
const FLD_SYNC_INPROGRESS = 2;
const FLD_SYNC_COMPLETED = 4;
private $device;
private $deviceHash;
private $statemachine;
private $stateManager;
private $incomingData = 0;
private $outgoingData = 0;
private $windowSize;
private $latestFolder;
private $loopdetection;
private $hierarchySyncRequired;
/**
* Constructor
*
* @access public
*/
public function DeviceManager() {
$this->statemachine = ZPush::GetStateMachine();
$this->deviceHash = false;
$this->devid = Request::GetDeviceID();
$this->windowSize = array();
$this->latestFolder = false;
$this->hierarchySyncRequired = false;
// only continue if deviceid is set
if ($this->devid) {
$this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent());
$this->loadDeviceData();
ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent());
}
else
throw new FatalNotImplementedException("Can not proceed without a device id.");
$this->loopdetection = new LoopDetection();
$this->loopdetection->ProcessLoopDetectionInit();
$this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed();
$this->stateManager = new StateManager();
$this->stateManager->SetDevice($this->device);
}
/**
* Returns the StateManager for the current device
*
* @access public
* @return StateManager
*/
public function GetStateManager() {
return $this->stateManager;
}
/**----------------------------------------------------------------------------------------------------------
* Device operations
*/
/**
* Announces amount of transmitted data to the DeviceManager
*
* @param int $datacounter
*
* @access public
* @return boolean
*/
public function SentData($datacounter) {
// TODO save this somewhere
$this->incomingData = Request::GetContentLength();
$this->outgoingData = $datacounter;
}
/**
* Called at the end of the request
* Statistics about received/sent data is saved here
*
* @access public
* @return boolean
*/
public function Save() {
// TODO save other stuff
// check if previousily ignored messages were synchronized for the current folder
// on multifolder operations of AS14 this is done by setLatestFolder()
if ($this->latestFolder !== false)
$this->checkBrokenMessages($this->latestFolder);
// update the user agent and AS version on the device
$this->device->SetUserAgent(Request::GetUserAgent());
$this->device->SetASVersion(Request::GetProtocolVersion());
// data to be saved
$data = $this->device->GetData();
if ($data && Request::IsValidDeviceID()) {
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data changed");
try {
// check if this is the first time the device data is saved and it is authenticated. If so, link the user to the device id
if ($this->device->IsNewDevice() && RequestProcessor::isUserAuthenticated()) {
ZLog::Write(LOGLEVEL_INFO, sprintf("Linking device ID '%s' to user '%s'", $this->devid, $this->device->GetDeviceUser()));
$this->statemachine->LinkUserDevice($this->device->GetDeviceUser(), $this->devid);
}
if (RequestProcessor::isUserAuthenticated() || $this->device->GetForceSave() ) {
$this->statemachine->SetState($data, $this->devid, IStateMachine::DEVICEDATA);
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->Save(): Device data saved");
}
}
catch (StateNotFoundException $snfex) {
ZLog::Write(LOGLEVEL_ERROR, "DeviceManager->Save(): Exception: ". $snfex->getMessage());
}
}
// remove old search data
$oldpid = $this->loopdetection->ProcessLoopDetectionGetOutdatedSearchPID();
if ($oldpid) {
ZPush::GetBackend()->GetSearchProvider()->TerminateSearch($oldpid);
}
// we terminated this process
if ($this->loopdetection)
$this->loopdetection->ProcessLoopDetectionTerminate();
return true;
}
/**
* Newer mobiles send extensive device informations with the Settings command
* These informations are saved in the ASDevice
*
* @param SyncDeviceInformation $deviceinformation
*
* @access public
* @return boolean
*/
public function SaveDeviceInformation($deviceinformation) {
ZLog::Write(LOGLEVEL_DEBUG, "Saving submitted device information");
// set the user agent
if (isset($deviceinformation->useragent))
$this->device->SetUserAgent($deviceinformation->useragent);
// save other informations
foreach (array("model", "imei", "friendlyname", "os", "oslanguage", "phonenumber", "mobileoperator", "enableoutboundsms") as $info) {
if (isset($deviceinformation->$info) && $deviceinformation->$info != "") {
$this->device->__set("device".$info, $deviceinformation->$info);
}
}
return true;
}
/**----------------------------------------------------------------------------------------------------------
* Provisioning operations
*/
/**
* Checks if the sent policykey matches the latest policykey
* saved for the device
*
* @param string $policykey
* @param boolean $noDebug (opt) by default, debug message is shown
*
* @access public
* @return boolean
*/
public function ProvisioningRequired($policykey, $noDebug = false) {
$this->loadDeviceData();
// check if a remote wipe is required
if ($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ProvisioningRequired('%s'): YES, remote wipe requested", $policykey));
return true;
}
$p = ( ($this->device->GetWipeStatus() != SYNC_PROVISION_RWSTATUS_NA && $policykey != $this->device->GetPolicyKey()) ||
Request::WasPolicyKeySent() && $this->device->GetPolicyKey() == ASDevice::UNDEFINED );
if (!$noDebug || $p)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->ProvisioningRequired('%s') saved device key '%s': %s", $policykey, $this->device->GetPolicyKey(), Utils::PrintAsString($p)));
return $p;
}
/**
* Generates a new Policykey
*
* @access public
* @return int
*/
public function GenerateProvisioningPolicyKey() {
return mt_rand(100000000, 999999999);
}
/**
* Attributes a provisioned policykey to a device
*
* @param int $policykey
*
* @access public
* @return boolean status
*/
public function SetProvisioningPolicyKey($policykey) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetPolicyKey('%s')", $policykey));
return $this->device->SetPolicyKey($policykey);
}
/**
* Builds a Provisioning SyncObject with policies
*
* @access public
* @return SyncProvisioning
*/
public function GetProvisioningObject() {
$p = new SyncProvisioning();
// TODO load systemwide Policies
$p->Load($this->device->GetPolicies());
return $p;
}
/**
* Returns the status of the remote wipe policy
*
* @access public
* @return int returns the current status of the device - SYNC_PROVISION_RWSTATUS_*
*/
public function GetProvisioningWipeStatus() {
return $this->device->GetWipeStatus();
}
/**
* Updates the status of the remote wipe
*
* @param int $status - SYNC_PROVISION_RWSTATUS_*
*
* @access public
* @return boolean could fail if trying to update status to a wipe status which was not requested before
*/
public function SetProvisioningWipeStatus($status) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->SetProvisioningWipeStatus() change from '%d' to '%d'",$this->device->GetWipeStatus(), $status));
if ($status > SYNC_PROVISION_RWSTATUS_OK && !($this->device->GetWipeStatus() > SYNC_PROVISION_RWSTATUS_OK)) {
ZLog::Write(LOGLEVEL_ERROR, "Not permitted to update remote wipe status to a higher value as remote wipe was not initiated!");
return false;
}
$this->device->SetWipeStatus($status);
return true;
}
/**----------------------------------------------------------------------------------------------------------
* LEGACY AS 1.0 and WRAPPER operations
*/
/**
* Returns a wrapped Importer & Exporter to use the
* HierarchyChache
*
* @see ChangesMemoryWrapper
* @access public
* @return object HierarchyCache
*/
public function GetHierarchyChangesWrapper() {
return $this->device->GetHierarchyCache();
}
/**
* Initializes the HierarchyCache for legacy syncs
* this is for AS 1.0 compatibility:
* save folder information synched with GetHierarchy()
*
* @param string $folders Array with folder information
*
* @access public
* @return boolean
*/
public function InitializeFolderCache($folders) {
$this->stateManager->SetDevice($this->device);
return $this->stateManager->InitializeFolderCache($folders);
}
/**
* Returns the ActiveSync folder type for a FolderID
*
* @param string $folderid
*
* @access public
* @return int/boolean boolean if no type is found
*/
public function GetFolderTypeFromCacheById($folderid) {
return $this->device->GetFolderType($folderid);
}
/**
* Returns a FolderID of default classes
* this is for AS 1.0 compatibility:
* this information was made available during GetHierarchy()
*
* @param string $class The class requested
*
* @access public
* @return string
* @throws NoHierarchyCacheAvailableException
*/
public function GetFolderIdFromCacheByClass($class) {
$folderidforClass = false;
// look at the default foldertype for this class
$type = ZPush::getDefaultFolderTypeFromFolderClass($class);
if ($type && $type > SYNC_FOLDER_TYPE_OTHER && $type < SYNC_FOLDER_TYPE_USER_MAIL) {
$folderids = $this->device->GetAllFolderIds();
foreach ($folderids as $folderid) {
if ($type == $this->device->GetFolderType($folderid)) {
$folderidforClass = $folderid;
break;
}
}
// Old Palm Treos always do initial sync for calendar and contacts, even if they are not made available by the backend.
// We need to fake these folderids, allowing a fake sync/ping, even if they are not supported by the backend
// if the folderid would be available, they would already be returned in the above statement
if ($folderidforClass == false && ($type == SYNC_FOLDER_TYPE_APPOINTMENT || $type == SYNC_FOLDER_TYPE_CONTACT))
$folderidforClass = SYNC_FOLDER_TYPE_DUMMY;
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetFolderIdFromCacheByClass('%s'): '%s' => '%s'", $class, $type, $folderidforClass));
return $folderidforClass;
}
/**
* Returns a FolderClass for a FolderID which is known to the mobile
*
* @param string $folderid
*
* @access public
* @return int
* @throws NoHierarchyCacheAvailableException, NotImplementedException
*/
public function GetFolderClassFromCacheByID($folderid) {
//TODO check if the parent folder exists and is also beeing synchronized
$typeFromCache = $this->device->GetFolderType($folderid);
if ($typeFromCache === false)
throw new NoHierarchyCacheAvailableException(sprintf("Folderid '%s' is not fully synchronized on the device", $folderid));
$class = ZPush::GetFolderClassFromFolderType($typeFromCache);
if ($class === false)
throw new NotImplementedException(sprintf("Folderid '%s' is saved to be of type '%d' but this type is not implemented", $folderid, $typeFromCache));
return $class;
}
/**
* Checks if the message should be streamed to a mobile
* Should always be called before a message is sent to the mobile
* Returns true if there is something wrong and the content could break the
* synchronization
*
* @param string $id message id
* @param SyncObject &$message the method could edit the message to change the flags
*
* @access public
* @return boolean returns true if the message should NOT be send!
*/
public function DoNotStreamMessage($id, &$message) {
$folderid = $this->getLatestFolder();
if (isset($message->parentid))
$folder = $message->parentid;
// message was identified to be causing a loop
if ($this->loopdetection->IgnoreNextMessage(true, $id, $folderid)) {
$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_CAUSINGLOOP);
return true;
}
// message is semantically incorrect
if (!$message->Check(true)) {
$this->AnnounceIgnoredMessage($folderid, $id, $message, self::MSG_BROKEN_SEMANTICERR);
return true;
}
// check if this message is broken
if ($this->device->HasIgnoredMessage($folderid, $id)) {
// reset the flags so the message is always streamed with <Add>
$message->flags = false;
// track the broken message in the loop detection
$this->loopdetection->SetBrokenMessage($folderid, $id);
}
return false;
}
/**
* Removes device information about a broken message as it is been removed from the mobile.
*
* @param string $id message id
*
* @access public
* @return boolean
*/
public function RemoveBrokenMessage($id) {
$folderid = $this->getLatestFolder();
if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->RemoveBrokenMessage('%s', '%s'): cleared data about previously ignored message", $folderid, $id));
return true;
}
return false;
}
/**
* Amount of items to me synchronized
*
* @param string $folderid
* @param string $type
* @param int $queuedmessages;
* @access public
* @return int
*/
public function GetWindowSize($folderid, $type, $uuid, $statecounter, $queuedmessages) {
if (isset($this->windowSize[$folderid]))
$items = $this->windowSize[$folderid];
else
$items = (defined("SYNC_MAX_ITEMS")) ? SYNC_MAX_ITEMS : 100;
if (defined("SYNC_MAX_ITEMS") && SYNC_MAX_ITEMS < $items) {
if ($queuedmessages > SYNC_MAX_ITEMS)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("DeviceManager->GetWindowSize() overwriting max itmes requested of %d by %d forced in configuration.", $items, SYNC_MAX_ITEMS));
$items = SYNC_MAX_ITEMS;
}
$this->setLatestFolder($folderid);
// detect if this is a loop condition
if ($this->loopdetection->Detect($folderid, $type, $uuid, $statecounter, $items, $queuedmessages))
$items = ($items == 0) ? 0: 1+($this->loopdetection->IgnoreNextMessage(false)?1:0) ;
if ($items >= 0 && $items <= 2)
ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Messages sent to the mobile will be restricted to %d items in order to identify the conflict", $items));
return $items;
}
/**
* Sets the amount of items the device is requesting
*
* @param string $folderid
* @param int $maxItems
*
* @access public
* @return boolean
*/
public function SetWindowSize($folderid, $maxItems) {
$this->windowSize[$folderid] = $maxItems;
return true;
}
/**
* Sets the supported fields transmitted by the device for a certain folder
*
* @param string $folderid
* @param array $fieldlist supported fields
*
* @access public
* @return boolean
*/
public function SetSupportedFields($folderid, $fieldlist) {
return $this->device->SetSupportedFields($folderid, $fieldlist);
}
/**
* Gets the supported fields transmitted previousely by the device
* for a certain folder
*
* @param string $folderid
*
* @access public
* @return array/boolean
*/
public function GetSupportedFields($folderid) {
return $this->device->GetSupportedFields($folderid);
}
/**
* Removes all linked states of a specific folder.
* During next request the folder is resynchronized.
*
* @param string $folderid
*
* @access public
* @return boolean
*/
public function ForceFolderResync($folderid) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->ForceFolderResync('%s'): folder resync", $folderid));
// delete folder states
StateManager::UnLinkState($this->device, $folderid);
return true;
}
/**
* Removes all linked states from a device.
* During next requests a full resync is triggered.
*
* @access public
* @return boolean
*/
public function ForceFullResync() {
ZLog::Write(LOGLEVEL_INFO, "Full device resync requested");
// delete hierarchy states
StateManager::UnLinkState($this->device, false);
// delete all other uuids
foreach ($this->device->GetAllFolderIds() as $folderid)
$uuid = StateManager::UnLinkState($this->device, $folderid);
return true;
}
/**
* Indicates if the hierarchy should be resynchronized
* e.g. during PING
*
* @access public
* @return boolean
*/
public function IsHierarchySyncRequired() {
// check if a hierarchy sync might be necessary
if ($this->device->GetFolderUUID(false) === false)
$this->hierarchySyncRequired = true;
return $this->hierarchySyncRequired;
}
/**
* Indicates if a full hierarchy resync should be triggered due to loops
*
* @access public
* @return boolean
*/
public function IsHierarchyFullResyncRequired() {
// check for potential process loops like described in ZP-5
return $this->loopdetection->ProcessLoopDetectionIsHierarchyResyncRequired();
}
/**
* Adds an Exceptions to the process tracking
*
* @param Exception $exception
*
* @access public
* @return boolean
*/
public function AnnounceProcessException($exception) {
return $this->loopdetection->ProcessLoopDetectionAddException($exception);
}
/**
* Adds a non-ok status for a folderid to the process tracking.
* On 'false' a hierarchy status is assumed
*
* @access public
* @return boolean
*/
public function AnnounceProcessStatus($folderid, $status) {
return $this->loopdetection->ProcessLoopDetectionAddStatus($folderid, $status);
}
/**
* Announces that the current process is a push connection to the process loop
* detection and to the Top collector
*
* @access public
* @return boolean
*/
public function AnnounceProcessAsPush() {
ZLog::Write(LOGLEVEL_DEBUG, "Announce process as PUSH connection");
return $this->loopdetection->ProcessLoopDetectionSetAsPush() && ZPush::GetTopCollector()->SetAsPushConnection();
}
/**
* Checks if the given counter for a certain uuid+folderid was already exported or modified.
* This is called when a heartbeat request found changes to make sure that the same
* changes are not exported twice, as during the heartbeat there could have been a normal
* sync request.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return boolean indicating if an uuid+counter were exported (with changes) before
*/
public function CheckHearbeatStateIntegrity($folderid, $uuid, $counter) {
return $this->loopdetection->IsSyncStateObsolete($folderid, $uuid, $counter);
}
/**
* Marks a syncstate as obsolete for Heartbeat, as e.g. an import was started using it.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return
*/
public function SetHeartbeatStateIntegrity($folderid, $uuid, $counter) {
return $this->loopdetection->SetSyncStateUsage($folderid, $uuid, $counter);
}
/**
* Sets the current status of the folder
*
* @param string $folderid folder id
* @param int $statusflag current status: DeviceManager::FLD_SYNC_INITIALIZED, DeviceManager::FLD_SYNC_INPROGRESS, DeviceManager::FLD_SYNC_COMPLETED
*
* @access public
* @return
*/
public function SetFolderSyncStatus($folderid, $statusflag) {
$currentStatus = $this->device->GetFolderSyncStatus($folderid);
// status available or just initialized
if (isset($currentStatus[ASDevice::FOLDERSYNCSTATUS]) || $statusflag == self::FLD_SYNC_INITIALIZED) {
// only update if there is a change
if ($statusflag !== $currentStatus[ASDevice::FOLDERSYNCSTATUS] && $statusflag != self::FLD_SYNC_COMPLETED) {
$this->device->SetFolderSyncStatus($folderid, array(ASDevice::FOLDERSYNCSTATUS => $statusflag));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): set %s for %s", $statusflag, $folderid));
}
// if completed, remove the status
else if ($statusflag == self::FLD_SYNC_COMPLETED) {
$this->device->SetFolderSyncStatus($folderid, false);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SetFolderSyncStatus(): completed for %s", $folderid));
}
}
return true;
}
/**
* Indicates if the device needs an AS version update
*
* @access public
* @return boolean
*/
public function AnnounceASVersion() {
$latest = ZPush::GetSupportedASVersion();
$announced = $this->device->GetAnnouncedASversion();
$this->device->SetAnnouncedASversion($latest);
return ($announced != $latest);
}
/**----------------------------------------------------------------------------------------------------------
* private DeviceManager methods
*/
/**
* Loads devicedata from the StateMachine and loads it into the device
*
* @access public
* @return boolean
*/
private function loadDeviceData() {
if (!Request::IsValidDeviceID())
return false;
try {
$deviceHash = $this->statemachine->GetStateHash($this->devid, IStateMachine::DEVICEDATA);
if ($deviceHash != $this->deviceHash) {
if ($this->deviceHash)
ZLog::Write(LOGLEVEL_DEBUG, "DeviceManager->loadDeviceData(): Device data was changed, reloading");
$this->device->SetData($this->statemachine->GetState($this->devid, IStateMachine::DEVICEDATA));
$this->deviceHash = $deviceHash;
}
}
catch (StateNotFoundException $snfex) {
$this->hierarchySyncRequired = true;
}
return true;
}
/**
* Called when a SyncObject is not being streamed to the mobile.
* The user can be informed so he knows about this issue
*
* @param string $folderid id of the parent folder (may be false if unknown)
* @param string $id message id
* @param SyncObject $message the broken message
* @param string $reason (self::MSG_BROKEN_UNKNOWN, self::MSG_BROKEN_CAUSINGLOOP, self::MSG_BROKEN_SEMANTICERR)
*
* @access public
* @return boolean
*/
public function AnnounceIgnoredMessage($folderid, $id, SyncObject $message, $reason = self::MSG_BROKEN_UNKNOWN) {
if ($folderid === false)
$folderid = $this->getLatestFolder();
$class = get_class($message);
$brokenMessage = new StateObject();
$brokenMessage->id = $id;
$brokenMessage->folderid = $folderid;
$brokenMessage->ASClass = $class;
$brokenMessage->folderid = $folderid;
$brokenMessage->reasonCode = $reason;
$brokenMessage->reasonString = 'unknown cause';
$brokenMessage->timestamp = time();
$brokenMessage->asobject = $message;
$brokenMessage->reasonString = ZLog::GetLastMessage(LOGLEVEL_WARN);
$this->device->AddIgnoredMessage($brokenMessage);
ZLog::Write(LOGLEVEL_ERROR, sprintf("Ignored broken message (%s). Reason: '%s' Folderid: '%s' message id '%s'", $class, $reason, $folderid, $id));
return true;
}
/**
* Called when a SyncObject was streamed to the mobile.
* If the message could not be sent before this data is obsolete
*
* @param string $folderid id of the parent folder
* @param string $id message id
*
* @access public
* @return boolean returns true if the message was ignored before
*/
private function announceAcceptedMessage($folderid, $id) {
if ($this->device->RemoveIgnoredMessage($folderid, $id)) {
ZLog::Write(LOGLEVEL_INFO, sprintf("DeviceManager->announceAcceptedMessage('%s', '%s'): cleared previously ignored message as message is sucessfully streamed",$folderid, $id));
return true;
}
return false;
}
/**
* Checks if there were broken messages streamed to the mobile.
* If the sync completes/continues without further erros they are marked as accepted
*
* @param string $folderid folderid which is to be checked
*
* @access private
* @return boolean
*/
private function checkBrokenMessages($folderid) {
// check for correctly synchronized messages of the folder
foreach($this->loopdetection->GetSyncedButBeforeIgnoredMessages($folderid) as $okID) {
$this->announceAcceptedMessage($folderid, $okID);
}
return true;
}
/**
* Setter for the latest folder id
* on multi-folder operations of AS 14 this is used to set the new current folder id
*
* @param string $folderid the current folder
*
* @access private
* @return boolean
*/
private function setLatestFolder($folderid) {
// this is a multi folder operation
// check on ignoredmessages before discaring the folderid
if ($this->latestFolder !== false)
$this->checkBrokenMessages($this->latestFolder);
$this->latestFolder = $folderid;
return true;
}
/**
* Getter for the latest folder id
*
* @access private
* @return string $folderid the current folder
*/
private function getLatestFolder() {
return $this->latestFolder;
}
}
?>

View file

@ -0,0 +1,216 @@
<?php
/***********************************************
* File : hierarchycache.php
* Project : Z-Push
* Descr : HierarchyCache implementation
*
* Created : 18.08.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class HierarchyCache {
private $changed = false;
protected $cacheById;
private $cacheByIdOld;
/**
* Constructor of the HierarchyCache
*
* @access public
* @return
*/
public function HierarchyCache() {
$this->cacheById = array();
$this->cacheByIdOld = $this->cacheById;
$this->changed = true;
}
/**
* Indicates if the cache was changed
*
* @access public
* @return boolean
*/
public function IsStateChanged() {
return $this->changed;
}
/**
* Copy current CacheById to memory
*
* @access public
* @return boolean
*/
public function CopyOldState() {
$this->cacheByIdOld = $this->cacheById;
return true;
}
/**
* Returns the SyncFolder object for a folder id
* If $oldstate is set, then the data from the previous state is returned
*
* @param string $serverid
* @param boolean $oldstate (optional) by default false
*
* @access public
* @return SyncObject/boolean false if not found
*/
public function GetFolder($serverid, $oldState = false) {
if (!$oldState && array_key_exists($serverid, $this->cacheById)) {
return $this->cacheById[$serverid];
}
else if ($oldState && array_key_exists($serverid, $this->cacheByIdOld)) {
return $this->cacheByIdOld[$serverid];
}
return false;
}
/**
* Adds a folder to the HierarchyCache
*
* @param SyncObject $folder
*
* @access public
* @return boolean
*/
public function AddFolder($folder) {
ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache: AddFolder() serverid: {$folder->serverid} displayname: {$folder->displayname}");
// on update the $folder does most of the times not contain a type
// we copy the value in this case to the new $folder object
if (isset($this->cacheById[$folder->serverid]) && (!isset($folder->type) || $folder->type == false) && isset($this->cacheById[$folder->serverid]->type)) {
$folder->type = $this->cacheById[$folder->serverid]->type;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("HierarchyCache: AddFolder() is an update: used type '%s' from old object", $folder->type));
}
// add/update
$this->cacheById[$folder->serverid] = $folder;
$this->changed = true;
return true;
}
/**
* Removes a folder to the HierarchyCache
*
* @param string $serverid id of folder to be removed
*
* @access public
* @return boolean
*/
public function DelFolder($serverid) {
$ftype = $this->GetFolder($serverid);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("HierarchyCache: DelFolder() serverid: '%s' - type: '%s'", $serverid, $ftype->type));
unset($this->cacheById[$serverid]);
$this->changed = true;
return true;
}
/**
* Imports a folder array to the HierarchyCache
*
* @param array $folders folders to the HierarchyCache
*
* @access public
* @return boolean
*/
public function ImportFolders($folders) {
if (!is_array($folders))
return false;
$this->cacheById = array();
foreach ($folders as $folder) {
if (!isset($folder->type))
continue;
$this->AddFolder($folder);
}
return true;
}
/**
* Exports all folders from the HierarchyCache
*
* @param boolean $oldstate (optional) by default false
*
* @access public
* @return array
*/
public function ExportFolders($oldstate = false) {
if ($oldstate === false)
return $this->cacheById;
else
return $this->cacheByIdOld;
}
/**
* Returns all folder objects which were deleted in this operation
*
* @access public
* @return array with SyncFolder objects
*/
public function GetDeletedFolders() {
// diffing the OldCacheById with CacheById we know if folders were deleted
return array_diff_key($this->cacheByIdOld, $this->cacheById);
}
/**
* Returns some statistics about the HierarchyCache
*
* @access public
* @return string
*/
public function GetStat() {
return sprintf("HierarchyCache is %s - Cached objects: %d", ((isset($this->cacheById))?"up":"down"), ((isset($this->cacheById))?count($this->cacheById):"0"));
}
/**
* Returns objects which should be persistent
* called before serialization
*
* @access public
* @return array
*/
public function __sleep() {
return array("cacheById");
}
}
?>

View file

@ -0,0 +1,297 @@
<?php
/***********************************************
* File : interprocessdata.php
* Project : Z-Push
* Descr : Class takes care of interprocess
* communicaton for different purposes
*
* Created : 20.10.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
abstract class InterProcessData {
const CLEANUPTIME = 1;
static protected $devid;
static protected $pid;
static protected $user;
static protected $start;
protected $type;
protected $allocate;
private $mutexid;
private $memid;
/**
* Constructor
*
* @access public
*/
public function InterProcessData() {
if (!isset($this->type) || !isset($this->allocate))
throw new FatalNotImplementedException(sprintf("Class InterProcessData can not be initialized. Subclass %s did not initialize type and allocable memory.", get_class($this)));
if ($this->InitSharedMem())
ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s(): Initialized mutexid %s and memid %s.", get_class($this), $this->mutexid, $this->memid));
}
/**
* Initializes internal parameters
*
* @access public
* @return boolean
*/
public function InitializeParams() {
if (!isset(self::$devid)) {
self::$devid = Request::GetDeviceID();
self::$pid = @getmypid();
self::$user = Request::GetAuthUser();
self::$start = time();
}
return true;
}
/**
* Allocates shared memory
*
* @access private
* @return boolean
*/
private function InitSharedMem() {
// shared mem general "turn off switch"
if (defined("USE_SHARED_MEM") && USE_SHARED_MEM === false) {
ZLog::Write(LOGLEVEL_INFO, "InterProcessData::InitSharedMem(): the usage of shared memory for Z-Push has been disabled. Check your config for 'USE_SHARED_MEM'.");
return false;
}
if (!function_exists('sem_get') || !function_exists('shm_attach') || !function_exists('sem_acquire')|| !function_exists('shm_get_var')) {
ZLog::Write(LOGLEVEL_INFO, "InterProcessData::InitSharedMem(): PHP libraries for the use shared memory are not available. Functionalities like z-push-top or loop detection are not available. Check your php packages.");
return false;
}
// Create mutex
$this->mutexid = @sem_get($this->type, 1);
if ($this->mutexid === false) {
ZLog::Write(LOGLEVEL_ERROR, "InterProcessData::InitSharedMem(): could not aquire semaphore");
return false;
}
// Attach shared memory
$this->memid = shm_attach($this->type+10, $this->allocate);
if ($this->memid === false) {
ZLog::Write(LOGLEVEL_ERROR, "InterProcessData::InitSharedMem(): could not attach shared memory");
@sem_remove($this->mutexid);
$this->mutexid = false;
return false;
}
// TODO mem cleanup has to be implemented
//$this->setInitialCleanTime();
return true;
}
/**
* Removes and detaches shared memory
*
* @access private
* @return boolean
*/
private function RemoveSharedMem() {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) {
@sem_acquire($this->mutexid);
$memid = $this->memid;
$this->memid = false;
@sem_release($this->mutexid);
@sem_remove($this->mutexid);
@shm_remove($memid);
@shm_detach($memid);
$this->mutexid = false;
return true;
}
return false;
}
/**
* Reinitializes shared memory by removing, detaching and re-allocating it
*
* @access public
* @return boolean
*/
public function ReInitSharedMem() {
return ($this->RemoveSharedMem() && $this->InitSharedMem());
}
/**
* Cleans up the shared memory block
*
* @access public
* @return boolean
*/
public function Clean() {
$stat = false;
// exclusive block
if ($this->blockMutex()) {
$cleanuptime = ($this->hasData(1)) ? $this->getData(1) : false;
// TODO implement Shared Memory cleanup
$this->releaseMutex();
}
// end exclusive block
return $stat;
}
/**
* Indicates if the shared memory is active
*
* @access public
* @return boolean
*/
public function IsActive() {
return ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false));
}
/**
* Blocks the class mutex
* Method blocks until mutex is available!
* ATTENTION: make sure that you *always* release a blocked mutex!
*
* @access protected
* @return boolean
*/
protected function blockMutex() {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false))
return @sem_acquire($this->mutexid);
return false;
}
/**
* Releases the class mutex
* After the release other processes are able to block the mutex themselfs
*
* @access protected
* @return boolean
*/
protected function releaseMutex() {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false))
return @sem_release($this->mutexid);
return false;
}
/**
* Indicates if the requested variable is available in shared memory
*
* @param int $id int indicating the variable
*
* @access protected
* @return boolean
*/
protected function hasData($id = 2) {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) {
if (function_exists("shm_has_var"))
return @shm_has_var($this->memid, $id);
else {
$some = $this->getData($id);
return isset($some);
}
}
return false;
}
/**
* Returns the requested variable from shared memory
*
* @param int $id int indicating the variable
*
* @access protected
* @return mixed
*/
protected function getData($id = 2) {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false))
return @shm_get_var($this->memid, $id);
return ;
}
/**
* Writes the transmitted variable to shared memory
* Subclasses may never use an id < 2!
*
* @param mixed $data data which should be saved into shared memory
* @param int $id int indicating the variable (bigger than 2!)
*
* @access protected
* @return boolean
*/
protected function setData($data, $id = 2) {
if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false))
return @shm_put_var($this->memid, $id, $data);
return false;
}
/**
* Sets the time when the shared memory block was created
*
* @access private
* @return boolean
*/
private function setInitialCleanTime() {
$stat = false;
// exclusive block
if ($this->blockMutex()) {
if ($this->hasData(1) == false)
$stat = $this->setData(time(), 1);
$this->releaseMutex();
}
// end exclusive block
return $stat;
}
}
?>

View file

@ -0,0 +1,936 @@
<?php
/***********************************************
* File : loopdetection.php
* Project : Z-Push
* Descr : detects an outgoing loop by looking
* if subsequent requests do try to get changes
* for the same sync key. If more than once a synckey
* is requested, the amount of items to be sent to the mobile
* is reduced to one. If then (again) the same synckey is
* requested, we have most probably found the 'broken' item.
*
* Created : 20.10.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class LoopDetection extends InterProcessData {
const INTERPROCESSLD = "ipldkey";
const BROKENMSGS = "bromsgs";
static private $processident;
static private $processentry;
private $ignore_messageid;
private $broken_message_uuid;
private $broken_message_counter;
/**
* Constructor
*
* @access public
*/
public function LoopDetection() {
// initialize super parameters
$this->allocate = 1024000; // 1 MB
$this->type = 1337;
parent::__construct();
$this->ignore_messageid = false;
}
/**
* PROCESS LOOP DETECTION
*/
/**
* Adds the process entry to the process stack
*
* @access public
* @return boolean
*/
public function ProcessLoopDetectionInit() {
return $this->updateProcessStack();
}
/**
* Marks the process entry as termineted successfully on the process stack
*
* @access public
* @return boolean
*/
public function ProcessLoopDetectionTerminate() {
// just to be sure that the entry is there
self::GetProcessEntry();
self::$processentry['end'] = time();
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionTerminate()");
return $this->updateProcessStack();
}
/**
* Returns a unique identifier for the internal process tracking
*
* @access public
* @return string
*/
public static function GetProcessIdentifier() {
if (!isset(self::$processident))
self::$processident = sprintf('%04x%04', mt_rand(0, 0xffff), mt_rand(0, 0xffff));
return self::$processident;
}
/**
* Returns a unique entry with informations about the current process
*
* @access public
* @return array
*/
public static function GetProcessEntry() {
if (!isset(self::$processentry)) {
self::$processentry = array();
self::$processentry['id'] = self::GetProcessIdentifier();
self::$processentry['pid'] = self::$pid;
self::$processentry['time'] = self::$start;
self::$processentry['cc'] = Request::GetCommandCode();
}
return self::$processentry;
}
/**
* Adds an Exceptions to the process tracking
*
* @param Exception $exception
*
* @access public
* @return boolean
*/
public function ProcessLoopDetectionAddException($exception) {
// generate entry if not already there
self::GetProcessEntry();
if (!isset(self::$processentry['stat']))
self::$processentry['stat'] = array();
self::$processentry['stat'][get_class($exception)] = $exception->getCode();
$this->updateProcessStack();
return true;
}
/**
* Adds a folderid and connected status code to the process tracking
*
* @param string $folderid
* @param int $status
*
* @access public
* @return boolean
*/
public function ProcessLoopDetectionAddStatus($folderid, $status) {
// generate entry if not already there
self::GetProcessEntry();
if ($folderid === false)
$folderid = "hierarchy";
if (!isset(self::$processentry['stat']))
self::$processentry['stat'] = array();
self::$processentry['stat'][$folderid] = $status;
$this->updateProcessStack();
return true;
}
/**
* Marks the current process as a PUSH connection
*
* @access public
* @return boolean
*/
public function ProcessLoopDetectionSetAsPush() {
// generate entry if not already there
self::GetProcessEntry();
self::$processentry['push'] = true;
return $this->updateProcessStack();
}
/**
* Indicates if a full Hierarchy Resync is necessary
*
* In some occasions the mobile tries to sync a folder with an invalid/not-existing ID.
* In these cases a status exception like SYNC_STATUS_FOLDERHIERARCHYCHANGED is returned
* so the mobile executes a FolderSync expecting that some action is taken on that folder (e.g. remove).
*
* If the FolderSync is not doing anything relevant, then the Sync is attempted again
* resulting in the same error and looping between these two processes.
*
* This method checks if in the last process stack a Sync and FolderSync were triggered to
* catch the loop at the 2nd interaction (Sync->FolderSync->Sync->FolderSync => ReSync)
* Ticket: https://jira.zarafa.com/browse/ZP-5
*
* @access public
* @return boolean
*
*/
public function ProcessLoopDetectionIsHierarchyResyncRequired() {
$seenFailed = array();
$seenFolderSync = false;
$lookback = self::$start - 600; // look at the last 5 min
foreach ($this->getProcessStack() as $se) {
if ($se['time'] > $lookback && $se['time'] < (self::$start-1)) {
// look for sync command
if (isset($se['stat']) && ($se['cc'] == ZPush::COMMAND_SYNC || $se['cc'] == ZPush::COMMAND_PING)) {
foreach($se['stat'] as $key => $value) {
if (!isset($seenFailed[$key]))
$seenFailed[$key] = 0;
$seenFailed[$key]++;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen command with Exception or folderid '%s' and code '%s'", $key, $value ));
}
}
// look for FolderSync command with previous failed commands
if ($se['cc'] == ZPush::COMMAND_FOLDERSYNC && !empty($seenFailed) && $se['id'] != self::GetProcessIdentifier()) {
// a full folderresync was already triggered
if (isset($se['stat']) && isset($se['stat']['hierarchy']) && $se['stat']['hierarchy'] == SYNC_FSSTATUS_SYNCKEYERROR) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): a full FolderReSync was already requested. Resetting fail counter.");
$seenFailed = array();
}
else {
$seenFolderSync = true;
if (!empty($seenFailed))
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): seen FolderSync after other failing command");
}
}
}
}
$filtered = array();
foreach ($seenFailed as $k => $count) {
if ($count>1)
$filtered[] = $k;
}
if ($seenFolderSync && !empty($filtered)) {
ZLog::Write(LOGLEVEL_INFO, "LoopDetection->ProcessLoopDetectionIsHierarchyResyncRequired(): Potential loop detected. Full hierarchysync indicated.");
return true;
}
return false;
}
/**
* Indicates if a previous process could not be terminated
*
* Checks if there is an end time for the last entry on the stack
*
* @access public
* @return boolean
*
*/
public function ProcessLoopDetectionPreviousConnectionFailed() {
$stack = $this->getProcessStack();
if (count($stack) > 1) {
$se = $stack[0];
if (!isset($se['end']) && $se['cc'] != ZPush::COMMAND_PING && !isset($se['push']) ) {
// there is no end time
ZLog::Write(LOGLEVEL_ERROR, sprintf("LoopDetection->ProcessLoopDetectionPreviousConnectionFailed(): Command '%s' at %s with pid '%d' terminated unexpectedly or is still running.", Utils::GetCommandFromCode($se['cc']), Utils::GetFormattedTime($se['time']), $se['pid']));
ZLog::Write(LOGLEVEL_ERROR, "Please check your logs for this PID and errors like PHP-Fatals or Apache segmentation faults and report your results to the Z-Push dev team.");
}
}
}
/**
* Gets the PID of an outdated search process
*
* Returns false if there isn't any process
*
* @access public
* @return boolean
*
*/
public function ProcessLoopDetectionGetOutdatedSearchPID() {
$stack = $this->getProcessStack();
if (count($stack) > 1) {
$se = $stack[0];
if ($se['cc'] == ZPush::COMMAND_SEARCH) {
return $se['pid'];
}
}
return false;
}
/**
* Inserts or updates the current process entry on the stack
*
* @access private
* @return boolean
*/
private function updateProcessStack() {
// initialize params
$this->InitializeParams();
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, self::INTERPROCESSLD);
$stack = $loopdata[self::$devid][self::$user][self::INTERPROCESSLD];
// insert/update current process entry
$nstack = array();
$updateentry = self::GetProcessEntry();
$found = false;
foreach ($stack as $entry) {
if ($entry['id'] != $updateentry['id']) {
$nstack[] = $entry;
}
else {
$nstack[] = $updateentry;
$found = true;
}
}
if (!$found)
$nstack[] = $updateentry;
if (count($nstack) > 10)
$nstack = array_slice($nstack, -10, 10);
// update loop data
$loopdata[self::$devid][self::$user][self::INTERPROCESSLD] = $nstack;
$ok = $this->setData($loopdata);
$this->releaseMutex();
}
// end exclusive block
return true;
}
/**
* Returns the current process stack
*
* @access private
* @return array
*/
private function getProcessStack() {
// initialize params
$this->InitializeParams();
$stack = array();
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, self::INTERPROCESSLD);
$stack = $loopdata[self::$devid][self::$user][self::INTERPROCESSLD];
$this->releaseMutex();
}
// end exclusive block
return $stack;
}
/**
* TRACKING OF BROKEN MESSAGES
* if a previousily ignored message is streamed again to the device it's tracked here
*
* There are two outcomes:
* - next uuid counter is higher than current -> message is fixed and successfully synchronized
* - next uuid counter is the same or uuid changed -> message is still broken
*/
/**
* Adds a message to the tracking of broken messages
* Being tracked means that a broken message was streamed to the device.
* We save the latest uuid and counter so if on the next sync the counter is higher
* the message was accepted by the device.
*
* @param string $folderid the parent folder of the message
* @param string $id the id of the message
*
* @access public
* @return boolean
*/
public function SetBrokenMessage($folderid, $id) {
if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false)
return false;
$ok = false;
$brokenkey = self::BROKENMSGS ."-". $folderid;
// initialize params
$this->InitializeParams();
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $brokenkey);
$brokenmsgs = $loopdata[self::$devid][self::$user][$brokenkey];
$brokenmsgs[$id] = array('uuid' => $this->broken_message_uuid, 'counter' => $this->broken_message_counter);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetBrokenMessage('%s', '%s'): tracking broken message", $folderid, $id));
// update data
$loopdata[self::$devid][self::$user][$brokenkey] = $brokenmsgs;
$ok = $this->setData($loopdata);
$this->releaseMutex();
}
// end exclusive block
return $ok;
}
/**
* Gets a list of all ids of a folder which were tracked and which were
* accepted by the device from the last sync.
*
* @param string $folderid the parent folder of the message
* @param string $id the id of the message
*
* @access public
* @return array
*/
public function GetSyncedButBeforeIgnoredMessages($folderid) {
if ($folderid == false || !isset($this->broken_message_uuid) || !isset($this->broken_message_counter) || $this->broken_message_uuid == false || $this->broken_message_counter == false)
return array();
$brokenkey = self::BROKENMSGS ."-". $folderid;
$removeIds = array();
$okIds = array();
// initialize params
$this->InitializeParams();
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $brokenkey);
$brokenmsgs = $loopdata[self::$devid][self::$user][$brokenkey];
if (!empty($brokenmsgs)) {
foreach ($brokenmsgs as $id => $data) {
// previously broken message was sucessfully synced!
if ($data['uuid'] == $this->broken_message_uuid && $data['counter'] < $this->broken_message_counter) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): message '%s' was successfully synchronized", $folderid, $id));
$okIds[] = $id;
}
// if the uuid has changed this is old data which should also be removed
if ($data['uuid'] != $this->broken_message_uuid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): stored message id '%s' for uuid '%s' is obsolete", $folderid, $id, $data['uuid']));
$removeIds[] = $id;
}
}
// remove data
foreach (array_merge($okIds,$removeIds) as $id) {
unset($brokenmsgs[$id]);
}
if (empty($brokenmsgs) && isset($loopdata[self::$devid][self::$user][$brokenkey])) {
unset($loopdata[self::$devid][self::$user][$brokenkey]);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->GetSyncedButBeforeIgnoredMessages('%s'): removed folder from tracking of ignored messages", $folderid));
}
else {
// update data
$loopdata[self::$devid][self::$user][$brokenkey] = $brokenmsgs;
}
$ok = $this->setData($loopdata);
}
$this->releaseMutex();
}
// end exclusive block
return $okIds;
}
/**
* Marks a SyncState as "already used", e.g. when an import process started.
* This is most critical for DiffBackends, as an imported message would be exported again
* in the heartbeat if the notification is triggered before the import is complete.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return boolean
*/
public function SetSyncStateUsage($folderid, $uuid, $counter) {
// initialize params
$this->InitializeParams();
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->SetSyncStateUsage(): uuid: %s counter: %d", $uuid, $counter));
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $folderid);
$current = $loopdata[self::$devid][self::$user][$folderid];
// update the usage flag
$current["usage"] = $counter;
// update loop data
$loopdata[self::$devid][self::$user][$folderid] = $current;
$ok = $this->setData($loopdata);
$this->releaseMutex();
}
// end exclusive block
}
/**
* Checks if the given counter for a certain uuid+folderid was exported before.
* Returns also true if the counter are the same but previously there were
* changes to be exported.
*
* @param string $folderid folder id
* @param string $uuid synkkey
* @param string $counter synckey counter
*
* @access public
* @return boolean indicating if an uuid+counter were exported (with changes) before
*/
public function IsSyncStateObsolete($folderid, $uuid, $counter) {
// initialize params
$this->InitializeParams();
$obsolete = false;
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
$this->releaseMutex();
// end exclusive block
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $folderid);
$current = $loopdata[self::$devid][self::$user][$folderid];
if (!empty($current)) {
if (!isset($current["uuid"]) || $current["uuid"] != $uuid) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed or not set");
$obsolete = true;
}
else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IsSyncStateObsolete(): check uuid counter: %d - last known counter: %d with %d queued objects", $counter, $current["count"], $current["queued"]));
if ($current["uuid"] == $uuid && ($current["count"] > $counter || ($current["count"] == $counter && $current["queued"] > 0) || (isset($current["usage"]) && $current["usage"] >= $counter))) {
$usage = isset($current["usage"]) ? sprintf(" - counter %d already expired",$current["usage"]) : "";
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed". $usage);
$obsolete = true;
}
}
}
}
return $obsolete;
}
/**
* MESSAGE LOOP DETECTION
*/
/**
* Loop detection mechanism
*
* 1. request counter is higher than the previous counter (somehow default)
* 1.1) standard situation -> do nothing
* 1.2) loop information exists
* 1.2.1) request counter < maxCounter AND no ignored data -> continue in loop mode
* 1.2.2) request counter < maxCounter AND ignored data -> we have already encountered issue, return to normal
*
* 2. request counter is the same as the previous, but no data was sent on the last request (standard situation)
*
* 3. request counter is the same as the previous and last time objects were sent (loop!)
* 3.1) no loop was detected before, entereing loop mode -> save loop data, loopcount = 1
* 3.2) loop was detected before, but are gone -> loop resolved
* 3.3) loop was detected before, continuing in loop mode -> this is probably the broken element,loopcount++,
* 3.3.1) item identified, loopcount >= 3 -> ignore item, set ignoredata flag
*
* @param string $folderid the current folder id to be worked on
* @param string $type the type of that folder (Email, Calendar, Contact, Task)
* @param string $uuid the synkkey
* @param string $counter the synckey counter
* @param string $maxItems the current amount of items to be sent to the mobile
* @param string $queuedMessages the amount of messages which were found by the exporter
*
* @access public
* @return boolean when returning true if a loop has been identified
*/
public function Detect($folderid, $type, $uuid, $counter, $maxItems, $queuedMessages) {
$this->broken_message_uuid = $uuid;
$this->broken_message_counter = $counter;
// if an incoming loop is already detected, do nothing
if ($maxItems === 0 && $queuedMessages > 0) {
ZPush::GetTopCollector()->AnnounceInformation("Incoming loop!", true);
return true;
}
// initialize params
$this->InitializeParams();
$loop = false;
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $folderid);
$current = $loopdata[self::$devid][self::$user][$folderid];
// completely new/unknown UUID
if (empty($current))
$current = array("type" => $type, "uuid" => $uuid, "count" => $counter-1, "queued" => $queuedMessages);
// old UUID in cache - the device requested a new state!!
else if (isset($current['type']) && $current['type'] == $type && isset($current['uuid']) && $current['uuid'] != $uuid ) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed for folder");
// some devices (iPhones) may request new UUIDs after broken items were sent several times
if (isset($current['queued']) && $current['queued'] > 0 &&
(isset($current['maxCount']) && $current['count']+1 < $current['maxCount'] || $counter == 1)) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): UUID changed and while items where sent to device - forcing loop mode");
$loop = true; // force loop mode
$current['queued'] = $queuedMessages;
}
else {
$current['queued'] = 0;
}
// set new data, unset old loop information
$current["uuid"] = $uuid;
$current['count'] = $counter;
unset($current['loopcount']);
unset($current['ignored']);
unset($current['maxCount']);
unset($current['potential']);
}
// see if there are values
if (isset($current['uuid']) && $current['uuid'] == $uuid &&
isset($current['type']) && $current['type'] == $type &&
isset($current['count'])) {
// case 1 - standard, during loop-resolving & resolving
if ($current['count'] < $counter) {
// case 1.1
$current['count'] = $counter;
$current['queued'] = $queuedMessages;
if (isset($current["usage"]) && $current["usage"] < $current['count'])
unset($current["usage"]);
// case 1.2
if (isset($current['maxCount'])) {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2 detected");
// case 1.2.1
// broken item not identified yet
if (!isset($current['ignored']) && $counter < $current['maxCount']) {
$loop = true; // continue in loop-resolving
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.1 detected");
}
// case 1.2.2 - if there were any broken items they should be gone, return to normal
else {
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 1.2.2 detected");
unset($current['loopcount']);
unset($current['ignored']);
unset($current['maxCount']);
unset($current['potential']);
}
}
}
// case 2 - same counter, but there were no changes before and are there now
else if ($current['count'] == $counter && $current['queued'] == 0 && $queuedMessages > 0) {
$current['queued'] = $queuedMessages;
if (isset($current["usage"]) && $current["usage"] < $current['count'])
unset($current["usage"]);
}
// case 3 - same counter, changes sent before, hanging loop and ignoring
else if ($current['count'] == $counter && $current['queued'] > 0) {
if (!isset($current['loopcount'])) {
// case 3.1) we have just encountered a loop!
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.1 detected - loop detected, init loop mode");
$current['loopcount'] = 1;
// the MaxCount is the max number of messages exported before
$current['maxCount'] = $counter + (($maxItems < $queuedMessages)? $maxItems: $queuedMessages);
$loop = true; // loop mode!!
}
else if ($queuedMessages == 0) {
// case 3.2) there was a loop before but now the changes are GONE
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.2 detected - changes gone - clearing loop data");
$current['queued'] = 0;
unset($current['loopcount']);
unset($current['ignored']);
unset($current['maxCount']);
unset($current['potential']);
}
else {
// case 3.3) still looping the same message! Increase counter
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->Detect(): case 3.3 detected - in loop mode, increase loop counter");
$current['loopcount']++;
// case 3.3.1 - we got our broken item!
if ($current['loopcount'] >= 3 && isset($current['potential'])) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): case 3.3.1 detected - broken item should be next, attempt to ignore it - id '%s'", $current['potential']));
$this->ignore_messageid = $current['potential'];
}
$current['maxCount'] = $counter + $queuedMessages;
$loop = true; // loop mode!!
}
}
}
if (isset($current['loopcount']))
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->Detect(): loop data: loopcount(%d), maxCount(%d), queued(%d), ignored(%s)", $current['loopcount'], $current['maxCount'], $current['queued'], (isset($current['ignored'])?$current['ignored']:'false')));
// update loop data
$loopdata[self::$devid][self::$user][$folderid] = $current;
$ok = $this->setData($loopdata);
$this->releaseMutex();
}
// end exclusive block
if ($loop == true && $this->ignore_messageid == false) {
ZPush::GetTopCollector()->AnnounceInformation("Loop detection", true);
}
return $loop;
}
/**
* Indicates if the next messages should be ignored (not be sent to the mobile!)
*
* @param string $messageid (opt) id of the message which is to be exported next
* @param string $folderid (opt) parent id of the message
* @param boolean $markAsIgnored (opt) to peek without setting the next message to be
* ignored, set this value to false
* @access public
* @return boolean
*/
public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) {
// as the next message id is not available at all point this method is called, we use different indicators.
// potentialbroken indicates that we know that the broken message should be exported next,
// alltho we do not know for sure as it's export message orders can change
// if the $messageid is available and matches then we are sure and only then really ignore it
$potentialBroken = false;
$realBroken = false;
if (Request::GetCommandCode() == ZPush::COMMAND_SYNC && $this->ignore_messageid !== false)
$potentialBroken = true;
if ($messageid !== false && $this->ignore_messageid == $messageid)
$realBroken = true;
// this call is just to know what should be happening
// no further actions necessary
if ($markAsIgnored === false) {
return $potentialBroken;
}
// we should really do something here
// first we check if we are in the loop mode, if so,
// we update the potential broken id message so we loop count the same message
$changedData = false;
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
// check and initialize the array structure
$this->checkArrayStructure($loopdata, $folderid);
$current = $loopdata[self::$devid][self::$user][$folderid];
// we found our broken message!
if ($realBroken) {
$this->ignore_messageid = false;
$current['ignored'] = $messageid;
$changedData = true;
// check if this message was broken before - here we know that it still is and remove it from the tracking
$brokenkey = self::BROKENMSGS ."-". $folderid;
if (isset($loopdata[self::$devid][self::$user][$brokenkey]) && isset($loopdata[self::$devid][self::$user][$brokenkey][$messageid])) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): previously broken message '%s' is still broken and will not be tracked anymore", $messageid));
unset($loopdata[self::$devid][self::$user][$brokenkey][$messageid]);
}
}
// not the broken message yet
else {
// update potential id if looping on an item
if (isset($current['loopcount'])) {
$current['potential'] = $messageid;
// this message should be the broken one, but is not!!
// we should reset the loop count because this is certainly not the broken one
if ($potentialBroken) {
$current['loopcount'] = 1;
ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count.");
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $current['potential']));
$changedData = true;
}
}
// update loop data
if ($changedData == true) {
$loopdata[self::$devid][self::$user][$folderid] = $current;
$ok = $this->setData($loopdata);
}
$this->releaseMutex();
}
// end exclusive block
if ($realBroken)
ZPush::GetTopCollector()->AnnounceInformation("Broken message ignored", true);
return $realBroken;
}
/**
* Clears loop detection data
*
* @param string $user (opt) user which data should be removed - user can not be specified without
* @param string $devid (opt) device id which data to be removed
*
* @return boolean
* @access public
*/
public function ClearData($user = false, $devid = false) {
$stat = true;
$ok = false;
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
if ($user == false && $devid == false)
$loopdata = array();
elseif ($user == false && $devid != false)
$loopdata[$devid] = array();
elseif ($user != false && $devid != false)
$loopdata[$devid][$user] = array();
elseif ($user != false && $devid == false) {
ZLog::Write(LOGLEVEL_WARN, sprintf("Not possible to reset loop detection data for user '%s' without a specifying a device id", $user));
$stat = false;
}
if ($stat)
$ok = $this->setData($loopdata);
$this->releaseMutex();
}
// end exclusive block
return $stat && $ok;
}
/**
* Returns loop detection data for a user and device
*
* @param string $user
* @param string $devid
*
* @return array/boolean returns false if data not available
* @access public
*/
public function GetCachedData($user, $devid) {
// exclusive block
if ($this->blockMutex()) {
$loopdata = ($this->hasData()) ? $this->getData() : array();
$this->releaseMutex();
}
// end exclusive block
if (isset($loopdata) && isset($loopdata[$devid]) && isset($loopdata[$devid][$user]))
return $loopdata[$devid][$user];
return false;
}
/**
* Builds an array structure for the loop detection data
*
* @param array $loopdata reference to the topdata array
*
* @access private
* @return
*/
private function checkArrayStructure(&$loopdata, $folderid) {
if (!isset($loopdata) || !is_array($loopdata))
$loopdata = array();
if (!isset($loopdata[self::$devid]))
$loopdata[self::$devid] = array();
if (!isset($loopdata[self::$devid][self::$user]))
$loopdata[self::$devid][self::$user] = array();
if (!isset($loopdata[self::$devid][self::$user][$folderid]))
$loopdata[self::$devid][self::$user][$folderid] = array();
}
}
?>

View file

@ -0,0 +1,101 @@
<?php
/***********************************************
* File : paddingfilter.php
* Project : Z-Push
* Descr : Our own filter for stream padding with zero strings.
*
* Created : 18.07.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/* Define our filter class
*
* Usage: stream_filter_append($stream, 'padding.X');
* where X is a number a stream will be padded to be
* multiple of (e.g. padding.3 will pad the stream
* to be multiple of 3 which is useful in base64
* encoding).
*
* */
class padding_filter extends php_user_filter {
private $padding = 4; // default padding
/**
* This method is called whenever data is read from or written to the attached stream
*
* @see php_user_filter::filter()
*
* @param resource $in
* @param resource $out
* @param int $consumed
* @param boolean $closing
*
* @access public
* @return int
*
*/
function filter($in, $out, &$consumed, $closing) {
while ($bucket = stream_bucket_make_writeable($in)) {
if ($this->padding != 0 && $bucket->datalen < 8192) {
$bucket->data .= str_pad($bucket->data, $this->padding, 0x0);
}
$consumed += ($this->padding != 0 && $bucket->datalen < 8192) ? ($bucket->datalen + $this->padding) : $bucket->datalen;
stream_bucket_append($out, $bucket);
}
return PSFS_PASS_ON;
}
/**
* Called when creating the filter
*
* @see php_user_filter::onCreate()
*
* @access public
* @return boolean
*/
function onCreate() {
$delim = strrpos($this->filtername, '.');
if ($delim !== false) {
$padding = substr($this->filtername, $delim + 1);
if (is_numeric($padding))
$this->padding = $padding;
}
return true;
}
}
stream_filter_register("padding.*", "padding_filter");
?>

View file

@ -0,0 +1,156 @@
<?php
/***********************************************
* File : pingtracking.php
* Project : Z-Push
* Descr :
*
* Created : 20.10.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class PingTracking extends InterProcessData {
/**
* Constructor
*
* @access public
*/
public function PingTracking() {
// initialize super parameters
$this->allocate = 512000; // 500 KB
$this->type = 2;
parent::__construct();
$this->initPing();
}
/**
* Destructor
* Used to remove the current ping data from shared memory
*
* @access public
*/
public function __destruct() {
// exclusive block
if ($this->blockMutex()) {
$pings = $this->getData();
// check if our ping is still in the list
if (isset($pings[self::$devid][self::$user][self::$pid])) {
unset($pings[self::$devid][self::$user][self::$pid]);
$stat = $this->setData($pings);
}
$this->releaseMutex();
}
// end exclusive block
}
/**
* Initialized the current request
*
* @access public
* @return boolean
*/
protected function initPing() {
$stat = false;
// initialize params
$this->InitializeParams();
// exclusive block
if ($this->blockMutex()) {
$pings = ($this->hasData()) ? $this->getData() : array();
// set the start time for the current process
$this->checkArrayStructure($pings);
$pings[self::$devid][self::$user][self::$pid] = self::$start;
$stat = $this->setData($pings);
$this->releaseMutex();
}
// end exclusive block
return $stat;
}
/**
* Checks if there are newer ping requests for the same device & user so
* the current process could be terminated
*
* @access public
* @return boolean true if the current process is obsolete
*/
public function DoForcePingTimeout() {
$pings = false;
// exclusive block
if ($this->blockMutex()) {
$pings = $this->getData();
$this->releaseMutex();
}
// end exclusive block
// check if there is another (and newer) active ping connection
if (is_array($pings) && isset($pings[self::$devid][self::$user]) && count($pings[self::$devid][self::$user]) > 1) {
foreach ($pings[self::$devid][self::$user] as $pid=>$starttime)
if ($starttime > self::$start)
return true;
}
return false;
}
/**
* Builds an array structure for the concurrent ping connection detection
*
* @param array $array reference to the ping data array
*
* @access private
* @return
*/
private function checkArrayStructure(&$array) {
if (!is_array($array))
$array = array();
if (!isset($array[self::$devid]))
$array[self::$devid] = array();
if (!isset($array[self::$devid][self::$user]))
$array[self::$devid][self::$user] = array();
}
}
?>

View file

@ -0,0 +1,540 @@
<?php
/***********************************************
* File : statemanager.php
* Project : Z-Push
* Descr : The StateManager uses a IStateMachine
* implementation to save data.
* SyncKey's are of the form {UUID}N, in
* which UUID is allocated during the
* first sync, and N is incremented
* for each request to 'GetNewSyncKey()'.
* A sync state is simple an opaque
* string value that can differ
* for each backend used - normally
* a list of items as the backend has
* sent them to the PIM. The backend
* can then use this backend
* information to compute the increments
* with current data.
* See FileStateMachine and IStateMachine
* for additional information.
*
* Created : 26.12.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StateManager {
const FIXEDHIERARCHYCOUNTER = 99999;
// backend storage types
const BACKENDSTORAGE_PERMANENT = 1;
const BACKENDSTORAGE_STATE = 2;
private $statemachine;
private $device;
private $hierarchyOperation = false;
private $deleteOldStates = false;
private $foldertype;
private $uuid;
private $oldStateCounter;
private $newStateCounter;
private $synchedFolders;
/**
* Constructor
*
* @access public
*/
public function StateManager() {
$this->statemachine = ZPush::GetStateMachine();
$this->hierarchyOperation = ZPush::HierarchyCommand(Request::GetCommandCode());
$this->deleteOldStates = (Request::GetCommandCode() === ZPush::COMMAND_SYNC || $this->hierarchyOperation);
$this->synchedFolders = array();
}
/**
* Prevents the StateMachine from removing old states
*
* @access public
* @return void
*/
public function DoNotDeleteOldStates() {
$this->deleteOldStates = false;
}
/**
* Sets an ASDevice for the Statemanager to work with
*
* @param ASDevice $device
*
* @access public
* @return boolean
*/
public function SetDevice(&$device) {
$this->device = $device;
return true;
}
/**
* Returns an array will all synchronized folderids
*
* @access public
* @return array
*/
public function GetSynchedFolders() {
$synched = array();
foreach ($this->device->GetAllFolderIds() as $folderid) {
$uuid = $this->device->GetFolderUUID($folderid);
if ($uuid)
$synched[] = $folderid;
}
return $synched;
}
/**
* Returns a folder state (SyncParameters) for a folder id
*
* @param $folderid
*
* @access public
* @return SyncParameters
*/
public function GetSynchedFolderState($folderid) {
// new SyncParameters are cached
if (isset($this->synchedFolders[$folderid]))
return $this->synchedFolders[$folderid];
$uuid = $this->device->GetFolderUUID($folderid);
if ($uuid) {
try {
$data = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $uuid);
if ($data !== false) {
$this->synchedFolders[$folderid] = $data;
}
}
catch (StateNotFoundException $ex) { }
}
if (!isset($this->synchedFolders[$folderid]))
$this->synchedFolders[$folderid] = new SyncParameters();
return $this->synchedFolders[$folderid];
}
/**
* Saves a folder state - SyncParameters object
*
* @param SyncParamerters $spa
*
* @access public
* @return boolean
*/
public function SetSynchedFolderState($spa) {
// make sure the current uuid is linked on the device for the folder.
// if not, old states will be automatically removed and the new ones linked
self::LinkState($this->device, $spa->GetUuid(), $spa->GetFolderId());
$spa->SetReferencePolicyKey($this->device->GetPolicyKey());
return $this->statemachine->SetState($spa, $this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $spa->GetUuid());
}
/**
* Gets the new sync key for a specified sync key. The new sync state must be
* associated to this sync key when calling SetSyncState()
*
* @param string $synckey
*
* @access public
* @return string
*/
function GetNewSyncKey($synckey) {
if(!isset($synckey) || $synckey == "0" || $synckey == false) {
$this->uuid = $this->getNewUuid();
$this->newStateCounter = 1;
}
else {
list($uuid, $counter) = self::ParseStateKey($synckey);
$this->uuid = $uuid;
$this->newStateCounter = $counter + 1;
}
return self::BuildStateKey($this->uuid, $this->newStateCounter);
}
/**
* Gets the state for a specified synckey (uuid + counter)
*
* @param string $synckey
*
* @access public
* @return string
* @throws StateInvalidException, StateNotFoundException
*/
public function GetSyncState($synckey) {
// No sync state for sync key '0'
if($synckey == "0") {
$this->oldStateCounter = 0;
return "";
}
// Check if synckey is allowed and set uuid and counter
list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey);
// make sure the hierarchy cache is in place
if ($this->hierarchyOperation)
$this->loadHierarchyCache();
// the state machine will discard any sync states before this one, as they are no longer required
return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates);
}
/**
* Writes the sync state to a new synckey
*
* @param string $synckey
* @param string $syncstate
* @param string $folderid (opt) the synckey is associated with the folder - should always be set when performing CONTENT operations
*
* @access public
* @return boolean
* @throws StateInvalidException
*/
public function SetSyncState($synckey, $syncstate, $folderid = false) {
$internalkey = self::BuildStateKey($this->uuid, $this->newStateCounter);
if ($this->oldStateCounter != 0 && $synckey != $internalkey)
throw new StateInvalidException(sprintf("Unexpected synckey value oldcounter: '%s' synckey: '%s' internal key: '%s'", $this->oldStateCounter, $synckey, $internalkey));
// make sure the hierarchy cache is also saved
if ($this->hierarchyOperation)
$this->saveHierarchyCache();
// announce this uuid to the device, while old uuid/states should be deleted
self::LinkState($this->device, $this->uuid, $folderid);
return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->newStateCounter);
}
/**
* Gets the failsave sync state for the current synckey
*
* @access public
* @return array/boolean false if not available
*/
public function GetSyncFailState() {
if (!$this->uuid)
return false;
try {
return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates);
}
catch (StateNotFoundException $snfex) {
return false;
}
}
/**
* Writes the failsave sync state for the current (old) synckey
*
* @param mixed $syncstate
*
* @access public
* @return boolean
*/
public function SetSyncFailState($syncstate) {
if ($this->oldStateCounter == 0)
return false;
return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter);
}
/**
* Gets the backendstorage data
*
* @param int $type permanent or state related storage
*
* @access public
* @return mixed
* @throws StateNotYetAvailableException, StateNotFoundException
*/
public function GetBackendStorage($type = self::BACKENDSTORAGE_PERMANENT) {
if ($type == self::BACKENDSTORAGE_STATE) {
if (!$this->uuid)
throw new StateNotYetAvailableException();
return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates);
}
else {
return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime());
}
}
/**
* Writes the backendstorage data
*
* @param mixed $data
* @param int $type permanent or state related storage
*
* @access public
* @return int amount of bytes saved
* @throws StateNotYetAvailableException, StateNotFoundException
*/
public function SetBackendStorage($data, $type = self::BACKENDSTORAGE_PERMANENT) {
if ($type == self::BACKENDSTORAGE_STATE) {
if (!$this->uuid)
throw new StateNotYetAvailableException();
// TODO serialization should be done in the StateMachine
return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->newStateCounter);
}
else {
return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime());
}
}
/**
* Initializes the HierarchyCache for legacy syncs
* this is for AS 1.0 compatibility:
* save folder information synched with GetHierarchy()
* handled by StateManager
*
* @param string $folders Array with folder information
*
* @access public
* @return boolean
*/
public function InitializeFolderCache($folders) {
if (!is_array($folders))
return false;
if (!isset($this->device))
throw new FatalException("ASDevice not initialized");
// redeclare this operation as hierarchyOperation
$this->hierarchyOperation = true;
// as there is no hierarchy uuid, we have to create one
$this->uuid = $this->getNewUuid();
$this->newStateCounter = self::FIXEDHIERARCHYCOUNTER;
// initialize legacy HierarchCache
$this->device->SetHierarchyCache($folders);
// force saving the hierarchy cache!
return $this->saveHierarchyCache(true);
}
/**----------------------------------------------------------------------------------------------------------
* static StateManager methods
*/
/**
* Links a folderid to the a UUID
* Old states are removed if an folderid is linked to a new UUID
* assisting the StateMachine to get rid of old data.
*
* @param ASDevice $device
* @param string $uuid the uuid to link to
* @param string $folderid (opt) if not set, hierarchy state is linked
*
* @access public
* @return boolean
*/
static public function LinkState(&$device, $newUuid, $folderid = false) {
$savedUuid = $device->GetFolderUUID($folderid);
// delete 'old' states!
if ($savedUuid != $newUuid) {
// remove states but no need to notify device
self::UnLinkState($device, $folderid, false);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::linkState(#ASDevice, '%s','%s'): linked to uuid '%s'.", $newUuid, (($folderid === false)?'HierarchyCache':$folderid), $newUuid));
return $device->SetFolderUUID($newUuid, $folderid);
}
return true;
}
/**
* UnLinks all states from a folder id
* Old states are removed assisting the StateMachine to get rid of old data.
* The UUID is then removed from the device
*
* @param ASDevice $device
* @param string $folderid
* @param boolean $removeFromDevice indicates if the device should be
* notified that the state was removed
* @param boolean $retrieveUUIDFromDevice indicates if the UUID should be retrieved from
* device. If not true this parameter will be used as UUID.
*
* @access public
* @return boolean
*/
static public function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) {
if ($retrieveUUIDFromDevice === true)
$savedUuid = $device->GetFolderUUID($folderid);
else
$savedUuid = $retrieveUUIDFromDevice;
if ($savedUuid) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::UnLinkState('%s'): saved state '%s' will be deleted.", $folderid, $savedUuid));
ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::DEFTYPE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2);
ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FOLDERDATA, $savedUuid); // CPO
ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FAILSAVE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2);
ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2);
// remove all messages which could not be synched before
$device->RemoveIgnoredMessage($folderid, false);
if ($folderid === false && $savedUuid !== false)
ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::HIERARCHY, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2);
}
// delete this id from the uuid cache
if ($removeFromDevice)
return $device->SetFolderUUID(false, $folderid);
else
return true;
}
/**
* Parses a SyncKey and returns UUID and counter
*
* @param string $synckey
*
* @access public
* @return array uuid, counter
* @throws StateInvalidException
*/
static public function ParseStateKey($synckey) {
$matches = array();
if(!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $synckey, $matches))
throw new StateInvalidException(sprintf("SyncKey '%s' is invalid", $synckey));
return array($matches[1], (int)$matches[2]);
}
/**
* Builds a SyncKey from a UUID and counter
*
* @param string $uuid
* @param int $counter
*
* @access public
* @return string syncKey
* @throws StateInvalidException
*/
static public function BuildStateKey($uuid, $counter) {
if(!preg_match('/^([0-9A-Za-z-]+)$/', $uuid, $matches))
throw new StateInvalidException(sprintf("UUID '%s' is invalid", $uuid));
return "{" . $uuid . "}" . $counter;
}
/**----------------------------------------------------------------------------------------------------------
* private StateManager methods
*/
/**
* Loads the HierarchyCacheState and initializes the HierarchyChache
* if this is an hierarchy operation
*
* @access private
* @return boolean
* @throws StateNotFoundException
*/
private function loadHierarchyCache() {
if (!$this->hierarchyOperation)
return false;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter));
// check if a full hierarchy sync might be necessary
if ($this->device->GetFolderUUID(false) === false) {
self::UnLinkState($this->device, false, false, $this->uuid);
throw new StateNotFoundException("No hierarchy UUID linked to device. Requesting folder resync.");
}
$hierarchydata = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid , $this->oldStateCounter, $this->deleteOldStates);
$this->device->SetHierarchyCache($hierarchydata);
return true;
}
/**
* Saves the HierarchyCacheState of the HierarchyChache
* if this is an hierarchy operation
*
* @param boolean $forceLoad indicates if the cache should be saved also if not a hierary operation
*
* @access private
* @return boolean
* @throws StateInvalidException
*/
private function saveHierarchyCache($forceSaving = false) {
if (!$this->hierarchyOperation && !$forceSaving)
return false;
// link the hierarchy cache again, if the UUID does not match the UUID saved in the devicedata
if (($this->uuid != $this->device->GetFolderUUID() || $forceSaving) )
self::LinkState($this->device, $this->uuid);
// check all folders and deleted folders to update data of ASDevice and delete old states
$hc = $this->device->getHierarchyCache();
foreach ($hc->GetDeletedFolders() as $delfolder)
self::UnLinkState($this->device, $delfolder->serverid);
foreach ($hc->ExportFolders() as $folder)
$this->device->SetFolderType($folder->serverid, $folder->type);
return $this->statemachine->SetState($this->device->GetHierarchyCacheData(), $this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->newStateCounter);
}
/**
* Generates a new UUID
*
* @access private
* @return string
*/
private function getNewUuid() {
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) );
}
}
?>

View file

@ -0,0 +1,268 @@
<?php
/***********************************************
* File : stateobject.php
* Project : Z-Push
* Descr : simple data object with some php magic
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StateObject implements Serializable {
private $SO_internalid;
protected $data = array();
protected $unsetdata = array();
protected $changed = false;
/**
* Returns the unique id of that data object
*
* @access public
* @return array
*/
public function GetID() {
if (!isset($this->SO_internalid))
$this->SO_internalid = sprintf('%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff));
return $this->SO_internalid;
}
/**
* Returns the internal array which contains all data of this object
*
* @access public
* @return array
*/
public function GetDataArray() {
return $this->data;
}
/**
* Sets the internal array which contains all data of this object
*
* @param array $data the data to be written
* @param boolean $markAsChanged (opt) indicates if the object should be marked as "changed", default false
*
* @access public
* @return array
*/
public function SetDataArray($data, $markAsChanged = false) {
$this->data = $data;
$this->changed = $markAsChanged;
}
/**
* Indicates if the data contained in this object was modified
*
* @access public
* @return array
*/
public function IsDataChanged() {
return $this->changed;
}
/**
* PHP magic to set an instance variable
*
* @access public
* @return
*/
public function __set($name, $value) {
$lname = strtolower($name);
if (isset($this->data[$lname]) && is_scalar($value) && !is_array($value) && $this->data[$lname] === $value)
return false;
$this->data[$lname] = $value;
$this->changed = true;
}
/**
* PHP magic to get an instance variable
* if the variable was not set previousely, the value of the
* Unsetdata array is returned
*
* @access public
* @return
*/
public function __get($name) {
$lname = strtolower($name);
if (array_key_exists($lname, $this->data))
return $this->data[$lname];
if (isset($this->unsetdata) && is_array($this->unsetdata) && array_key_exists($lname, $this->unsetdata))
return $this->unsetdata[$lname];
return null;
}
/**
* PHP magic to check if an instance variable is set
*
* @access public
* @return
*/
public function __isset($name) {
return isset($this->data[strtolower($name)]);
}
/**
* PHP magic to remove an instance variable
*
* @access public
* @return
*/
public function __unset($name) {
if (isset($this->$name)) {
unset($this->data[strtolower($name)]);
$this->changed = true;
}
}
/**
* PHP magic to implement any getter, setter, has and delete operations
* on an instance variable.
* Methods like e.g. "SetVariableName($x)" and "GetVariableName()" are supported
*
* @access public
* @return mixed
*/
public function __call($name, $arguments) {
$name = strtolower($name);
$operator = substr($name, 0,3);
$var = substr($name,3);
if ($operator == "set" && count($arguments) == 1){
$this->$var = $arguments[0];
return true;
}
if ($operator == "set" && count($arguments) == 2 && $arguments[1] === false){
$this->data[$var] = $arguments[0];
return true;
}
// getter without argument = return variable, null if not set
if ($operator == "get" && count($arguments) == 0) {
return $this->$var;
}
// getter with one argument = return variable if set, else the argument
else if ($operator == "get" && count($arguments) == 1) {
if (isset($this->$var)) {
return $this->$var;
}
else
return $arguments[0];
}
if ($operator == "has" && count($arguments) == 0)
return isset($this->$var);
if ($operator == "del" && count($arguments) == 0) {
unset($this->$var);
return true;
}
throw new FatalNotImplementedException(sprintf("StateObject->__call('%s'): not implemented. op: {$operator} args:". count($arguments), $name));
}
/**
* Method to serialize a StateObject
*
* @access public
* @return array
*/
public function serialize() {
// perform tasks just before serialization
$this->preSerialize();
return serialize(array($this->SO_internalid,$this->data));
}
/**
* Method to unserialize a StateObject
*
* @access public
* @return array
* @throws StateInvalidException
*/
public function unserialize($data) {
// throw a StateInvalidException if unserialize fails
ini_set('unserialize_callback_func', 'StateObject::ThrowStateInvalidException');
list($this->SO_internalid, $this->data) = unserialize($data);
// perform tasks just after unserialization
$this->postUnserialize();
return true;
}
/**
* Called before the StateObject is serialized
*
* @access protected
* @return boolean
*/
protected function preSerialize() {
// make sure the object has an id before serialization
$this->GetID();
return true;
}
/**
* Called after the StateObject was unserialized
*
* @access protected
* @return boolean
*/
protected function postUnserialize() {
return true;
}
/**
* Callback function for failed unserialize
*
* @access public
* @throws StateInvalidException
*/
public static function ThrowStateInvalidException() {
throw new StateInvalidException("Unserialization failed as class was not found or not compatible");
}
}
?>

View file

@ -0,0 +1,465 @@
<?php
/***********************************************
* File : streamer.php
* Project : Z-Push
* Descr : This file handles streaming of
* WBXML SyncObjects. It must be
* subclassed so the internals of
* the object can be specified via
* $mapping. Basically we set/read
* the object variables of the
* subclass according to the mappings
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class Streamer implements Serializable {
const STREAMER_VAR = 1;
const STREAMER_ARRAY = 2;
const STREAMER_TYPE = 3;
const STREAMER_PROP = 4;
const STREAMER_TYPE_DATE = 1;
const STREAMER_TYPE_HEX = 2;
const STREAMER_TYPE_DATE_DASHES = 3;
const STREAMER_TYPE_STREAM = 4;
const STREAMER_TYPE_IGNORE = 5;
const STREAMER_TYPE_SEND_EMPTY = 6;
const STREAMER_TYPE_NO_CONTAINER = 7;
const STREAMER_TYPE_COMMA_SEPARATED = 8;
const STREAMER_TYPE_SEMICOLON_SEPARATED = 9;
const STREAMER_TYPE_MULTIPART = 10;
protected $mapping;
public $flags;
public $content;
/**
* Constructor
*
* @param array $mapping internal mapping of variables
* @access public
*/
function Streamer($mapping) {
$this->mapping = $mapping;
$this->flags = false;
}
/**
* Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML.
* This means that if there are multiple objects at this level, then only the first is
* decoded SubOjects are auto-instantiated and decoded using the same functionality
*
* @param WBXMLDecoder $decoder
*
* @access public
*/
public function Decode(&$decoder) {
while(1) {
$entity = $decoder->getElement();
if($entity[EN_TYPE] == EN_TYPE_STARTTAG) {
if(! ($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) {
$map = $this->mapping[$entity[EN_TAG]];
if (isset($map[self::STREAMER_ARRAY])) {
$this->$map[self::STREAMER_VAR] = array();
} else if(!isset($map[self::STREAMER_TYPE])) {
$this->$map[self::STREAMER_VAR] = "";
}
else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) {
$this->$map[self::STREAMER_VAR] = "";
}
else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
$this->$map[self::STREAMER_VAR] = "";
continue;
}
// Found a start tag
if(!isset($this->mapping[$entity[EN_TAG]])) {
// This tag shouldn't be here, abort
ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this)));
return false;
}
else {
$map = $this->mapping[$entity[EN_TAG]];
// Handle an array
if(isset($map[self::STREAMER_ARRAY])) {
while(1) {
//do not get start tag for an array without a container
if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
if(!$decoder->getElementStartTag($map[self::STREAMER_ARRAY]))
break;
}
if(isset($map[self::STREAMER_TYPE])) {
$decoded = new $map[self::STREAMER_TYPE];
$decoded->Decode($decoder);
}
else {
$decoded = $decoder->getElementContent();
}
if(!isset($this->$map[self::STREAMER_VAR]))
$this->$map[self::STREAMER_VAR] = array($decoded);
else
array_push($this->$map[self::STREAMER_VAR], $decoded);
if(!$decoder->getElementEndTag()) //end tag of a container element
return false;
if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) {
$e = $decoder->peek();
//go back to the initial while if another block of no container elements is found
if ($e[EN_TYPE] == EN_TYPE_STARTTAG) {
continue 2;
}
//break on end tag because no container elements block end is reached
if ($e[EN_TYPE] == EN_TYPE_ENDTAG)
break;
if (empty($e))
break;
}
}
//do not get end tag for an array without a container
if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) {
if(!$decoder->getElementEndTag()) //end tag of container
return false;
}
}
else { // Handle single value
if(isset($map[self::STREAMER_TYPE])) {
// Complex type, decode recursively
if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) {
$decoded = $this->parseDate($decoder->getElementContent());
if(!$decoder->getElementEndTag())
return false;
}
else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
$decoded = hex2bin($decoder->getElementContent());
if(!$decoder->getElementEndTag())
return false;
}
// explode comma or semicolon strings into arrays
else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED) {
$glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; ";
$decoded = explode($glue, $decoder->getElementContent());
if(!$decoder->getElementEndTag())
return false;
}
else {
$subdecoder = new $map[self::STREAMER_TYPE]();
if($subdecoder->Decode($decoder) === false)
return false;
$decoded = $subdecoder;
if(!$decoder->getElementEndTag()) {
ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG]));
return false;
}
}
}
else {
// Simple type, just get content
$decoded = $decoder->getElementContent();
if($decoded === false) {
// the tag is declared to have content, but no content is available.
// set an empty content
$decoded = "";
}
if(!$decoder->getElementEndTag()) {
ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG]));
return false;
}
}
// $decoded now contains data object (or string)
$this->$map[self::STREAMER_VAR] = $decoded;
}
}
}
else if($entity[EN_TYPE] == EN_TYPE_ENDTAG) {
$decoder->ungetElement($entity);
break;
}
else {
ZLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type");
break;
}
}
}
/**
* Encodes this object and any subobjects - output is ordered according to mapping
*
* @param WBXMLEncoder $encoder
*
* @access public
*/
public function Encode(&$encoder) {
// A return value if anything was streamed. We need for empty tags.
$streamed = false;
foreach($this->mapping as $tag => $map) {
if(isset($this->$map[self::STREAMER_VAR])) {
// Variable is available
if(is_object($this->$map[self::STREAMER_VAR])) {
// Subobjects can do their own encoding
if ($this->$map[self::STREAMER_VAR] instanceof Streamer) {
$encoder->startTag($tag);
$res = $this->$map[self::STREAMER_VAR]->Encode($encoder);
$encoder->endTag();
// nothing was streamed in previous encode but it should be streamed empty anyway
if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
$encoder->startTag($tag, false, true);
}
else
ZLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this)));
}
// Array of objects
else if(isset($map[self::STREAMER_ARRAY])) {
if (empty($this->$map[self::STREAMER_VAR]) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) {
$encoder->startTag($tag, false, true);
}
else {
// Outputs array container (eg Attachments)
// Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER
if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
$encoder->startTag($tag);
foreach ($this->$map[self::STREAMER_VAR] as $element) {
if(is_object($element)) {
$encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment)
$element->Encode($encoder);
$encoder->endTag();
}
else {
if(strlen($element) == 0)
// Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true);
;
else {
$encoder->startTag($map[self::STREAMER_ARRAY]);
$encoder->content($element);
$encoder->endTag();
$streamed = true;
}
}
}
if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER)
$encoder->endTag();
}
}
else {
if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) {
continue;
}
if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) {
$encoder->addBodypartStream($this->$map[self::STREAMER_VAR]);
$encoder->startTag(SYNC_ITEMOPERATIONS_PART);
$encoder->content(count($encoder->getBodypartsCount()));
$encoder->endTag();
continue;
}
// Simple type
if(!isset($map[self::STREAMER_TYPE]) && strlen($this->$map[self::STREAMER_VAR]) == 0) {
// send empty tags
if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY)
$encoder->startTag($tag, false, true);
// Do not output empty items. See above: $encoder->startTag($tag, false, true);
continue;
} else
$encoder->startTag($tag);
if(isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) {
if($this->$map[self::STREAMER_VAR] != 0) // don't output 1-1-1970
$encoder->content($this->formatDate($this->$map[self::STREAMER_VAR], $map[self::STREAMER_TYPE]));
}
else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) {
$encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR])));
}
else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) {
//encode stream with base64
$stream = $this->$map[self::STREAMER_VAR];
$stat = fstat($stream);
// the padding size muss be calculated for the entire stream,
// the base64 filter seems to process 8192 byte chunks correctly itself
$padding = (isset($stat['size']) && $stat['size'] > 8192) ? ($stat['size'] % 3) : 0;
$paddingfilter = stream_filter_append($stream, 'padding.'.$padding);
$base64filter = stream_filter_append($stream, 'convert.base64-encode');
$d = "";
while (!feof($stream)) {
$d .= fgets($stream, 4096);
}
$encoder->content($d);
stream_filter_remove($base64filter);
stream_filter_remove($paddingfilter);
}
// implode comma or semicolon arrays into a string
else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) &&
($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) {
$glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; ";
$encoder->content(implode($glue, $this->$map[self::STREAMER_VAR]));
}
else {
$encoder->content($this->$map[self::STREAMER_VAR]);
}
$encoder->endTag();
$streamed = true;
}
}
}
// Output our own content
if(isset($this->content))
$encoder->content($this->content);
return $streamed;
}
/**
* Removes not necessary data from the object
*
* @access public
* @return boolean
*/
public function StripData() {
foreach ($this->mapping as $k=>$v) {
if (isset($this->$v[self::STREAMER_VAR])) {
if (is_object($this->$v[self::STREAMER_VAR]) && method_exists($this->$v[self::STREAMER_VAR], "StripData") ) {
$this->$v[self::STREAMER_VAR]->StripData();
}
else if (isset($v[self::STREAMER_ARRAY]) && !empty($this->$v[self::STREAMER_VAR])) {
foreach ($this->$v[self::STREAMER_VAR] as $element) {
if (is_object($element) && method_exists($element, "StripData") ) {
$element->StripData();
}
}
}
}
}
unset($this->mapping);
return true;
}
/**
* Method to serialize a Streamer and respective SyncObject
*
* @access public
* @return array
*/
public function serialize() {
$values = array();
foreach ($this->mapping as $k=>$v) {
if (isset($this->$v[self::STREAMER_VAR]))
$values[$v[self::STREAMER_VAR]] = serialize($this->$v[self::STREAMER_VAR]);
}
return serialize($values);
}
/**
* Method to unserialize a Streamer and respective SyncObject
*
* @access public
* @return array
*/
public function unserialize($data) {
$class = get_class($this);
$this->$class();
$values = unserialize($data);
foreach ($values as $k=>$v)
$this->$k = unserialize($v);
return true;
}
/**----------------------------------------------------------------------------------------------------------
* Private methods for conversion
*/
/**
* Formats a timestamp
* Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items
* and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for
* exchange' depends on this quirk. So we have to send a different date type depending on where
* it's used. Sigh.
*
* @param long $ts
* @param int $type
*
* @access private
* @return string
*/
private function formatDate($ts, $type) {
if($type == self::STREAMER_TYPE_DATE)
return gmstrftime("%Y%m%dT%H%M%SZ", $ts);
else if($type == self::STREAMER_TYPE_DATE_DASHES)
return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts);
}
/**
* Transforms an AS timestamp into a unix timestamp
*
* @param string $ts
*
* @access private
* @return long
*/
function parseDate($ts) {
if(preg_match("/(\d{4})[^0-9]*(\d{2})[^0-9]*(\d{2})(T(\d{2})[^0-9]*(\d{2})[^0-9]*(\d{2})(.\d+)?Z){0,1}$/", $ts, $matches)) {
if ($matches[1] >= 2038){
$matches[1] = 2038;
$matches[2] = 1;
$matches[3] = 18;
$matches[5] = $matches[6] = $matches[7] = 0;
}
if (!isset($matches[5])) $matches[5] = 0;
if (!isset($matches[6])) $matches[6] = 0;
if (!isset($matches[7])) $matches[7] = 0;
return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]);
}
return 0;
}
}
?>

View file

@ -0,0 +1,261 @@
<?php
/***********************************************
* File : streamimporter.php
* Project : Z-Push
* Descr : sends changes directly to the wbxml stream
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ImportChangesStream implements IImportChanges {
private $encoder;
private $objclass;
private $seenObjects;
private $importedMsgs;
private $checkForIgnoredMessages;
/**
* Constructor of the StreamImporter
*
* @param WBXMLEncoder $encoder Objects are streamed to this encoder
* @param SyncObject $class SyncObject class (only these are accepted when streaming content messages)
*
* @access public
*/
public function ImportChangesStream(&$encoder, $class) {
$this->encoder = &$encoder;
$this->objclass = $class;
$this->classAsString = (is_object($class))?get_class($class):'';
$this->seenObjects = array();
$this->importedMsgs = 0;
$this->checkForIgnoredMessages = true;
}
/**
* Implement interface - never used
*/
public function Config($state, $flags = 0) { return true; }
public function ConfigContentParameters($contentparameters) { return true; }
public function GetState() { return false;}
public function LoadConflicts($contentparameters, $state) { return true; }
/**
* Imports a single message
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean
*/
public function ImportMessageChange($id, $message) {
// ignore other SyncObjects
if(!($message instanceof $this->classAsString))
return false;
// prevent sending the same object twice in one request
if (in_array($id, $this->seenObjects)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Object '%s' discarded! Object already sent in this request.", $id));
return true;
}
$this->importedMsgs++;
$this->seenObjects[] = $id;
// checks if the next message may cause a loop or is broken
if (ZPush::GetDeviceManager()->DoNotStreamMessage($id, $message)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): message ignored and requested to be removed from mobile", $id));
// this is an internal operation & should not trigger an update in the device manager
$this->checkForIgnoredMessages = false;
$stat = $this->ImportMessageDeletion($id);
$this->checkForIgnoredMessages = true;
return $stat;
}
if ($message->flags === false || $message->flags === SYNC_NEWMESSAGE)
$this->encoder->startTag(SYNC_ADD);
else {
// on update of an SyncEmail we only export the flags
if($message instanceof SyncMail && isset($message->flag) && $message->flag instanceof SyncMailFlags) {
$newmessage = new SyncMail();
$newmessage->read = $message->read;
$newmessage->flag = $message->flag;
if (isset($message->lastverbexectime)) $newmessage->lastverbexectime = $message->lastverbexectime;
if (isset($message->lastverbexecuted)) $newmessage->lastverbexecuted = $message->lastverbexecuted;
$message = $newmessage;
unset($newmessage);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): SyncMail message updated. Message content is striped, only flags are streamed.", $id));
}
$this->encoder->startTag(SYNC_MODIFY);
}
$this->encoder->startTag(SYNC_SERVERENTRYID);
$this->encoder->content($id);
$this->encoder->endTag();
$this->encoder->startTag(SYNC_DATA);
$message->Encode($this->encoder);
$this->encoder->endTag();
$this->encoder->endTag();
return true;
}
/**
* Imports a deletion
*
* @param string $id
*
* @access public
* @return boolean
*/
public function ImportMessageDeletion($id) {
if ($this->checkForIgnoredMessages) {
ZPush::GetDeviceManager()->RemoveBrokenMessage($id);
}
$this->importedMsgs++;
$this->encoder->startTag(SYNC_REMOVE);
$this->encoder->startTag(SYNC_SERVERENTRYID);
$this->encoder->content($id);
$this->encoder->endTag();
$this->encoder->endTag();
return true;
}
/**
* Imports a change in 'read' flag
* Can only be applied to SyncMail (Email) requests
*
* @param string $id
* @param int $flags - read/unread
*
* @access public
* @return boolean
*/
public function ImportMessageReadFlag($id, $flags) {
if(!($this->objclass instanceof SyncMail))
return false;
$this->importedMsgs++;
$this->encoder->startTag(SYNC_MODIFY);
$this->encoder->startTag(SYNC_SERVERENTRYID);
$this->encoder->content($id);
$this->encoder->endTag();
$this->encoder->startTag(SYNC_DATA);
$this->encoder->startTag(SYNC_POOMMAIL_READ);
$this->encoder->content($flags);
$this->encoder->endTag();
$this->encoder->endTag();
$this->encoder->endTag();
return true;
}
/**
* ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder
*
* @param string $id
* @param int $flags read/unread
*
* @access public
* @return boolean
*/
public function ImportMessageMove($id, $newfolder) {
return true;
}
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return string id of the folder
*/
public function ImportFolderChange($folder) {
// checks if the next message may cause a loop or is broken
if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid));
return true;
}
// send a modify flag if the folder is already known on the device
if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE)
$this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD);
else
$this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE);
$folder->Encode($this->encoder);
$this->encoder->endTag();
return true;
}
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent id
*
* @access public
* @return boolean
*/
public function ImportFolderDeletion($id, $parent = false) {
$this->encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE);
$this->encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID);
$this->encoder->content($id);
$this->encoder->endTag();
$this->encoder->endTag();
return true;
}
/**
* Returns the number of messages which were changed, deleted and had changed read status
*
* @access public
* @return int
*/
public function GetImportedMessages() {
return $this->importedMsgs;
}
}
?>

View file

@ -0,0 +1,714 @@
<?php
/***********************************************
* File : synccollections.php
* Project : Z-Push
* Descr : This is basically a list of synched folders with it's
* respective SyncParameters, while some additional parameters
* which are not stored there can be kept here.
* The class also provides CheckForChanges which is basically
* a loop through all collections checking for changes.
* SyncCollections is used for Sync (with and without heartbeat)
* and Ping connections.
* To check for changes in Heartbeat and Ping requeste the same
* sync states as for the default synchronization are used.
*
* Created : 06.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class SyncCollections implements Iterator {
const ERROR_NO_COLLECTIONS = 1;
const ERROR_WRONG_HIERARCHY = 2;
const OBSOLETE_CONNECTION = 3;
private $stateManager;
private $collections = array();
private $addparms = array();
private $changes = array();
private $saveData = true;
private $refPolicyKey = false;
private $refLifetime = false;
private $globalWindowSize;
private $lastSyncTime;
private $waitingTime = 0;
/**
* Constructor
*/
public function SyncCollections() {
}
/**
* Sets the StateManager for this object
* If this is not done and a method needs it, the StateManager will be
* requested from the DeviceManager
*
* @param StateManager $statemanager
*
* @access public
* @return
*/
public function SetStateManager($statemanager) {
$this->stateManager = $statemanager;
}
/**
* Loads all collections known for the current device
*
* @param boolean $overwriteLoaded (opt) overwrites Collection with saved state if set to true
* @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default true
* @param boolean $checkPermissions (opt) if set to true each folder will pass
* through a backend->Setup() to check permissions.
* If this fails a StatusException will be thrown.
*
* @access public
* @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails
* @throws StateNotFoundException if the sync state can not be found ($loadState = true)
* @return boolean
*/
public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false) {
$this->loadStateManager();
// this operation should not remove old state counters
$this->stateManager->DoNotDeleteOldStates();
$invalidStates = false;
foreach($this->stateManager->GetSynchedFolders() as $folderid) {
if ($overwriteLoaded === false && isset($this->collections[$folderid]))
continue;
// Load Collection!
if (! $this->LoadCollection($folderid, $loadState, $checkPermissions))
$invalidStates = true;
}
if ($invalidStates)
throw new StateInvalidException("Invalid states found while loading collections. Forcing sync");
return true;
}
/**
* Loads all collections known for the current device
*
* @param string $folderid folder id to be loaded
* @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default true
* @param boolean $checkPermissions (opt) if set to true each folder will pass
* through a backend->Setup() to check permissions.
* If this fails a StatusException will be thrown.
*
* @access public
* @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails
* @throws StateNotFoundException if the sync state can not be found ($loadState = true)
* @return boolean
*/
public function LoadCollection($folderid, $loadState = false, $checkPermissions = false) {
$this->loadStateManager();
try {
// Get SyncParameters for the folder from the state
$spa = $this->stateManager->GetSynchedFolderState($folderid);
// TODO remove resync of folders for < Z-Push 2 beta4 users
// this forces a resync of all states previous to Z-Push 2 beta4
if (! $spa instanceof SyncParameters)
throw new StateInvalidException("Saved state are not of type SyncParameters");
}
catch (StateInvalidException $sive) {
// in case there is something wrong with the state, just stop here
// later when trying to retrieve the SyncParameters nothing will be found
// we also generate a fake change, so a sync on this folder is triggered
$this->changes[$folderid] = 1;
return false;
}
// if this is an additional folder the backend has to be setup correctly
if ($checkPermissions === true && ! ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId())))
throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), self::ERROR_WRONG_HIERARCHY);
// add collection to object
$addStatus = $this->AddCollection($spa);
// load the latest known syncstate if requested
if ($addStatus && $loadState === true)
$this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey());
return $addStatus;
}
/**
* Saves a SyncParameters Object
*
* @param SyncParamerts $spa
*
* @access public
* @return boolean
*/
public function SaveCollection($spa) {
if (! $this->saveData || !$spa->HasFolderId())
return false;
if ($spa->IsDataChanged()) {
$this->loadStateManager();
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->SaveCollection(): Data of folder '%s' changed", $spa->GetFolderId()));
// save new windowsize
if (isset($this->globalWindowSize))
$spa->SetWindowSize($this->globalWindowSize);
// update latest lifetime
if (isset($this->refLifetime))
$spa->SetReferenceLifetime($this->refLifetime);
return $this->stateManager->SetSynchedFolderState($spa);
}
return false;
}
/**
* Adds a SyncParameters object to the current list of collections
*
* @param SyncParameters $spa
*
* @access public
* @return boolean
*/
public function AddCollection($spa) {
if (! $spa->HasFolderId())
return false;
$this->collections[$spa->GetFolderId()] = $spa;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Folder id '%s' : ref. PolicyKey '%s', ref. Lifetime '%s', last sync at '%s'", $spa->GetFolderId(), $spa->GetReferencePolicyKey(), $spa->GetReferenceLifetime(), $spa->GetLastSyncTime()));
if ($spa->HasLastSyncTime() && $spa->GetLastSyncTime() > $this->lastSyncTime) {
$this->lastSyncTime = $spa->GetLastSyncTime();
// use SyncParameters PolicyKey as reference if available
if ($spa->HasReferencePolicyKey())
$this->refPolicyKey = $spa->GetReferencePolicyKey();
// use SyncParameters LifeTime as reference if available
if ($spa->HasReferenceLifetime())
$this->refLifetime = $spa->GetReferenceLifetime();
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->AddCollection(): Updated reference PolicyKey '%s', reference Lifetime '%s', Last sync at '%s'", $this->refPolicyKey, $this->refLifetime, $this->lastSyncTime));
}
return true;
}
/**
* Returns a previousily added or loaded SyncParameters object for a folderid
*
* @param SyncParameters $spa
*
* @access public
* @return SyncParameters / boolean false if no SyncParameters object is found for folderid
*/
public function GetCollection($folderid) {
if (isset($this->collections[$folderid]))
return $this->collections[$folderid];
else
return false;
}
/**
* Indicates if there are any loaded CPOs
*
* @access public
* @return boolean
*/
public function HasCollections() {
return ! empty($this->collections);
}
/**
* Add a non-permanent key/value pair for a SyncParameters object
*
* @param SyncParameters $spa target SyncParameters
* @param string $key
* @param mixed $value
*
* @access public
* @return boolean
*/
public function AddParameter($spa, $key, $value) {
if (!$spa->HasFolderId())
return false;
$folderid = $spa->GetFolderId();
if (!isset($this->addparms[$folderid]))
$this->addparms[$folderid] = array();
$this->addparms[$folderid][$key] = $value;
return true;
}
/**
* Returns a previousily set non-permanent value for a SyncParameters object
*
* @param SyncParameters $spa target SyncParameters
* @param string $key
*
* @access public
* @return mixed returns 'null' if nothing set
*/
public function GetParameter($spa, $key) {
if (!$spa->HasFolderId())
return null;
if (isset($this->addparms[$spa->GetFolderId()]) && isset($this->addparms[$spa->GetFolderId()][$key]))
return $this->addparms[$spa->GetFolderId()][$key];
else
return null;
}
/**
* Returns the latest known PolicyKey to be used as reference
*
* @access public
* @return int/boolen returns false if nothing found in collections
*/
public function GetReferencePolicyKey() {
return $this->refPolicyKey;
}
/**
* Sets a global window size which should be used for all collections
* in a case of a heartbeat and/or partial sync
*
* @param int $windowsize
*
* @access public
* @return boolean
*/
public function SetGlobalWindowSize($windowsize) {
$this->globalWindowSize = $windowsize;
return true;
}
/**
* Returns the global window size which should be used for all collections
* in a case of a heartbeat and/or partial sync
*
* @access public
* @return int/boolean returns false if not set or not available
*/
public function GetGlobalWindowSize() {
if (!isset($this->globalWindowSize))
return false;
return $this->globalWindowSize;
}
/**
* Sets the lifetime for heartbeat or ping connections
*
* @param int $lifetime time in seconds
*
* @access public
* @return boolean
*/
public function SetLifetime($lifetime) {
$this->refLifetime = $lifetime;
return true;
}
/**
* Sets the lifetime for heartbeat or ping connections
* previousily set or saved in a collection
*
* @access public
* @return int returns 600 as default if nothing set or not available
*/
public function GetLifetime() {
if (!isset( $this->refLifetime) || $this->refLifetime === false)
return 600;
return $this->refLifetime;
}
/**
* Returns the timestamp of the last synchronization for all
* loaded collections
*
* @access public
* @return int timestamp
*/
public function GetLastSyncTime() {
return $this->lastSyncTime;
}
/**
* Checks if the currently known collections for changes for $lifetime seconds.
* If the backend provides a ChangesSink the sink will be used.
* If not every $interval seconds an exporter will be configured for each
* folder to perform GetChangeCount().
*
* @param int $lifetime (opt) total lifetime to wait for changes / default 600s
* @param int $interval (opt) time between blocking operations of sink or polling / default 30s
* @param boolean $onlyPingable (opt) only check for folders which have the PingableFlag
*
* @access public
* @return boolean indicating if changes were found
* @throws StatusException with code SyncCollections::ERROR_NO_COLLECTIONS if no collections available
* with code SyncCollections::ERROR_WRONG_HIERARCHY if there were errors getting changes
*/
public function CheckForChanges($lifetime = 600, $interval = 30, $onlyPingable = false) {
$classes = array();
foreach ($this->collections as $folderid => $spa){
if ($onlyPingable && $spa->GetPingableFlag() !== true)
continue;
if (!isset($classes[$spa->GetContentClass()]))
$classes[$spa->GetContentClass()] = 0;
$classes[$spa->GetContentClass()] += 1;
}
if (empty($classes))
$checkClasses = "policies only";
else if (array_sum($classes) > 4) {
$checkClasses = "";
foreach($classes as $class=>$count) {
if ($count == 1)
$checkClasses .= sprintf("%s ", $class);
else
$checkClasses .= sprintf("%s(%d) ", $class, $count);
}
}
else
$checkClasses = implode(" ", array_keys($classes));
$pingTracking = new PingTracking();
$this->changes = array();
$changesAvailable = false;
ZPush::GetDeviceManager()->AnnounceProcessAsPush();
ZPush::GetTopCollector()->AnnounceInformation(sprintf("lifetime %ds", $lifetime), true);
ZLog::Write(LOGLEVEL_INFO, sprintf("SyncCollections->CheckForChanges(): Waiting for %s changes... (lifetime %d seconds)", (empty($classes))?'policy':'store', $lifetime));
// use changes sink where available
$changesSink = false;
$forceRealExport = 0;
// do not create changessink if there are no folders
if (!empty($classes) && ZPush::GetBackend()->HasChangesSink()) {
$changesSink = true;
// initialize all possible folders
foreach ($this->collections as $folderid => $spa) {
if ($onlyPingable && $spa->GetPingableFlag() !== true)
continue;
// switch user store if this is a additional folder and initialize sink
ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid));
if (! ZPush::GetBackend()->ChangesSinkInitialize($folderid))
throw new StatusException(sprintf("Error initializing ChangesSink for folder id '%s'", $folderid), self::ERROR_WRONG_HIERARCHY);
}
}
// wait for changes
$started = time();
$endat = time() + $lifetime;
while(($now = time()) < $endat) {
// how long are we waiting for changes
$this->waitingTime = $now-$started;
$nextInterval = $interval;
// we should not block longer than the lifetime
if ($endat - $now < $nextInterval)
$nextInterval = $endat - $now;
// Check if provisioning is necessary
// if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey
if (PROVISIONING === true && $this->GetReferencePolicyKey() !== false && ZPush::GetDeviceManager()->ProvisioningRequired($this->GetReferencePolicyKey(), true))
// the hierarchysync forces provisioning
throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY);
// Check if a hierarchy sync is necessary
if (ZPush::GetDeviceManager()->IsHierarchySyncRequired())
throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::ERROR_WRONG_HIERARCHY);
// Check if there are newer requests
// If so, this process should be terminated if more than 60 secs to go
if ($pingTracking->DoForcePingTimeout()) {
// do not update CPOs because another process has already read them!
$this->saveData = false;
// more than 60 secs to go?
if (($now + 60) < $endat) {
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Forced timeout after %ds", ($now-$started)), true);
throw new StatusException(sprintf("SyncCollections->CheckForChanges(): Timeout forced after %ss from %ss due to other process", ($now-$started), $lifetime), self::OBSOLETE_CONNECTION);
}
}
// Use changes sink if available
if ($changesSink) {
// in some occasions we do realize a full export to see if there are pending changes
// every 5 minutes this is also done to see if there were "missed" notifications
if (SINK_FORCERECHECK !== false && $forceRealExport+SINK_FORCERECHECK <= $now) {
if ($this->CountChanges($onlyPingable)) {
ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found relevant changes on regular export");
return true;
}
$forceRealExport = $now;
}
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Sink %d/%ds on %s", ($now-$started), $lifetime, $checkClasses));
$notifications = ZPush::GetBackend()->ChangesSink($nextInterval);
$validNotifications = false;
foreach ($notifications as $folderid) {
// check if the notification on the folder is within our filter
if ($this->CountChange($folderid)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid));
$validNotifications = true;
$this->waitingTime = time()-$started;
}
else {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid));
}
}
if ($validNotifications)
return true;
}
// use polling mechanism
else {
ZPush::GetTopCollector()->AnnounceInformation(sprintf("Polling %d/%ds on %s", ($now-$started), $lifetime, $checkClasses));
if ($this->CountChanges($onlyPingable)) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Found changes polling"));
return true;
}
else {
sleep($nextInterval);
}
} // end polling
} // end wait for changes
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): no changes found after %ds", time() - $started));
return false;
}
/**
* Checks if the currently known collections for
* changes performing Exporter->GetChangeCount()
*
* @param boolean $onlyPingable (opt) only check for folders which have the PingableFlag
*
* @access public
* @return boolean indicating if changes were found or not
*/
public function CountChanges($onlyPingable = false) {
$changesAvailable = false;
foreach ($this->collections as $folderid => $spa) {
if ($onlyPingable && $spa->GetPingableFlag() !== true)
continue;
if (isset($this->addparms[$spa->GetFolderId()]["status"]) && $this->addparms[$spa->GetFolderId()]["status"] != SYNC_STATUS_SUCCESS)
continue;
if ($this->CountChange($folderid))
$changesAvailable = true;
}
return $changesAvailable;
}
/**
* Checks a folder for changes performing Exporter->GetChangeCount()
*
* @param string $folderid counts changes for a folder
*
* @access private
* @return boolean indicating if changes were found or not
*/
private function CountChange($folderid) {
$spa = $this->GetCollection($folderid);
// switch user store if this is a additional folder (additional true -> do not debug)
ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid, true));
$changecount = false;
try {
$exporter = ZPush::GetBackend()->GetExporter($folderid);
if ($exporter !== false && isset($this->addparms[$folderid]["state"])) {
$importer = false;
$exporter->Config($this->addparms[$folderid]["state"], BACKEND_DISCARD_DATA);
$exporter->ConfigContentParameters($spa->GetCPO());
$ret = $exporter->InitializeExporter($importer);
if ($ret !== false)
$changecount = $exporter->GetChangeCount();
}
}
catch (StatusException $ste) {
throw new StatusException("SyncCollections->CountChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
}
// start over if exporter can not be configured atm
if ($changecount === false )
ZLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): no changes received from Exporter.");
$this->changes[$folderid] = $changecount;
if(isset($this->addparms[$folderid]['savestate'])) {
try {
// Discard any data
while(is_array($exporter->Synchronize()));
$this->addparms[$folderid]['savestate'] = $exporter->GetState();
}
catch (StatusException $ste) {
throw new StatusException("SyncCollections->CountChange(): could not get new state from exporter", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN);
}
}
return ($changecount > 0);
}
/**
* Returns an array with all folderid and the amount of changes found
*
* @access public
* @return array
*/
public function GetChangedFolderIds() {
return $this->changes;
}
/**
* Indicates if there are folders which are pingable
*
* @access public
* @return boolean
*/
public function PingableFolders() {
$pingable = false;
foreach ($this->collections as $folderid => $spa) {
if ($spa->GetPingableFlag() == true)
$pingable = true;
}
return $pingable;
}
/**
* Indicates if the process did wait in a sink, polling or before running a
* regular export to find changes
*
* @access public
* @return array
*/
public function WaitedForChanges() {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->WaitedForChanges: waited for %d seconds", $this->waitingTime));
return ($this->waitingTime > 0);
}
/**
* Simple Iterator Interface implementation to traverse through collections
*/
/**
* Rewind the Iterator to the first element
*
* @access public
* @return
*/
public function rewind() {
return reset($this->collections);
}
/**
* Returns the current element
*
* @access public
* @return mixed
*/
public function current() {
return current($this->collections);
}
/**
* Return the key of the current element
*
* @access public
* @return scalar on success, or NULL on failure.
*/
public function key() {
return key($this->collections);
}
/**
* Move forward to next element
*
* @access public
* @return
*/
public function next() {
return next($this->collections);
}
/**
* Checks if current position is valid
*
* @access public
* @return boolean
*/
public function valid() {
return (key($this->collections) !== null);
}
/**
* Gets the StateManager from the DeviceManager
* if it's not available
*
* @access private
* @return
*/
private function loadStateManager() {
if (!isset($this->stateManager))
$this->stateManager = ZPush::GetDeviceManager()->GetStateManager();
}
}
?>

View file

@ -0,0 +1,419 @@
<?php
/***********************************************
* File : syncparameters.php
* Project : Z-Push
* Descr : Transportation container for
* requested content parameters and information
* about the container and states
*
* Created : 11.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class SyncParameters extends StateObject {
const DEFAULTOPTIONS = "DEFAULT";
const EMAILOPTIONS = "EMAIL";
const CALENDAROPTIONS = "CALENDAR";
const CONTACTOPTIONS = "CONTACTS";
const NOTEOPTIONS = "NOTES";
const TASKOPTIONS = "TASKS";
const SMSOPTIONS = "SMS";
private $synckeyChanged = false;
private $currentCPO = self::DEFAULTOPTIONS;
protected $unsetdata = array(
'uuid' => false,
'uuidcounter' => false,
'uuidnewcounter' => false,
'folderid' => false,
'referencelifetime' => 10,
'lastsynctime' => false,
'referencepolicykey' => true,
'pingableflag' => false,
'contentclass' => false,
'deletesasmoves' => false,
'conversationmode' => false,
'windowsize' => 5,
'contentparameters' => array(),
'foldersynctotal' => false,
'foldersyncremaining' => false,
);
/**
* SyncParameters constructor
*/
public function SyncParameters() {
// initialize ContentParameters for the current option
$this->checkCPO();
}
/**
* SyncKey methods
*
* The current and next synckey is saved as uuid and counter
* so partial and ping can access the latest states.
*/
/**
* Returns the latest SyncKey of this folder
*
* @access public
* @return string/boolean false if no uuid/counter available
*/
public function GetSyncKey() {
if (isset($this->uuid) && isset($this->uuidCounter))
return StateManager::BuildStateKey($this->uuid, $this->uuidCounter);
return false;
}
/**
* Sets the the current synckey.
* This is done by parsing it and saving uuid and counter.
* By setting the current key, the "next" key is obsolete
*
* @param string $synckey
*
* @access public
* @return boolean
*/
public function SetSyncKey($synckey) {
list($this->uuid, $this->uuidCounter) = StateManager::ParseStateKey($synckey);
// remove newSyncKey
unset($this->uuidNewCounter);
return true;
}
/**
* Indicates if this folder has a synckey
*
* @access public
* @return booleans
*/
public function HasSyncKey() {
return (isset($this->uuid) && isset($this->uuidCounter));
}
/**
* Sets the the next synckey.
* This is done by parsing it and saving uuid and next counter.
* if the folder has no synckey until now (new sync), the next counter becomes current asl well.
*
* @param string $synckey
*
* @access public
* @throws FatalException if the uuids of current and next do not match
* @return boolean
*/
public function SetNewSyncKey($synckey) {
list($uuid, $uuidNewCounter) = StateManager::ParseStateKey($synckey);
if (!$this->HasSyncKey()) {
$this->uuid = $uuid;
$this->uuidCounter = $uuidNewCounter;
}
else if ($uuid !== $this->uuid)
throw new FatalException("SyncParameters->SetNewSyncKey(): new SyncKey must have the same UUID as current SyncKey");
$this->uuidNewCounter = $uuidNewCounter;
$this->synckeyChanged = true;
}
/**
* Returns the next synckey
*
* @access public
* @return string/boolean returns false if uuid or counter are not available
*/
public function GetNewSyncKey() {
if (isset($this->uuid) && isset($this->uuidNewCounter))
return StateManager::BuildStateKey($this->uuid, $this->uuidNewCounter);
return false;
}
/**
* Indicates if the folder has a next synckey
*
* @access public
* @return boolean
*/
public function HasNewSyncKey() {
return (isset($this->uuid) && isset($this->uuidNewCounter));
}
/**
* Return the latest synckey.
* When this is called the new key becomes the current key (if a new key is available).
* The current key is then returned.
*
* @access public
* @return string
*/
public function GetLatestSyncKey() {
// New becomes old
if ($this->HasUuidNewCounter()) {
$this->uuidCounter = $this->uuidNewCounter;
unset($this->uuidNewCounter);
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->GetLatestSyncKey(): '%s'", $this->GetSyncKey()));
return $this->GetSyncKey();
}
/**
* Removes the saved SyncKey of this folder
*
* @access public
* @return boolean
*/
public function RemoveSyncKey() {
if (isset($this->uuid))
unset($this->uuid);
if (isset($this->uuidCounter))
unset($this->uuidCounter);
if (isset($this->uuidNewCounter))
unset($this->uuidNewCounter);
ZLog::Write(LOGLEVEL_DEBUG, "SyncParameters->RemoveSyncKey(): saved sync key removed");
return true;
}
/**
* CPO methods
*
* A sync request can have several options blocks. Each block is saved into an own CPO object
*
*/
/**
* Returns the a specified CPO
*
* @param string $options (opt) If not specified, the default Options (CPO) will be used
* Valid option SyncParameters::SMSOPTIONS (string "SMS")
*
* @access public
* @return ContentParameters object
*/
public function GetCPO($options = self::DEFAULTOPTIONS) {
$options = strtoupper($options);
$this->isValidType($options);
$options = $this->normalizeType($options);
$this->checkCPO($options);
// copy contentclass and conversationmode to the CPO
$this->contentParameters[$options]->SetContentClass($this->contentclass);
$this->contentParameters[$options]->SetConversationMode($this->conversationmode);
return $this->contentParameters[$options];
}
/**
* Use the submitted CPO type for next setters/getters
*
* @param string $options (opt) If not specified, the default Options (CPO) will be used
* Valid option SyncParameters::SMSOPTIONS (string "SMS")
*
* @access public
* @return
*/
public function UseCPO($options = self::DEFAULTOPTIONS) {
$options = strtoupper($options);
$this->isValidType($options);
// remove potential old default CPO if available
if (isset($this->contentParameters[self::DEFAULTOPTIONS]) && $options != self::DEFAULTOPTIONS && $options !== self::SMSOPTIONS) {
$a = $this->contentParameters;
unset($a[self::DEFAULTOPTIONS]);
$this->contentParameters = $a;
ZLog::Write(LOGLEVEL_DEBUG, "SyncParameters->UseCPO(): removed existing DEFAULT CPO as it is obsolete");
}
ZLOG::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->UseCPO('%s')", $options));
$this->currentCPO = $options;
$this->checkCPO($this->currentCPO);
}
/**
* Checks if a CPO is correctly inicialized and inicializes it if necessary
*
* @param string $options (opt) If not specified, the default Options (CPO) will be used
* Valid option SyncParameters::SMSOPTIONS (string "SMS")
*
* @access private
* @return boolean
*/
private function checkCPO($options = self::DEFAULTOPTIONS) {
$this->isValidType($options);
if (!isset($this->contentParameters[$options])) {
$a = $this->contentParameters;
$a[$options] = new ContentParameters();
$this->contentParameters = $a;
}
return true;
}
/**
* Checks if the requested option type is available
*
* @param string $options CPO type
*
* @access private
* @return boolean
* @throws FatalNotImplementedException
*/
private function isValidType($options) {
if ($options !== self::DEFAULTOPTIONS &&
$options !== self::EMAILOPTIONS &&
$options !== self::CALENDAROPTIONS &&
$options !== self::CONTACTOPTIONS &&
$options !== self::NOTEOPTIONS &&
$options !== self::TASKOPTIONS &&
$options !== self::SMSOPTIONS)
throw new FatalNotImplementedException(sprintf("SyncParameters->isAllowedType('%s') ContentParameters is invalid. Such type is not available.", $options));
return true;
}
/**
* Normalizes the requested option type and returns it as
* default option if no default is available
*
* @param string $options CPO type
*
* @access private
* @return string
* @throws FatalNotImplementedException
*/
private function normalizeType($options) {
// return the requested CPO as it is defined
if (isset($this->contentParameters[$options]))
return $options;
$returnCPO = $options;
// return email, calendar, contact or note CPO as default CPO if there no explicit default CPO defined
if ($options == self::DEFAULTOPTIONS && !isset($this->contentParameters[self::DEFAULTOPTIONS])) {
if (isset($this->contentParameters[self::EMAILOPTIONS]))
$returnCPO = self::EMAILOPTIONS;
elseif (isset($this->contentParameters[self::CALENDAROPTIONS]))
$returnCPO = self::CALENDAROPTIONS;
elseif (isset($this->contentParameters[self::CONTACTOPTIONS]))
$returnCPO = self::CONTACTOPTIONS;
elseif (isset($this->contentParameters[self::NOTEOPTIONS]))
$returnCPO = self::NOTEOPTIONS;
elseif (isset($this->contentParameters[self::TASKOPTIONS]))
$returnCPO = self::TASKOPTIONS;
if ($returnCPO != $options)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncParameters->normalizeType(): using %s for requested %s", $returnCPO, $options));
return $returnCPO;
}
// something unexpected happened, just return default, empty in the worst case
else {
ZLog::Write(LOGLEVEL_WARN, "SyncParameters->normalizeType(): no DEFAULT CPO available, creating empty CPO");
$this->checkCPO(self::DEFAULTOPTIONS);
return self::DEFAULTOPTIONS;
}
}
/**
* PHP magic to implement any getter, setter, has and delete operations
* on an instance variable.
*
* NOTICE: All magic getters and setters of this object which are not defined in the unsetdata array are passed to the current CPO.
*
* Methods like e.g. "SetVariableName($x)" and "GetVariableName()" are supported
*
* @access public
* @return mixed
*/
public function __call($name, $arguments) {
$lowname = strtolower($name);
$operator = substr($lowname, 0,3);
$var = substr($lowname,3);
if (array_key_exists($var, $this->unsetdata)) {
return parent::__call($name, $arguments);
}
return $this->contentParameters[$this->currentCPO]->__call($name, $arguments);
}
/**
* un/serialization methods
*/
/**
* Called before the StateObject is serialized
*
* @access protected
* @return boolean
*/
protected function preSerialize() {
parent::preSerialize();
if ($this->changed === true && ($this->synckeyChanged || $this->lastsynctime === false))
$this->lastsynctime = time();
return true;
}
/**
* Called after the StateObject was unserialized
*
* @access protected
* @return boolean
*/
protected function postUnserialize() {
// init with default options
$this->UseCPO();
return true;
}
}
?>

View file

@ -0,0 +1,299 @@
<?php
/***********************************************
* File : topcollector.php
* Project : Z-Push
* Descr : available everywhere to collect
* data which could be displayed in z-push-top
* the 'persistent' flag should be used with care, so
* there is not too much information
*
* Created : 20.10.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class TopCollector extends InterProcessData {
const ENABLEDAT = 2;
const TOPDATA = 3;
protected $preserved;
protected $latest;
/**
* Constructor
*
* @access public
*/
public function TopCollector() {
// initialize super parameters
$this->allocate = 2097152; // 2 MB
$this->type = 20;
parent::__construct();
// initialize params
$this->InitializeParams();
$this->preserved = array();
// static vars come from the parent class
$this->latest = array( "pid" => self::$pid,
"ip" => Request::GetRemoteAddr(),
"user" => self::$user,
"start" => self::$start,
"devtype" => Request::GetDeviceType(),
"devid" => self::$devid,
"devagent" => Request::GetUserAgent(),
"command" => Request::GetCommandCode(),
"ended" => 0,
"push" => false,
);
$this->AnnounceInformation("initializing");
}
/**
* Destructor
* indicates that the process is shutting down
*
* @access public
*/
public function __destruct() {
$this->AnnounceInformation("OK", false, true);
}
/**
* Advices all other processes that they should start/stop
* collecting data. The data saved is a timestamp. It has to be
* reactivated every couple of seconds
*
* @param boolean $stop (opt) default false (do collect)
*
* @access public
* @return boolean indicating if it was set to collect before
*/
public function CollectData($stop = false) {
$wasEnabled = false;
// exclusive block
if ($this->blockMutex()) {
$wasEnabled = ($this->hasData(self::ENABLEDAT)) ? $this->getData(self::ENABLEDAT) : false;
$time = time();
if ($stop === true) $time = 0;
if (! $this->setData($time, self::ENABLEDAT))
return false;
$this->releaseMutex();
}
// end exclusive block
return $wasEnabled;
}
/**
* Announces a string to the TopCollector
*
* @param string $info
* @param boolean $preserve info should be displayed when process terminates
* @param boolean $terminating indicates if the process is terminating
*
* @access public
* @return boolean
*/
public function AnnounceInformation($addinfo, $preserve = false, $terminating = false) {
$this->latest["addinfo"] = $addinfo;
$this->latest["update"] = time();
if ($terminating) {
$this->latest["ended"] = time();
foreach ($this->preserved as $p)
$this->latest["addinfo"] .= " : ".$p;
}
if ($preserve)
$this->preserved[] = $addinfo;
// exclusive block
if ($this->blockMutex()) {
if ($this->isEnabled()) {
$topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA): array();
$this->checkArrayStructure($topdata);
// update
$topdata[self::$devid][self::$user][self::$pid] = $this->latest;
$ok = $this->setData($topdata, self::TOPDATA);
}
$this->releaseMutex();
}
// end exclusive block
if ($this->isEnabled() === true && !$ok) {
ZLog::Write(LOGLEVEL_WARN, "TopCollector::AnnounceInformation(): could not write to shared memory. Z-Push top will not display this data.");
return false;
}
return true;
}
/**
* Returns all available top data
*
* @access public
* @return array
*/
public function ReadLatest() {
$topdata = array();
// exclusive block
if ($this->blockMutex()) {
$topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA) : array();
$this->releaseMutex();
}
// end exclusive block
return $topdata;
}
/**
* Cleans up data collected so far
*
* @param boolean $all (optional) if set all data independently from the age is removed
*
* @access public
* @return boolean status
*/
public function ClearLatest($all = false) {
// it's ok when doing this every 10 sec
if ($all == false && time() % 10 != 0 )
return true;
$stat = false;
// exclusive block
if ($this->blockMutex()) {
if ($all == true) {
$topdata = array();
}
else {
$topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA) : array();
$toClear = array();
foreach ($topdata as $devid=>$users) {
foreach ($users as $user=>$pids) {
foreach ($pids as $pid=>$line) {
// remove everything which terminated for 20 secs or is not updated for more than 120 secs
if (($line["ended"] != 0 && time() - $line["ended"] > 20) ||
time() - $line["update"] > 120) {
$toClear[] = array($devid, $user, $pid);
}
}
}
}
foreach ($toClear as $tc)
unset($topdata[$tc[0]][$tc[1]][$tc[2]]);
}
$stat = $this->setData($topdata, self::TOPDATA);
$this->releaseMutex();
}
// end exclusive block
return $stat;
}
/**
* Sets a different UserAgent for this connection
*
* @param string $agent
*
* @access public
* @return boolean
*/
public function SetUserAgent($agent) {
$this->latest["devagent"] = $agent;
return true;
}
/**
* Marks this process as push connection
*
* @param string $agent
*
* @access public
* @return boolean
*/
public function SetAsPushConnection() {
$this->latest["push"] = true;
return true;
}
/**
* Indicates if top data should be saved or not
* Returns true for 10 seconds after the latest CollectData()
* SHOULD only be called with locked mutex!
*
* @access private
* @return boolean
*/
private function isEnabled() {
$isEnabled = ($this->hasData(self::ENABLEDAT)) ? $this->getData(self::ENABLEDAT) : false;
return ($isEnabled !== false && ($isEnabled +300) > time());
}
/**
* Builds an array structure for the top data
*
* @param array $topdata reference to the topdata array
*
* @access private
* @return
*/
private function checkArrayStructure(&$topdata) {
if (!isset($topdata) || !is_array($topdata))
$topdata = array();
if (!isset($topdata[self::$devid]))
$topdata[self::$devid] = array();
if (!isset($topdata[self::$devid][self::$user]))
$topdata[self::$devid][self::$user] = array();
if (!isset($topdata[self::$devid][self::$user][self::$pid]))
$topdata[self::$devid][self::$user][self::$pid] = array();
}
}
?>

280
sources/lib/core/zlog.php Normal file
View file

@ -0,0 +1,280 @@
<?php
/***********************************************
* File : zlog.php
* Project : Z-Push
* Descr : Debug and logging
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ZLog {
static private $devid = '';
static private $user = '';
static private $authUser = false;
static private $pidstr;
static private $wbxmlDebug = '';
static private $lastLogs = array();
static private $userLog = false;
static private $unAuthCache = array();
/**
* Initializes the logging
*
* @access public
* @return boolean
*/
static public function Initialize() {
global $specialLogUsers;
// define some constants for the logging
if (!defined('LOGUSERLEVEL'))
define('LOGUSERLEVEL', LOGLEVEL_OFF);
if (!defined('LOGLEVEL'))
define('LOGLEVEL', LOGLEVEL_OFF);
list($user,) = Utils::SplitDomainUser(strtolower(Request::GetGETUser()));
self::$userLog = in_array($user, $specialLogUsers);
if (!defined('WBXML_DEBUG') && $user) {
// define the WBXML_DEBUG mode on user basis depending on the configurations
if (LOGLEVEL >= LOGLEVEL_WBXML || (LOGUSERLEVEL >= LOGLEVEL_WBXML && self::$userLog))
define('WBXML_DEBUG', true);
else
define('WBXML_DEBUG', false);
}
if ($user)
self::$user = '['. $user .'] ';
else
self::$user = '';
// log the device id if the global loglevel is set to log devid or the user is in and has the right log level
if (Request::GetDeviceID() != "" && (LOGLEVEL >= LOGLEVEL_DEVICEID || (LOGUSERLEVEL >= LOGLEVEL_DEVICEID && self::$userLog)))
self::$devid = '['. Request::GetDeviceID() .'] ';
else
self::$devid = '';
return true;
}
/**
* Writes a log line
*
* @param int $loglevel one of the defined LOGLEVELS
* @param string $message
* @param boolean $truncate indicate if the message should be truncated, default true
*
* @access public
* @return
*/
static public function Write($loglevel, $message, $truncate = true) {
// truncate messages longer than 10 KB
$messagesize = strlen($message);
if ($truncate && $messagesize > 10240)
$message = substr($message, 0, 10240) . sprintf(" <log message with %d bytes truncated>", $messagesize);
self::$lastLogs[$loglevel] = $message;
$data = self::buildLogString($loglevel) . $message . "\n";
if ($loglevel <= LOGLEVEL) {
@file_put_contents(LOGFILE, $data, FILE_APPEND);
}
// should we write this into the user log?
if ($loglevel <= LOGUSERLEVEL && self::$userLog) {
// padd level for better reading
$data = str_replace(self::getLogLevelString($loglevel), self::getLogLevelString($loglevel,true), $data);
// is the user authenticated?
if (self::logToUserFile()) {
// something was logged before the user was authenticated, write this to the log
if (!empty(self::$unAuthCache)) {
@file_put_contents(LOGFILEDIR . self::logToUserFile() . ".log", implode('', self::$unAuthCache), FILE_APPEND);
self::$unAuthCache = array();
}
// only use plain old a-z characters for the generic log file
@file_put_contents(LOGFILEDIR . self::logToUserFile() . ".log", $data, FILE_APPEND);
}
// the user is not authenticated yet, we save the log into memory for now
else {
self::$unAuthCache[] = $data;
}
}
if (($loglevel & LOGLEVEL_FATAL) || ($loglevel & LOGLEVEL_ERROR)) {
@file_put_contents(LOGERRORFILE, $data, FILE_APPEND);
}
if ($loglevel & LOGLEVEL_WBXMLSTACK) {
self::$wbxmlDebug .= $message. "\n";
}
}
/**
* Returns logged information about the WBXML stack
*
* @access public
* @return string
*/
static public function GetWBXMLDebugInfo() {
return trim(self::$wbxmlDebug);
}
/**
* Returns the last message logged for a log level
*
* @param int $loglevel one of the defined LOGLEVELS
*
* @access public
* @return string/false returns false if there was no message logged in that level
*/
static public function GetLastMessage($loglevel) {
return (isset(self::$lastLogs[$loglevel]))?self::$lastLogs[$loglevel]:false;
}
/**----------------------------------------------------------------------------------------------------------
* private log stuff
*/
/**
* Returns the filename logs for a WBXML debug log user should be saved to
*
* @access private
* @return string
*/
static private function logToUserFile() {
global $specialLogUsers;
if (self::$authUser === false) {
if (RequestProcessor::isUserAuthenticated()) {
$authuser = Request::GetAuthUser();
if ($authuser && in_array($authuser, $specialLogUsers))
self::$authUser = preg_replace('/[^a-z0-9]/', '_', strtolower($authuser));
}
}
return self::$authUser;
}
/**
* Returns the string to be logged
*
* @access private
* @return string
*/
static private function buildLogString($loglevel) {
if (!isset(self::$pidstr))
self::$pidstr = '[' . str_pad(@getmypid(),5," ",STR_PAD_LEFT) . '] ';
if (!isset(self::$user))
self::$user = '';
if (!isset(self::$devid))
self::$devid = '';
return Utils::GetFormattedTime() ." ". self::$pidstr . self::getLogLevelString($loglevel, (LOGLEVEL > LOGLEVEL_INFO)) ." ". self::$user . self::$devid;
}
/**
* Returns the string representation of the LOGLEVEL.
* String can be padded
*
* @param int $loglevel one of the LOGLEVELs
* @param boolean $pad
*
* @access private
* @return string
*/
static private function getLogLevelString($loglevel, $pad = false) {
if ($pad) $s = " ";
else $s = "";
switch($loglevel) {
case LOGLEVEL_OFF: return ""; break;
case LOGLEVEL_FATAL: return "[FATAL]"; break;
case LOGLEVEL_ERROR: return "[ERROR]"; break;
case LOGLEVEL_WARN: return "[".$s."WARN]"; break;
case LOGLEVEL_INFO: return "[".$s."INFO]"; break;
case LOGLEVEL_DEBUG: return "[DEBUG]"; break;
case LOGLEVEL_WBXML: return "[WBXML]"; break;
case LOGLEVEL_DEVICEID: return "[DEVICEID]"; break;
case LOGLEVEL_WBXMLSTACK: return "[WBXMLSTACK]"; break;
}
}
}
/**----------------------------------------------------------------------------------------------------------
* Legacy debug stuff
*/
// deprecated
// backwards compatible
function debugLog($message) {
ZLog::Write(LOGLEVEL_DEBUG, $message);
}
// TODO review error handler
function zarafa_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
$bt = debug_backtrace();
switch ($errno) {
case 8192: // E_DEPRECATED since PHP 5.3.0
// do not handle this message
break;
case E_NOTICE:
case E_WARNING:
// TODO check if there is a better way to avoid these messages
if (stripos($errfile,'interprocessdata') !== false && stripos($errstr,'shm_get_var()') !== false)
break;
ZLog::Write(LOGLEVEL_WARN, "$errfile:$errline $errstr ($errno)");
break;
default:
ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps");
for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) {
$file = $line = "unknown";
if (isset($bt[$i]['file'])) $file = $bt[$i]['file'];
if (isset($bt[$i]['line'])) $line = $bt[$i]['line'];
ZLog::Write(LOGLEVEL_ERROR, "trace: $i:". $file . ":" . $line. " - " . ((isset($bt[$i]['class']))? $bt[$i]['class'] . $bt[$i]['type']:""). $bt[$i]['function']. "()");
}
//throw new Exception("An error occured.");
break;
}
}
error_reporting(E_ALL);
set_error_handler("zarafa_error_handler");
?>

822
sources/lib/core/zpush.php Normal file
View file

@ -0,0 +1,822 @@
<?php
/***********************************************
* File : zpush.php
* Project : Z-Push
* Descr : Core functionalities
*
* Created : 12.04.2011
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ZPush {
const UNAUTHENTICATED = 1;
const UNPROVISIONED = 2;
const NOACTIVESYNCCOMMAND = 3;
const WEBSERVICECOMMAND = 4;
const HIERARCHYCOMMAND = 5;
const PLAININPUT = 6;
const REQUESTHANDLER = 7;
const CLASS_NAME = 1;
const CLASS_REQUIRESPROTOCOLVERSION = 2;
const CLASS_DEFAULTTYPE = 3;
const CLASS_OTHERTYPES = 4;
// AS versions
const ASV_1 = "1.0";
const ASV_2 = "2.0";
const ASV_21 = "2.1";
const ASV_25 = "2.5";
const ASV_12 = "12.0";
const ASV_121 = "12.1";
const ASV_14 = "14.0";
/**
* Command codes for base64 encoded requests (AS >= 12.1)
*/
const COMMAND_SYNC = 0;
const COMMAND_SENDMAIL = 1;
const COMMAND_SMARTFORWARD = 2;
const COMMAND_SMARTREPLY = 3;
const COMMAND_GETATTACHMENT = 4;
const COMMAND_FOLDERSYNC = 9;
const COMMAND_FOLDERCREATE = 10;
const COMMAND_FOLDERDELETE = 11;
const COMMAND_FOLDERUPDATE = 12;
const COMMAND_MOVEITEMS = 13;
const COMMAND_GETITEMESTIMATE = 14;
const COMMAND_MEETINGRESPONSE = 15;
const COMMAND_SEARCH = 16;
const COMMAND_SETTINGS = 17;
const COMMAND_PING = 18;
const COMMAND_ITEMOPERATIONS = 19;
const COMMAND_PROVISION = 20;
const COMMAND_RESOLVERECIPIENTS = 21;
const COMMAND_VALIDATECERT = 22;
// Deprecated commands
const COMMAND_GETHIERARCHY = -1;
const COMMAND_CREATECOLLECTION = -2;
const COMMAND_DELETECOLLECTION = -3;
const COMMAND_MOVECOLLECTION = -4;
const COMMAND_NOTIFY = -5;
// Webservice commands
const COMMAND_WEBSERVICE_DEVICE = -100;
const COMMAND_WEBSERVICE_USERS = -101;
// Latest supported State version
const STATE_VERSION = IStateMachine::STATEVERSION_02;
static private $autoloadBackendPreference = array(
"BackendZarafa",
"BackendCombined",
"BackendIMAP",
"BackendVCardDir",
"BackendMaildir"
);
static private $supportedASVersions = array(
self::ASV_1,
self::ASV_2,
self::ASV_21,
self::ASV_25,
self::ASV_12,
self::ASV_121,
self::ASV_14
);
static private $supportedCommands = array(
// COMMAND // AS VERSION // REQUESTHANDLER // OTHER SETTINGS
self::COMMAND_SYNC => array(self::ASV_1, self::REQUESTHANDLER => "Sync"),
self::COMMAND_SENDMAIL => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"),
self::COMMAND_SMARTFORWARD => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"),
self::COMMAND_SMARTREPLY => array(self::ASV_1, self::REQUESTHANDLER => "SendMail"),
self::COMMAND_GETATTACHMENT => array(self::ASV_1, self::REQUESTHANDLER => "GetAttachment"),
self::COMMAND_GETHIERARCHY => array(self::ASV_1, self::REQUESTHANDLER => "GetHierarchy", self::HIERARCHYCOMMAND), // deprecated but implemented
self::COMMAND_CREATECOLLECTION => array(self::ASV_1), // deprecated & not implemented
self::COMMAND_DELETECOLLECTION => array(self::ASV_1), // deprecated & not implemented
self::COMMAND_MOVECOLLECTION => array(self::ASV_1), // deprecated & not implemented
self::COMMAND_FOLDERSYNC => array(self::ASV_2, self::REQUESTHANDLER => "FolderSync", self::HIERARCHYCOMMAND),
self::COMMAND_FOLDERCREATE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND),
self::COMMAND_FOLDERDELETE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND),
self::COMMAND_FOLDERUPDATE => array(self::ASV_2, self::REQUESTHANDLER => "FolderChange", self::HIERARCHYCOMMAND),
self::COMMAND_MOVEITEMS => array(self::ASV_1, self::REQUESTHANDLER => "MoveItems"),
self::COMMAND_GETITEMESTIMATE => array(self::ASV_1, self::REQUESTHANDLER => "GetItemEstimate"),
self::COMMAND_MEETINGRESPONSE => array(self::ASV_1, self::REQUESTHANDLER => "MeetingResponse"),
self::COMMAND_RESOLVERECIPIENTS => array(self::ASV_1, self::REQUESTHANDLER => "ResolveRecipients"),
self::COMMAND_VALIDATECERT => array(self::ASV_1, self::REQUESTHANDLER => "ValidateCert"),
self::COMMAND_PROVISION => array(self::ASV_25, self::REQUESTHANDLER => "Provisioning", self::UNAUTHENTICATED, self::UNPROVISIONED),
self::COMMAND_SEARCH => array(self::ASV_1, self::REQUESTHANDLER => "Search"),
self::COMMAND_PING => array(self::ASV_2, self::REQUESTHANDLER => "Ping", self::UNPROVISIONED),
self::COMMAND_NOTIFY => array(self::ASV_1, self::REQUESTHANDLER => "Notify"), // deprecated & not implemented
self::COMMAND_ITEMOPERATIONS => array(self::ASV_12, self::REQUESTHANDLER => "ItemOperations"),
self::COMMAND_SETTINGS => array(self::ASV_12, self::REQUESTHANDLER => "Settings"),
self::COMMAND_WEBSERVICE_DEVICE => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
self::COMMAND_WEBSERVICE_USERS => array(self::REQUESTHANDLER => "Webservice", self::PLAININPUT, self::NOACTIVESYNCCOMMAND, self::WEBSERVICECOMMAND),
);
static private $classes = array(
"Email" => array(
self::CLASS_NAME => "SyncMail",
self::CLASS_REQUIRESPROTOCOLVERSION => false,
self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_INBOX,
self::CLASS_OTHERTYPES => array(SYNC_FOLDER_TYPE_OTHER, SYNC_FOLDER_TYPE_DRAFTS, SYNC_FOLDER_TYPE_WASTEBASKET,
SYNC_FOLDER_TYPE_SENTMAIL, SYNC_FOLDER_TYPE_OUTBOX, SYNC_FOLDER_TYPE_USER_MAIL,
SYNC_FOLDER_TYPE_JOURNAL, SYNC_FOLDER_TYPE_USER_JOURNAL),
),
"Contacts" => array(
self::CLASS_NAME => "SyncContact",
self::CLASS_REQUIRESPROTOCOLVERSION => true,
self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_CONTACT,
self::CLASS_OTHERTYPES => array(SYNC_FOLDER_TYPE_USER_CONTACT),
),
"Calendar" => array(
self::CLASS_NAME => "SyncAppointment",
self::CLASS_REQUIRESPROTOCOLVERSION => false,
self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_APPOINTMENT,
self::CLASS_OTHERTYPES => array(SYNC_FOLDER_TYPE_USER_APPOINTMENT),
),
"Tasks" => array(
self::CLASS_NAME => "SyncTask",
self::CLASS_REQUIRESPROTOCOLVERSION => false,
self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_TASK,
self::CLASS_OTHERTYPES => array(SYNC_FOLDER_TYPE_USER_TASK),
),
"Notes" => array(
self::CLASS_NAME => "SyncNote",
self::CLASS_REQUIRESPROTOCOLVERSION => false,
self::CLASS_DEFAULTTYPE => SYNC_FOLDER_TYPE_NOTE,
self::CLASS_OTHERTYPES => array(SYNC_FOLDER_TYPE_USER_NOTE),
),
);
static private $stateMachine;
static private $searchProvider;
static private $deviceManager;
static private $topCollector;
static private $backend;
static private $addSyncFolders;
/**
* Verifies configuration
*
* @access public
* @return boolean
* @throws FatalMisconfigurationException
*/
static public function CheckConfig() {
// check the php version
if (version_compare(phpversion(),'5.1.0') < 0)
throw new FatalException("The configured PHP version is too old. Please make sure at least PHP 5.1 is used.");
// some basic checks
if (!defined('BASE_PATH'))
throw new FatalMisconfigurationException("The BASE_PATH is not configured. Check if the config.php file is in place.");
if (substr(BASE_PATH, -1,1) != "/")
throw new FatalMisconfigurationException("The BASE_PATH should terminate with a '/'");
if (!file_exists(BASE_PATH))
throw new FatalMisconfigurationException("The configured BASE_PATH does not exist or can not be accessed.");
if (defined('BASE_PATH_CLI') && file_exists(BASE_PATH_CLI))
define('REAL_BASE_PATH', BASE_PATH_CLI);
else
define('REAL_BASE_PATH', BASE_PATH);
if (!defined('LOGFILEDIR'))
throw new FatalMisconfigurationException("The LOGFILEDIR is not configured. Check if the config.php file is in place.");
if (substr(LOGFILEDIR, -1,1) != "/")
throw new FatalMisconfigurationException("The LOGFILEDIR should terminate with a '/'");
if (!file_exists(LOGFILEDIR))
throw new FatalMisconfigurationException("The configured LOGFILEDIR does not exist or can not be accessed.");
if ((!file_exists(LOGFILE) && !touch(LOGFILE)) || !is_writable(LOGFILE))
throw new FatalMisconfigurationException("The configured LOGFILE can not be modified.");
if ((!file_exists(LOGERRORFILE) && !touch(LOGERRORFILE)) || !is_writable(LOGERRORFILE))
throw new FatalMisconfigurationException("The configured LOGERRORFILE can not be modified.");
// check ownership on the (eventually) just created files
Utils::FixFileOwner(LOGFILE);
Utils::FixFileOwner(LOGERRORFILE);
// set time zone
// code contributed by Robert Scheck (rsc) - more information: https://developer.berlios.de/mantis/view.php?id=479
if(function_exists("date_default_timezone_set")) {
if(defined('TIMEZONE') ? constant('TIMEZONE') : false) {
if (! @date_default_timezone_set(TIMEZONE))
throw new FatalMisconfigurationException(sprintf("The configured TIMEZONE '%s' is not valid. Please check supported timezones at http://www.php.net/manual/en/timezones.php", constant('TIMEZONE')));
}
else if(!ini_get('date.timezone')) {
date_default_timezone_set('Europe/Amsterdam');
}
}
return true;
}
/**
* Verifies Timezone, StateMachine and Backend configuration
*
* @access public
* @return boolean
* @trows FatalMisconfigurationException
*/
static public function CheckAdvancedConfig() {
global $specialLogUsers, $additionalFolders;
if (!is_array($specialLogUsers))
throw new FatalMisconfigurationException("The WBXML log users is not an array.");
if (!defined('SINK_FORCERECHECK')) {
define('SINK_FORCERECHECK', 300);
}
else if (SINK_FORCERECHECK !== false && (!is_int(SINK_FORCERECHECK) || SINK_FORCERECHECK < 1))
throw new FatalMisconfigurationException("The SINK_FORCERECHECK value must be 'false' or a number higher than 0.");
if (!defined('SYNC_CONTACTS_MAXPICTURESIZE')) {
define('SYNC_CONTACTS_MAXPICTURESIZE', 49152);
}
else if ((!is_int(SYNC_CONTACTS_MAXPICTURESIZE) || SYNC_CONTACTS_MAXPICTURESIZE < 1))
throw new FatalMisconfigurationException("The SYNC_CONTACTS_MAXPICTURESIZE value must be a number higher than 0.");
// the check on additional folders will not throw hard errors, as this is probably changed on live systems
if (isset($additionalFolders) && !is_array($additionalFolders))
ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : The additional folders synchronization not available as array.");
else {
self::$addSyncFolders = array();
// process configured data
foreach ($additionalFolders as $af) {
if (!is_array($af) || !isset($af['store']) || !isset($af['folderid']) || !isset($af['name']) || !isset($af['type'])) {
ZLog::Write(LOGLEVEL_ERROR, "ZPush::CheckConfig() : the additional folder synchronization is not configured correctly. Missing parameters. Entry will be ignored.");
continue;
}
if ($af['store'] == "" || $af['folderid'] == "" || $af['name'] == "" || $af['type'] == "") {
ZLog::Write(LOGLEVEL_WARN, "ZPush::CheckConfig() : the additional folder synchronization is not configured correctly. Empty parameters. Entry will be ignored.");
continue;
}
if (!in_array($af['type'], array(SYNC_FOLDER_TYPE_USER_CONTACT, SYNC_FOLDER_TYPE_USER_APPOINTMENT, SYNC_FOLDER_TYPE_USER_TASK, SYNC_FOLDER_TYPE_USER_MAIL))) {
ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPush::CheckConfig() : the type of the additional synchronization folder '%s is not permitted.", $af['name']));
continue;
}
$folder = new SyncFolder();
$folder->serverid = $af['folderid'];
$folder->parentid = 0; // only top folders are supported
$folder->displayname = $af['name'];
$folder->type = $af['type'];
// save store as custom property which is not streamed directly to the device
$folder->NoBackendFolder = true;
$folder->Store = $af['store'];
self::$addSyncFolders[$folder->serverid] = $folder;
}
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Used timezone '%s'", date_default_timezone_get()));
// get the statemachine, which will also try to load the backend.. This could throw errors
self::GetStateMachine();
}
/**
* Returns the StateMachine object
* which has to be an IStateMachine implementation
*
* @access public
* @throws FatalNotImplementedException
* @throws HTTPReturnCodeException
* @return object implementation of IStateMachine
*/
static public function GetStateMachine() {
if (!isset(ZPush::$stateMachine)) {
// the backend could also return an own IStateMachine implementation
$backendStateMachine = self::GetBackend()->GetStateMachine();
// if false is returned, use the default StateMachine
if ($backendStateMachine !== false) {
ZLog::Write(LOGLEVEL_DEBUG, "Backend implementation of IStateMachine: ".get_class($backendStateMachine));
if (in_array('IStateMachine', class_implements($backendStateMachine)))
ZPush::$stateMachine = $backendStateMachine;
else
throw new FatalNotImplementedException("State machine returned by the backend does not implement the IStateMachine interface!");
}
else {
// Initialize the default StateMachine
include_once('lib/default/filestatemachine.php');
ZPush::$stateMachine = new FileStateMachine();
}
if (ZPush::$stateMachine->GetStateVersion() !== ZPush::GetLatestStateVersion()) {
if (class_exists("TopCollector")) self::GetTopCollector()->AnnounceInformation("Run migration script!", true);
throw new HTTPReturnCodeException(sprintf("The state version available to the %s is not the latest version - please run the state upgrade script. See release notes for more information.", get_class(ZPush::$stateMachine), 503));
}
}
return ZPush::$stateMachine;
}
/**
* Returns the latest version of supported states
*
* @access public
* @return int
*/
static public function GetLatestStateVersion() {
return self::STATE_VERSION;
}
/**
* Returns the DeviceManager object
*
* @param boolean $initialize (opt) default true: initializes the DeviceManager if not already done
*
* @access public
* @return object DeviceManager
*/
static public function GetDeviceManager($initialize = true) {
if (!isset(ZPush::$deviceManager) && $initialize)
ZPush::$deviceManager = new DeviceManager();
return ZPush::$deviceManager;
}
/**
* Returns the Top data collector object
*
* @access public
* @return object TopCollector
*/
static public function GetTopCollector() {
if (!isset(ZPush::$topCollector))
ZPush::$topCollector = new TopCollector();
return ZPush::$topCollector;
}
/**
* Loads a backend file
*
* @param string $backendname
* @access public
* @throws FatalNotImplementedException
* @return boolean
*/
static public function IncludeBackend($backendname) {
if ($backendname == false) return false;
$backendname = strtolower($backendname);
if (substr($backendname, 0, 7) !== 'backend')
throw new FatalNotImplementedException(sprintf("Backend '%s' is not allowed",$backendname));
$rbn = substr($backendname, 7);
$subdirbackend = REAL_BASE_PATH . "backend/" . $rbn . "/" . $rbn . ".php";
$stdbackend = REAL_BASE_PATH . "backend/" . $rbn . ".php";
if (is_file($subdirbackend))
$toLoad = $subdirbackend;
else if (is_file($stdbackend))
$toLoad = $stdbackend;
else
return false;
ZLog::Write(LOGLEVEL_DEBUG, sprintf("Including backend file: '%s'", $toLoad));
include_once($toLoad);
return true;
}
/**
* Returns the SearchProvider object
* which has to be an ISearchProvider implementation
*
* @access public
* @return object implementation of ISearchProvider
* @throws FatalMisconfigurationException, FatalNotImplementedException
*/
static public function GetSearchProvider() {
if (!isset(ZPush::$searchProvider)) {
// is a global searchprovider configured ? It will outrank the backend
if (defined('SEARCH_PROVIDER') && @constant('SEARCH_PROVIDER') != "") {
$searchClass = @constant('SEARCH_PROVIDER');
if (! class_exists($searchClass))
self::IncludeBackend($searchClass);
if (class_exists($searchClass))
$aSearchProvider = new $searchClass();
else
throw new FatalMisconfigurationException(sprintf("Search provider '%s' can not be loaded. Check configuration!", $searchClass));
}
// get the searchprovider from the backend
else
$aSearchProvider = self::GetBackend()->GetSearchProvider();
if (in_array('ISearchProvider', class_implements($aSearchProvider)))
ZPush::$searchProvider = $aSearchProvider;
else
throw new FatalNotImplementedException("Instantiated SearchProvider does not implement the ISearchProvider interface!");
}
return ZPush::$searchProvider;
}
/**
* Returns the Backend for this request
* the backend has to be an IBackend implementation
*
* @access public
* @return object IBackend implementation
*/
static public function GetBackend() {
// if the backend is not yet loaded, load backend drivers and instantiate it
if (!isset(ZPush::$backend)) {
// Initialize our backend
$ourBackend = @constant('BACKEND_PROVIDER');
// if no backend provider is defined, try to include automatically
if ($ourBackend == false || $ourBackend == "") {
$loaded = false;
foreach (self::$autoloadBackendPreference as $autoloadBackend) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::GetBackend(): trying autoload backend '%s'", $autoloadBackend));
$loaded = self::IncludeBackend($autoloadBackend);
if ($loaded) {
$ourBackend = $autoloadBackend;
break;
}
}
if (!$ourBackend || !$loaded)
throw new FatalMisconfigurationException("No Backend provider can not be loaded. Check your installation and configuration!");
}
else
self::IncludeBackend($ourBackend);
if (class_exists($ourBackend))
ZPush::$backend = new $ourBackend();
else
throw new FatalMisconfigurationException(sprintf("Backend provider '%s' can not be loaded. Check configuration!", $ourBackend));
}
return ZPush::$backend;
}
/**
* Returns additional folder objects which should be synchronized to the device
*
* @access public
* @return array
*/
static public function GetAdditionalSyncFolders() {
// TODO if there are any user based folders which should be synchronized, they have to be returned here as well!!
return self::$addSyncFolders;
}
/**
* Returns additional folder objects which should be synchronized to the device
*
* @param string $folderid
* @param boolean $noDebug (opt) by default, debug message is shown
*
* @access public
* @return string
*/
static public function GetAdditionalSyncFolderStore($folderid, $noDebug = false) {
$val = (isset(self::$addSyncFolders[$folderid]->Store))? self::$addSyncFolders[$folderid]->Store : false;
if (!$noDebug)
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::GetAdditionalSyncFolderStore('%s'): '%s'", $folderid, Utils::PrintAsString($val)));
return $val;
}
/**
* Returns a SyncObject class name for a folder class
*
* @param string $folderclass
*
* @access public
* @return string
* @throws FatalNotImplementedException
*/
static public function getSyncObjectFromFolderClass($folderclass) {
if (!isset(self::$classes[$folderclass]))
throw new FatalNotImplementedException("Class '$folderclass' is not supported");
$class = self::$classes[$folderclass][self::CLASS_NAME];
if (self::$classes[$folderclass][self::CLASS_REQUIRESPROTOCOLVERSION])
return new $class(Request::GetProtocolVersion());
else
return new $class();
}
/**
* Returns the default foldertype for a folder class
*
* @param string $folderclass folderclass sent by the mobile
*
* @access public
* @return string
*/
static public function getDefaultFolderTypeFromFolderClass($folderclass) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::getDefaultFolderTypeFromFolderClass('%s'): '%d'", $folderclass, self::$classes[$folderclass][self::CLASS_DEFAULTTYPE]));
return self::$classes[$folderclass][self::CLASS_DEFAULTTYPE];
}
/**
* Returns the folder class for a foldertype
*
* @param string $foldertype
*
* @access public
* @return string/false false if no class for this type is available
*/
static public function GetFolderClassFromFolderType($foldertype) {
$class = false;
foreach (self::$classes as $aClass => $cprops) {
if ($cprops[self::CLASS_DEFAULTTYPE] == $foldertype || in_array($foldertype, $cprops[self::CLASS_OTHERTYPES])) {
$class = $aClass;
break;
}
}
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::GetFolderClassFromFolderType('%s'): %s", $foldertype, Utils::PrintAsString($class)));
return $class;
}
/**
* Prints the Z-Push legal header to STDOUT
* Using this breaks ActiveSync synchronization if wbxml is expected
*
* @param string $message (opt) message to be displayed
* @param string $additionalMessage (opt) additional message to be displayed
* @access public
* @return
*
*/
static public function PrintZPushLegal($message = "", $additionalMessage = "") {
ZLog::Write(LOGLEVEL_DEBUG,"ZPush::PrintZPushLegal()");
$zpush_version = @constant('ZPUSH_VERSION');
if ($message)
$message = "<h3>". $message . "</h3>";
if ($additionalMessage)
$additionalMessage .= "<br>";
header("Content-type: text/html");
print <<<END
<html>
<header>
<title>Z-Push ActiveSync</title>
</header>
<body>
<font face="verdana">
<h2>Z-Push - Open Source ActiveSync</h2>
<b>Version $zpush_version</b><br>
$message $additionalMessage
<br><br>
More information about Z-Push can be found at:<br>
<a href="http://z-push.sf.net/">Z-Push homepage</a><br>
<a href="http://z-push.sf.net/download">Z-Push download page at BerliOS</a><br>
<a href="http://z-push.sf.net/tracker">Z-Push Bugtracker and Roadmap</a><br>
<br>
All modifications to this sourcecode must be published and returned to the community.<br>
Please see <a href="http://www.gnu.org/licenses/agpl-3.0.html">AGPLv3 License</a> for details.<br>
</font face="verdana">
</body>
</html>
END;
}
/**
* Indicates the latest AS version supported by Z-Push
*
* @access public
* @return string
*/
static public function GetLatestSupportedASVersion() {
return end(self::$supportedASVersions);
}
/**
* Indicates which is the highest AS version supported by the backend
*
* @access public
* @return string
* @throws FatalNotImplementedException if the backend returns an invalid version
*/
static public function GetSupportedASVersion() {
$version = self::GetBackend()->GetSupportedASVersion();
if (!in_array($version, self::$supportedASVersions))
throw new FatalNotImplementedException(sprintf("AS version '%s' reported by the backend is not supported", $version));
return $version;
}
/**
* Returns AS server header
*
* @access public
* @return string
*/
static public function GetServerHeader() {
if (self::GetSupportedASVersion() == self::ASV_25)
return "MS-Server-ActiveSync: 6.5.7638.1";
else
return "MS-Server-ActiveSync: ". self::GetSupportedASVersion();
}
/**
* Returns AS protocol versions which are supported
*
* @param boolean $valueOnly (opt) default: false (also returns the header name)
*
* @access public
* @return string
*/
static public function GetSupportedProtocolVersions($valueOnly = false) {
$versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1)));
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions);
if ($valueOnly === true)
return $versions;
return "MS-ASProtocolVersions: " . $versions;
}
/**
* Returns AS commands which are supported
*
* @access public
* @return string
*/
static public function GetSupportedCommands() {
$asCommands = array();
// filter all non-activesync commands
foreach (self::$supportedCommands as $c=>$v)
if (!self::checkCommandOptions($c, self::NOACTIVESYNCCOMMAND) &&
self::checkCommandOptions($c, self::GetSupportedASVersion()))
$asCommands[] = Utils::GetCommandFromCode($c);
$commands = implode(',', $asCommands);
ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedCommands(): " . $commands);
return "MS-ASProtocolCommands: " . $commands;
}
/**
* Loads and instantiates a request processor for a command
*
* @param int $commandCode
*
* @access public
* @return RequestProcessor sub-class
*/
static public function GetRequestHandlerForCommand($commandCode) {
if (!array_key_exists($commandCode, self::$supportedCommands) ||
!array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode]) )
throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode)));
$class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER];
if ($class == "Webservice")
$handlerclass = REAL_BASE_PATH . "lib/webservice/webservice.php";
else
$handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php";
if (is_file($handlerclass))
include($handlerclass);
if (class_exists($class))
return new $class();
else
throw new FatalNotImplementedException(sprintf("Request handler '%s' can not be loaded", $class));
}
/**
* Indicates if a commands requires authentication or not
*
* @param int $commandCode
*
* @access public
* @return boolean
*/
static public function CommandNeedsAuthentication($commandCode) {
$stat = ! self::checkCommandOptions($commandCode, self::UNAUTHENTICATED);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsAuthentication(%d): %s", $commandCode, Utils::PrintAsString($stat)));
return $stat;
}
/**
* Indicates if the Provisioning check has to be forced on these commands
*
* @param string $commandCode
* @access public
* @return boolean
*/
static public function CommandNeedsProvisioning($commandCode) {
$stat = ! self::checkCommandOptions($commandCode, self::UNPROVISIONED);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsProvisioning(%s): %s", $commandCode, Utils::PrintAsString($stat)));
return $stat;
}
/**
* Indicates if these commands expect plain text input instead of wbxml
*
* @param string $commandCode
*
* @access public
* @return boolean
*/
static public function CommandNeedsPlainInput($commandCode) {
$stat = self::checkCommandOptions($commandCode, self::PLAININPUT);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString($stat)));
return $stat;
}
/**
* Indicates if the comand to be executed operates on the hierarchy
*
* @param int $commandCode
* @access public
* @return boolean
*/
static public function HierarchyCommand($commandCode) {
$stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat)));
return $stat;
}
/**
* Checks access types of a command
*
* @param string $commandCode a commandCode
* @param string $option e.g. self::UNAUTHENTICATED
* @access private
* @throws FatalNotImplementedException
* @return object StateMachine
*/
static private function checkCommandOptions($commandCode, $option) {
if ($commandCode === false) return false;
if (!array_key_exists($commandCode, self::$supportedCommands))
throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode($commandCode)));
$capa = self::$supportedCommands[$commandCode];
$defcapa = in_array($option, $capa, true);
// if not looking for a default capability, check if the command is supported since a previous AS version
if (!$defcapa) {
$verkey = array_search($option, self::$supportedASVersions, true);
if ($verkey !== false && ($verkey >= array_search($capa[0], self::$supportedASVersions))) {
$defcapa = true;
}
}
return $defcapa;
}
}
?>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,294 @@
<?php
/***********************************************
* File : backend.php
* Project : Z-Push
* Descr : This is what C++ people
* (and PHP5) would call an
* abstract class. The
* backend module itself is
* responsible for converting any
* necessary types and formats.
*
* If you wish to implement a new
* backend, all you need to do is
* to subclass the following class
* (or implement an IBackend)
* and place the subclassed file in
* the backend/yourBackend directory. You can
* then use your backend by
* specifying it in the config.php file
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
abstract class Backend implements IBackend {
protected $permanentStorage;
protected $stateStorage;
/**
* Constructor
*
* @access public
*/
public function Backend() {
}
/**
* Returns a IStateMachine implementation used to save states
* The default StateMachine should be used here, so, false is fine
*
* @access public
* @return boolean/object
*/
public function GetStateMachine() {
return false;
}
/**
* Returns a ISearchProvider implementation used for searches
* the SearchProvider is just a stub
*
* @access public
* @return object Implementation of ISearchProvider
*/
public function GetSearchProvider() {
return new SearchProvider();
}
/**
* Indicates which AS version is supported by the backend.
* By default AS version 2.5 (ASV_25) is returned (Z-Push 1 standard).
* Subclasses can overwrite this method to set another AS version
*
* @access public
* @return string AS version constant
*/
public function GetSupportedASVersion() {
return ZPush::ASV_25;
}
/*********************************************************************
* Methods to be implemented
*
* public function Logon($username, $domain, $password);
* public function Setup($store, $checkACLonly = false, $folderid = false);
* public function Logoff();
* public function GetHierarchy();
* public function GetImporter($folderid = false);
* public function GetExporter($folderid = false);
* public function SendMail($sm);
* public function Fetch($folderid, $id, $contentparameters);
* public function GetWasteBasket();
* public function GetAttachmentData($attname);
* public function MeetingResponse($requestid, $folderid, $response);
*
*/
/**
* Deletes all contents of the specified folder.
* This is generally used to empty the trash (wastebasked), but could also be used on any
* other folder.
*
* @param string $folderid
* @param boolean $includeSubfolders (opt) also delete sub folders, default true
*
* @access public
* @return boolean
* @throws StatusException
*/
public function EmptyFolder($folderid, $includeSubfolders = true) {
return false;
}
/**
* Indicates if the backend has a ChangesSink.
* A sink is an active notification mechanism which does not need polling.
*
* @access public
* @return boolean
*/
public function HasChangesSink() {
return false;
}
/**
* The folder should be considered by the sink.
* Folders which were not initialized should not result in a notification
* of IBacken->ChangesSink().
*
* @param string $folderid
*
* @access public
* @return boolean false if there is any problem with that folder
*/
public function ChangesSinkInitialize($folderid) {
return false;
}
/**
* The actual ChangesSink.
* For max. the $timeout value this method should block and if no changes
* are available return an empty array.
* If changes are available a list of folderids is expected.
*
* @param int $timeout max. amount of seconds to block
*
* @access public
* @return array
*/
public function ChangesSink($timeout = 30) {
return array();
}
/**
* Applies settings to and gets informations from the device
*
* @param SyncObject $settings (SyncOOF or SyncUserInformation possible)
*
* @access public
* @return SyncObject $settings
*/
public function Settings($settings) {
if ($settings instanceof SyncOOF || $settings instanceof SyncUserInformation)
$settings->Status = SYNC_SETTINGSSTATUS_SUCCESS;
return $settings;
}
/**
* Resolves recipients
*
* @param SyncObject $resolveRecipients
*
* @access public
* @return SyncObject $resolveRecipients
*/
public function ResolveRecipients($resolveRecipients) {
$r = new SyncResolveRecipients();
$r->status = SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR;
$r->recipient = array();
return $r;
}
/**----------------------------------------------------------------------------------------------------------
* Protected methods for BackendStorage
*
* Backends can use a permanent and a state related storage to save additional data
* used during the synchronization.
*
* While permament storage is bound to the device and user, state related data works linked
* to the regular states (and its counters).
*
* Both consist of a StateObject, while the backend can decide what to save in it.
*
* Before using $this->permanentStorage and $this->stateStorage the initilize methods have to be
* called from the backend.
*
* Backend->LogOff() must call $this->SaveStorages() so the data is written to disk!
*
* These methods are an abstraction layer for StateManager->Get/SetBackendStorage()
* which can also be used independently.
*/
/**
* Loads the permanent storage data of the user and device
*
* @access protected
* @return
*/
protected function InitializePermanentStorage() {
if (!isset($this->permanentStorage)) {
try {
$this->permanentStorage = ZPush::GetDeviceManager()->GetStateManager()->GetBackendStorage(StateManager::BACKENDSTORAGE_PERMANENT);
}
catch (StateNotYetAvailableException $snyae) {
$this->permanentStorage = new StateObject();
}
catch(StateNotFoundException $snfe) {
$this->permanentStorage = new StateObject();
}
}
}
/**
* Loads the state related storage data of the user and device
* All data not necessary for the next state should be removed
*
* @access protected
* @return
*/
protected function InitializeStateStorage() {
if (!isset($this->stateStorage)) {
try {
$this->stateStorage = ZPush::GetDeviceManager()->GetStateManager()->GetBackendStorage(StateManager::BACKENDSTORAGE_STATE);
}
catch (StateNotYetAvailableException $snyae) {
$this->stateStorage = new StateObject();
}
catch(StateNotFoundException $snfe) {
$this->stateStorage = new StateObject();
}
}
}
/**
* Saves the permanent and state related storage data of the user and device
* if they were loaded previousily
* If the backend storage is used this should be called
*
* @access protected
* @return
*/
protected function SaveStorages() {
if (isset($this->permanentStorage)) {
try {
ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->permanentStorage, StateManager::BACKENDSTORAGE_PERMANENT);
}
catch (StateNotYetAvailableException $snyae) { }
catch(StateNotFoundException $snfe) { }
}
if (isset($this->stateStorage)) {
try {
$this->storage_state = ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->stateStorage, StateManager::BACKENDSTORAGE_STATE);
}
catch (StateNotYetAvailableException $snyae) { }
catch(StateNotFoundException $snfe) { }
}
}
}
?>

View file

@ -0,0 +1,378 @@
<?php
/***********************************************
* File : diffbackend.php
* Project : Z-Push
* Descr : This is the abstract differential backend.
* By implementing this backends there is no need
* to worry about importers and exporters. Also
* tracking incremental changes is not necessary,
* as the DiffState used by the DiffBackend offers
* this functionality by comparing the list of objects
* available "last time" with the list of objects
* available "now".
* Please note that the differential mechanism
* can consume a considerable amount of memory and cpu
* power when synchronizing folders with many items.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// default backend
include_once('lib/default/backend.php');
// DiffBackend components
include_once('diffstate.php');
include_once('importchangesdiff.php');
include_once('exportchangesdiff.php');
abstract class BackendDiff extends Backend {
protected $store;
/**
* Setup the backend to work on a specific store or checks ACLs there.
* If only the $store is submitted, all Import/Export/Fetch/Etc operations should be
* performed on this store (switch operations store).
* If the ACL check is enabled, this operation should just indicate the ACL status on
* the submitted store, without changing the store for operations.
* For the ACL status, the currently logged on user MUST have access rights on
* - the entire store - admin access if no folderid is sent, or
* - on a specific folderid in the store (secretary/full access rights)
*
* The ACLcheck MUST fail if a folder of the authenticated user is checked!
*
* @param string $store target store, could contain a "domain\user" value
* @param boolean $checkACLonly if set to true, Setup() should just check ACLs
* @param string $folderid if set, only ACLs on this folderid are relevant
*
* @access public
* @return boolean
*/
public function Setup($store, $checkACLonly = false, $folderid = false) {
$this->store = $store;
// we don't know if and how diff backends implement the "admin" check, but this will disable it for the webservice
// backends which want to implement this, need to overwrite this method explicitely. For more info see https://jira.zarafa.com/browse/ZP-462
if ($store == "SYSTEM" && $checkACLonly == true)
return false;
return true;
}
/**
* Returns an array of SyncFolder types with the entire folder hierarchy
* on the server (the array itself is flat, but refers to parents via the 'parent' property
*
* provides AS 1.0 compatibility
*
* @access public
* @return array SYNC_FOLDER
*/
function GetHierarchy() {
$folders = array();
$fl = $this->GetFolderList();
if (is_array($fl))
foreach($fl as $f)
$folders[] = $this->GetFolder($f['id']);
return $folders;
}
/**
* Returns the importer to process changes from the mobile
* If no $folderid is given, hierarchy importer is expected
*
* @param string $folderid (opt)
*
* @access public
* @return object(ImportChanges)
* @throws StatusException
*/
public function GetImporter($folderid = false) {
return new ImportChangesDiff($this, $folderid);
}
/**
* Returns the exporter to send changes to the mobile
* If no $folderid is given, hierarchy exporter is expected
*
* @param string $folderid (opt)
*
* @access public
* @return object(ExportChanges)
* @throws StatusException
*/
public function GetExporter($folderid = false) {
return new ExportChangesDiff($this, $folderid);
}
/**
* Returns all available data of a single message
*
* @param string $folderid
* @param string $id
* @param ContentParameters $contentparameters flag
*
* @access public
* @return object(SyncObject)
* @throws StatusException
*/
public function Fetch($folderid, $id, $contentparameters) {
// override truncation
$contentparameters->SetTruncation(SYNC_TRUNCATION_ALL);
$msg = $this->GetMessage($folderid, $id, $contentparameters);
if ($msg === false)
throw new StatusException("BackendDiff->Fetch('%s','%s'): Error, unable retrieve message from backend", SYNC_STATUS_OBJECTNOTFOUND);
return $msg;
}
/**
* Processes a response to a meeting request.
* CalendarID is a reference and has to be set if a new calendar item is created
*
* @param string $requestid id of the object containing the request
* @param string $folderid id of the parent folder of $requestid
* @param string $response
*
* @access public
* @return string id of the created/updated calendar obj
* @throws StatusException
*/
public function MeetingResponse($requestid, $folderid, $response) {
throw new StatusException(sprintf("BackendDiff->MeetingResponse('%s','%s','%s'): Error, this functionality is not supported by the diff backend", $requestid, $folderid, $response), SYNC_MEETRESPSTATUS_MAILBOXERROR);
}
/**----------------------------------------------------------------------------------------------------------
* Abstract DiffBackend methods
*
* Need to be implemented in the actual diff backend
*/
/**
* Returns a list (array) of folders, each entry being an associative array
* with the same entries as StatFolder(). This method should return stable information; ie
* if nothing has changed, the items in the array must be exactly the same. The order of
* the items within the array is not important though.
*
* @access protected
* @return array/boolean false if the list could not be retrieved
*/
public abstract function GetFolderList();
/**
* Returns an actual SyncFolder object with all the properties set. Folders
* are pretty simple, having only a type, a name, a parent and a server ID.
*
* @param string $id id of the folder
*
* @access public
* @return object SyncFolder with information
*/
public abstract function GetFolder($id);
/**
* Returns folder stats. An associative array with properties is expected.
*
* @param string $id id of the folder
*
* @access public
* @return array
* Associative array(
* string "id" The server ID that will be used to identify the folder. It must be unique, and not too long
* How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
* string "parent" The server ID of the parent of the folder. Same restrictions as 'id' apply.
* long "mod" This is the modification signature. It is any arbitrary string which is constant as long as
* the folder has not changed. In practice this means that 'mod' can be equal to the folder name
* as this is the only thing that ever changes in folders. (the type is normally constant)
* )
*/
public abstract function StatFolder($id);
/**
* Creates or modifies a folder
*
* @param string $folderid id of the parent folder
* @param string $oldid if empty -> new folder created, else folder is to be renamed
* @param string $displayname new folder name (to be created, or to be renamed to)
* @param int $type folder type
*
* @access public
* @return boolean status
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*
*/
public abstract function ChangeFolder($folderid, $oldid, $displayname, $type);
/**
* Deletes a folder
*
* @param string $id
* @param string $parent is normally false
*
* @access public
* @return boolean status - false if e.g. does not exist
* @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions
*/
public abstract function DeleteFolder($id, $parentid);
/**
* Returns a list (array) of messages, each entry being an associative array
* with the same entries as StatMessage(). This method should return stable information; ie
* if nothing has changed, the items in the array must be exactly the same. The order of
* the items within the array is not important though.
*
* The $cutoffdate is a date in the past, representing the date since which items should be shown.
* This cutoffdate is determined by the user's setting of getting 'Last 3 days' of e-mail, etc. If
* the cutoffdate is ignored, the user will not be able to select their own cutoffdate, but all
* will work OK apart from that.
*
* @param string $folderid id of the parent folder
* @param long $cutoffdate timestamp in the past from which on messages should be returned
*
* @access public
* @return array/false array with messages or false if folder is not available
*/
public abstract function GetMessageList($folderid, $cutoffdate);
/**
* Returns the actual SyncXXX object type. The '$folderid' of parent folder can be used.
* Mixing item types returned is illegal and will be blocked by the engine; ie returning an Email object in a
* Tasks folder will not do anything. The SyncXXX objects should be filled with as much information as possible,
* but at least the subject, body, to, from, etc.
*
* @param string $folderid id of the parent folder
* @param string $id id of the message
* @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc)
*
* @access public
* @return object/false false if the message could not be retrieved
*/
public abstract function GetMessage($folderid, $id, $contentparameters);
/**
* Returns message stats, analogous to the folder stats from StatFolder().
*
* @param string $folderid id of the folder
* @param string $id id of the message
*
* @access public
* @return array or boolean if fails
* Associative array(
* string "id" Server unique identifier for the message. Again, try to keep this short (under 20 chars)
* int "flags" simply '0' for unread, '1' for read
* long "mod" This is the modification signature. It is any arbitrary string which is constant as long as
* the message has not changed. As soon as this signature changes, the item is assumed to be completely
* changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
* time for this field, which will change as soon as the contents have changed.
* )
*/
public abstract function StatMessage($folderid, $id);
/**
* Called when a message has been changed on the mobile. The new message must be saved to disk.
* The return value must be whatever would be returned from StatMessage() after the message has been saved.
* This way, the 'flags' and the 'mod' properties of the StatMessage() item may change via ChangeMessage().
* This method will never be called on E-mail items as it's not 'possible' to change e-mail items. It's only
* possible to set them as 'read' or 'unread'.
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param SyncXXX $message the SyncObject containing a message
* @param ContentParameters $contentParameters
*
* @access public
* @return array same return value as StatMessage()
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public abstract function ChangeMessage($folderid, $id, $message, $contentParameters);
/**
* Changes the 'read' flag of a message on disk. The $flags
* parameter can only be '1' (read) or '0' (unread). After a call to
* SetReadFlag(), GetMessageList() should return the message with the
* new 'flags' but should not modify the 'mod' parameter. If you do
* change 'mod', simply setting the message to 'read' on the mobile will trigger
* a full resync of the item from the server.
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param int $flags read flag of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public abstract function SetReadFlag($folderid, $id, $flags, $contentParameters);
/**
* Called when the user has requested to delete (really delete) a message. Usually
* this means just unlinking the file its in or somesuch. After this call has succeeded, a call to
* GetMessageList() should no longer list the message. If it does, the message will be re-sent to the mobile
* as it will be seen as a 'new' item. This means that if this method is not implemented, it's possible to
* delete messages on the PDA, but as soon as a sync is done, the item will be resynched to the mobile
*
* @param string $folderid id of the folder
* @param string $id id of the message
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_STATUS_* exceptions
*/
public abstract function DeleteMessage($folderid, $id, $contentParameters);
/**
* Called when the user moves an item on the PDA from one folder to another. Whatever is needed
* to move the message on disk has to be done here. After this call, StatMessage() and GetMessageList()
* should show the items to have a new parent. This means that it will disappear from GetMessageList()
* of the sourcefolder and the destination folder will show the new message
*
* @param string $folderid id of the source folder
* @param string $id id of the message
* @param string $newfolderid id of the destination folder
* @param ContentParameters $contentParameters
*
* @access public
* @return boolean status of the operation
* @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
*/
public abstract function MoveMessage($folderid, $id, $newfolderid, $contentParameters);
}
?>

View file

@ -0,0 +1,296 @@
<?php
/***********************************************
* File : diffstate.php
* Project : Z-Push
* Descr : This is the differential engine.
* We do a standard differential
* change detection by sorting both
* lists of items by their unique id,
* and then traversing both arrays
* of items at once. Changes can be
* detected by comparing items at
* the same position in both arrays.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class DiffState implements IChanges {
protected $syncstate;
protected $backend;
protected $flags;
protected $contentparameters;
protected $cutoffdate;
/**
* Initializes the state
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean status flag
* @throws StatusException
*/
public function Config($state, $flags = 0) {
if ($state == "")
$state = array();
if (!is_array($state))
throw new StatusException("Invalid state", SYNC_FSSTATUS_CODEUNKNOWN);
$this->syncstate = $state;
$this->flags = $flags;
return true;
}
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters) {
$this->contentparameters = $contentparameters;
$this->cutoffdate = Utils::GetCutOffDate($contentparameters->GetFilterType());
}
/**
* Returns state
*
* @access public
* @return string
* @throws StatusException
*/
public function GetState() {
if (!isset($this->syncstate) || !is_array($this->syncstate))
throw new StatusException("DiffState->GetState(): Error, state not available", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
return $this->syncstate;
}
/**----------------------------------------------------------------------------------------------------------
* DiffState specific stuff
*/
/**
* Comparing function used for sorting of the differential engine
*
* @param array $a
* @param array $b
*
* @access public
* @return boolean
*/
static public function RowCmp($a, $b) {
// TODO implement different comparing functions
return $a["id"] < $b["id"] ? 1 : -1;
}
/**
* Differential mechanism
* Compares the current syncstate to the sent $new
*
* @param array $new
*
* @access protected
* @return array
*/
protected function getDiffTo($new) {
$changes = array();
// Sort both arrays in the same way by ID
usort($this->syncstate, array("DiffState", "RowCmp"));
usort($new, array("DiffState", "RowCmp"));
$inew = 0;
$iold = 0;
// Get changes by comparing our list of messages with
// our previous state
while(1) {
$change = array();
if($iold >= count($this->syncstate) || $inew >= count($new))
break;
if($this->syncstate[$iold]["id"] == $new[$inew]["id"]) {
// Both messages are still available, compare flags and mod
if(isset($this->syncstate[$iold]["flags"]) && isset($new[$inew]["flags"]) && $this->syncstate[$iold]["flags"] != $new[$inew]["flags"]) {
// Flags changed
$change["type"] = "flags";
$change["id"] = $new[$inew]["id"];
$change["flags"] = $new[$inew]["flags"];
$changes[] = $change;
}
if($this->syncstate[$iold]["mod"] != $new[$inew]["mod"]) {
$change["type"] = "change";
$change["id"] = $new[$inew]["id"];
$changes[] = $change;
}
$inew++;
$iold++;
} else {
if($this->syncstate[$iold]["id"] > $new[$inew]["id"]) {
// Message in state seems to have disappeared (delete)
$change["type"] = "delete";
$change["id"] = $this->syncstate[$iold]["id"];
$changes[] = $change;
$iold++;
} else {
// Message in new seems to be new (add)
$change["type"] = "change";
$change["flags"] = SYNC_NEWMESSAGE;
$change["id"] = $new[$inew]["id"];
$changes[] = $change;
$inew++;
}
}
}
while($iold < count($this->syncstate)) {
// All data left in 'syncstate' have been deleted
$change["type"] = "delete";
$change["id"] = $this->syncstate[$iold]["id"];
$changes[] = $change;
$iold++;
}
while($inew < count($new)) {
// All data left in new have been added
$change["type"] = "change";
$change["flags"] = SYNC_NEWMESSAGE;
$change["id"] = $new[$inew]["id"];
$changes[] = $change;
$inew++;
}
return $changes;
}
/**
* Update the state to reflect changes
*
* @param string $type of change
* @param array $change
*
*
* @access protected
* @return
*/
protected function updateState($type, $change) {
// Change can be a change or an add
if($type == "change") {
for($i=0; $i < count($this->syncstate); $i++) {
if($this->syncstate[$i]["id"] == $change["id"]) {
$this->syncstate[$i] = $change;
return;
}
}
// Not found, add as new
$this->syncstate[] = $change;
} else {
for($i=0; $i < count($this->syncstate); $i++) {
// Search for the entry for this item
if($this->syncstate[$i]["id"] == $change["id"]) {
if($type == "flags") {
// Update flags
$this->syncstate[$i]["flags"] = $change["flags"];
} else if($type == "delete") {
// Delete item
array_splice($this->syncstate, $i, 1);
}
return;
}
}
}
}
/**
* Returns TRUE if the given ID conflicts with the given operation. This is only true in the following situations:
* - Changed here and changed there
* - Changed here and deleted there
* - Deleted here and changed there
* Any other combination of operations can be done (e.g. change flags & move or move & delete)
*
* @param string $type of change
* @param string $folderid
* @param string $id
*
* @access protected
* @return
*/
protected function isConflict($type, $folderid, $id) {
$stat = $this->backend->StatMessage($folderid, $id);
if(!$stat) {
// Message is gone
if($type == "change")
return true; // deleted here, but changed there
else
return false; // all other remote changes still result in a delete (no conflict)
}
foreach($this->syncstate as $state) {
if($state["id"] == $id) {
$oldstat = $state;
break;
}
}
if(!isset($oldstat)) {
// New message, can never conflict
return false;
}
if($stat["mod"] != $oldstat["mod"]) {
// Changed here
if($type == "delete" || $type == "change")
return true; // changed here, but deleted there -> conflict, or changed here and changed there -> conflict
else
return false; // changed here, and other remote changes (move or flags)
}
}
}
?>

View file

@ -0,0 +1,218 @@
<?php
/***********************************************
* File : exportchangesdiff.php
* Project : Z-Push
* Descr : IExportChanges implementation using
* the differential engine
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ExportChangesDiff extends DiffState implements IExportChanges{
private $importer;
private $folderid;
private $changes;
private $step;
/**
* Constructor
*
* @param object $backend
* @param string $folderid
*
* @access public
* @throws StatusException
*/
public function ExportChangesDiff($backend, $folderid) {
$this->backend = $backend;
$this->folderid = $folderid;
}
/**
* Sets the importer the exporter will sent it's changes to
* and initializes the Exporter
*
* @param object &$importer Implementation of IImportChanges
*
* @access public
* @return boolean
* @throws StatusException
*/
public function InitializeExporter(&$importer) {
$this->changes = array();
$this->step = 0;
$this->importer = $importer;
if($this->folderid) {
// Get the changes since the last sync
if(!isset($this->syncstate) || !$this->syncstate)
$this->syncstate = array();
ZLog::Write(LOGLEVEL_DEBUG,sprintf("ExportChangesDiff->InitializeExporter(): Initializing message diff engine. '%d' messages in state", count($this->syncstate)));
//do nothing if it is a dummy folder
if ($this->folderid != SYNC_FOLDER_TYPE_DUMMY) {
// Get our lists - syncstate (old) and msglist (new)
$msglist = $this->backend->GetMessageList($this->folderid, $this->cutoffdate);
// if the folder was deleted, no information is available anymore. A hierarchysync should be executed
if($msglist === false)
throw new StatusException("ExportChangesDiff->InitializeExporter(): Error, no message list available from the backend", SYNC_STATUS_FOLDERHIERARCHYCHANGED, null, LOGLEVEL_INFO);
$this->changes = $this->getDiffTo($msglist);
}
}
else {
ZLog::Write(LOGLEVEL_DEBUG, "Initializing folder diff engine");
ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesDiff->InitializeExporter(): Initializing folder diff engine");
$folderlist = $this->backend->GetFolderList();
if($folderlist === false)
throw new StatusException("ExportChangesDiff->InitializeExporter(): error, no folders available from the backend", SYNC_FSSTATUS_CODEUNKNOWN, null, LOGLEVEL_WARN);
if(!isset($this->syncstate) || !$this->syncstate)
$this->syncstate = array();
$this->changes = $this->getDiffTo($folderlist);
}
ZLog::Write(LOGLEVEL_INFO, sprintf("ExportChangesDiff->InitializeExporter(): Found '%d' changes", count($this->changes) ));
}
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount() {
return count($this->changes);
}
/**
* Synchronizes a change
*
* @access public
* @return array
*/
public function Synchronize() {
$progress = array();
// Get one of our stored changes and send it to the importer, store the new state if
// it succeeds
if($this->folderid == false) {
if($this->step < count($this->changes)) {
$change = $this->changes[$this->step];
switch($change["type"]) {
case "change":
$folder = $this->backend->GetFolder($change["id"]);
$stat = $this->backend->StatFolder($change["id"]);
if(!$folder)
return;
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportFolderChange($folder))
$this->updateState("change", $stat);
break;
case "delete":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportFolderDeletion($change["id"]))
$this->updateState("delete", $change);
break;
}
$this->step++;
$progress = array();
$progress["steps"] = count($this->changes);
$progress["progress"] = $this->step;
return $progress;
} else {
return false;
}
}
else {
if($this->step < count($this->changes)) {
$change = $this->changes[$this->step];
switch($change["type"]) {
case "change":
// Note: because 'parseMessage' and 'statMessage' are two seperate
// calls, we have a chance that the message has changed between both
// calls. This may cause our algorithm to 'double see' changes.
$stat = $this->backend->StatMessage($this->folderid, $change["id"]);
$message = $this->backend->GetMessage($this->folderid, $change["id"], $this->contentparameters);
// copy the flag to the message
$message->flags = (isset($change["flags"])) ? $change["flags"] : 0;
if($stat && $message) {
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageChange($change["id"], $message) == true)
$this->updateState("change", $stat);
}
break;
case "delete":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageDeletion($change["id"]) == true)
$this->updateState("delete", $change);
break;
case "flags":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageReadFlag($change["id"], $change["flags"]) == true)
$this->updateState("flags", $change);
break;
case "move":
if($this->flags & BACKEND_DISCARD_DATA || $this->importer->ImportMessageMove($change["id"], $change["parent"]) == true)
$this->updateState("move", $change);
break;
}
$this->step++;
$progress = array();
$progress["steps"] = count($this->changes);
$progress["progress"] = $this->step;
return $progress;
} else {
return false;
}
}
}
}
?>

View file

@ -0,0 +1,275 @@
<?php
/***********************************************
* File : importchangesdiff.php
* Project : Z-Push
* Descr : IImportChanges implementation using
* the differential engine
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ImportChangesDiff extends DiffState implements IImportChanges {
private $folderid;
/**
* Constructor
*
* @param object $backend
* @param string $folderid
*
* @access public
* @throws StatusException
*/
public function ImportChangesDiff($backend, $folderid = false) {
$this->backend = $backend;
$this->folderid = $folderid;
}
/**
* Would load objects which are expected to be exported with this state
* The DiffBackend implements conflict detection on the fly
*
* @param ContentParameters $contentparameters class of objects
* @param string $state
*
* @access public
* @return boolean
* @throws StatusException
*/
public function LoadConflicts($contentparameters, $state) {
// changes are detected on the fly
return true;
}
/**
* Imports a single message
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean/string - failure / id of message
* @throws StatusException
*/
public function ImportMessageChange($id, $message) {
//do nothing if it is in a dummy folder
if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageChange('%s','%s'): can not be done on a dummy folder", $id, get_class($message)), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
if($id) {
// See if there's a conflict
$conflict = $this->isConflict("change", $this->folderid, $id);
// Update client state if this is an update
$change = array();
$change["id"] = $id;
$change["mod"] = 0; // dummy, will be updated later if the change succeeds
$change["parent"] = $this->folderid;
$change["flags"] = (isset($message->read)) ? $message->read : 0;
$this->updateState("change", $change);
if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM)
// in these cases the status SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT should be returned, so the mobile client can inform the end user
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageChange('%s','%s'): Conflict detected. Data from PIM will be dropped! Server overwrites PIM. User is informed.", $id, get_class($message)), SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT, null, LOGLEVEL_INFO);
}
$stat = $this->backend->ChangeMessage($this->folderid, $id, $message, $this->contentparameters);
if(!is_array($stat))
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageChange('%s','%s'): unknown error in backend", $id, get_class($message)), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
// Record the state of the message
$this->updateState("change", $stat);
return $stat["id"];
}
/**
* Imports a deletion. This may conflict if the local object has been modified
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageDeletion($id) {
//do nothing if it is in a dummy folder
if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageDeletion('%s'): can not be done on a dummy folder", $id), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
// See if there's a conflict
$conflict = $this->isConflict("delete", $this->folderid, $id);
// Update client state
$change = array();
$change["id"] = $id;
$this->updateState("delete", $change);
// If there is a conflict, and the server 'wins', then return without performing the change
// this will cause the exporter to 'see' the overriding item as a change, and send it back to the PIM
if($conflict && $this->flags == SYNC_CONFLICT_OVERWRITE_PIM) {
ZLog::Write(LOGLEVEL_INFO, sprintf("ImportChangesDiff->ImportMessageDeletion('%s'): Conflict detected. Data from PIM will be dropped! Object was deleted.", $id));
return false;
}
$stat = $this->backend->DeleteMessage($this->folderid, $id, $this->contentparameters);
if(!$stat)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageDeletion('%s'): Unknown error in backend", $id), SYNC_STATUS_OBJECTNOTFOUND);
return true;
}
/**
* Imports a change in 'read' flag
* This can never conflict
*
* @param string $id
* @param int $flags - read/unread
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageReadFlag($id, $flags) {
//do nothing if it is a dummy folder
if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageReadFlag('%s','%s'): can not be done on a dummy folder", $id, $flags), SYNC_STATUS_SYNCCANNOTBECOMPLETED);
// Update client state
$change = array();
$change["id"] = $id;
$change["flags"] = $flags;
$this->updateState("flags", $change);
$stat = $this->backend->SetReadFlag($this->folderid, $id, $flags, $this->contentparameters);
if (!$stat)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageReadFlag('%s','%s'): Error, unable retrieve message from backend", $id, $flags), SYNC_STATUS_OBJECTNOTFOUND);
return true;
}
/**
* Imports a move of a message. This occurs when a user moves an item to another folder
*
* @param string $id
* @param int $flags - read/unread
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageMove($id, $newfolder) {
// don't move messages from or to a dummy folder (GetHierarchy compatibility)
if ($this->folderid == SYNC_FOLDER_TYPE_DUMMY || $newfolder == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportMessageMove('%s'): can not be done on a dummy folder", $id), SYNC_MOVEITEMSSTATUS_CANNOTMOVE);
return $this->backend->MoveMessage($this->folderid, $id, $newfolder, $this->contentparameters);
}
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return string id of the folder
* @throws StatusException
*/
public function ImportFolderChange($folder) {
$id = $folder->serverid;
$parent = $folder->parentid;
$displayname = $folder->displayname;
$type = $folder->type;
//do nothing if it is a dummy folder
if ($parent == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportFolderChange('%s'): can not be done on a dummy folder", $id), SYNC_FSSTATUS_SERVERERROR);
if($id) {
$change = array();
$change["id"] = $id;
$change["mod"] = $displayname;
$change["parent"] = $parent;
$change["flags"] = 0;
$this->updateState("change", $change);
}
$stat = $this->backend->ChangeFolder($parent, $id, $displayname, $type);
if($stat)
$this->updateState("change", $stat);
return $stat["id"];
}
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent id
*
* @access public
* @return int SYNC_FOLDERHIERARCHY_STATUS
* @throws StatusException
*/
public function ImportFolderDeletion($id, $parent = false) {
//do nothing if it is a dummy folder
if ($parent == SYNC_FOLDER_TYPE_DUMMY)
throw new StatusException(sprintf("ImportChangesDiff->ImportFolderDeletion('%s','%s'): can not be done on a dummy folder", $id, $parent), SYNC_FSSTATUS_SERVERERROR);
// check the foldertype
$folder = $this->backend->GetFolder($id);
if (isset($folder->type) && Utils::IsSystemFolder($folder->type))
throw new StatusException(sprintf("ImportChangesDiff->ImportFolderDeletion('%s','%s'): Error deleting system/default folder", $id, $parent), SYNC_FSSTATUS_SYSTEMFOLDER);
$ret = $this->backend->DeleteFolder($id, $parent);
if (!$ret)
throw new StatusException(sprintf("ImportChangesDiff->ImportFolderDeletion('%s','%s'): can not be done on a dummy folder", $id, $parent), SYNC_FSSTATUS_FOLDERDOESNOTEXIST);
$change = array();
$change["id"] = $id;
$this->updateState("delete", $change);
return true;
}
}
?>

View file

@ -0,0 +1,494 @@
<?php
/***********************************************
* File : filestatemachine.php
* Project : Z-Push
* Descr : This class handles state requests;
* Each Import/Export mechanism can
* store its own state information,
* which is stored through the
* state machine.
*
* Created : 01.10.2007
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FileStateMachine implements IStateMachine {
const SUPPORTED_STATE_VERSION = IStateMachine::STATEVERSION_02;
const VERSION = "version";
private $userfilename;
private $settingsfilename;
/**
* Constructor
*
* Performs some basic checks and initilizes the state directory
*
* @access public
* @throws FatalMisconfigurationException
*/
public function FileStateMachine() {
if (!defined('STATE_DIR'))
throw new FatalMisconfigurationException("No configuration for the state directory available.");
if (substr(STATE_DIR, -1,1) != "/")
throw new FatalMisconfigurationException("The configured state directory should terminate with a '/'");
if (!file_exists(STATE_DIR))
throw new FatalMisconfigurationException("The configured state directory does not exist or can not be accessed: ". STATE_DIR);
// checks if the directory exists and tries to create the necessary subfolders if they do not exist
$this->getDirectoryForDevice(Request::GetDeviceID());
$this->userfilename = STATE_DIR . 'users';
$this->settingsfilename = STATE_DIR . 'settings';
if ((!file_exists($this->userfilename) && !touch($this->userfilename)) || !is_writable($this->userfilename))
throw new FatalMisconfigurationException("Not possible to write to the configured state directory.");
Utils::FixFileOwner($this->userfilename);
}
/**
* Gets a hash value indicating the latest dataset of the named
* state with a specified key and counter.
* If the state is changed between two calls of this method
* the returned hash should be different
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
*
* @access public
* @return string
* @throws StateNotFoundException, StateInvalidException
*/
public function GetStateHash($devid, $type, $key = false, $counter = false) {
$filename = $this->getFullFilePath($devid, $type, $key, $counter);
// the filemodification time is enough to track changes
if(file_exists($filename))
return filemtime($filename);
else
throw new StateNotFoundException(sprintf("FileStateMachine->GetStateHash(): Could not locate state '%s'",$filename));
}
/**
* Gets a state for a specified key and counter.
* This method sould call IStateMachine->CleanStates()
* to remove older states (same key, previous counters)
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
* @param string $cleanstates (opt)
*
* @access public
* @return mixed
* @throws StateNotFoundException, StateInvalidException
*/
public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true) {
if ($counter && $cleanstates)
$this->CleanStates($devid, $type, $key, $counter);
// Read current sync state
$filename = $this->getFullFilePath($devid, $type, $key, $counter);
ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->GetState() on file: '%s'", $filename));
if(file_exists($filename)) {
return unserialize(file_get_contents($filename));
}
// throw an exception on all other states, but not FAILSAVE as it's most of the times not there by default
else if ($type !== IStateMachine::FAILSAVE)
throw new StateNotFoundException(sprintf("FileStateMachine->GetState(): Could not locate state '%s'",$filename));
}
/**
* Writes ta state to for a key and counter
*
* @param mixed $state
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param int $counter (opt)
*
* @access public
* @return boolean
* @throws StateInvalidException
*/
public function SetState($state, $devid, $type, $key = false, $counter = false) {
$state = serialize($state);
$filename = $this->getFullFilePath($devid, $type, $key, $counter);
if (($bytes = file_put_contents($filename, $state)) === false)
throw new FatalMisconfigurationException(sprintf("FileStateMachine->SetState(): Could not write state '%s'",$filename));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->SetState() written %d bytes on file: '%s'", $bytes, $filename));
return $bytes;
}
/**
* Cleans up all older states
* If called with a $counter, all states previous state counter can be removed
* If called without $counter, all keys (independently from the counter) can be removed
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key
* @param string $counter (opt)
*
* @access public
* @return
* @throws StateInvalidException
*/
public function CleanStates($devid, $type, $key, $counter = false) {
$matching_files = glob($this->getFullFilePath($devid, $type, $key). "*", GLOB_NOSORT);
if (is_array($matching_files)) {
foreach($matching_files as $state) {
$file = false;
if($counter !== false && preg_match('/([0-9]+)$/', $state, $matches)) {
if($matches[1] < $counter) {
$candidate = $this->getFullFilePath($devid, $type, $key, (int)$matches[1]);
if ($candidate == $state)
$file = $candidate;
}
}
else if ($counter === false)
$file = $this->getFullFilePath($devid, $type, $key);
if ($file !== false) {
ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->CleanStates(): Deleting file: '%s'", $file));
unlink ($file);
}
}
}
}
/**
* Links a user to a device
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean indicating if the user was added or not (existed already)
*/
public function LinkUserDevice($username, $devid) {
include_once("simplemutex.php");
$mutex = new SimpleMutex();
$changed = false;
// exclusive block
if ($mutex->Block()) {
$filecontents = @file_get_contents($this->userfilename);
if ($filecontents)
$users = unserialize($filecontents);
else
$users = array();
// add user/device to the list
if (!isset($users[$username])) {
$users[$username] = array();
$changed = true;
}
if (!isset($users[$username][$devid])) {
$users[$username][$devid] = 1;
$changed = true;
}
if ($changed) {
$bytes = file_put_contents($this->userfilename, serialize($users));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->LinkUserDevice(): wrote %d bytes to users file", $bytes));
}
else
ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->LinkUserDevice(): nothing changed");
$mutex->Release();
}
return $changed;
}
/**
* Unlinks a device from a user
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean
*/
public function UnLinkUserDevice($username, $devid) {
include_once("simplemutex.php");
$mutex = new SimpleMutex();
$changed = false;
// exclusive block
if ($mutex->Block()) {
$filecontents = @file_get_contents($this->userfilename);
if ($filecontents)
$users = unserialize($filecontents);
else
$users = array();
// is this user listed at all?
if (isset($users[$username])) {
if (isset($users[$username][$devid])) {
unset($users[$username][$devid]);
$changed = true;
}
// if there is no device left, remove the user
if (empty($users[$username])) {
unset($users[$username]);
$changed = true;
}
}
if ($changed) {
$bytes = file_put_contents($this->userfilename, serialize($users));
ZLog::Write(LOGLEVEL_DEBUG, sprintf("FileStateMachine->UnLinkUserDevice(): wrote %d bytes to users file", $bytes));
}
else
ZLog::Write(LOGLEVEL_DEBUG, "FileStateMachine->UnLinkUserDevice(): nothing changed");
$mutex->Release();
}
return $changed;
}
/**
* Returns an array with all device ids for a user.
* If no user is set, all device ids should be returned
*
* @param string $username (opt)
*
* @access public
* @return array
*/
public function GetAllDevices($username = false) {
$out = array();
if ($username === false) {
foreach (glob(STATE_DIR. "/*/*/*-".IStateMachine::DEVICEDATA, GLOB_NOSORT) as $devdata)
if (preg_match('/\/([A-Za-z0-9]+)-'. IStateMachine::DEVICEDATA. '$/', $devdata, $matches))
$out[] = $matches[1];
return $out;
}
else {
$filecontents = file_get_contents($this->userfilename);
if ($filecontents)
$users = unserialize($filecontents);
else
$users = array();
// get device list for the user
if (isset($users[$username]))
return array_keys($users[$username]);
else
return array();
}
}
/**
* Returns the current version of the state files
*
* @access public
* @return int
*/
public function GetStateVersion() {
if (file_exists($this->settingsfilename)) {
$settings = unserialize(file_get_contents($this->settingsfilename));
if (strtolower(gettype($settings) == "string") && strtolower($settings) == '2:1:{s:7:"version";s:1:"2";}') {
ZLog::Write(LOGLEVEL_INFO, "Broken state version file found. Attempt to autofix it. See https://jira.zarafa.com/browse/ZP-493 for more information.");
unlink($this->settingsfilename);
$this->SetStateVersion(IStateMachine::STATEVERSION_02);
$settings = array(self::VERSION => IStateMachine::STATEVERSION_02);
}
}
else {
$filecontents = @file_get_contents($this->userfilename);
if ($filecontents)
$settings = array(self::VERSION => IStateMachine::STATEVERSION_01);
else {
$settings = array(self::VERSION => self::SUPPORTED_STATE_VERSION);
$this->SetStateVersion(self::SUPPORTED_STATE_VERSION);
}
}
return $settings[self::VERSION];
}
/**
* Sets the current version of the state files
*
* @param int $version the new supported version
*
* @access public
* @return boolean
*/
public function SetStateVersion($version) {
if (file_exists($this->settingsfilename))
$settings = unserialize(file_get_contents($this->settingsfilename));
else
$settings = array(self::VERSION => IStateMachine::STATEVERSION_01);
$settings[self::VERSION] = $version;
ZLog::Write(LOGLEVEL_INFO, sprintf("FileStateMachine->SetStateVersion() saving supported state version, value '%d'", $version));
$status = file_put_contents($this->settingsfilename, serialize($settings));
Utils::FixFileOwner($this->settingsfilename);
return $status;
}
/**
* Returns all available states for a device id
*
* @param string $devid the device id
*
* @access public
* @return array(mixed)
*/
public function GetAllStatesForDevice($devid) {
$out = array();
$devdir = $this->getDirectoryForDevice($devid) . "/$devid-";
foreach (glob($devdir . "*", GLOB_NOSORT) as $devdata) {
// cut the device dir away and split into parts
$parts = explode("-", substr($devdata, strlen($devdir)));
$state = array('type' => false, 'counter' => false, 'uuid' => false);
if (isset($parts[0]) && $parts[0] == IStateMachine::DEVICEDATA)
$state['type'] = IStateMachine::DEVICEDATA;
if (isset($parts[0]) && strlen($parts[0]) == 8 &&
isset($parts[1]) && strlen($parts[1]) == 4 &&
isset($parts[2]) && strlen($parts[2]) == 4 &&
isset($parts[3]) && strlen($parts[3]) == 4 &&
isset($parts[4]) && strlen($parts[4]) == 12)
$state['uuid'] = $parts[0]."-".$parts[1]."-".$parts[2]."-".$parts[3]."-".$parts[4];
if (isset($parts[5]) && is_numeric($parts[5])) {
$state['counter'] = $parts[5];
$state['type'] = ""; // default
}
if (isset($parts[5])) {
if (is_int($parts[5]))
$state['counter'] = $parts[5];
else if (in_array($parts[5], array(IStateMachine::FOLDERDATA, IStateMachine::FAILSAVE, IStateMachine::HIERARCHY, IStateMachine::BACKENDSTORAGE)))
$state['type'] = $parts[5];
}
if (isset($parts[6]) && is_numeric($parts[6]))
$state['counter'] = $parts[6];
$out[] = $state;
}
return $out;
}
/**----------------------------------------------------------------------------------------------------------
* Private FileStateMachine stuff
*/
/**
* Returns the full path incl. filename for a key (generally uuid) and a counter
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt) default false
* @param boolean $doNotCreateDirs (opt) indicates if missing subdirectories should be created, default false
*
* @access private
* @return string
* @throws StateInvalidException
*/
private function getFullFilePath($devid, $type, $key = false, $counter = false, $doNotCreateDirs = false) {
$testkey = $devid . (($key !== false)? "-". $key : "") . (($type !== "")? "-". $type : "");
if (preg_match('/^[a-zA-Z0-9-]+$/', $testkey, $matches) || ($type == "" && $key === false))
$internkey = $testkey . (($counter && is_int($counter))?"-".$counter:"");
else
throw new StateInvalidException("FileStateMachine->getFullFilePath(): Invalid state deviceid, type, key or in any combination");
return $this->getDirectoryForDevice($devid, $doNotCreateDirs) ."/". $internkey;
}
/**
* Checks if the configured path exists and if a subfolder structure is available
* A two level deep subdirectory structure is build to save the states.
* The subdirectories where to save, are determined with device id
*
* @param string $devid the device id
* @param boolen $doNotCreateDirs (opt) by default false - indicates if the subdirs should be created
*
* @access private
* @return string/boolean returns the full directory of false if the dirs can not be created
* @throws FatalMisconfigurationException when configured directory is not writeable
*/
private function getDirectoryForDevice($devid, $doNotCreateDirs = false) {
$firstLevel = substr(strtolower($devid), -1, 1);
$secondLevel = substr(strtolower($devid), -2, 1);
$dir = STATE_DIR . $firstLevel . "/" . $secondLevel;
if (is_dir($dir))
return $dir;
if ($doNotCreateDirs === false) {
// try to create the subdirectory structure necessary
$fldir = STATE_DIR . $firstLevel;
if (!is_dir($fldir)) {
$dirOK = mkdir($fldir);
if (!$dirOK)
throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $fldir);
}
if (!is_dir($dir)) {
$dirOK = mkdir($dir);
if (!$dirOK)
throw new FatalMisconfigurationException("FileStateMachine->getDirectoryForDevice(): Not possible to create state sub-directory: ". $dir);
}
else
return $dir;
}
return false;
}
}
?>

View file

@ -0,0 +1,125 @@
<?php
/***********************************************
* File : searchprovider.php
* Project : Z-Push
* Descr : The searchprovider can be used to
* implement an alternative way perform
* searches.
* This is a stub implementation.
*
* Created : 03.08.2010
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
/*********************************************************************
* The SearchProvider is a stub to implement own search funtionality
*
* If you wish to implement an alternative search method, you should implement the
* ISearchProvider interface like the BackendSearchLDAP backend
*/
class SearchProvider implements ISearchProvider{
/**
* Constructor
* initializes the searchprovider to perform the search
*
* @access public
* @return
* @throws StatusException, FatalException
*/
public function SearchProvider() {
}
/**
* Indicates if a search type is supported by this SearchProvider
* Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented
*
* @param string $searchtype
*
* @access public
* @return boolean
*/
public function SupportsType($searchtype) {
return ($searchtype == ISearchProvider::SEARCH_GAL);
}
/**
* Searches the GAL
*
* @param string $searchquery string to be searched for
* @param string $searchrange specified searchrange
*
* @access public
* @return array search results
* @throws StatusException
*/
public function GetGALSearchResults($searchquery, $searchrange) {
return array();
}
/**
* Searches for the emails on the server
*
* @param ContentParameter $cpo
*
* @return array
*/
public function GetMailboxSearchResults($cpo){
return array();
}
/**
* Terminates a search for a given PID
*
* @param int $pid
*
* @return boolean
*/
public function TerminateSearch($pid) {
return true;
}
/**
* Disconnects from the current search provider
*
* @access public
* @return boolean
*/
public function Disconnect() {
return true;
}
}
?>

View file

@ -0,0 +1,89 @@
<?php
/***********************************************
* File : simplemutex.php
* Project : Z-Push
* Descr : Implements a simple mutex using InterProcessData
*
* Created : 29.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class SimpleMutex extends InterProcessData {
/**
* Constructor
*/
public function SimpleMutex() {
// initialize super parameters
$this->allocate = 64;
$this->type = 5173;
parent::__construct();
if (!$this->IsActive()) {
ZLog::Write(LOGLEVEL_ERROR, "SimpleMutex not available as InterProcessData is not available. This is not recommended on duty systems and may result in corrupt user/device linking.");
}
}
/**
* Blocks the mutex
* Method blocks until mutex is available!
* ATTENTION: make sure that you *always* release a blocked mutex!
*
* @access public
* @return boolean
*/
public function Block() {
if ($this->IsActive())
return $this->blockMutex();
ZLog::Write(LOGLEVEL_WARN, "Could not enter mutex as InterProcessData is not available. This is not recommended on duty systems and may result in corrupt user/device linking!");
return true;
}
/**
* Releases the mutex
* After the release other processes are able to block the mutex themselfs
*
* @access public
* @return boolean
*/
public function Release() {
if ($this->IsActive())
return $this->releaseMutex();
return true;
}
}
?>

View file

@ -0,0 +1,52 @@
<?php
/***********************************************
* File : authenticationrequiredexception.php
* Project : Z-Push
* Descr : Exception sending a '401 Unauthorized' to the mobile
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class AuthenticationRequiredException extends HTTPReturnCodeException {
protected $defaultLogLevel = LOGLEVEL_INFO;
protected $httpReturnCode = HTTP_CODE_401;
protected $httpReturnMessage = "Unauthorized";
protected $httpHeaders = array('WWW-Authenticate: Basic realm="ZPush"');
protected $showLegal = true;
}
?>

View file

@ -0,0 +1,66 @@
<?php
/***********************************************
* File : exceptions.php
* Project : Z-Push
* Descr : Includes all Z-Push exceptions
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
// main exception
include_once('zpushexception.php');
// Fatal exceptions
include_once('fatalexception.php');
include_once('fatalmisconfigurationexception.php');
include_once('fatalnotimplementedexception.php');
include_once('wbxmlexception.php');
include_once('nopostrequestexception.php');
include_once('httpreturncodeexception.php');
include_once('authenticationrequiredexception.php');
include_once('provisioningrequiredexception.php');
// Non fatal exceptions
include_once('notimplementedexception.php');
include_once('syncobjectbrokenexception.php');
include_once('statusexception.php');
include_once('statenotfoundexception.php');
include_once('stateinvalidexception.php');
include_once('nohierarchycacheavailableexception.php');
include_once('statenotyetavailableexception.php');
?>

View file

@ -0,0 +1,47 @@
<?php
/***********************************************
* File : fatalexception.php
* Project : Z-Push
* Descr : Main class for Fatal Z-Push exceptions
* execution stops
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FatalException extends ZPushException {}
?>

View file

@ -0,0 +1,46 @@
<?php
/***********************************************
* File : fatalmisconfigurationexception.php
* Project : Z-Push
* Descr : Fatal exception related to configuration
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FatalMisconfigurationException extends FatalException {}
?>

View file

@ -0,0 +1,47 @@
<?php
/***********************************************
* File : fatalnotimplementedexception.php
* Project : Z-Push
* Descr : Fatal exception related to functionality
* which is not available
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FatalNotImplementedException extends FatalException {}
?>

View file

@ -0,0 +1,56 @@
<?php
/***********************************************
* File : nopostrequestexception.php
* Project : Z-Push
* Descr : Exception thrown to return a an non 200 http return code
* to the mobile
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class HTTPReturnCodeException extends FatalException {
protected $defaultLogLevel = LOGLEVEL_ERROR;
protected $showLegal = false;
public function HTTPReturnCodeException($message = "", $code = 0, $previous = NULL, $logLevel = false) {
if ($code)
$this->httpReturnCode = $code;
parent::__construct($message, (int) $code, $previous, $logLevel);
}
}
?>

View file

@ -0,0 +1,46 @@
<?php
/***********************************************
* File : nohierarchycacheavailableexception.php
* Project : Z-Push
* Descr : Indicates that the HierarchyException is not available.
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class NoHierarchyCacheAvailableException extends StateNotFoundException {}
?>

View file

@ -0,0 +1,51 @@
<?php
/***********************************************
* File : nopostrequestexception.php
* Project : Z-Push
* Descr : Exception thrown if the request is not a POST request
* The code indicates if the request identified was a OPTIONS or GET request
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class NoPostRequestException extends FatalException {
const OPTIONS_REQUEST = 1;
const GET_REQUEST = 2;
protected $defaultLogLevel = LOGLEVEL_DEBUG;
}
?>

View file

@ -0,0 +1,49 @@
<?php
/***********************************************
* File : notimplementedexception.php
* Project : Z-Push
* Descr : Exception indicating that that some code is not
* available which is non-fatal
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class NotImplementedException extends ZPushException {
protected $defaultLogLevel = LOGLEVEL_ERROR;
}
?>

View file

@ -0,0 +1,51 @@
<?php
/***********************************************
* File : provisioningrequiredexception.php
* Project : Z-Push
* Descr : Exception announcing to the mobile that a
* provisioning request is required
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ProvisioningRequiredException extends HTTPReturnCodeException {
protected $defaultLogLevel = LOGLEVEL_INFO;
protected $httpReturnCode = HTTP_CODE_449;
protected $httpReturnMessage = "Retry after sending a PROVISION command";
}
?>

View file

@ -0,0 +1,46 @@
<?php
/***********************************************
* File : stateinvalidexception.php
* Project : Z-Push
* Descr : Indicates that the state is not invalid.
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StateInvalidException extends StatusException {}
?>

View file

@ -0,0 +1,47 @@
<?php
/***********************************************
* File : statenotfoundexception.php
* Project : Z-Push
* Descr : Indicates that a synchronization state could not be retrieved
* from an IStateMachine
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StateNotFoundException extends StatusException {}
?>

View file

@ -0,0 +1,46 @@
<?php
/***********************************************
* File : statenotyetavailableexception.php
* Project : Z-Push
* Descr : Indicates that a state was not yet loaded
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StateNotYetAvailableException extends StatusException {}
?>

View file

@ -0,0 +1,48 @@
<?php
/***********************************************
* File : statusexception.php
* Project : Z-Push
* Descr : Main exception related to errors regarding synchronization
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class StatusException extends ZPushException {
protected $defaultLogLevel = LOGLEVEL_INFO;
}
?>

View file

@ -0,0 +1,73 @@
<?php
/***********************************************
* File : syncobjectbrokenexception.php
* Project : Z-Push
* Descr : Indicates that an object was identified as broken.
* The SyncObject may be available for further analisis
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class SyncObjectBrokenException extends ZPushException {
protected $defaultLogLevel = LOGLEVEL_WARN;
private $syncObject;
/**
* Returns the SyncObject which caused this Exception (if set)
*
* @access public
* @return SyncObject
*/
public function GetSyncObject() {
return isset($this->syncObject) ? $this->syncObject : false;
}
/**
* Sets the SyncObject which caused the exception so it can be later retrieved
*
* @param SyncObject $syncobject
*
* @access public
* @return boolean
*/
public function SetSyncObject($syncobject) {
$this->syncObject = $syncobject;
return true;
}
}
?>

View file

@ -0,0 +1,46 @@
<?php
/***********************************************
* File : wbxmlexception.php
* Project : Z-Push
* Descr : Exception related to WBXML
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class WBXMLException extends FatalNotImplementedException {}
?>

View file

@ -0,0 +1,74 @@
<?php
/***********************************************
* File : zpushexceptions.php
* Project : Z-Push
* Descr : Main Z-Push exception
*
* Created : 06.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class ZPushException extends Exception {
protected $defaultLogLevel = LOGLEVEL_FATAL;
protected $httpReturnCode = HTTP_CODE_500;
protected $httpReturnMessage = "Internal Server Error";
protected $httpHeaders = array();
protected $showLegal = true;
public function ZPushException($message = "", $code = 0, $previous = NULL, $logLevel = false) {
if (! $message)
$message = $this->httpReturnMessage;
if (!$logLevel)
$logLevel = $this->defaultLogLevel;
ZLog::Write($logLevel, get_class($this) .': '. $message . ' - code: '.$code);
parent::__construct($message, (int) $code);
}
public function getHTTPCodeString() {
return $this->httpReturnCode . " ". $this->httpReturnMessage;
}
public function getHTTPHeaders() {
return $this->httpHeaders;
}
public function showLegalNotice() {
return $this->showLegal;
}
}
?>

View file

@ -0,0 +1,294 @@
<?php
/***********************************************
* File : ibackend.php
* Project : Z-Push
* Descr : All Z-Push backends must implement this interface.
* It describes all generic methods expected to be
* implemented by a backend.
* The next abstraction layer is the default Backend
* implementation which already implements some generics.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface IBackend {
/**
* Returns a IStateMachine implementation used to save states
*
* @access public
* @return boolean/object if false is returned, the default Statemachine is
* used else the implementation of IStateMachine
*/
public function GetStateMachine();
/**
* Returns a ISearchProvider implementation used for searches
*
* @access public
* @return object Implementation of ISearchProvider
*/
public function GetSearchProvider();
/**
* Indicates which AS version is supported by the backend.
* Depending on this value the supported AS version announced to the
* mobile device is set.
*
* @access public
* @return string AS version constant
*/
public function GetSupportedASVersion();
/**
* Authenticates the user
*
* @param string $username
* @param string $domain
* @param string $password
*
* @access public
* @return boolean
* @throws FatalException e.g. some required libraries are unavailable
*/
public function Logon($username, $domain, $password);
/**
* Setup the backend to work on a specific store or checks ACLs there.
* If only the $store is submitted, all Import/Export/Fetch/Etc operations should be
* performed on this store (switch operations store).
* If the ACL check is enabled, this operation should just indicate the ACL status on
* the submitted store, without changing the store for operations.
* For the ACL status, the currently logged on user MUST have access rights on
* - the entire store - admin access if no folderid is sent, or
* - on a specific folderid in the store (secretary/full access rights)
*
* The ACLcheck MUST fail if a folder of the authenticated user is checked!
*
* @param string $store target store, could contain a "domain\user" value
* @param boolean $checkACLonly if set to true, Setup() should just check ACLs
* @param string $folderid if set, only ACLs on this folderid are relevant
*
* @access public
* @return boolean
*/
public function Setup($store, $checkACLonly = false, $folderid = false);
/**
* Logs off
* non critical operations closing the session should be done here
*
* @access public
* @return boolean
*/
public function Logoff();
/**
* Returns an array of SyncFolder types with the entire folder hierarchy
* on the server (the array itself is flat, but refers to parents via the 'parent' property
*
* provides AS 1.0 compatibility
*
* @access public
* @return array SYNC_FOLDER
*/
public function GetHierarchy();
/**
* Returns the importer to process changes from the mobile
* If no $folderid is given, hierarchy data will be imported
* With a $folderid a content data will be imported
*
* @param string $folderid (opt)
*
* @access public
* @return object implements IImportChanges
* @throws StatusException
*/
public function GetImporter($folderid = false);
/**
* Returns the exporter to send changes to the mobile
* If no $folderid is given, hierarchy data should be exported
* With a $folderid a content data is expected
*
* @param string $folderid (opt)
*
* @access public
* @return object implements IExportChanges
* @throws StatusException
*/
public function GetExporter($folderid = false);
/**
* Sends an e-mail
* This messages needs to be saved into the 'sent items' folder
*
* Basically two things can be done
* 1) Send the message to an SMTP server as-is
* 2) Parse the message, and send it some other way
*
* @param SyncSendMail $sm SyncSendMail object
*
* @access public
* @return boolean
* @throws StatusException
*/
public function SendMail($sm);
/**
* Returns all available data of a single message
*
* @param string $folderid
* @param string $id
* @param ContentParameters $contentparameters flag
*
* @access public
* @return object(SyncObject)
* @throws StatusException
*/
public function Fetch($folderid, $id, $contentparameters);
/**
* Returns the waste basket
*
* The waste basked is used when deleting items; if this function returns a valid folder ID,
* then all deletes are handled as moves and are sent to the backend as a move.
* If it returns FALSE, then deletes are handled as real deletes
*
* @access public
* @return string
*/
public function GetWasteBasket();
/**
* Returns the content of the named attachment as stream. The passed attachment identifier is
* the exact string that is returned in the 'AttName' property of an SyncAttachment.
* Any information necessary to locate the attachment must be encoded in that 'attname' property.
* Data is written directly - 'print $data;'
*
* @param string $attname
*
* @access public
* @return SyncItemOperationsAttachment
* @throws StatusException
*/
public function GetAttachmentData($attname);
/**
* Deletes all contents of the specified folder.
* This is generally used to empty the trash (wastebasked), but could also be used on any
* other folder.
*
* @param string $folderid
* @param boolean $includeSubfolders (opt) also delete sub folders, default true
*
* @access public
* @return boolean
* @throws StatusException
*/
public function EmptyFolder($folderid, $includeSubfolders = true);
/**
* Processes a response to a meeting request.
* CalendarID is a reference and has to be set if a new calendar item is created
*
* @param string $requestid id of the object containing the request
* @param string $folderid id of the parent folder of $requestid
* @param string $response
*
* @access public
* @return string id of the created/updated calendar obj
* @throws StatusException
*/
public function MeetingResponse($requestid, $folderid, $response);
/**
* Indicates if the backend has a ChangesSink.
* A sink is an active notification mechanism which does not need polling.
*
* @access public
* @return boolean
*/
public function HasChangesSink();
/**
* The folder should be considered by the sink.
* Folders which were not initialized should not result in a notification
* of IBacken->ChangesSink().
*
* @param string $folderid
*
* @access public
* @return boolean false if there is any problem with that folder
*/
public function ChangesSinkInitialize($folderid);
/**
* The actual ChangesSink.
* For max. the $timeout value this method should block and if no changes
* are available return an empty array.
* If changes are available a list of folderids is expected.
*
* @param int $timeout max. amount of seconds to block
*
* @access public
* @return array
*/
public function ChangesSink($timeout = 30);
/**
* Applies settings to and gets informations from the device
*
* @param SyncObject $settings (SyncOOF or SyncUserInformation possible)
*
* @access public
* @return SyncObject $settings
*/
public function Settings($settings);
/**
* Resolves recipients
*
* @param SyncObject $resolveRecipients
*
* @access public
* @return SyncObject $resolveRecipients
*/
public function ResolveRecipients($resolveRecipients);
}
?>

View file

@ -0,0 +1,86 @@
<?php
/***********************************************
* File : ichanges.php
* Project : Z-Push
* Descr : Generic IChanges interface. This interface can
* not be implemented alone.
* IImportChanges and IExportChanges interfaces
* inherit from this interface
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface IChanges {
/**
* Constructor
*
* @throws StatusException
*/
/**
* Initializes the state and flags
*
* @param string $state
* @param int $flags
*
* @access public
* @return boolean status flag
* @throws StatusException
*/
public function Config($state, $flags = 0);
/**
* Configures additional parameters used for content synchronization
*
* @param ContentParameters $contentparameters
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ConfigContentParameters($contentparameters);
/**
* Reads and returns the current state
*
* @access public
* @return string
*/
public function GetState();
}
?>

View file

@ -0,0 +1,76 @@
<?php
/***********************************************
* File : iexportchanges.php
* Project : Z-Push
* Descr : IExportChanges interface. It's responsible for
* exporting (sending) data, content and hierarchy changes.
* This interface extends the IChanges interface.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface IExportChanges extends IChanges {
/**
* Sets the importer where the exporter will sent its changes to
* This exporter should also be ready to accept calls after this
*
* @param object &$importer Implementation of IImportChanges
*
* @access public
* @return boolean
* @throws StatusException
*/
public function InitializeExporter(&$importer);
/**
* Returns the amount of changes to be exported
*
* @access public
* @return int
*/
public function GetChangeCount();
/**
* Synchronizes a change to the configured importer
*
* @access public
* @return array with status information
*/
public function Synchronize();
}
?>

View file

@ -0,0 +1,143 @@
<?php
/***********************************************
* File : iimportchanges.php
* Project : Z-Push
* Descr : IImportChanges interface. It's responsible for
* importing (receiving) data, content and hierarchy changes.
* This interface extends the IChanges interface.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface IImportChanges extends IChanges {
/**----------------------------------------------------------------------------------------------------------
* Methods for to import contents
*/
/**
* Loads objects which are expected to be exported with the state
* Before importing/saving the actual message from the mobile, a conflict detection should be done
*
* @param ContentParameters $contentparameters
* @param string $state
*
* @access public
* @return boolean
* @throws StatusException
*/
public function LoadConflicts($contentparameters, $state);
/**
* Imports a single message
*
* @param string $id
* @param SyncObject $message
*
* @access public
* @return boolean/string failure / id of message
* @throws StatusException
*/
public function ImportMessageChange($id, $message);
/**
* Imports a deletion. This may conflict if the local object has been modified
*
* @param string $id
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageDeletion($id);
/**
* Imports a change in 'read' flag
* This can never conflict
*
* @param string $id
* @param int $flags
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageReadFlag($id, $flags);
/**
* Imports a move of a message. This occurs when a user moves an item to another folder
*
* @param string $id
* @param string $newfolder destination folder
*
* @access public
* @return boolean
* @throws StatusException
*/
public function ImportMessageMove($id, $newfolder);
/**----------------------------------------------------------------------------------------------------------
* Methods to import hierarchy
*/
/**
* Imports a change on a folder
*
* @param object $folder SyncFolder
*
* @access public
* @return boolean/string status/id of the folder
* @throws StatusException
*/
public function ImportFolderChange($folder);
/**
* Imports a folder deletion
*
* @param string $id
* @param string $parent id
*
* @access public
* @return boolean/int success/SYNC_FOLDERHIERARCHY_STATUS
* @throws StatusException
*/
public function ImportFolderDeletion($id, $parent = false);
}
?>

View file

@ -0,0 +1,107 @@
<?php
/***********************************************
* File : isearchprovider.php
* Project : Z-Push
* Descr : The ISearchProvider interface for searching
* functionalities on the mobile.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface ISearchProvider {
const SEARCH_GAL = "GAL";
const SEARCH_MAILBOX = "MAILBOX";
const SEARCH_DOCUMENTLIBRARY = "DOCUMENTLIBRARY";
/**
* Constructor
*
* @throws StatusException, FatalException
*/
/**
* Indicates if a search type is supported by this SearchProvider
* Currently only the type SEARCH_GAL (Global Address List) is implemented
*
* @param string $searchtype
*
* @access public
* @return boolean
*/
public function SupportsType($searchtype);
/**
* Searches the GAL
*
* @param string $searchquery
* @param string $searchrange
*
* @access public
* @return array
* @throws StatusException
*/
public function GetGALSearchResults($searchquery, $searchrange);
/**
* Searches for the emails on the server
*
* @param ContentParameter $cpo
*
* @return array
*/
public function GetMailboxSearchResults($cpo);
/**
* Terminates a search for a given PID
*
* @param int $pid
*
* @return boolean
*/
public function TerminateSearch($pid);
/**
* Disconnects from the current search provider
*
* @access public
* @return boolean
*/
public function Disconnect();
}
?>

View file

@ -0,0 +1,199 @@
<?php
/***********************************************
* File : istatemachine.php
* Project : Z-Push
* Descr : Interface called from the Device and
* StateManager to save states for a user/device/folder.
* Z-Push implements the FileStateMachine which
* saves states to disk.
* Backends provide their own IStateMachine
implementation of this interface and return
* an IStateMachine instance with IBackend->GetStateMachine().
* Old sync states are not deleted until a new sync state
* is requested.
* At that moment, the PIM is apparently requesting an update
* since sync key X, so any sync states before X are already on
* the PIM, and can therefore be removed. This algorithm should be
* automatically enforced by the IStateMachine implementation.
*
* Created : 02.01.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
interface IStateMachine {
const DEFTYPE = "";
const DEVICEDATA = "devicedata";
const FOLDERDATA = "fd";
const FAILSAVE = "fs";
const HIERARCHY = "hc";
const BACKENDSTORAGE = "bs";
const STATEVERSION_01 = "1"; // Z-Push 2.0.x - default value if unset
const STATEVERSION_02 = "2"; // Z-Push 2.1.0 Milestone 1
/**
* Constructor
* @throws FatalMisconfigurationException
*/
/**
* Gets a hash value indicating the latest dataset of the named
* state with a specified key and counter.
* If the state is changed between two calls of this method
* the returned hash should be different
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
*
* @access public
* @return string
* @throws StateNotFoundException, StateInvalidException
*/
public function GetStateHash($devid, $type, $key = false, $counter = false);
/**
* Gets a state for a specified key and counter.
* This method sould call IStateMachine->CleanStates()
* to remove older states (same key, previous counters)
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param string $counter (opt)
* @param string $cleanstates (opt)
*
* @access public
* @return mixed
* @throws StateNotFoundException, StateInvalidException
*/
public function GetState($devid, $type, $key = false, $counter = false, $cleanstates = true);
/**
* Writes ta state to for a key and counter
*
* @param mixed $state
* @param string $devid the device id
* @param string $type the state type
* @param string $key (opt)
* @param int $counter (opt)
*
* @access public
* @return boolean
* @throws StateInvalidException
*/
public function SetState($state, $devid, $type, $key = false, $counter = false);
/**
* Cleans up all older states
* If called with a $counter, all states previous state counter can be removed
* If called without $counter, all keys (independently from the counter) can be removed
*
* @param string $devid the device id
* @param string $type the state type
* @param string $key
* @param string $counter (opt)
*
* @access public
* @return
* @throws StateInvalidException
*/
public function CleanStates($devid, $type, $key, $counter = false);
/**
* Links a user to a device
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean indicating if the user was added or not (existed already)
*/
public function LinkUserDevice($username, $devid);
/**
* Unlinks a device from a user
*
* @param string $username
* @param string $devid
*
* @access public
* @return boolean
*/
public function UnLinkUserDevice($username, $devid);
/**
* Returns an array with all device ids for a user.
* If no user is set, all device ids should be returned
*
* @param string $username (opt)
*
* @access public
* @return array
*/
public function GetAllDevices($username = false);
/**
* Returns the current version of the state files
*
* @access public
* @return int
*/
public function GetStateVersion();
/**
* Sets the current version of the state files
*
* @param int $version the new supported version
*
* @access public
* @return boolean
*/
public function SetStateVersion($version);
/**
* Returns all available states for a device id
*
* @param string $devid the device id
*
* @access public
* @return array(mixed)
*/
public function GetAllStatesForDevice($devid);
}
?>

View file

@ -0,0 +1,247 @@
<?php
/***********************************************
* File : folderchange.php
* Project : Z-Push
* Descr : Provides the FOLDERCREATE, FOLDERDELETE, FOLDERUPDATE command
*
* Created : 16.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FolderChange extends RequestProcessor {
/**
* Handles creates, updates or deletes of a folder
* issued by the commands FolderCreate, FolderUpdate and FolderDelete
*
* @param int $commandCode
*
* @access public
* @return boolean
*/
public function Handle ($commandCode) {
$el = self::$decoder->getElement();
if($el[EN_TYPE] != EN_TYPE_STARTTAG)
return false;
$create = $update = $delete = false;
if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERCREATE)
$create = true;
else if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERUPDATE)
$update = true;
else if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERDELETE)
$delete = true;
if(!$create && !$update && !$delete)
return false;
// SyncKey
if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY))
return false;
$synckey = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
// ServerID
$serverid = false;
if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID)) {
$serverid = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
// Parent
$parentid = false;
// when creating or updating more information is necessary
if (!$delete) {
if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_PARENTID)) {
$parentid = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
// Displayname
if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_DISPLAYNAME))
return false;
$displayname = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
// Type
$type = false;
if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_TYPE)) {
$type = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
}
// endtag foldercreate, folderupdate, folderdelete
if(!self::$decoder->getElementEndTag())
return false;
$status = SYNC_FSSTATUS_SUCCESS;
// Get state of hierarchy
try {
$syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
$newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
// Over the ChangesWrapper the HierarchyCache is notified about all changes
$changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
// the hierarchyCache should now fully be initialized - check for changes in the additional folders
$changesMem->Config(ZPush::GetAdditionalSyncFolders());
// there are unprocessed changes in the hierarchy, trigger resync
if ($changesMem->GetChangeCount() > 0)
throw new StatusException("HandleFolderChange() can not proceed as there are unprocessed hierarchy changes", SYNC_FSSTATUS_SERVERERROR);
// any additional folders can not be modified!
if ($serverid !== false && ZPush::GetAdditionalSyncFolderStore($serverid))
throw new StatusException("HandleFolderChange() can not change additional folders which are configured", SYNC_FSSTATUS_SYSTEMFOLDER);
// switch user store if this this happens inside an additional folder
// if this is an additional folder the backend has to be setup correctly
if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore((($parentid != false)?$parentid:$serverid))))
throw new StatusException(sprintf("HandleFolderChange() could not Setup() the backend for folder id '%s'", (($parentid != false)?$parentid:$serverid)), SYNC_FSSTATUS_SERVERERROR);
}
catch (StateNotFoundException $snfex) {
$status = SYNC_FSSTATUS_SYNCKEYERROR;
}
catch (StatusException $stex) {
$status = $stex->getCode();
}
// set $newsynckey in case of an error
if (!isset($newsynckey))
$newsynckey = $synckey;
if ($status == SYNC_FSSTATUS_SUCCESS) {
try {
// Configure importer with last state
$importer = self::$backend->GetImporter();
$importer->Config($syncstate);
// the messages from the PIM will be forwarded to the real importer
$changesMem->SetDestinationImporter($importer);
// process incoming change
if (!$delete) {
// Send change
$folder = new SyncFolder();
$folder->serverid = $serverid;
$folder->parentid = $parentid;
$folder->displayname = $displayname;
$folder->type = $type;
$serverid = $changesMem->ImportFolderChange($folder);
}
else {
// delete folder
$changesMem->ImportFolderDeletion($serverid, 0);
}
}
catch (StatusException $stex) {
$status = $stex->getCode();
}
}
self::$encoder->startWBXML();
if ($create) {
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERCREATE);
{
{
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
self::$encoder->content($status);
self::$encoder->endTag();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
self::$encoder->content($newsynckey);
self::$encoder->endTag();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID);
self::$encoder->content($serverid);
self::$encoder->endTag();
}
}
self::$encoder->endTag();
}
elseif ($update) {
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERUPDATE);
{
{
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
self::$encoder->content($status);
self::$encoder->endTag();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
self::$encoder->content($newsynckey);
self::$encoder->endTag();
}
}
self::$encoder->endTag();
}
elseif ($delete) {
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERDELETE);
{
{
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
self::$encoder->content($status);
self::$encoder->endTag();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
self::$encoder->content($newsynckey);
self::$encoder->endTag();
}
}
self::$encoder->endTag();
}
self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true);
// Save the sync state for the next time
if (isset($importer))
self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $importer->GetState());
return true;
}
}
?>

View file

@ -0,0 +1,239 @@
<?php
/***********************************************
* File : foldersync.php
* Project : Z-Push
* Descr : Provides the FOLDERSYNC command
*
* Created : 16.02.2012
*
* Copyright 2007 - 2013 Zarafa Deutschland GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation with the following additional
* term according to sec. 7:
*
* According to sec. 7 of the GNU Affero General Public License, version 3,
* the terms of the AGPL are supplemented with the following terms:
*
* "Zarafa" is a registered trademark of Zarafa B.V.
* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
* The licensing of the Program under the AGPL does not imply a trademark license.
* Therefore any rights, title and interest in our trademarks remain entirely with us.
*
* However, if you propagate an unmodified version of the Program you are
* allowed to use the term "Z-Push" to indicate that you distribute the Program.
* Furthermore you may use our trademarks where it is necessary to indicate
* the intended purpose of a product or service provided you use it in accordance
* with honest practices in industrial or commercial matters.
* If you want to propagate modified versions of the Program under the name "Z-Push",
* you may only do so if you have a written permission by Zarafa Deutschland GmbH
* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Consult LICENSE file for details
************************************************/
class FolderSync extends RequestProcessor {
/**
* Handles the FolderSync command
*
* @param int $commandCode
*
* @access public
* @return boolean
*/
public function Handle ($commandCode) {
// Maps serverid -> clientid for items that are received from the PIM
$map = array();
// Parse input
if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC))
return false;
if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY))
return false;
$synckey = self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
// every FolderSync with SyncKey 0 should return the supported AS version & command headers
if($synckey == "0") {
self::$specialHeaders = array();
self::$specialHeaders[] = ZPush::GetSupportedProtocolVersions();
self::$specialHeaders[] = ZPush::GetSupportedCommands();
}
$status = SYNC_FSSTATUS_SUCCESS;
$newsynckey = $synckey;
try {
$syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey);
// We will be saving the sync state under 'newsynckey'
$newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey);
}
catch (StateNotFoundException $snfex) {
$status = SYNC_FSSTATUS_SYNCKEYERROR;
}
catch (StateInvalidException $sive) {
$status = SYNC_FSSTATUS_SYNCKEYERROR;
}
// The ChangesWrapper caches all imports in-memory, so we can send a change count
// before sending the actual data.
// the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend
$changesMem = self::$deviceManager->GetHierarchyChangesWrapper();
// the hierarchyCache should now fully be initialized - check for changes in the additional folders
$changesMem->Config(ZPush::GetAdditionalSyncFolders());
// process incoming changes
if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) {
// Ignore <Count> if present
if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) {
self::$decoder->getElementContent();
if(!self::$decoder->getElementEndTag())
return false;
}
// Process the changes (either <Add>, <Modify>, or <Remove>)
$element = self::$decoder->getElement();
if($element[EN_TYPE] != EN_TYPE_STARTTAG)
return false;
$importer = false;
while(1) {
$folder = new SyncFolder();
if(!$folder->Decode(self::$decoder))
break;
try {
if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) {
// Configure the backends importer with last state
$importer = self::$backend->GetImporter();
$importer->Config($syncstate);
// the messages from the PIM will be forwarded to the backend
$changesMem->forwardImporter($importer);
}
if ($status == SYNC_FSSTATUS_SUCCESS) {
switch($element[EN_TAG]) {
case SYNC_ADD:
case SYNC_MODIFY:
$serverid = $changesMem->ImportFolderChange($folder);
break;
case SYNC_REMOVE:
$serverid = $changesMem->ImportFolderDeletion($folder);
break;
}
// TODO what does $map??
if($serverid)
$map[$serverid] = $folder->clientid;
}
else {
ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname));
self::$topCollector->AnnounceInformation("Incoming change ignored", true);
}
}
catch (StatusException $stex) {
$status = $stex->getCode();
}
}
if(!self::$decoder->getElementEndTag())
return false;
}
// no incoming changes
else {
// check for a potential process loop like described in Issue ZP-5
if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired())
$status = SYNC_FSSTATUS_SYNCKEYERROR;
self::$deviceManager->AnnounceProcessStatus(false, $status);
}
if(!self::$decoder->getElementEndTag())
return false;
// We have processed incoming foldersync requests, now send the PIM
// our changes
// Output our WBXML reply now
self::$encoder->StartWBXML();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC);
{
if ($status == SYNC_FSSTATUS_SUCCESS) {
try {
// do nothing if this is an invalid device id (like the 'validate' Androids internal client sends)
if (!Request::IsValidDeviceID())
throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR);
// Changes from backend are sent to the MemImporter and processed for the HierarchyCache.
// The state which is saved is from the backend, as the MemImporter is only a proxy.
$exporter = self::$backend->GetExporter();
$exporter->Config($syncstate);
$exporter->InitializeExporter($changesMem);
// Stream all changes to the ImportExportChangesMem
while(is_array($exporter->Synchronize()));
// get the new state from the backend
$newsyncstate = (isset($exporter))?$exporter->GetState():"";
}
catch (StatusException $stex) {
if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN)
$status = SYNC_FSSTATUS_SYNCKEYERROR;
else
$status = $stex->getCode();
}
}
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS);
self::$encoder->content($status);
self::$encoder->endTag();
if ($status == SYNC_FSSTATUS_SUCCESS) {
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY);
$synckey = ($changesMem->IsStateChanged()) ? $newsynckey : $synckey;
self::$encoder->content($synckey);
self::$encoder->endTag();
// Stream folders directly to the PDA
$streamimporter = new ImportChangesStream(self::$encoder, false);
$changesMem->InitializeExporter($streamimporter);
$changeCount = $changesMem->GetChangeCount();
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES);
{
self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT);
self::$encoder->content($changeCount);
self::$encoder->endTag();
while($changesMem->Synchronize());
}
self::$encoder->endTag();
self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders",$changeCount), true);
// everything fine, save the sync state for the next time
if ($synckey == $newsynckey)
self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate);
}
}
self::$encoder->endTag();
return true;
}
}
?>

Some files were not shown because too many files have changed in this diff Show more