Index: /trunk/zpush/setup/default_records.inc.php
===================================================================
--- /trunk/zpush/setup/default_records.inc.php (revision 7589)
+++ /trunk/zpush/setup/default_records.inc.php (revision 7589)
@@ -0,0 +1,3 @@
+
Index: /trunk/zpush/setup/tables_current.inc.php
===================================================================
--- /trunk/zpush/setup/tables_current.inc.php (revision 7589)
+++ /trunk/zpush/setup/tables_current.inc.php (revision 7589)
@@ -0,0 +1,13 @@
+Permission failure when trying to write in folder \"/prototype/config/\". Grant write permission in folder and try again. ";
+// die(); //Mata o restante da execução.
+//}
+///
+
+ $phpgw_baseline = array();
+?>
Index: /trunk/zpush/setup/tables_update.inc.php
===================================================================
--- /trunk/zpush/setup/tables_update.inc.php (revision 7589)
+++ /trunk/zpush/setup/tables_update.inc.php (revision 7589)
@@ -0,0 +1,3 @@
+
Index: /trunk/zpush/setup/setup.inc.php
===================================================================
--- /trunk/zpush/setup/setup.inc.php (revision 7589)
+++ /trunk/zpush/setup/setup.inc.php (revision 7589)
@@ -0,0 +1,28 @@
+
Index: /trunk/zpush/LICENSE
===================================================================
--- /trunk/zpush/LICENSE (revision 7589)
+++ /trunk/zpush/LICENSE (revision 7589)
@@ -0,0 +1,696 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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
+.
+
+
+---
+
+Copyright 2007 - 2012 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 .
Index: /trunk/zpush/z-push-admin.php
===================================================================
--- /trunk/zpush/z-push-admin.php (revision 7589)
+++ /trunk/zpush/z-push-admin.php (revision 7589)
@@ -0,0 +1,642 @@
+#!/usr/bin/php
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+include('lib/core/zpushdefs.php');
+include('lib/core/zpush.php');
+include('lib/core/stateobject.php');
+include('lib/core/syncparameters.php');
+include('lib/core/bodypreference.php');
+include('lib/core/contentparameters.php');
+include('lib/core/synccollections.php');
+include('lib/core/zlog.php');
+include('lib/core/statemanager.php');
+include('lib/core/streamer.php');
+include('lib/core/asdevice.php');
+include('lib/core/interprocessdata.php');
+include('lib/core/loopdetection.php');
+include('lib/exceptions/exceptions.php');
+include('lib/utils/utils.php');
+include('lib/utils/zpushadmin.php');
+include('lib/request/request.php');
+include('lib/request/requestprocessor.php');
+include('lib/interface/ibackend.php');
+include('lib/interface/ichanges.php');
+include('lib/interface/iexportchanges.php');
+include('lib/interface/iimportchanges.php');
+include('lib/interface/isearchprovider.php');
+include('lib/interface/istatemachine.php');
+include('lib/syncobjects/syncobject.php');
+include('lib/syncobjects/syncbasebody.php');
+include('lib/syncobjects/syncbaseattachment.php');
+include('lib/syncobjects/syncmailflags.php');
+include('lib/syncobjects/syncrecurrence.php');
+include('lib/syncobjects/syncappointment.php');
+include('lib/syncobjects/syncappointmentexception.php');
+include('lib/syncobjects/syncattachment.php');
+include('lib/syncobjects/syncattendee.php');
+include('lib/syncobjects/syncmeetingrequestrecurrence.php');
+include('lib/syncobjects/syncmeetingrequest.php');
+include('lib/syncobjects/syncmail.php');
+include('lib/syncobjects/syncnote.php');
+include('lib/syncobjects/synccontact.php');
+include('lib/syncobjects/syncfolder.php');
+include('lib/syncobjects/syncprovisioning.php');
+include('lib/syncobjects/synctaskrecurrence.php');
+include('lib/syncobjects/synctask.php');
+include('lib/syncobjects/syncoofmessage.php');
+include('lib/syncobjects/syncoof.php');
+include('lib/syncobjects/syncuserinformation.php');
+include('lib/syncobjects/syncdeviceinformation.php');
+include('lib/syncobjects/syncdevicepassword.php');
+include('lib/syncobjects/syncitemoperationsattachment.php');
+include('config.php');
+include('version.php');
+
+/**
+ * //TODO resync of single folders of a users device
+ */
+
+/************************************************
+ * MAIN
+ */
+ define('BASE_PATH_CLI', dirname(__FILE__) ."/");
+ set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
+ try {
+ ZPush::CheckConfig();
+ ZPushAdminCLI::CheckEnv();
+ ZPushAdminCLI::CheckOptions();
+
+ if (! ZPushAdminCLI::SureWhatToDo()) {
+ // show error message if available
+ if (ZPushAdminCLI::GetErrorMessage())
+ echo "ERROR: ". ZPushAdminCLI::GetErrorMessage() . "\n";
+
+ echo ZPushAdminCLI::UsageInstructions();
+ exit(1);
+ }
+
+ ZPushAdminCLI::RunCommand();
+ }
+ catch (ZPushException $zpe) {
+ die(get_class($zpe) . ": ". $zpe->getMessage() . "\n");
+ }
+
+
+/************************************************
+ * Z-Push-Admin CLI
+ */
+class ZPushAdminCLI {
+ const COMMAND_SHOWALLDEVICES = 1;
+ const COMMAND_SHOWDEVICESOFUSER = 2;
+ const COMMAND_SHOWUSERSOFDEVICE = 3;
+ const COMMAND_WIPEDEVICE = 4;
+ const COMMAND_REMOVEDEVICE = 5;
+ const COMMAND_RESYNCDEVICE = 6;
+ const COMMAND_CLEARLOOP = 7;
+ const COMMAND_SHOWLASTSYNC = 8;
+
+ static private $command;
+ static private $user = false;
+ static private $device = false;
+ static private $errormessage;
+
+ /**
+ * Returns usage instructions
+ *
+ * @return string
+ * @access public
+ */
+ static public function UsageInstructions() {
+ return "Usage:\n\tz-push-admin.php -a ACTION [options]\n\n" .
+ "Parameters:\n\t-a list/wipe/remove/resync/clearloop\n\t[-u] username\n\t[-d] deviceid\n\n" .
+ "Actions:\n" .
+ "\tlist\t\t\t\t Lists all devices and synchronized users\n" .
+ "\tlist -u USER\t\t\t Lists all devices of user USER\n" .
+ "\tlist -d DEVICE\t\t\t Lists all users of device DEVICE\n" .
+ "\tlastsync\t\t\t Lists all devices and synchronized users and the last synchronization time\n" .
+ "\twipe -u USER\t\t\t Remote wipes all devices of user USER\n" .
+ "\twipe -d DEVICE\t\t\t Remote wipes device DEVICE\n" .
+ "\twipe -u USER -d DEVICE\t\t Remote wipes device DEVICE of user USER\n" .
+ "\tremove -u USER\t\t\t Removes all state data of all devices of user USER\n" .
+ "\tremove -d DEVICE\t\t Removes all state data of all users synchronized on device DEVICE\n" .
+ "\tremove -u USER -d DEVICE\t Removes all related state data of device DEVICE of user USER\n" .
+ "\tresync -u USER -d DEVICE\t Resynchronizes all data of device DEVICE of user USER\n" .
+ "\tclearloop\t\t\t Clears system wide loop detection data\n" .
+ "\tclearloop -d DEVICE -u USER\t Clears all loop detection data of a device DEVICE and an optional user USER\n" .
+ "\n";
+ }
+
+ /**
+ * Checks the environment
+ *
+ * @return
+ * @access public
+ */
+ static public function CheckEnv() {
+ if (!isset($_SERVER["TERM"]) || !isset($_SERVER["LOGNAME"]))
+ self::$errormessage = "This script should not be called in a browser.";
+
+ if (!function_exists("getopt"))
+ self::$errormessage = "PHP Function getopt not found. Please check your PHP version and settings.";
+ }
+
+ /**
+ * Checks the options from the command line
+ *
+ * @return
+ * @access public
+ */
+ static public function CheckOptions() {
+ if (self::$errormessage)
+ return;
+
+ $options = getopt("u:d:a:");
+
+ // get 'user'
+ if (isset($options['u']) && !empty($options['u']))
+ self::$user = strtolower(trim($options['u']));
+ else if (isset($options['user']) && !empty($options['user']))
+ self::$user = strtolower(trim($options['user']));
+
+ // get 'device'
+ if (isset($options['d']) && !empty($options['d']))
+ self::$device = trim($options['d']);
+ else if (isset($options['device']) && !empty($options['device']))
+ self::$device = trim($options['device']);
+
+ // get 'action'
+ $action = false;
+ if (isset($options['a']) && !empty($options['a']))
+ $action = strtolower(trim($options['a']));
+ elseif (isset($options['action']) && !empty($options['action']))
+ $action = strtolower(trim($options['action']));
+
+ // get a command for the requested action
+ switch ($action) {
+ // list data
+ case "list":
+ if (self::$user === false && self::$device === false)
+ self::$command = self::COMMAND_SHOWALLDEVICES;
+
+ if (self::$user !== false)
+ self::$command = self::COMMAND_SHOWDEVICESOFUSER;
+
+ if (self::$device !== false)
+ self::$command = self::COMMAND_SHOWUSERSOFDEVICE;
+ break;
+
+ // list data
+ case "lastsync":
+ self::$command = self::COMMAND_SHOWLASTSYNC;
+ break;
+
+ // remove wipe device
+ case "wipe":
+ if (self::$user === false && self::$device === false)
+ self::$errormessage = "Not possible to execute remote wipe. Device, user or both must be specified.";
+ else
+ self::$command = self::COMMAND_WIPEDEVICE;
+ break;
+
+ // remove device data of user
+ case "remove":
+ if (self::$user === false && self::$device === false)
+ self::$errormessage = "Not possible to remove data. Device, user or both must be specified.";
+ else
+ self::$command = self::COMMAND_REMOVEDEVICE;
+ break;
+
+ // resync a device
+ case "resync":
+ case "re-sync":
+ case "sync":
+ case "resynchronize":
+ case "re-synchronize":
+ case "synchronize":
+ if (self::$user === false || self::$device === false)
+ self::$errormessage = "Not possible to resynchronize device. Device and user must be specified.";
+ else
+ self::$command = self::COMMAND_RESYNCDEVICE;
+ break;
+
+ // clear loop detection data
+ case "clearloop":
+ case "clearloopdetection":
+ self::$command = self::COMMAND_CLEARLOOP;
+ break;
+
+
+ default:
+ self::UsageInstructions();
+ }
+ }
+
+ /**
+ * Indicates if the options from the command line
+ * could be processed correctly
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function SureWhatToDo() {
+ return isset(self::$command);
+ }
+
+ /**
+ * Returns a errormessage of things which could have gone wrong
+ *
+ * @return string
+ * @access public
+ */
+ static public function GetErrorMessage() {
+ return (isset(self::$errormessage))?self::$errormessage:"";
+ }
+
+ /**
+ * Runs a command requested from an action of the command line
+ *
+ * @return
+ * @access public
+ */
+ static public function RunCommand() {
+ echo "\n";
+ switch(self::$command) {
+ case self::COMMAND_SHOWALLDEVICES:
+ self::CommandShowDevices();
+ break;
+
+ case self::COMMAND_SHOWDEVICESOFUSER:
+ self::CommandShowDevices();
+ break;
+
+ case self::COMMAND_SHOWUSERSOFDEVICE:
+ self::CommandDeviceUsers();
+ break;
+
+ case self::COMMAND_SHOWLASTSYNC:
+ self::CommandShowLastSync();
+ break;
+
+ case self::COMMAND_WIPEDEVICE:
+ if (self::$device)
+ echo sprintf("Are you sure you want to REMOTE WIPE device '%s' [y/N]: ", self::$device);
+ else
+ echo sprintf("Are you sure you want to REMOTE WIPE all devices of user '%s' [y/N]: ", self::$user);
+
+ $confirm = strtolower(trim(fgets(STDIN)));
+ if ( $confirm === 'y' || $confirm === 'yes')
+ self::CommandWipeDevice();
+ else
+ echo "Aborted!\n";
+ break;
+
+ case self::COMMAND_REMOVEDEVICE:
+ self::CommandRemoveDevice();
+ break;
+
+ case self::COMMAND_RESYNCDEVICE:
+ if (self::$device == false) {
+ echo sprintf("Are you sure you want to re-synchronize all devices of user '%s' [y/N]: ", self::$user);
+ $confirm = strtolower(trim(fgets(STDIN)));
+ if ( !($confirm === 'y' || $confirm === 'yes'))
+ echo "Aborted!\n";
+ exit(1);
+ }
+ self::CommandResyncDevices();
+ break;
+
+ case self::COMMAND_CLEARLOOP:
+ self::CommandClearLoopDetectionData();
+ break;
+ }
+ echo "\n";
+ }
+
+ /**
+ * Command "Show all devices" and "Show devices of user"
+ * Prints the device id of/and connected users
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandShowDevices() {
+ $devicelist = ZPushAdmin::ListDevices(self::$user);
+ if (empty($devicelist))
+ echo "\tno devices found\n";
+ else {
+ if (self::$user === false) {
+ echo "All synchronized devices\n\n";
+ echo str_pad("Device id", 36). "Synchronized users\n";
+ echo "-----------------------------------------------------\n";
+ }
+ else
+ echo "Synchronized devices of user: ". self::$user. "\n";
+ }
+
+ foreach ($devicelist as $deviceId) {
+ if (self::$user === false) {
+ echo str_pad($deviceId, 36) . implode (",", ZPushAdmin::ListUsers($deviceId)) ."\n";
+ }
+ else
+ self::printDeviceData($deviceId, self::$user);
+ }
+ }
+
+ /**
+ * Command "Show all devices and users with last sync time"
+ * Prints the device id of/and connected users
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandShowLastSync() {
+ $devicelist = ZPushAdmin::ListDevices(false);
+ if (empty($devicelist))
+ echo "\tno devices found\n";
+ else {
+ echo "All known devices and users and their last synchronization time\n\n";
+ echo str_pad("Device id", 36). str_pad("Synchronized user", 30)."Last sync time\n";
+ echo "-----------------------------------------------------------------------------------------------------\n";
+ }
+
+ foreach ($devicelist as $deviceId) {
+ $users = ZPushAdmin::ListUsers($deviceId);
+ foreach ($users as $user) {
+ $device = ZPushAdmin::GetDeviceDetails($deviceId, $user);
+ echo str_pad($deviceId, 36) . str_pad($user, 30). ($device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) : "never") . "\n";
+ }
+ }
+ }
+
+ /**
+ * Command "Show users of device"
+ * Prints informations about all users which use a device
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandDeviceUsers() {
+ $users = ZPushAdmin::ListUsers(self::$device);
+
+ if (empty($users))
+ echo "\tno user data synchronized to device\n";
+
+ foreach ($users as $user) {
+ echo "Synchronized by user: ". $user. "\n";
+ self::printDeviceData(self::$device, $user);
+ }
+ }
+
+ /**
+ * Command "Wipe device"
+ * Marks a device of that user to be remotely wiped
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandWipeDevice() {
+ $stat = ZPushAdmin::WipeDevice($_SERVER["LOGNAME"], self::$user, self::$device);
+
+ if (self::$user !== false && self::$device !== false) {
+ echo sprintf("Mark device '%s' of user '%s' to be wiped: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+
+ if ($stat) {
+ echo "Updated information about this device:\n";
+ self::printDeviceData(self::$device, self::$user);
+ }
+ }
+ elseif (self::$user !== false) {
+ echo sprintf("Mark devices of user '%s' to be wiped: %s", self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ self::CommandShowDevices();
+ }
+ }
+
+ /**
+ * Command "Remove device"
+ * Remove a device of that user from the device list
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandRemoveDevice() {
+ $stat = ZPushAdmin::RemoveDevice(self::$user, self::$device);
+ if (self::$user === false)
+ echo sprintf("State data of device '%s' removed: %s", self::$device, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ elseif (self::$device === false)
+ echo sprintf("State data of all devices of user '%s' removed: %s", self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ else
+ echo sprintf("State data of device '%s' of user '%s' removed: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ }
+
+ /**
+ * Command "Resync device(s)"
+ * Resyncs one or all devices of that user
+ *
+ * @return
+ * @access public
+ */
+ static public function CommandResyncDevices() {
+ $stat = ZPushAdmin::ResyncDevice(self::$user, self::$device);
+ echo sprintf("Resync of device '%s' of user '%s': %s", self::$device, self::$user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ }
+
+ static public function CommandClearLoopDetectionData() {
+ $stat = false;
+ $stat = ZPushAdmin::ClearLoopDetectionData(self::$user, self::$device);
+ if (self::$user === false && self::$device === false)
+ echo sprintf("System wide loop detection data removed: %s", ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ elseif (self::$user === false)
+ echo sprintf("Loop detection data of device '%s' removed: %s", self::$device, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ elseif (self::$device === false && self::$user !== false)
+ echo sprintf("Error: %s", ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_WARN)). "\n";
+ else
+ echo sprintf("Loop detection data of device '%s' of user '%s' removed: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
+ }
+
+ /**
+ * Prints detailed informations about a device
+ *
+ * @param string $deviceId the id of the device
+ *
+ * @return
+ * @access private
+ */
+ static private function printDeviceData($deviceId, $user) {
+ $device = ZPushAdmin::GetDeviceDetails($deviceId, $user);
+
+ if (! $device instanceof ASDevice)
+ return false;
+
+ // Gather some statistics about synchronized folders
+ $folders = $device->GetAllFolderIds();
+ $synchedFolders = 0;
+ $synchedFolderTypes = array();
+ foreach ($folders as $folderid) {
+ if ($device->GetFolderUUID($folderid)) {
+ $synchedFolders++;
+ $type = $device->GetFolderType($folderid);
+ switch($type) {
+ case SYNC_FOLDER_TYPE_APPOINTMENT:
+ case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
+ $gentype = "Calendars";
+ break;
+ case SYNC_FOLDER_TYPE_CONTACT:
+ case SYNC_FOLDER_TYPE_USER_CONTACT:
+ $gentype = "Contacts";
+ break;
+ case SYNC_FOLDER_TYPE_TASK:
+ case SYNC_FOLDER_TYPE_USER_TASK:
+ $gentype = "Tasks";
+ break;
+ case SYNC_FOLDER_TYPE_NOTE:
+ case SYNC_FOLDER_TYPE_USER_NOTE:
+ $gentype = "Notes";
+ break;
+ default:
+ $gentype = "Emails";
+ break;
+ }
+ if (!isset($synchedFolderTypes[$gentype]))
+ $synchedFolderTypes[$gentype] = 0;
+ $synchedFolderTypes[$gentype]++;
+ }
+ }
+ $folderinfo = "";
+ foreach ($synchedFolderTypes as $gentype=>$count) {
+ $folderinfo .= $gentype;
+ if ($count>1) $folderinfo .= "($count)";
+ $folderinfo .= " ";
+ }
+ if (!$folderinfo) $folderinfo = "None available";
+
+ echo "-----------------------------------------------------\n";
+ echo "DeviceId:\t\t$deviceId\n";
+ echo "Device type:\t\t". ($device->GetDeviceType() !== ASDevice::UNDEFINED ? $device->GetDeviceType() : "unknown") ."\n";
+ echo "UserAgent:\t\t".($device->GetDeviceUserAgent()!== ASDevice::UNDEFINED ? $device->GetDeviceUserAgent() : "unknown") ."\n";
+ // TODO implement $device->GetDeviceUserAgentHistory()
+
+ // device information transmitted during Settings command
+ if ($device->GetDeviceModel())
+ echo "Device Model:\t\t". $device->GetDeviceModel(). "\n";
+ if ($device->GetDeviceIMEI())
+ echo "Device IMEI:\t\t". $device->GetDeviceIMEI(). "\n";
+ if ($device->GetDeviceFriendlyName())
+ echo "Device friendly name:\t". $device->GetDeviceFriendlyName(). "\n";
+ if ($device->GetDeviceOS())
+ echo "Device OS:\t\t". $device->GetDeviceOS(). "\n";
+ if ($device->GetDeviceOSLanguage())
+ echo "Device OS Language:\t". $device->GetDeviceOSLanguage(). "\n";
+ if ($device->GetDevicePhoneNumber())
+ echo "Device Phone nr:\t". $device->GetDevicePhoneNumber(). "\n";
+ if ($device->GetDeviceMobileOperator())
+ echo "Device Operator:\t\t". $device->GetDeviceMobileOperator(). "\n";
+ if ($device->GetDeviceEnableOutboundSMS())
+ echo "Device Outbound SMS:\t". $device->GetDeviceEnableOutboundSMS(). "\n";
+
+ echo "ActiveSync version:\t".($device->GetASVersion() ? $device->GetASVersion() : "unknown") ."\n";
+ echo "First sync:\t\t". strftime("%Y-%m-%d %H:%M", $device->GetFirstSyncTime()) ."\n";
+ echo "Last sync:\t\t". ($device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) : "never")."\n";
+ echo "Total folders:\t\t". count($folders). "\n";
+ echo "Synchronized folders:\t". $synchedFolders . "\n";
+ echo "Synchronized data:\t$folderinfo\n";
+ echo "Status:\t\t\t";
+ switch ($device->GetWipeStatus()) {
+ case SYNC_PROVISION_RWSTATUS_OK:
+ echo "OK\n";
+ break;
+ case SYNC_PROVISION_RWSTATUS_PENDING:
+ echo "Pending wipe\n";
+ break;
+ case SYNC_PROVISION_RWSTATUS_REQUESTED:
+ echo "Wipe requested on device\n";
+ break;
+ case SYNC_PROVISION_RWSTATUS_WIPED:
+ echo "Wiped\n";
+ break;
+ default:
+ echo "Not available\n";
+ break;
+ }
+
+ echo "WipeRequest on:\t\t". ($device->GetWipeRequestedOn() ? strftime("%Y-%m-%d %H:%M", $device->GetWipeRequestedOn()) : "not set")."\n";
+ echo "WipeRequest by:\t\t". ($device->GetWipeRequestedBy() ? $device->GetWipeRequestedBy() : "not set")."\n";
+ echo "Wiped on:\t\t". ($device->GetWipeActionOn() ? strftime("%Y-%m-%d %H:%M", $device->GetWipeActionOn()) : "not set")."\n";
+
+ echo "Attention needed:\t";
+
+ if ($device->GetDeviceError())
+ echo $device->GetDeviceError() ."\n";
+ else if (!isset($device->ignoredmessages) || empty($device->ignoredmessages)) {
+ echo "No errors known\n";
+ }
+ else {
+ printf("%d messages need attention because they could not be synchronized\n", count($device->ignoredmessages));
+ foreach ($device->ignoredmessages as $im) {
+ $info = "";
+ if (isset($im->asobject->subject))
+ $info .= sprintf("Subject: '%s'", $im->asobject->subject);
+ if (isset($im->asobject->fileas))
+ $info .= sprintf("FileAs: '%s'", $im->asobject->fileas);
+ if (isset($im->asobject->from))
+ $info .= sprintf(" - From: '%s'", $im->asobject->from);
+ if (isset($im->asobject->starttime))
+ $info .= sprintf(" - On: '%s'", strftime("%Y-%m-%d %H:%M", $im->asobject->starttime));
+ $reason = $im->reasonstring;
+ if ($im->reasoncode == 2)
+ $reason = "Message was causing loop";
+ printf("\tBroken object:\t'%s' ignored on '%s'\n", $im->asclass, strftime("%Y-%m-%d %H:%M", $im->timestamp));
+ printf("\tInformation:\t%s\n", $info);
+ printf("\tReason: \t%s (%s)\n", $reason, $im->reasoncode);
+ printf("\tItem/Parent id: %s/%s\n", $im->id, $im->folderid);
+ echo "\n";
+ }
+ }
+
+ }
+}
+
+
+?>
Index: /trunk/zpush/include/mimeDecode.php
===================================================================
--- /trunk/zpush/include/mimeDecode.php (revision 7589)
+++ /trunk/zpush/include/mimeDecode.php (revision 7589)
@@ -0,0 +1,923 @@
+
+ * Copyright (c) 2003-2006, PEAR
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * - 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.
+ * - Neither the name of the authors, nor the names of its contributors
+ * may 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.
+ *
+ * @category Mail
+ * @package Mail_Mime
+ * @author Richard Heyes
+ * @author George Schlossnagle
+ * @author Cipriano Groenendal
+ * @author Sean Coates
+ * @copyright 2003-2006 PEAR
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version CVS: $Id: mimeDecode.php 288500 2009-09-21 05:32:32Z alan_k $
+ * @link http://pear.php.net/package/Mail_mime
+ */
+
+
+/**
+ * Z-Push changes
+ *
+ * removed PEAR dependency by implementing own raiseError()
+ * removed deprecated referencing: $obj = &new Mail_mimeDecode($body); in _decode()
+ * implemented automated decoding of strings from mail charset
+ *
+ * Reference implementation used:
+ * http://download.pear.php.net/package/Mail_mimeDecode-1.5.1.tgz
+ *
+ */
+
+/**
+ * require PEAR
+ *
+ * This package depends on PEAR to raise errors.
+ */
+//require_once 'PEAR.php';
+
+/**
+ * The Mail_mimeDecode class is used to decode mail/mime messages
+ *
+ * This class will parse a raw mime email and return the structure.
+ * Returned structure is similar to that returned by imap_fetchstructure().
+ *
+ * +----------------------------- IMPORTANT ------------------------------+
+ * | Usage of this class compared to native php extensions such as |
+ * | mailparse or imap, is slow and may be feature deficient. If available|
+ * | you are STRONGLY recommended to use the php extensions. |
+ * +----------------------------------------------------------------------+
+ *
+ * @category Mail
+ * @package Mail_Mime
+ * @author Richard Heyes
+ * @author George Schlossnagle
+ * @author Cipriano Groenendal
+ * @author Sean Coates
+ * @copyright 2003-2006 PEAR
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @version Release: @package_version@
+ * @link http://pear.php.net/package/Mail_mime
+ */
+class Mail_mimeDecode
+{
+ /**
+ * The raw email to decode
+ *
+ * @var string
+ * @access private
+ */
+ var $_input;
+
+ /**
+ * The header part of the input
+ *
+ * @var string
+ * @access private
+ */
+ var $_header;
+
+ /**
+ * The body part of the input
+ *
+ * @var string
+ * @access private
+ */
+ var $_body;
+
+ /**
+ * If an error occurs, this is used to store the message
+ *
+ * @var string
+ * @access private
+ */
+ var $_error;
+
+ /**
+ * Flag to determine whether to include bodies in the
+ * returned object.
+ *
+ * @var boolean
+ * @access private
+ */
+ var $_include_bodies;
+
+ /**
+ * Flag to determine whether to decode bodies
+ *
+ * @var boolean
+ * @access private
+ */
+ var $_decode_bodies;
+
+ /**
+ * Flag to determine whether to decode headers
+ *
+ * @var boolean
+ * @access private
+ */
+ var $_decode_headers;
+
+ /**
+ * Flag to determine whether to include attached messages
+ * as body in the returned object. Depends on $_include_bodies
+ *
+ * @var boolean
+ * @access private
+ */
+ var $_rfc822_bodies;
+
+ /**
+ * Constructor.
+ *
+ * Sets up the object, initialise the variables, and splits and
+ * stores the header and body of the input.
+ *
+ * @param string The input to decode
+ * @access public
+ */
+ function Mail_mimeDecode($input, $deprecated_linefeed = '')
+ {
+ list($header, $body) = $this->_splitBodyHeader($input);
+
+ $this->_input = $input;
+ $this->_header = $header;
+ $this->_body = $body;
+ $this->_decode_bodies = false;
+ $this->_include_bodies = true;
+ $this->_rfc822_bodies = false;
+ }
+
+ /**
+ * Begins the decoding process. If called statically
+ * it will create an object and call the decode() method
+ * of it.
+ *
+ * @param array An array of various parameters that determine
+ * various things:
+ * include_bodies - Whether to include the body in the returned
+ * object.
+ * decode_bodies - Whether to decode the bodies
+ * of the parts. (Transfer encoding)
+ * decode_headers - Whether to decode headers
+ * input - If called statically, this will be treated
+ * as the input
+ * charset - convert all data to this charset
+ * @return object Decoded results
+ * @access public
+ */
+ function decode($params = null)
+ {
+ // determine if this method has been called statically
+ $isStatic = !(isset($this) && get_class($this) == __CLASS__);
+
+ // Have we been called statically?
+ // If so, create an object and pass details to that.
+ if ($isStatic AND isset($params['input'])) {
+
+ $obj = new Mail_mimeDecode($params['input']);
+ $structure = $obj->decode($params);
+
+ // Called statically but no input
+ } elseif ($isStatic) {
+ return $this->raiseError('Called statically and no input given');
+
+ // Called via an object
+ } else {
+ $this->_include_bodies = isset($params['include_bodies']) ?
+ $params['include_bodies'] : false;
+ $this->_decode_bodies = isset($params['decode_bodies']) ?
+ $params['decode_bodies'] : false;
+ $this->_decode_headers = isset($params['decode_headers']) ?
+ $params['decode_headers'] : false;
+ $this->_rfc822_bodies = isset($params['rfc_822bodies']) ?
+ $params['rfc_822bodies'] : false;
+ $this->_charset = isset($params['charset']) ?
+ strtolower($params['charset']) : 'utf-8';
+
+ $structure = $this->_decode($this->_header, $this->_body);
+ if ($structure === false) {
+ $structure = $this->raiseError($this->_error);
+ }
+ }
+
+ return $structure;
+ }
+
+ /**
+ * Performs the decoding. Decodes the body string passed to it
+ * If it finds certain content-types it will call itself in a
+ * recursive fashion
+ *
+ * @param string Header section
+ * @param string Body section
+ * @return object Results of decoding process
+ * @access private
+ */
+ function _decode($headers, $body, $default_ctype = 'text/plain')
+ {
+ $return = new stdClass;
+ $return->headers = array();
+ $headers = $this->_parseHeaders($headers);
+
+ foreach ($headers as $value) {
+ if (isset($return->headers[strtolower($value['name'])]) AND !is_array($return->headers[strtolower($value['name'])])) {
+ $return->headers[strtolower($value['name'])] = array($return->headers[strtolower($value['name'])]);
+ $return->headers[strtolower($value['name'])][] = $value['value'];
+
+ } elseif (isset($return->headers[strtolower($value['name'])])) {
+ $return->headers[strtolower($value['name'])][] = $value['value'];
+
+ } else {
+ $return->headers[strtolower($value['name'])] = $value['value'];
+ }
+ }
+
+ reset($headers);
+ while (list($key, $value) = each($headers)) {
+ $headers[$key]['name'] = strtolower($headers[$key]['name']);
+ switch ($headers[$key]['name']) {
+
+ case 'content-type':
+ $content_type = $this->_parseHeaderValue($headers[$key]['value']);
+
+ if (preg_match('/([0-9a-z+.-]+)\/([0-9a-z+.-]+)/i', $content_type['value'], $regs)) {
+ $return->ctype_primary = $regs[1];
+ $return->ctype_secondary = $regs[2];
+ }
+
+ if (isset($content_type['other'])) {
+ while (list($p_name, $p_value) = each($content_type['other'])) {
+ $return->ctype_parameters[$p_name] = $p_value;
+ }
+ }
+ break;
+
+ case 'content-disposition':
+ $content_disposition = $this->_parseHeaderValue($headers[$key]['value']);
+ $return->disposition = $content_disposition['value'];
+ if (isset($content_disposition['other'])) {
+ while (list($p_name, $p_value) = each($content_disposition['other'])) {
+ $return->d_parameters[$p_name] = $p_value;
+ }
+ }
+ break;
+
+ case 'content-transfer-encoding':
+ $content_transfer_encoding = $this->_parseHeaderValue($headers[$key]['value']);
+ break;
+ }
+ }
+
+ if (isset($content_type)) {
+ switch (strtolower($content_type['value'])) {
+ case 'text/plain':
+ $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+ $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
+ $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null;
+ break;
+
+ case 'text/html':
+ $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+ $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
+ $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body) : null;
+ break;
+
+ case 'multipart/parallel':
+ case 'multipart/appledouble': // Appledouble mail
+ case 'multipart/report': // RFC1892
+ case 'multipart/signed': // PGP
+ case 'multipart/digest':
+ case 'multipart/alternative':
+ case 'multipart/related':
+ case 'multipart/mixed':
+ if(!isset($content_type['other']['boundary'])){
+ $this->_error = 'No boundary found for ' . $content_type['value'] . ' part';
+ return false;
+ }
+
+ $default_ctype = (strtolower($content_type['value']) === 'multipart/digest') ? 'message/rfc822' : 'text/plain';
+
+ $parts = $this->_boundarySplit($body, $content_type['other']['boundary']);
+ for ($i = 0; $i < count($parts); $i++) {
+ list($part_header, $part_body) = $this->_splitBodyHeader($parts[$i]);
+ $part = $this->_decode($part_header, $part_body, $default_ctype);
+ if($part === false)
+ $part = $this->raiseError($this->_error);
+ $return->parts[] = $part;
+ }
+ break;
+
+ case 'message/rfc822':
+ if ($this->_rfc822_bodies) {
+ $encoding = isset($content_transfer_encoding) ? $content_transfer_encoding['value'] : '7bit';
+ $charset = isset($return->ctype_parameters['charset']) ? $return->ctype_parameters['charset'] : $this->_charset;
+ $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $encoding, $charset) : $body);
+ }
+
+ $obj = new Mail_mimeDecode($body);
+ $return->parts[] = $obj->decode(array('include_bodies' => $this->_include_bodies,
+ 'decode_bodies' => $this->_decode_bodies,
+ 'decode_headers' => $this->_decode_headers));
+ unset($obj);
+ break;
+
+ default:
+ if(!isset($content_transfer_encoding['value']))
+ $content_transfer_encoding['value'] = '7bit';
+ // if there is no explicit charset, then don't try to convert to default charset, and make sure that only text mimetypes are converted
+ $charset = (isset($return->ctype_parameters['charset']) && ((isset($return->ctype_primary) && $return->ctype_primary == 'text') || !isset($return->ctype_primary)) )? $return->ctype_parameters['charset']: '';
+ $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body, $content_transfer_encoding['value'], $charset) : $body) : null;
+ break;
+ }
+
+ } else {
+ $ctype = explode('/', $default_ctype);
+ $return->ctype_primary = $ctype[0];
+ $return->ctype_secondary = $ctype[1];
+ $this->_include_bodies ? $return->body = ($this->_decode_bodies ? $this->_decodeBody($body) : $body) : null;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Given the output of the above function, this will return an
+ * array of references to the parts, indexed by mime number.
+ *
+ * @param object $structure The structure to go through
+ * @param string $mime_number Internal use only.
+ * @return array Mime numbers
+ */
+ function &getMimeNumbers(&$structure, $no_refs = false, $mime_number = '', $prepend = '')
+ {
+ $return = array();
+ if (!empty($structure->parts)) {
+ if ($mime_number != '') {
+ $structure->mime_id = $prepend . $mime_number;
+ $return[$prepend . $mime_number] = &$structure;
+ }
+ for ($i = 0; $i < count($structure->parts); $i++) {
+
+
+ if (!empty($structure->headers['content-type']) AND substr(strtolower($structure->headers['content-type']), 0, 8) == 'message/') {
+ $prepend = $prepend . $mime_number . '.';
+ $_mime_number = '';
+ } else {
+ $_mime_number = ($mime_number == '' ? $i + 1 : sprintf('%s.%s', $mime_number, $i + 1));
+ }
+
+ $arr = &Mail_mimeDecode::getMimeNumbers($structure->parts[$i], $no_refs, $_mime_number, $prepend);
+ foreach ($arr as $key => $val) {
+ $no_refs ? $return[$key] = '' : $return[$key] = &$arr[$key];
+ }
+ }
+ } else {
+ if ($mime_number == '') {
+ $mime_number = '1';
+ }
+ $structure->mime_id = $prepend . $mime_number;
+ $no_refs ? $return[$prepend . $mime_number] = '' : $return[$prepend . $mime_number] = &$structure;
+ }
+
+ return $return;
+ }
+
+ /**
+ * Given a string containing a header and body
+ * section, this function will split them (at the first
+ * blank line) and return them.
+ *
+ * @param string Input to split apart
+ * @return array Contains header and body section
+ * @access private
+ */
+ function _splitBodyHeader($input)
+ {
+ if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $input, $match)) {
+ return array($match[1], $match[2]);
+ }
+ $this->_error = 'Could not split header and body';
+ return false;
+ }
+
+ /**
+ * Parse headers given in $input and return
+ * as assoc array.
+ *
+ * @param string Headers to parse
+ * @return array Contains parsed headers
+ * @access private
+ */
+ function _parseHeaders($input)
+ {
+
+ if ($input !== '') {
+ // Unfold the input
+ $input = preg_replace("/\r?\n/", "\r\n", $input);
+ $input = preg_replace("/\r\n(\t| )+/", ' ', $input);
+ $headers = explode("\r\n", trim($input));
+
+ foreach ($headers as $value) {
+ $hdr_name = substr($value, 0, $pos = strpos($value, ':'));
+ $hdr_value = substr($value, $pos+1);
+ if($hdr_value[0] == ' ')
+ $hdr_value = substr($hdr_value, 1);
+
+ $return[] = array(
+ 'name' => $hdr_name,
+ 'value' => $this->_decode_headers ? $this->_decodeHeader($hdr_value) : $hdr_value
+ );
+ }
+ } else {
+ $return = array();
+ }
+
+ return $return;
+ }
+
+ /**
+ * Function to parse a header value,
+ * extract first part, and any secondary
+ * parts (after ;) This function is not as
+ * robust as it could be. Eg. header comments
+ * in the wrong place will probably break it.
+ *
+ * @param string Header value to parse
+ * @return array Contains parsed result
+ * @access private
+ */
+ function _parseHeaderValue($input)
+ {
+
+ if (($pos = strpos($input, ';')) !== false) {
+
+ $return['value'] = trim(substr($input, 0, $pos));
+ $input = trim(substr($input, $pos+1));
+
+ if (strlen($input) > 0) {
+
+ // This splits on a semi-colon, if there's no preceeding backslash
+ // Now works with quoted values; had to glue the \; breaks in PHP
+ // the regex is already bordering on incomprehensible
+ //$splitRegex = '/([^;\'"]*[\'"]([^\'"]*([^\'"]*)*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+ // simplyfied RegEx - Nokia Mail2 sends boundaries containing ' which break the above regex
+ $splitRegex = '/([^;\'"]*[\'"]([^\'"]*)[\'"][^;\'"]*|([^;]+))(;|$)/';
+ preg_match_all($splitRegex, $input, $matches);
+
+ $parameters = array();
+ for ($i=0; $i_fromCharset($charset, $text), $input);
+ }
+
+ return $input;
+ }
+
+ /**
+ * Given a body string and an encoding type,
+ * this function will decode and return it.
+ *
+ * @param string Input body to decode
+ * @param string Encoding type to use.
+ * @return string Decoded body
+ * @access private
+ */
+ function _decodeBody($input, $encoding = '7bit', $charset = '')
+ {
+ switch (strtolower($encoding)) {
+ case '7bit':
+ return $this->_fromCharset($charset, $input);;
+ break;
+
+ case '8bit':
+ return $this->_fromCharset($charset, $input);
+ break;
+
+ case 'quoted-printable':
+ return $this->_fromCharset($charset, $this->_quotedPrintableDecode($input));
+ break;
+
+ case 'base64':
+ return $this->_fromCharset($charset, base64_decode($input));
+ break;
+
+ default:
+ return $input;
+ }
+ }
+
+ /**
+ * Given a quoted-printable string, this
+ * function will decode and return it.
+ *
+ * @param string Input body to decode
+ * @return string Decoded body
+ * @access private
+ */
+ function _quotedPrintableDecode($input)
+ {
+ // Remove soft line breaks
+ $input = preg_replace("/=\r?\n/", '', $input);
+
+ // Replace encoded characters
+ $input = preg_replace('/=([a-f0-9]{2})/ie', "chr(hexdec('\\1'))", $input);
+
+ return $input;
+ }
+
+ /**
+ * Checks the input for uuencoded files and returns
+ * an array of them. Can be called statically, eg:
+ *
+ * $files =& Mail_mimeDecode::uudecode($some_text);
+ *
+ * It will check for the begin 666 ... end syntax
+ * however and won't just blindly decode whatever you
+ * pass it.
+ *
+ * @param string Input body to look for attahcments in
+ * @return array Decoded bodies, filenames and permissions
+ * @access public
+ * @author Unknown
+ */
+ function &uudecode($input)
+ {
+ // Find all uuencoded sections
+ preg_match_all("/begin ([0-7]{3}) (.+)\r?\n(.+)\r?\nend/Us", $input, $matches);
+
+ for ($j = 0; $j < count($matches[3]); $j++) {
+
+ $str = $matches[3][$j];
+ $filename = $matches[2][$j];
+ $fileperm = $matches[1][$j];
+
+ $file = '';
+ $str = preg_split("/\r?\n/", trim($str));
+ $strlen = count($str);
+
+ for ($i = 0; $i < $strlen; $i++) {
+ $pos = 1;
+ $d = 0;
+ $len=(int)(((ord(substr($str[$i],0,1)) -32) - ' ') & 077);
+
+ while (($d + 3 <= $len) AND ($pos + 4 <= strlen($str[$i]))) {
+ $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+ $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+ $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+ $c3 = (ord(substr($str[$i],$pos+3,1)) ^ 0x20);
+ $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+ $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+ $file .= chr(((($c2 - ' ') & 077) << 6) | (($c3 - ' ') & 077));
+
+ $pos += 4;
+ $d += 3;
+ }
+
+ if (($d + 2 <= $len) && ($pos + 3 <= strlen($str[$i]))) {
+ $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+ $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+ $c2 = (ord(substr($str[$i],$pos+2,1)) ^ 0x20);
+ $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+ $file .= chr(((($c1 - ' ') & 077) << 4) | ((($c2 - ' ') & 077) >> 2));
+
+ $pos += 3;
+ $d += 2;
+ }
+
+ if (($d + 1 <= $len) && ($pos + 2 <= strlen($str[$i]))) {
+ $c0 = (ord(substr($str[$i],$pos,1)) ^ 0x20);
+ $c1 = (ord(substr($str[$i],$pos+1,1)) ^ 0x20);
+ $file .= chr(((($c0 - ' ') & 077) << 2) | ((($c1 - ' ') & 077) >> 4));
+
+ }
+ }
+ $files[] = array('filename' => $filename, 'fileperm' => $fileperm, 'filedata' => $file);
+ }
+
+ return $files;
+ }
+
+ /**
+ * getSendArray() returns the arguments required for Mail::send()
+ * used to build the arguments for a mail::send() call
+ *
+ * Usage:
+ * $mailtext = Full email (for example generated by a template)
+ * $decoder = new Mail_mimeDecode($mailtext);
+ * $parts = $decoder->getSendArray();
+ * if (!PEAR::isError($parts) {
+ * list($recipents,$headers,$body) = $parts;
+ * $mail = Mail::factory('smtp');
+ * $mail->send($recipents,$headers,$body);
+ * } else {
+ * echo $parts->message;
+ * }
+ * @return mixed array of recipeint, headers,body or Pear_Error
+ * @access public
+ * @author Alan Knowles
+ */
+ function getSendArray()
+ {
+ // prevent warning if this is not set
+ $this->_decode_headers = FALSE;
+ $headerlist =$this->_parseHeaders($this->_header);
+ $to = "";
+ if (!$headerlist) {
+ return $this->raiseError("Message did not contain headers");
+ }
+ foreach($headerlist as $item) {
+ $header[$item['name']] = $item['value'];
+ switch (strtolower($item['name'])) {
+ case "to":
+ case "cc":
+ case "bcc":
+ $to .= ",".$item['value'];
+ default:
+ break;
+ }
+ }
+ if ($to == "") {
+ return $this->raiseError("Message did not contain any recipents");
+ }
+ $to = substr($to,1);
+ return array($to,$header,$this->_body);
+ }
+
+ /**
+ * Returns a xml copy of the output of
+ * Mail_mimeDecode::decode. Pass the output in as the
+ * argument. This function can be called statically. Eg:
+ *
+ * $output = $obj->decode();
+ * $xml = Mail_mimeDecode::getXML($output);
+ *
+ * The DTD used for this should have been in the package. Or
+ * alternatively you can get it from cvs, or here:
+ * http://www.phpguru.org/xmail/xmail.dtd.
+ *
+ * @param object Input to convert to xml. This should be the
+ * output of the Mail_mimeDecode::decode function
+ * @return string XML version of input
+ * @access public
+ */
+ function getXML($input)
+ {
+ $crlf = "\r\n";
+ $output = '' . $crlf .
+ '' . $crlf .
+ '' . $crlf .
+ Mail_mimeDecode::_getXML($input) .
+ '';
+
+ return $output;
+ }
+
+ /**
+ * Function that does the actual conversion to xml. Does a single
+ * mimepart at a time.
+ *
+ * @param object Input to convert to xml. This is a mimepart object.
+ * It may or may not contain subparts.
+ * @param integer Number of tabs to indent
+ * @return string XML version of input
+ * @access private
+ */
+ function _getXML($input, $indent = 1)
+ {
+ $htab = "\t";
+ $crlf = "\r\n";
+ $output = '';
+ $headers = @(array)$input->headers;
+
+ foreach ($headers as $hdr_name => $hdr_value) {
+
+ // Multiple headers with this name
+ if (is_array($headers[$hdr_name])) {
+ for ($i = 0; $i < count($hdr_value); $i++) {
+ $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value[$i], $indent);
+ }
+
+ // Only one header of this sort
+ } else {
+ $output .= Mail_mimeDecode::_getXML_helper($hdr_name, $hdr_value, $indent);
+ }
+ }
+
+ if (!empty($input->parts)) {
+ for ($i = 0; $i < count($input->parts); $i++) {
+ $output .= $crlf . str_repeat($htab, $indent) . '' . $crlf .
+ Mail_mimeDecode::_getXML($input->parts[$i], $indent+1) .
+ str_repeat($htab, $indent) . '' . $crlf;
+ }
+ } elseif (isset($input->body)) {
+ $output .= $crlf . str_repeat($htab, $indent) . 'body . ']]>' . $crlf;
+ }
+
+ return $output;
+ }
+
+ /**
+ * Helper function to _getXML(). Returns xml of a header.
+ *
+ * @param string Name of header
+ * @param string Value of header
+ * @param integer Number of tabs to indent
+ * @return string XML version of input
+ * @access private
+ */
+ function _getXML_helper($hdr_name, $hdr_value, $indent)
+ {
+ $htab = "\t";
+ $crlf = "\r\n";
+ $return = '';
+
+ $new_hdr_value = ($hdr_name != 'received') ? Mail_mimeDecode::_parseHeaderValue($hdr_value) : array('value' => $hdr_value);
+ $new_hdr_name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $hdr_name)));
+
+ // Sort out any parameters
+ if (!empty($new_hdr_value['other'])) {
+ foreach ($new_hdr_value['other'] as $paramname => $paramvalue) {
+ $params[] = str_repeat($htab, $indent) . $htab . '' . $crlf .
+ str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramname) . '' . $crlf .
+ str_repeat($htab, $indent) . $htab . $htab . '' . htmlspecialchars($paramvalue) . '' . $crlf .
+ str_repeat($htab, $indent) . $htab . '' . $crlf;
+ }
+
+ $params = implode('', $params);
+ } else {
+ $params = '';
+ }
+
+ $return = str_repeat($htab, $indent) . '' . $crlf .
+ str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_name) . '' . $crlf .
+ str_repeat($htab, $indent) . $htab . '' . htmlspecialchars($new_hdr_value['value']) . '' . $crlf .
+ $params .
+ str_repeat($htab, $indent) . '' . $crlf;
+
+ return $return;
+ }
+
+ /**
+ * Z-Push helper to decode text
+ *
+ * @param string current charset of input
+ * @param string input
+ * @return string XML version of input
+ * @access private
+ */
+ function _fromCharset($charset, $input) {
+ if($charset == '' || (strtolower($charset) == $this->_charset))
+ return $input;
+
+ // all ISO-8859-1 are converted as if they were Windows-1252 - see Mantis #456
+ if (strtolower($charset) == 'iso-8859-1')
+ $charset = 'Windows-1252';
+
+ return @iconv($charset, $this->_charset. "//TRANSLIT", $input);
+ }
+
+ /**
+ * 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, "mimeDecode error: ". $message);
+ return false;
+ }
+} // End of class
Index: /trunk/zpush/include/z_RFC822.php
===================================================================
--- /trunk/zpush/include/z_RFC822.php (revision 7589)
+++ /trunk/zpush/include/z_RFC822.php (revision 7589)
@@ -0,0 +1,937 @@
+ |
+// | Chuck Hagenbuch |
+// +-----------------------------------------------------------------------+
+
+/**
+ * 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" (A comment), ted@example.com (Ted Bloggs), Barney;';
+ * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', true)
+ * print_r($structure);
+ *
+ * @author Richard Heyes
+ * @author Chuck Hagenbuch
+ * @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
+ // ... 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*
+ *
+ * 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('/(?@. 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;
+ }
+}
Index: /trunk/zpush/include/stringstreamwrapper.php
===================================================================
--- /trunk/zpush/include/stringstreamwrapper.php (revision 7589)
+++ /trunk/zpush/include/stringstreamwrapper.php (revision 7589)
@@ -0,0 +1,144 @@
+.
+*
+* 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")
+
+?>
Index: /trunk/zpush/INSTALL
===================================================================
--- /trunk/zpush/INSTALL (revision 7589)
+++ /trunk/zpush/INSTALL (revision 7589)
@@ -0,0 +1,210 @@
+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
+
+
+Additioal 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: php5 php5-soap php5-pcntl php5-sysvshm php5-sysvsem
+- On RHEL based systems install the package: php-cli php-soap php-process
+ To install this package 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 777 /var/lib/z-push
+ chmod 777 /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.
+
+ php_flag magic_quotes_gpc off
+ php_flag register_globals off
+ php_flag magic_quotes_runtime off
+ php_flag short_open_tag on
+
+
+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.
+
+
+Upgrading from Z-Push 1.X versions
+------------------------------------
+
+The easiest way to upgrade is to follow the steps for a new installation. The states
+of Z-Push 1.X are not compatible and there is no upgrade path, but as this version
+implements a fully automatic resynchronisation of devices it should not affect the
+users and work without the user interaction.
+
+
+Update to newer Z-Push versions
+-------------------------------
+
+Upgrading to a newer Z-Push version follows the same path as the initial
+installation.
+
+Please observe the published release notes of the new Z-Push version.
+For some releases it is necessary to e.g. resynchronize the mobile.
+
+
+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:///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.
Index: /trunk/zpush/lib/interface/isearchprovider.php
===================================================================
--- /trunk/zpush/lib/interface/isearchprovider.php (revision 7589)
+++ /trunk/zpush/lib/interface/isearchprovider.php (revision 7589)
@@ -0,0 +1,107 @@
+.
+*
+* 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();
+}
+
+?>
Index: /trunk/zpush/lib/interface/iimportchanges.php
===================================================================
--- /trunk/zpush/lib/interface/iimportchanges.php (revision 7589)
+++ /trunk/zpush/lib/interface/iimportchanges.php (revision 7589)
@@ -0,0 +1,143 @@
+.
+*
+* 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);
+
+}
+
+?>
Index: /trunk/zpush/lib/interface/istatemachine.php
===================================================================
--- /trunk/zpush/lib/interface/istatemachine.php (revision 7589)
+++ /trunk/zpush/lib/interface/istatemachine.php (revision 7589)
@@ -0,0 +1,168 @@
+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 - 2012 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 .
+*
+* Consult LICENSE file for details
+************************************************/
+
+interface IStateMachine {
+ const DEFTYPE = "";
+ const DEVICEDATA = "devicedata";
+ const FOLDERDATA = "fd";
+ const FAILSAVE = "fs";
+ const HIERARCHY = "hc";
+ const BACKENDSTORAGE = "bs";
+
+ /**
+ * 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 array
+ */
+ public function LinkUserDevice($username, $devid);
+
+ /**
+ * Unlinks a device from a user
+ *
+ * @param string $username
+ * @param string $devid
+ *
+ * @access public
+ * @return array
+ */
+ 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);
+}
+
+?>
Index: /trunk/zpush/lib/interface/ibackend.php
===================================================================
--- /trunk/zpush/lib/interface/ibackend.php (revision 7589)
+++ /trunk/zpush/lib/interface/ibackend.php (revision 7589)
@@ -0,0 +1,284 @@
+.
+*
+* 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);
+}
+
+?>
Index: /trunk/zpush/lib/interface/ichanges.php
===================================================================
--- /trunk/zpush/lib/interface/ichanges.php (revision 7589)
+++ /trunk/zpush/lib/interface/ichanges.php (revision 7589)
@@ -0,0 +1,75 @@
+.
+*
+* 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);
+
+ /**
+ * Reads and returns the current state
+ *
+ * @access public
+ * @return string
+ */
+ public function GetState();
+}
+
+?>
Index: /trunk/zpush/lib/interface/iexportchanges.php
===================================================================
--- /trunk/zpush/lib/interface/iexportchanges.php (revision 7589)
+++ /trunk/zpush/lib/interface/iexportchanges.php (revision 7589)
@@ -0,0 +1,87 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+interface IExportChanges extends IChanges {
+ /**
+ * Configures additional parameters used for content synchronization
+ *
+ * @param ContentParameters $contentparameters
+ *
+ * @access public
+ * @return boolean
+ * @throws StatusException
+ */
+ public function ConfigContentParameters($contentparameters);
+
+ /**
+ * 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();
+}
+
+?>
Index: /trunk/zpush/lib/utils/utils.php
===================================================================
--- /trunk/zpush/lib/utils/utils.php (revision 7589)
+++ /trunk/zpush/lib/utils/utils.php (revision 7589)
@@ -0,0 +1,908 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class Utils {
+ /**
+ * Prints a variable as string
+ * If a boolean is sent, 'true' or 'false' is displayed
+ *
+ * @param string $var
+ * @access public
+ * @return string
+ */
+ static public function PrintAsString($var) {
+ return ($var)?(($var===true)?'true':$var):(($var===false)?'false':(($var==='')?'empty':$var));
+//return ($var)?(($var===true)?'true':$var):'false';
+ }
+
+ /**
+ * Splits a "domain\user" string into two values
+ * If the string cotains only the user, domain is returned empty
+ *
+ * @param string $domainuser
+ *
+ * @access public
+ * @return array index 0: user 1: domain
+ */
+ static public function SplitDomainUser($domainuser) {
+ $pos = strrpos($domainuser, '\\');
+ if($pos === false){
+ $user = $domainuser;
+ $domain = '';
+ }
+ else{
+ $domain = substr($domainuser,0,$pos);
+ $user = substr($domainuser,$pos+1);
+ }
+ return array($user, $domain);
+ }
+
+ /**
+ * iPhone defines standard summer time information for current year only,
+ * starting with time change in February. Dates from the 1st January until
+ * the time change are undefined and the server uses GMT or its current time.
+ * The function parses the ical attachment and replaces DTSTART one year back
+ * in VTIMEZONE section if the event takes place in this undefined time.
+ * See also http://developer.berlios.de/mantis/view.php?id=311
+ *
+ * @param string $ical iCalendar data
+ *
+ * @access public
+ * @return string
+ */
+ static public function IcalTimezoneFix($ical) {
+ $eventDate = substr($ical, (strpos($ical, ":", strpos($ical, "DTSTART", strpos($ical, "BEGIN:VEVENT")))+1), 8);
+ $posStd = strpos($ical, "DTSTART:", strpos($ical, "BEGIN:STANDARD")) + strlen("DTSTART:");
+ $posDst = strpos($ical, "DTSTART:", strpos($ical, "BEGIN:DAYLIGHT")) + strlen("DTSTART:");
+ $beginStandard = substr($ical, $posStd , 8);
+ $beginDaylight = substr($ical, $posDst , 8);
+
+ if (($eventDate < $beginStandard) && ($eventDate < $beginDaylight) ) {
+ ZLog::Write(LOGLEVEL_DEBUG,"icalTimezoneFix for event on $eventDate, standard:$beginStandard, daylight:$beginDaylight");
+ $year = intval(date("Y")) - 1;
+ $ical = substr_replace($ical, $year, (($beginStandard < $beginDaylight) ? $posDst : $posStd), strlen($year));
+ }
+
+ return $ical;
+ }
+
+ /**
+ * Build an address string from the components
+ *
+ * @param string $street the street
+ * @param string $zip the zip code
+ * @param string $city the city
+ * @param string $state the state
+ * @param string $country the country
+ *
+ * @access public
+ * @return string the address string or null
+ */
+ static public function BuildAddressString($street, $zip, $city, $state, $country) {
+ $out = "";
+
+ if (isset($country) && $street != "") $out = $country;
+
+ $zcs = "";
+ if (isset($zip) && $zip != "") $zcs = $zip;
+ if (isset($city) && $city != "") $zcs .= (($zcs)?" ":"") . $city;
+ if (isset($state) && $state != "") $zcs .= (($zcs)?" ":"") . $state;
+ if ($zcs) $out = $zcs . "\r\n" . $out;
+
+ if (isset($street) && $street != "") $out = $street . (($out)?"\r\n\r\n". $out: "") ;
+
+ return ($out)?$out:null;
+ }
+
+ /**
+ * Build the fileas string from the components according to the configuration.
+ *
+ * @param string $lastname
+ * @param string $firstname
+ * @param string $middlename
+ * @param string $company
+ *
+ * @access public
+ * @return string fileas
+ */
+ static public function BuildFileAs($lastname = "", $firstname = "", $middlename = "", $company = "") {
+ if (defined('FILEAS_ORDER')) {
+ $fileas = $lastfirst = $firstlast = "";
+ $names = trim ($firstname . " " . $middlename);
+ $lastname = trim($lastname);
+ $company = trim($company);
+
+ // lastfirst is "lastname, firstname middlename"
+ // firstlast is "firstname middlename lastname"
+ if (strlen($lastname) > 0) {
+ $lastfirst = $lastname;
+ if (strlen($names) > 0){
+ $lastfirst .= ", $names";
+ $firstlast = "$names $lastname";
+ }
+ else {
+ $firstlast = $lastname;
+ }
+ }
+ elseif (strlen($names) > 0) {
+ $lastfirst = $firstlast = $names;
+ }
+
+ // if fileas with a company is selected
+ // but company is emtpy then it will
+ // fallback to firstlast or lastfirst
+ // (depending on which is selected for company)
+ switch (FILEAS_ORDER) {
+ case SYNC_FILEAS_COMPANYONLY:
+ if (strlen($company) > 0) {
+ $fileas = $company;
+ }
+ elseif (strlen($firstlast) > 0)
+ $fileas = $lastfirst;
+ break;
+ case SYNC_FILEAS_COMPANYLAST:
+ if (strlen($company) > 0) {
+ $fileas = $company;
+ if (strlen($lastfirst) > 0)
+ $fileas .= "($lastfirst)";
+ }
+ elseif (strlen($lastfirst) > 0)
+ $fileas = $lastfirst;
+ break;
+ case SYNC_FILEAS_COMPANYFIRST:
+ if (strlen($company) > 0) {
+ $fileas = $company;
+ if (strlen($firstlast) > 0) {
+ $fileas .= " ($firstlast)";
+ }
+ }
+ elseif (strlen($firstlast) > 0) {
+ $fileas = $firstlast;
+ }
+ break;
+ case SYNC_FILEAS_FIRSTCOMPANY:
+ if (strlen($firstlast) > 0) {
+ $fileas = $firstlast;
+ if (strlen($company) > 0) {
+ $fileas .= " ($company)";
+ }
+ }
+ elseif (strlen($company) > 0) {
+ $fileas = $company;
+ }
+ break;
+ case SYNC_FILEAS_LASTCOMPANY:
+ if (strlen($lastfirst) > 0) {
+ $fileas = $lastfirst;
+ if (strlen($company) > 0) {
+ $fileas .= " ($company)";
+ }
+ }
+ elseif (strlen($company) > 0) {
+ $fileas = $company;
+ }
+ break;
+ case SYNC_FILEAS_LASTFIRST:
+ if (strlen($lastfirst) > 0) {
+ $fileas = $lastfirst;
+ }
+ break;
+ default:
+ $fileas = $firstlast;
+ break;
+ }
+ if (strlen($fileas) == 0)
+ ZLog::Write(LOGLEVEL_DEBUG, "Fileas is empty.");
+ return $fileas;
+ }
+ ZLog::Write(LOGLEVEL_DEBUG, "FILEAS_ORDER not defined. Add it to your config.php.");
+ return null;
+ }
+ /**
+ * Checks if the PHP-MAPI extension is available and in a requested version
+ *
+ * @param string $version the version to be checked ("6.30.10-18495", parts or build number)
+ *
+ * @access public
+ * @return boolean installed version is superior to the checked strin
+ */
+ static public function CheckMapiExtVersion($version = "") {
+ // compare build number if requested
+ if (preg_match('/^\d+$/', $version) && strlen($version) > 3) {
+ $vs = preg_split('/-/', phpversion("mapi"));
+ return ($version <= $vs[1]);
+ }
+
+ if (extension_loaded("mapi")){
+ if (version_compare(phpversion("mapi"), $version) == -1){
+ return false;
+ }
+ }
+ else
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Parses and returns an ecoded vCal-Uid from an
+ * OL compatible GlobalObjectID
+ *
+ * @param string $olUid an OL compatible GlobalObjectID
+ *
+ * @access public
+ * @return string the vCal-Uid if available in the olUid, else the original olUid as HEX
+ */
+ static public function GetICalUidFromOLUid($olUid){
+ //check if "vCal-Uid" is somewhere in outlookid case-insensitive
+ $icalUid = stristr($olUid, "vCal-Uid");
+ if ($icalUid !== false) {
+ //get the length of the ical id - go back 4 position from where "vCal-Uid" was found
+ $begin = unpack("V", substr($olUid, strlen($icalUid) * (-1) - 4, 4));
+ //remove "vCal-Uid" and packed "1" and use the ical id length
+ return substr($icalUid, 12, ($begin[1] - 13));
+ }
+ return strtoupper(bin2hex($olUid));
+ }
+
+ /**
+ * Checks the given UID if it is an OL compatible GlobalObjectID
+ * If not, the given UID is encoded inside the GlobalObjectID
+ *
+ * @param string $icalUid an appointment uid as HEX
+ *
+ * @access public
+ * @return string an OL compatible GlobalObjectID
+ *
+ */
+ static public function GetOLUidFromICalUid($icalUid) {
+ if (strlen($icalUid) <= 64) {
+ $len = 13 + strlen($icalUid);
+ $OLUid = pack("V", $len);
+ $OLUid .= "vCal-Uid";
+ $OLUid .= pack("V", 1);
+ $OLUid .= $icalUid;
+ return hex2bin("040000008200E00074C5B7101A82E0080000000000000000000000000000000000000000". bin2hex($OLUid). "00");
+ }
+ else
+ return hex2bin($icalUid);
+ }
+
+ /**
+ * Extracts the basedate of the GlobalObjectID and the RecurStartTime
+ *
+ * @param string $goid OL compatible GlobalObjectID
+ * @param long $recurStartTime
+ *
+ * @access public
+ * @return long basedate
+ */
+ static public function ExtractBaseDate($goid, $recurStartTime) {
+ $hexbase = substr(bin2hex($goid), 32, 8);
+ $day = hexdec(substr($hexbase, 6, 2));
+ $month = hexdec(substr($hexbase, 4, 2));
+ $year = hexdec(substr($hexbase, 0, 4));
+
+ if ($day && $month && $year) {
+ $h = $recurStartTime >> 12;
+ $m = ($recurStartTime - $h * 4096) >> 6;
+ $s = $recurStartTime - $h * 4096 - $m * 64;
+
+ return gmmktime($h, $m, $s, $month, $day, $year);
+ }
+ else
+ return false;
+ }
+
+ /**
+ * Converts SYNC_FILTERTYPE into a timestamp
+ *
+ * @param int Filtertype
+ *
+ * @access public
+ * @return long
+ */
+ static public function GetCutOffDate($restrict) {
+ switch($restrict) {
+ case SYNC_FILTERTYPE_1DAY:
+ $back = 60 * 60 * 24;
+ break;
+ case SYNC_FILTERTYPE_3DAYS:
+ $back = 60 * 60 * 24 * 3;
+ break;
+ case SYNC_FILTERTYPE_1WEEK:
+ $back = 60 * 60 * 24 * 7;
+ break;
+ case SYNC_FILTERTYPE_2WEEKS:
+ $back = 60 * 60 * 24 * 14;
+ break;
+ case SYNC_FILTERTYPE_1MONTH:
+ $back = 60 * 60 * 24 * 31;
+ break;
+ case SYNC_FILTERTYPE_3MONTHS:
+ $back = 60 * 60 * 24 * 31 * 3;
+ break;
+ case SYNC_FILTERTYPE_6MONTHS:
+ $back = 60 * 60 * 24 * 31 * 6;
+ break;
+ default:
+ break;
+ }
+
+ if(isset($back)) {
+ $date = time() - $back;
+ return $date;
+ } else
+ return 0; // unlimited
+ }
+
+ /**
+ * Converts SYNC_TRUNCATION into bytes
+ *
+ * @param int SYNC_TRUNCATION
+ *
+ * @return long
+ */
+ static public function GetTruncSize($truncation) {
+ switch($truncation) {
+ case SYNC_TRUNCATION_HEADERS:
+ return 0;
+ case SYNC_TRUNCATION_512B:
+ return 512;
+ case SYNC_TRUNCATION_1K:
+ return 1024;
+ case SYNC_TRUNCATION_2K:
+ return 2*1024;
+ case SYNC_TRUNCATION_5K:
+ return 5*1024;
+ case SYNC_TRUNCATION_10K:
+ return 10*1024;
+ case SYNC_TRUNCATION_20K:
+ return 20*1024;
+ case SYNC_TRUNCATION_50K:
+ return 50*1024;
+ case SYNC_TRUNCATION_100K:
+ return 100*1024;
+ case SYNC_TRUNCATION_ALL:
+ return 1024*1024; // We'll limit to 1MB anyway
+ default:
+ return 1024; // Default to 1Kb
+ }
+ }
+
+ /**
+ * Truncate an UTF-8 encoded sting correctly
+ *
+ * If it's not possible to truncate properly, an empty string is returned
+ *
+ * @param string $string - the string
+ * @param string $length - position where string should be cut
+ * @return string truncated string
+ */
+ static public function Utf8_truncate($string, $length) {
+ // make sure length is always an interger
+ $length = (int)$length;
+
+ if (strlen($string) <= $length)
+ return $string;
+
+ while($length >= 0) {
+ if ((ord($string[$length]) < 0x80) || (ord($string[$length]) >= 0xC0))
+ return substr($string, 0, $length);
+
+ $length--;
+ }
+ return "";
+ }
+
+ /**
+ * Indicates if the specified folder type is a system folder
+ *
+ * @param int $foldertype
+ *
+ * @access public
+ * @return boolean
+ */
+ static public function IsSystemFolder($foldertype) {
+ return ($foldertype == SYNC_FOLDER_TYPE_INBOX || $foldertype == SYNC_FOLDER_TYPE_DRAFTS || $foldertype == SYNC_FOLDER_TYPE_WASTEBASKET || $foldertype == SYNC_FOLDER_TYPE_SENTMAIL ||
+ $foldertype == SYNC_FOLDER_TYPE_OUTBOX || $foldertype == SYNC_FOLDER_TYPE_TASK || $foldertype == SYNC_FOLDER_TYPE_APPOINTMENT || $foldertype == SYNC_FOLDER_TYPE_CONTACT ||
+ $foldertype == SYNC_FOLDER_TYPE_NOTE || $foldertype == SYNC_FOLDER_TYPE_JOURNAL) ? true:false;
+ }
+
+ /**
+ * Our own utf7_decode function because imap_utf7_decode converts a string
+ * into ISO-8859-1 encoding which doesn't have euro sign (it will be converted
+ * into two chars: [space](ascii 32) and "¬" ("not sign", ascii 172)). Also
+ * php iconv function expects '+' as delimiter instead of '&' like in IMAP.
+ *
+ * @param string $string IMAP folder name
+ *
+ * @access public
+ * @return string
+ */
+ static public function Utf7_iconv_decode($string) {
+ //do not alter string if there aren't any '&' or '+' chars because
+ //it won't have any utf7-encoded chars and nothing has to be escaped.
+ if (strpos($string, '&') === false && strpos($string, '+') === false ) return $string;
+
+ //Get the string length and go back through it making the replacements
+ //necessary
+ $len = strlen($string) - 1;
+ while ($len > 0) {
+ //look for '&-' sequence and replace it with '&'
+ if ($len > 0 && $string{($len-1)} == '&' && $string{$len} == '-') {
+ $string = substr_replace($string, '&', $len - 1, 2);
+ $len--; //decrease $len as this char has alreasy been processed
+ }
+ //search for '&' which weren't found in if clause above and
+ //replace them with '+' as they mark an utf7-encoded char
+ if ($len > 0 && $string{($len-1)} == '&') {
+ $string = substr_replace($string, '+', $len - 1, 1);
+ $len--; //decrease $len as this char has alreasy been processed
+ }
+ //finally "escape" all remaining '+' chars
+ if ($len > 0 && $string{($len-1)} == '+') {
+ $string = substr_replace($string, '+-', $len - 1, 1);
+ }
+ $len--;
+ }
+ return $string;
+ }
+
+ /**
+ * Our own utf7_encode function because the string has to be converted from
+ * standard UTF7 into modified UTF7 (aka UTF7-IMAP).
+ *
+ * @param string $str IMAP folder name
+ *
+ * @access public
+ * @return string
+ */
+ static public function Utf7_iconv_encode($string) {
+ //do not alter string if there aren't any '&' or '+' chars because
+ //it won't have any utf7-encoded chars and nothing has to be escaped.
+ if (strpos($string, '&') === false && strpos($string, '+') === false ) return $string;
+
+ //Get the string length and go back through it making the replacements
+ //necessary
+ $len = strlen($string) - 1;
+ while ($len > 0) {
+ //look for '&-' sequence and replace it with '&'
+ if ($len > 0 && $string{($len-1)} == '+' && $string{$len} == '-') {
+ $string = substr_replace($string, '+', $len - 1, 2);
+ $len--; //decrease $len as this char has alreasy been processed
+ }
+ //search for '&' which weren't found in if clause above and
+ //replace them with '+' as they mark an utf7-encoded char
+ if ($len > 0 && $string{($len-1)} == '+') {
+ $string = substr_replace($string, '&', $len - 1, 1);
+ $len--; //decrease $len as this char has alreasy been processed
+ }
+ //finally "escape" all remaining '+' chars
+ if ($len > 0 && $string{($len-1)} == '&') {
+ $string = substr_replace($string, '&-', $len - 1, 1);
+ }
+ $len--;
+ }
+ return $string;
+ }
+
+ /**
+ * Converts an UTF-7 encoded string into an UTF-8 string.
+ *
+ * @param string $string to convert
+ *
+ * @access public
+ * @return string
+ */
+ static public function Utf7_to_utf8($string) {
+ if (function_exists("iconv")){
+ return @iconv("UTF-7", "UTF-8", $string);
+ }
+ return $string;
+ }
+
+ /**
+ * Converts an UTF-8 encoded string into an UTF-7 string.
+ *
+ * @param string $string to convert
+ *
+ * @access public
+ * @return string
+ */
+ static public function Utf8_to_utf7($string) {
+ if (function_exists("iconv")){
+ return @iconv("UTF-8", "UTF-7", $string);
+ }
+ return $string;
+ }
+
+ /**
+ * Checks for valid email addresses
+ * The used regex actually only checks if a valid email address is part of the submitted string
+ * it also returns true for the mailbox format, but this is not checked explicitly
+ *
+ * @param string $email address to be checked
+ *
+ * @access public
+ * @return boolean
+ */
+ static public function CheckEmail($email) {
+ return (bool) preg_match('#([a-zA-Z0-9_\-])+(\.([a-zA-Z0-9_\-])+)*@((\[(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5])))\.(((([0-1])?([0-9])?[0-9])|(2[0-4][0-9])|(2[0-5][0-5]))\]))|((([a-zA-Z0-9])+(([\-])+([a-zA-Z0-9])+)*\.)+([a-zA-Z])+(([\-])+([a-zA-Z0-9])+)*)|localhost)#', $email);
+ }
+
+ /**
+ * Checks if a string is base64 encoded
+ *
+ * @param string $string the string to be checked
+ *
+ * @access public
+ * @return boolean
+ */
+ static public function IsBase64String($string) {
+ return (bool) preg_match("#^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+/]{4})?$#", $string);
+ }
+
+ /**
+ * Decodes base64 encoded query parameters. Based on dw2412 contribution.
+ *
+ * @param string $query the query to decode
+ *
+ * @access public
+ * @return array
+ */
+ static public function DecodeBase64URI($query) {
+ /*
+ * The query string has a following structure. Number in () is position:
+ * 1 byte - protocoll version (0)
+ * 1 byte - command code (1)
+ * 2 bytes - locale (2)
+ * 1 byte - device ID length (4)
+ * variable - device ID (4+device ID length)
+ * 1 byte - policy key length (5+device ID length)
+ * 0 or 4 bytes - policy key (5+device ID length + policy key length)
+ * 1 byte - device type length (6+device ID length + policy key length)
+ * variable - device type (6+device ID length + policy key length + device type length)
+ * variable - command parameters, array which consists of:
+ * 1 byte - tag
+ * 1 byte - length
+ * variable - value of the parameter
+ *
+ */
+ $decoded = base64_decode($query);
+ $devIdLength = ord($decoded[4]); //device ID length
+ $polKeyLength = ord($decoded[5+$devIdLength]); //policy key length
+ $devTypeLength = ord($decoded[6+$devIdLength+$polKeyLength]); //device type length
+ //unpack the decoded query string values
+ $unpackedQuery = unpack("CProtVer/CCommand/vLocale/CDevIDLen/H".($devIdLength*2)."DevID/CPolKeyLen".($polKeyLength == 4 ? "/VPolKey" : "")."/CDevTypeLen/A".($devTypeLength)."DevType", $decoded);
+
+ //get the command parameters
+ $pos = 7 + $devIdLength + $polKeyLength + $devTypeLength;
+ $decoded = substr($decoded, $pos);
+ while (strlen($decoded) > 0) {
+ $paramLength = ord($decoded[1]);
+ $unpackedParam = unpack("CParamTag/CParamLength/A".$paramLength."ParamValue", $decoded);
+ $unpackedQuery[ord($decoded[0])] = $unpackedParam['ParamValue'];
+ //remove parameter from decoded query string
+ $decoded = substr($decoded, 2 + $paramLength);
+ }
+ return $unpackedQuery;
+ }
+
+ /**
+ * Returns a command string for a given command code.
+ *
+ * @param int $code
+ *
+ * @access public
+ * @return string or false if code is unknown
+ */
+ public static function GetCommandFromCode($code) {
+ switch ($code) {
+ case ZPush::COMMAND_SYNC: return 'Sync';
+ case ZPush::COMMAND_SENDMAIL: return 'SendMail';
+ case ZPush::COMMAND_SMARTFORWARD: return 'SmartForward';
+ case ZPush::COMMAND_SMARTREPLY: return 'SmartReply';
+ case ZPush::COMMAND_GETATTACHMENT: return 'GetAttachment';
+ case ZPush::COMMAND_FOLDERSYNC: return 'FolderSync';
+ case ZPush::COMMAND_FOLDERCREATE: return 'FolderCreate';
+ case ZPush::COMMAND_FOLDERDELETE: return 'FolderDelete';
+ case ZPush::COMMAND_FOLDERUPDATE: return 'FolderUpdate';
+ case ZPush::COMMAND_MOVEITEMS: return 'MoveItems';
+ case ZPush::COMMAND_GETITEMESTIMATE: return 'GetItemEstimate';
+ case ZPush::COMMAND_MEETINGRESPONSE: return 'MeetingResponse';
+ case ZPush::COMMAND_SEARCH: return 'Search';
+ case ZPush::COMMAND_SETTINGS: return 'Settings';
+ case ZPush::COMMAND_PING: return 'Ping';
+ case ZPush::COMMAND_ITEMOPERATIONS: return 'ItemOperations';
+ case ZPush::COMMAND_PROVISION: return 'Provision';
+ case ZPush::COMMAND_RESOLVERECIPIENTS: return 'ResolveRecipients';
+ case ZPush::COMMAND_VALIDATECERT: return 'ValidateCert';
+
+ // Deprecated commands
+ case ZPush::COMMAND_GETHIERARCHY: return 'GetHierarchy';
+ case ZPush::COMMAND_CREATECOLLECTION: return 'CreateCollection';
+ case ZPush::COMMAND_DELETECOLLECTION: return 'DeleteCollection';
+ case ZPush::COMMAND_MOVECOLLECTION: return 'MoveCollection';
+ case ZPush::COMMAND_NOTIFY: return 'Notify';
+
+ // Webservice commands
+ case ZPush::COMMAND_WEBSERVICE_DEVICE: return 'WebserviceDevice';
+ }
+ return false;
+ }
+
+ /**
+ * Returns a command code for a given command.
+ *
+ * @param string $command
+ *
+ * @access public
+ * @return int or false if command is unknown
+ */
+ public static function GetCodeFromCommand($command) {
+ switch ($command) {
+ case 'Sync': return ZPush::COMMAND_SYNC;
+ case 'SendMail': return ZPush::COMMAND_SENDMAIL;
+ case 'SmartForward': return ZPush::COMMAND_SMARTFORWARD;
+ case 'SmartReply': return ZPush::COMMAND_SMARTREPLY;
+ case 'GetAttachment': return ZPush::COMMAND_GETATTACHMENT;
+ case 'FolderSync': return ZPush::COMMAND_FOLDERSYNC;
+ case 'FolderCreate': return ZPush::COMMAND_FOLDERCREATE;
+ case 'FolderDelete': return ZPush::COMMAND_FOLDERDELETE;
+ case 'FolderUpdate': return ZPush::COMMAND_FOLDERUPDATE;
+ case 'MoveItems': return ZPush::COMMAND_MOVEITEMS;
+ case 'GetItemEstimate': return ZPush::COMMAND_GETITEMESTIMATE;
+ case 'MeetingResponse': return ZPush::COMMAND_MEETINGRESPONSE;
+ case 'Search': return ZPush::COMMAND_SEARCH;
+ case 'Settings': return ZPush::COMMAND_SETTINGS;
+ case 'Ping': return ZPush::COMMAND_PING;
+ case 'ItemOperations': return ZPush::COMMAND_ITEMOPERATIONS;
+ case 'Provision': return ZPush::COMMAND_PROVISION;
+ case 'ResolveRecipients': return ZPush::COMMAND_RESOLVERECIPIENTS;
+ case 'ValidateCert': return ZPush::COMMAND_VALIDATECERT;
+
+ // Deprecated commands
+ case 'GetHierarchy': return ZPush::COMMAND_GETHIERARCHY;
+ case 'CreateCollection': return ZPush::COMMAND_CREATECOLLECTION;
+ case 'DeleteCollection': return ZPush::COMMAND_DELETECOLLECTION;
+ case 'MoveCollection': return ZPush::COMMAND_MOVECOLLECTION;
+ case 'Notify': return ZPush::COMMAND_NOTIFY;
+
+ // Webservice commands
+ case 'WebserviceDevice': return ZPush::COMMAND_WEBSERVICE_DEVICE;
+ }
+ return false;
+ }
+
+ /**
+ * Normalize the given timestamp to the start of the day
+ *
+ * @param long $timestamp
+ *
+ * @access private
+ * @return long
+ */
+ public static function getDayStartOfTimestamp($timestamp) {
+ return $timestamp - ($timestamp % (60 * 60 * 24));
+ }
+
+ /**
+ * Returns a formatted string output from an optional timestamp.
+ * If no timestamp is sent, NOW is used.
+ *
+ * @param long $timestamp
+ *
+ * @access public
+ * @return string
+ */
+ public static function GetFormattedTime($timestamp = false) {
+ if (!$timestamp)
+ return @strftime("%d/%m/%Y %H:%M:%S");
+ else
+ return @strftime("%d/%m/%Y %H:%M:%S", $timestamp);
+ }
+
+
+ /**
+ * Get charset name from a codepage
+ *
+ * @see http://msdn.microsoft.com/en-us/library/dd317756(VS.85).aspx
+ *
+ * Table taken from common/codepage.cpp
+ *
+ * @param integer codepage Codepage
+ *
+ * @access public
+ * @return string iconv-compatible charset name
+ */
+ public static function GetCodepageCharset($codepage) {
+ $codepages = array(
+ 20106 => "DIN_66003",
+ 20108 => "NS_4551-1",
+ 20107 => "SEN_850200_B",
+ 950 => "big5",
+ 50221 => "csISO2022JP",
+ 51932 => "euc-jp",
+ 51936 => "euc-cn",
+ 51949 => "euc-kr",
+ 949 => "euc-kr",
+ 936 => "gb18030",
+ 52936 => "csgb2312",
+ 852 => "ibm852",
+ 866 => "ibm866",
+ 50220 => "iso-2022-jp",
+ 50222 => "iso-2022-jp",
+ 50225 => "iso-2022-kr",
+ 1252 => "windows-1252",
+ 28591 => "iso-8859-1",
+ 28592 => "iso-8859-2",
+ 28593 => "iso-8859-3",
+ 28594 => "iso-8859-4",
+ 28595 => "iso-8859-5",
+ 28596 => "iso-8859-6",
+ 28597 => "iso-8859-7",
+ 28598 => "iso-8859-8",
+ 28599 => "iso-8859-9",
+ 28603 => "iso-8859-13",
+ 28605 => "iso-8859-15",
+ 20866 => "koi8-r",
+ 21866 => "koi8-u",
+ 932 => "shift-jis",
+ 1200 => "unicode",
+ 1201 => "unicodebig",
+ 65000 => "utf-7",
+ 65001 => "utf-8",
+ 1250 => "windows-1250",
+ 1251 => "windows-1251",
+ 1253 => "windows-1253",
+ 1254 => "windows-1254",
+ 1255 => "windows-1255",
+ 1256 => "windows-1256",
+ 1257 => "windows-1257",
+ 1258 => "windows-1258",
+ 874 => "windows-874",
+ 20127 => "us-ascii"
+ );
+
+ if(isset($codepages[$codepage])) {
+ return $codepages[$codepage];
+ } else {
+ // Defaulting to iso-8859-15 since it is more likely for someone to make a mistake in the codepage
+ // when using west-european charsets then when using other charsets since utf-8 is binary compatible
+ // with the bottom 7 bits of west-european
+ return "iso-8859-15";
+ }
+ }
+
+ /**
+ * Converts a string encoded with codepage into an UTF-8 string
+ *
+ * @param int $codepage
+ * @param string $string
+ *
+ * @access public
+ * @return string
+ */
+ public static function ConvertCodepageStringToUtf8($codepage, $string) {
+ if (function_exists("iconv")) {
+ $charset = self::GetCodepageCharset($codepage);
+
+ return iconv($charset, "utf-8", $string);
+ }
+ return $string;
+ }
+
+ /**
+ * Returns the best match of preferred body preference types.
+ *
+ * @param array $bpTypes
+ *
+ * @access public
+ * @return int
+ */
+ public static function GetBodyPreferenceBestMatch($bpTypes) {
+ // The best choice is RTF, then HTML and then MIME in order to save bandwidth
+ // because MIME is a complete message including the headers and attachments
+ if (in_array(SYNC_BODYPREFERENCE_RTF, $bpTypes)) return SYNC_BODYPREFERENCE_RTF;
+ if (in_array(SYNC_BODYPREFERENCE_HTML, $bpTypes)) return SYNC_BODYPREFERENCE_HTML;
+ if (in_array(SYNC_BODYPREFERENCE_MIME, $bpTypes)) return SYNC_BODYPREFERENCE_MIME;
+ return SYNC_BODYPREFERENCE_PLAIN;
+ }
+
+ /* BEGIN fmbiete's contribution r1516, ZP-318 */
+ /**
+ * Converts a html string into a plain text string
+ *
+ * @param string $html
+ *
+ * @access public
+ * @return string
+ */
+ public static function ConvertHtmlToText($html) {
+ // remove css-style tags
+ $plaintext = preg_replace("//is", "", $html);
+ // remove all other html
+ $plaintext = strip_tags($plaintext);
+
+ return $plaintext;
+ }
+ /* END fmbiete's contribution r1516, ZP-318 */
+}
+
+
+
+// TODO Win1252/UTF8 functions are deprecated and will be removed sometime
+//if the ICS backend is loaded in CombinedBackend and Zarafa > 7
+//STORE_SUPPORTS_UNICODE is true and the convertion will not be done
+//for other backends.
+function utf8_to_windows1252($string, $option = "", $force_convert = false) {
+ //if the store supports unicode return the string without converting it
+ if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string;
+
+ if (function_exists("iconv")){
+ return @iconv("UTF-8", "Windows-1252" . $option, $string);
+ }else{
+ return utf8_decode($string); // no euro support here
+ }
+}
+
+function windows1252_to_utf8($string, $option = "", $force_convert = false) {
+ //if the store supports unicode return the string without converting it
+ if (!$force_convert && defined('STORE_SUPPORTS_UNICODE') && STORE_SUPPORTS_UNICODE == true) return $string;
+
+ if (function_exists("iconv")){
+ return @iconv("Windows-1252", "UTF-8" . $option, $string);
+ }else{
+ return utf8_encode($string); // no euro support here
+ }
+}
+
+function w2u($string) { return windows1252_to_utf8($string); }
+function u2w($string) { return utf8_to_windows1252($string); }
+
+function w2ui($string) { return windows1252_to_utf8($string, "//TRANSLIT"); }
+function u2wi($string) { return utf8_to_windows1252($string, "//TRANSLIT"); }
+
+
+?>
Index: /trunk/zpush/lib/utils/zpushadmin.php
===================================================================
--- /trunk/zpush/lib/utils/zpushadmin.php (revision 7589)
+++ /trunk/zpush/lib/utils/zpushadmin.php (revision 7589)
@@ -0,0 +1,418 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class ZPushAdmin {
+ /**
+ * //TODO resync of a foldertype for all users (e.g. Appointment)
+ */
+
+ /**
+ * List devices known to Z-Push.
+ * If no user is given, all devices are listed
+ *
+ * @param string $user devices of that user, if false all devices of all users
+ *
+ * @return array
+ * @access public
+ */
+ static public function ListDevices($user = false) {
+ return ZPush::GetStateMachine()->GetAllDevices($user);
+ }
+
+ /**
+ * List users of a device known to Z-Push.
+ *
+ * @param string $devid users of that device
+ *
+ * @return array
+ * @access public
+ */
+ static public function ListUsers($devid) {
+ try {
+ $devState = ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA);
+
+ if ($devState instanceof StateObject && isset($devState->devices) && is_array($devState->devices))
+ return array_keys($devState->devices);
+ else
+ return array();
+ }
+ catch (StateNotFoundException $stnf) {
+ return array();
+ }
+ }
+
+ /**
+ * Returns details of a device like synctimes,
+ * policy and wipe status, synched folders etc
+ *
+ * @param string $devid device id
+ * @param string $user user to be looked up
+ *
+ * @return ASDevice object
+ * @access public
+ */
+ static public function GetDeviceDetails($devid, $user) {
+
+ try {
+ $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
+ $device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
+ $device->StripData();
+
+ try {
+ $lastsync = SyncCollections::GetLastSyncTimeOfDevice($device);
+ if ($lastsync)
+ $device->SetLastSyncTime($lastsync);
+ }
+ catch (StateInvalidException $sive) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' has invalid states. Please sync to solve this issue.", $devid, $user));
+ $device->SetDeviceError("Invalid states. Please force synchronization!");
+ }
+
+ return $device;
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::GetDeviceDetails(): device '%s' of user '%s' can not be found", $devid, $user));
+ return false;
+ }
+ }
+
+ /**
+ * Wipes 'a' or all devices of a user.
+ * If no user is set, the device is generally wiped.
+ * If no device id is set, all devices of the user will be wiped.
+ * Device id or user must be set!
+ *
+ * @param string $requestedBy user which requested this operation
+ * @param string $user (opt)user of the device
+ * @param string $devid (opt) device id which should be wiped
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function WipeDevice($requestedBy, $user, $devid = false) {
+ if ($user === false && $devid === false)
+ return false;
+
+ if ($devid === false) {
+ $devicesIds = ZPush::GetStateMachine()->GetAllDevices($user);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::WipeDevice(): all '%d' devices for user '%s' found to be wiped", count($devicesIds), $user));
+ foreach ($devicesIds as $deviceid) {
+ if (!self::WipeDevice($requestedBy, $user, $deviceid)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::WipeDevice(): wipe devices failed for device '%s' of user '%s'. Aborting.", $deviceid, $user));
+ return false;
+ }
+ }
+ }
+
+ // wipe a device completely (for connected users to this device)
+ else if ($devid !== false && $user === false) {
+ $users = self::ListUsers($devid);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::WipeDevice(): device '%d' is used by '%d' users and will be wiped", $devid, count($users)));
+ if (count($users) == 0)
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::WipeDevice(): no user found on device '%s'. Aborting.", $devid));
+
+ return self::WipeDevice($requestedBy, $users[0], $devid);
+ }
+
+ else {
+ // load device data
+ $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
+ try {
+ $device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::WipeDevice(): device '%s' of user '%s' can not be found", $devid, $user));
+ return false;
+ }
+
+ // set wipe status
+ if ($device->GetWipeStatus() == SYNC_PROVISION_RWSTATUS_WIPED)
+ ZLog::Write(LOGLEVEL_INFO, sprintf("ZPushAdmin::WipeDevice(): device '%s' of user '%s' was alread sucessfully remote wiped on %s", $devid , $user, strftime("%Y-%m-%d %H:%M", $device->GetWipeActionOn())));
+ else
+ $device->SetWipeStatus(SYNC_PROVISION_RWSTATUS_PENDING, $requestedBy);
+
+ // save device data
+ try {
+ if ($device->IsNewDevice()) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::WipeDevice(): data of user '%s' not synchronized on device '%s'. Aborting.", $user, $devid));
+ return false;
+ }
+
+ ZPush::GetStateMachine()->SetState($device->GetData(), $devid, IStateMachine::DEVICEDATA);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::WipeDevice(): device '%s' of user '%s' marked to be wiped", $devid, $user));
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::WipeDevice(): state for device '%s' of user '%s' can not be saved", $devid, $user));
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Removes device details from the z-push directory.
+ * If device id is not set, all devices of a user are removed.
+ * If the user is not set, the details of the device (independently if used by several users) is removed.
+ * Device id or user must be set!
+ *
+ * @param string $user (opt) user of the device
+ * @param string $devid (opt) device id which should be wiped
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function RemoveDevice($user = false, $devid = false) {
+ if ($user === false && $devid === false)
+ return false;
+
+ // remove all devices for user
+ if ($devid === false && $user !== false) {
+ $devicesIds = ZPush::GetStateMachine()->GetAllDevices($user);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::RemoveDevice(): all '%d' devices for user '%s' found to be removed", count($devicesIds), $user));
+ foreach ($devicesIds as $deviceid) {
+ if (!self::RemoveDevice($user, $deviceid)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::RemoveDevice(): removing devices failed for device '%s' of user '%s'. Aborting", $deviceid, $user));
+ return false;
+ }
+ }
+ }
+ // remove a device completely (for connected users to this device)
+ else if ($devid !== false && $user === false) {
+ $users = self::ListUsers($devid);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::RemoveDevice(): device '%d' is used by '%d' users and will be removed", $devid, count($users)));
+ foreach ($users as $aUser) {
+ if (!self::RemoveDevice($aUser, $devid)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::RemoveDevice(): removing user '%s' from device '%s' failed. Aborting", $aUser, $devid));
+ return false;
+ }
+ }
+ }
+
+ // user and deviceid set
+ else {
+ // load device data
+ $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
+ $devices = array();
+ try {
+ $devicedata = ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA);
+ $device->SetData($devicedata, false);
+ $devices = $devicedata->devices;
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::RemoveDevice(): device '%s' of user '%s' can not be found", $devid, $user));
+ return false;
+ }
+
+ // remove all related states
+ foreach ($device->GetAllFolderIds() as $folderid)
+ StateManager::UnLinkState($device, $folderid);
+
+ // remove hierarchcache
+ StateManager::UnLinkState($device, false);
+
+ // remove backend storage permanent data
+ ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, 99999999999);
+
+ // remove devicedata and unlink user from device
+ unset($devices[$user]);
+ if (isset($devicedata->devices))
+ $devicedata->devices = $devices;
+ ZPush::GetStateMachine()->UnLinkUserDevice($user, $devid);
+
+ // no more users linked for device - remove device data
+ if (count($devices) == 0)
+ ZPush::GetStateMachine()->CleanStates($devid, IStateMachine::DEVICEDATA, false);
+
+ // save data if something left
+ else
+ ZPush::GetStateMachine()->SetState($devicedata, $devid, IStateMachine::DEVICEDATA);
+
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::RemoveDevice(): data of device '%s' of user '%s' removed", $devid, $user));
+ }
+ return true;
+ }
+
+
+ /**
+ * Marks a folder of a device of a user for re-synchronization
+ *
+ * @param string $user user of the device
+ * @param string $devid device id which should be wiped
+ * @param string $folderid if not set, hierarchy state is linked
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function ResyncFolder($user, $devid, $folderid) {
+ // load device data
+ $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
+ try {
+ $device->SetData(ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA), false);
+
+ if ($device->IsNewDevice()) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncFolder(): data of user '%s' not synchronized on device '%s'. Aborting.",$user, $devid));
+ return false;
+ }
+
+ // remove folder state
+ StateManager::UnLinkState($device, $folderid);
+
+ ZPush::GetStateMachine()->SetState($device->GetData(), $devid, IStateMachine::DEVICEDATA);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::ResyncFolder(): folder '%s' on device '%s' of user '%s' marked to be re-synchronized.", $devid, $user));
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncFolder(): state for device '%s' of user '%s' can not be found or saved", $devid, $user));
+ return false;
+ }
+ }
+
+
+ /**
+ * Marks a all folders synchronized to a device for re-synchronization
+ * If no user is set all user which are synchronized for a device are marked for re-synchronization.
+ * If no device id is set all devices of that user are marked for re-synchronization.
+ * If no user and no device are set then ALL DEVICES are marked for resynchronization (use with care!).
+ *
+ * @param string $user (opt) user of the device
+ * @param string $devid (opt)device id which should be wiped
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function ResyncDevice($user, $devid = false) {
+
+ // search for target devices
+ if ($devid === false) {
+ $devicesIds = ZPush::GetStateMachine()->GetAllDevices($user);
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::ResyncDevice(): all '%d' devices for user '%s' found to be re-synchronized", count($devicesIds), $user));
+ foreach ($devicesIds as $deviceid) {
+ if (!self::ResyncDevice($user, $deviceid)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncDevice(): wipe devices failed for device '%s' of user '%s'. Aborting", $deviceid, $user));
+ return false;
+ }
+ }
+ }
+ else {
+ // get devicedata
+ try {
+ $devicedata = ZPush::GetStateMachine()->GetState($devid, IStateMachine::DEVICEDATA);
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncDevice(): state for device '%s' can not be found", $devid));
+ return false;
+
+ }
+
+ // loop through all users which currently use this device
+ if ($user === false && $devicedata instanceof StateObject && isset($devicedata->devices) &&
+ is_array($devicedata->devices) && count($devicedata->devices) > 1) {
+ foreach (array_keys($devicedata) as $aUser) {
+ if (!self::ResyncDevice($aUser, $devid)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncDevice(): re-synchronization failed for device '%s' of user '%s'. Aborting", $devid, $aUser));
+ return false;
+ }
+ }
+ }
+
+ // load device data
+ $device = new ASDevice($devid, ASDevice::UNDEFINED, $user, ASDevice::UNDEFINED);
+ try {
+ $device->SetData($devicedata, false);
+
+ if ($device->IsNewDevice()) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncDevice(): data of user '%s' not synchronized on device '%s'. Aborting.",$user, $devid));
+ return false;
+ }
+
+ // delete all uuids
+ foreach ($device->GetAllFolderIds() as $folderid)
+ StateManager::UnLinkState($device, $folderid);
+
+ // remove hierarchcache
+ StateManager::UnLinkState($device, false);
+
+ ZPush::GetStateMachine()->SetState($device->GetData(), $devid, IStateMachine::DEVICEDATA);
+
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPushAdmin::ResyncDevice(): all folders synchronized to device '%s' of user '%s' marked to be re-synchronized.", $devid, $user));
+ }
+ catch (StateNotFoundException $e) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("ZPushAdmin::ResyncDevice(): state for device '%s' of user '%s' can not be found or saved", $devid, $user));
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Clears loop detection data
+ *
+ * @param string $user (opt) user which data should be removed - user may not be specified without device id
+ * @param string $devid (opt) device id which data to be removed
+ *
+ * @return boolean
+ * @access public
+ */
+ static public function ClearLoopDetectionData($user = false, $devid = false) {
+ $loopdetection = new LoopDetection();
+ return $loopdetection->ClearData($user, $devid);
+ }
+
+ /**
+ * Returns loop detection data of a user & device
+ *
+ * @param string $user
+ * @param string $devid
+ *
+ * @return array/boolean returns false if data is not available
+ * @access public
+ */
+ static public function GetLoopDetectionData($user, $devid) {
+ $loopdetection = new LoopDetection();
+ return $loopdetection->GetCachedData($user, $devid);
+ }
+
+
+}
+
+?>
Index: /trunk/zpush/lib/utils/compat.php
===================================================================
--- /trunk/zpush/lib/utils/compat.php (revision 7589)
+++ /trunk/zpush/lib/utils/compat.php (revision 7589)
@@ -0,0 +1,89 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+if (!function_exists("quoted_printable_encode")) {
+ /**
+ * Process a string to fit the requirements of RFC2045 section 6.7. Note that
+ * this works, but replaces more characters than the minimum set. For readability
+ * the spaces and CRLF pairs aren't encoded though.
+ *
+ * @param string $string string to be encoded
+ *
+ * @see http://www.php.net/manual/en/function.quoted-printable-decode.php#89417
+ */
+ function quoted_printable_encode($string) {
+ return preg_replace('/[^\r\n]{73}[^=\r\n]{2}/', "$0=\n", str_replace(array('%20', '%0D%0A', '%'), array(' ', "\r\n", '='), rawurlencode($string)));
+ }
+}
+
+if (!function_exists("apache_request_headers")) {
+ /**
+ * When using other webservers or using php as cgi in apache
+ * the function apache_request_headers() is not available.
+ * This function parses the environment variables to extract
+ * the necessary headers for Z-Push
+ */
+ function apache_request_headers() {
+ $headers = array();
+ foreach ($_SERVER as $key => $value)
+ if (substr($key, 0, 5) == 'HTTP_')
+ $headers[strtr(substr($key, 5), '_', '-')] = $value;
+
+ return $headers;
+ }
+}
+
+if (!function_exists("hex2bin")) {
+ /**
+ * Complementary function to bin2hex() which converts a hex entryid to a binary entryid.
+ * Since PHP 5.4 an internal hex2bin() implementation is available.
+ *
+ * @param string $data the hexadecimal string
+ *
+ * @returns string
+ */
+ function hex2bin($data) {
+ return pack("H*", $data);
+ }
+}
+?>
Index: /trunk/zpush/lib/utils/timezoneutil.php
===================================================================
--- /trunk/zpush/lib/utils/timezoneutil.php (revision 7589)
+++ /trunk/zpush/lib/utils/timezoneutil.php (revision 7589)
@@ -0,0 +1,1263 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class TimezoneUtil {
+
+ /**
+ * list of MS and AS compatible timezones
+ *
+ * origin: http://msdn.microsoft.com/en-us/library/ms912391%28v=winembedded.11%29.aspx
+ * dots of tz identifiers were removed
+ *
+ * Updated at: 01.06.2012
+ */
+ private static $mstzones = array(
+ "000" => array("Dateline Standard Time", "(GMT-12:00) International Date Line West"),
+ "001" => array("Samoa Standard Time", "(GMT-11:00) Midway Island, Samoa"),
+ "002" => array("Hawaiian Standard Time", "(GMT-10:00) Hawaii"),
+ "003" => array("Alaskan Standard Time", "(GMT-09:00) Alaska"),
+ "004" => array("Pacific Standard Time", "(GMT-08:00) Pacific Time (US and Canada); Tijuana"),
+ "010" => array("Mountain Standard Time", "(GMT-07:00) Mountain Time (US and Canada)"),
+ "013" => array("Mexico Standard Time 2", "(GMT-07:00) Chihuahua, La Paz, Mazatlan"),
+ "015" => array("US Mountain Standard Time", "(GMT-07:00) Arizona"),
+ "020" => array("Central Standard Time", "(GMT-06:00) Central Time (US and Canada"),
+ "025" => array("Canada Central Standard Time", "(GMT-06:00) Saskatchewan"),
+ "030" => array("Mexico Standard Time", "(GMT-06:00) Guadalajara, Mexico City, Monterrey"),
+ "033" => array("Central America Standard Time", "(GMT-06:00) Central America"),
+ "035" => array("Eastern Standard Time", "(GMT-05:00) Eastern Time (US and Canada)"),
+ "040" => array("US Eastern Standard Time", "(GMT-05:00) Indiana (East)"),
+ "045" => array("SA Pacific Standard Time", "(GMT-05:00) Bogota, Lima, Quito"),
+ "uk1" => array("Venezuela Standard Time", "(GMT-04:30) Caracas"), // added
+ "050" => array("Atlantic Standard Time", "(GMT-04:00) Atlantic Time (Canada)"),
+ "055" => array("SA Western Standard Time", "(GMT-04:00) Caracas, La Paz"),
+ "056" => array("Pacific SA Standard Time", "(GMT-04:00) Santiago"),
+ "060" => array("Newfoundland and Labrador Standard Time", "(GMT-03:30) Newfoundland and Labrador"),
+ "065" => array("E South America Standard Time" , "(GMT-03:00) Brasilia"),
+ "070" => array("SA Eastern Standard Time", "(GMT-03:00) Buenos Aires, Georgetown"),
+ "073" => array("Greenland Standard Time", "(GMT-03:00) Greenland"),
+ "075" => array("Mid-Atlantic Standard Time", "(GMT-02:00) Mid-Atlantic"),
+ "080" => array("Azores Standard Time", "(GMT-01:00) Azores"),
+ "083" => array("Cape Verde Standard Time", "(GMT-01:00) Cape Verde Islands"),
+ "085" => array("GMT Standard Time", "(GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London"),
+ "090" => array("Greenwich Standard Time", "(GMT) Casablanca, Monrovia"),
+ "095" => array("Central Europe Standard Time", "(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague"),
+ "100" => array("Central European Standard Time", "(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb"),
+ "105" => array("Romance Standard Time", "(GMT+01:00) Brussels, Copenhagen, Madrid, Paris"),
+ "110" => array("W Europe Standard Time", "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"),
+ "113" => array("W Central Africa Standard Time", "(GMT+01:00) West Central Africa"),
+ "115" => array("E Europe Standard Time", "(GMT+02:00) Bucharest"),
+ "120" => array("Egypt Standard Time", "(GMT+02:00) Cairo"),
+ "125" => array("FLE Standard Time", "(GMT+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius"),
+ "130" => array("GTB Standard Time", "(GMT+02:00) Athens, Istanbul, Minsk"),
+ "135" => array("Israel Standard Time", "(GMT+02:00) Jerusalem"),
+ "140" => array("South Africa Standard Time", "(GMT+02:00) Harare, Pretoria"),
+ "145" => array("Russian Standard Time", "(GMT+03:00) Moscow, St. Petersburg, Volgograd"),
+ "150" => array("Arab Standard Time", "(GMT+03:00) Kuwait, Riyadh"),
+ "155" => array("E Africa Standard Time", "(GMT+03:00) Nairobi"),
+ "158" => array("Arabic Standard Time", "(GMT+03:00) Baghdad"),
+ "160" => array("Iran Standard Time", "(GMT+03:30) Tehran"),
+ "165" => array("Arabian Standard Time", "(GMT+04:00) Abu Dhabi, Muscat"),
+ "170" => array("Caucasus Standard Time", "(GMT+04:00) Baku, Tbilisi, Yerevan"),
+ "175" => array("Transitional Islamic State of Afghanistan Standard Time","(GMT+04:30) Kabul"),
+ "180" => array("Ekaterinburg Standard Time", "(GMT+05:00) Ekaterinburg"),
+ "185" => array("West Asia Standard Time", "(GMT+05:00) Islamabad, Karachi, Tashkent"),
+ "190" => array("India Standard Time", "(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi"),
+ "193" => array("Nepal Standard Time", "(GMT+05:45) Kathmandu"),
+ "195" => array("Central Asia Standard Time", "(GMT+06:00) Astana, Dhaka"),
+ "200" => array("Sri Lanka Standard Time", "(GMT+06:00) Sri Jayawardenepura"),
+ "201" => array("N Central Asia Standard Time", "(GMT+06:00) Almaty, Novosibirsk"),
+ "203" => array("Myanmar Standard Time", "(GMT+06:30) Yangon Rangoon"),
+ "205" => array("SE Asia Standard Time", "(GMT+07:00) Bangkok, Hanoi, Jakarta"),
+ "207" => array("North Asia Standard Time", "(GMT+07:00) Krasnoyarsk"),
+ "210" => array("China Standard Time", "(GMT+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi"),
+ "215" => array("Singapore Standard Time", "(GMT+08:00) Kuala Lumpur, Singapore"),
+ "220" => array("Taipei Standard Time", "(GMT+08:00) Taipei"),
+ "225" => array("W Australia Standard Time", "(GMT+08:00) Perth"),
+ "227" => array("North Asia East Standard Time", "(GMT+08:00) Irkutsk, Ulaanbaatar"),
+ "230" => array("Korea Standard Time", "(GMT+09:00) Seoul"),
+ "235" => array("Tokyo Standard Time", "(GMT+09:00) Osaka, Sapporo, Tokyo"),
+ "240" => array("Yakutsk Standard Time", "(GMT+09:00) Yakutsk"),
+ "245" => array("AUS Central Standard Time", "(GMT+09:30) Darwin"),
+ "250" => array("Cen Australia Standard Time", "(GMT+09:30) Adelaide"),
+ "255" => array("AUS Eastern Standard Time", "(GMT+10:00) Canberra, Melbourne, Sydney"),
+ "260" => array("E Australia Standard Time", "(GMT+10:00) Brisbane"),
+ "265" => array("Tasmania Standard Time", "(GMT+10:00) Hobart"),
+ "270" => array("Vladivostok Standard Time", "(GMT+10:00) Vladivostok"),
+ "275" => array("West Pacific Standard Time", "(GMT+10:00) Guam, Port Moresby"),
+ "280" => array("Central Pacific Standard Time", "(GMT+11:00) Magadan, Solomon Islands, New Caledonia"),
+ "285" => array("Fiji Islands Standard Time", "(GMT+12:00) Fiji Islands, Kamchatka, Marshall Islands"),
+ "290" => array("New Zealand Standard Time", "(GMT+12:00) Auckland, Wellington"),
+ "300" => array("Tonga Standard Time", "(GMT+13:00) Nuku'alofa"),
+ );
+
+ /**
+ * Python generated offset list
+ * dots in keys were removed
+ *
+ * Array indices
+ * 0 = lBias
+ * 1 = lStandardBias
+ * 2 = lDSTBias
+ * 3 = wStartYear
+ * 4 = wStartMonth
+ * 5 = wStartDOW
+ * 6 = wStartDay
+ * 7 = wStartHour
+ * 8 = wStartMinute
+ * 9 = wStartSecond
+ * 10 = wStartMilliseconds
+ * 11 = wEndYear
+ * 12 = wEndMonth
+ * 13 = wEndDOW
+ * 14 = wEndDay
+ * 15 = wEndHour
+ * 16 = wEndMinute
+ * 17 = wEndSecond
+ * 18 = wEndMilloseconds
+ *
+ * As the $tzoneoffsets and the $mstzones need to be resolved in both directions,
+ * some offsets are commented as they are not available in the $mstzones.
+ *
+ * Created at: 01.06.2012
+ */
+ private static $tzonesoffsets = array(
+ "Transitional Islamic State of Afghanistan Standard Time"
+ => array(-270, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Alaskan Standard Time" => array(540, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "Arab Standard Time" => array(-180, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Arabian Standard Time" => array(-240, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Arabic Standard Time" => array(-180, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Argentina Standard Time" => array(180, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Atlantic Standard Time" => array(240, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "AUS Central Standard Time" => array(-570, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "AUS Eastern Standard Time" => array(-600, 0, -60, 0, 4, 0, 1, 3, 0, 0, 0, 0, 10, 0, 1, 2, 0, 0, 0),
+ //"Azerbaijan Standard Time" => array(-240, 0, -60, 0, 10, 0, 5, 5, 0, 0, 0, 0, 3, 0, 5, 4, 0, 0, 0),
+ "Azores Standard Time" => array(60, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ //"Bangladesh Standard Time" => array(-360, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Canada Central Standard Time" => array(360, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Cape Verde Standard Time" => array(60, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Caucasus Standard Time" => array(-240, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Cen Australia Standard Time" => array(-570, 0, -60, 0, 4, 0, 1, 3, 0, 0, 0, 0, 10, 0, 1, 2, 0, 0, 0),
+ "Central America Standard Time" => array(360, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Central Asia Standard Time" => array(-360, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Central Brazilian Standard Time" => array(240, 0, -60, 0, 2, 6, 4, 23, 59, 59, 999, 0, 10, 6, 3, 23, 59, 59, 999),
+ "Central Europe Standard Time" => array(-60, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Central European Standard Time" => array(-60, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Central Pacific Standard Time" => array(-660, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Central Standard Time" => array(360, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "Mexico Standard Time" => array(360, 0, -60, 0, 10, 0, 5, 2, 0, 0, 0, 0, 4, 0, 1, 2, 0, 0, 0),
+ "China Standard Time" => array(-480, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Dateline Standard Time" => array(720, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "E Africa Standard Time" => array(-180, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "E Australia Standard Time" => array(-600, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "E Europe Standard Time" => array(-120, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "E South America Standard Time" => array(180, 0, -60, 0, 2, 6, 4, 23, 59, 59, 999, 0, 10, 6, 3, 23, 59, 59, 999),
+ "Eastern Standard Time" => array(300, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "Egypt Standard Time" => array(-120, 0, -60, 0, 9, 4, 5, 23, 59, 59, 999, 0, 4, 4, 5, 23, 59, 59, 999),
+ "Ekaterinburg Standard Time" => array(-300, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Fiji Islands Standard Time" => array(-720, 0, -60, 0, 3, 0, 5, 3, 0, 0, 0, 0, 10, 0, 4, 2, 0, 0, 0),
+ "FLE Standard Time" => array(-120, 0, -60, 0, 10, 0, 5, 4, 0, 0, 0, 0, 3, 0, 5, 3, 0, 0, 0),
+ //"Georgian Standard Time" => array(-240, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "GMT Standard Time" => array(0, 0, -60, 0, 10, 0, 5, 2, 0, 0, 0, 0, 3, 0, 5, 1, 0, 0, 0),
+ "Greenland Standard Time" => array(180, 0, -60, 0, 10, 6, 5, 23, 0, 0, 0, 0, 3, 6, 4, 22, 0, 0, 0),
+ "Greenwich Standard Time" => array(0, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "GTB Standard Time" => array(-120, 0, -60, 0, 10, 0, 5, 4, 0, 0, 0, 0, 3, 0, 5, 3, 0, 0, 0),
+ "Hawaiian Standard Time" => array(600, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "India Standard Time" => array(-330, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Iran Standard Time" => array(-210, 0, -60, 0, 9, 1, 3, 23, 59, 59, 999, 0, 3, 6, 3, 23, 59, 59, 999),
+ "Israel Standard Time" => array(-120, 0, -60, 0, 9, 0, 4, 2, 0, 0, 0, 0, 3, 5, 5, 2, 0, 0, 0),
+ //"Jordan Standard Time" => array(-120, 0, -60, 0, 10, 5, 5, 1, 0, 0, 0, 0, 3, 4, 5, 23, 59, 59, 999),
+ //"Kamchatka Standard Time" => array(-720, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Korea Standard Time" => array(-540, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Magadan Standard Time" => array(-660, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ //"Mauritius Standard Time" => array(-240, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Mid-Atlantic Standard Time" => array(120, 0, -60, 0, 9, 0, 5, 2, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ //"Middle East Standard Time" => array(-120, 0, -60, 0, 10, 6, 5, 23, 59, 59, 999, 0, 3, 6, 4, 23, 59, 59, 999),
+ //"Montevideo Standard Time" => array(180, 0, -60, 0, 3, 0, 2, 2, 0, 0, 0, 0, 10, 0, 1, 2, 0, 0, 0),
+ //"Morocco Standard Time" => array(0, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Mountain Standard Time" => array(420, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "Mexico Standard Time 2" => array(420, 0, -60, 0, 10, 0, 5, 2, 0, 0, 0, 0, 4, 0, 1, 2, 0, 0, 0),
+ "Myanmar Standard Time" => array(-390, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "N Central Asia Standard Time" => array(-360, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ //"Namibia Standard Time" => array(-60, 0, -60, 0, 4, 0, 1, 2, 0, 0, 0, 0, 9, 0, 1, 2, 0, 0, 0),
+ "Nepal Standard Time" => array(-345, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "New Zealand Standard Time" => array(-720, 0, -60, 0, 4, 0, 1, 3, 0, 0, 0, 0, 9, 0, 5, 2, 0, 0, 0),
+ "Newfoundland and Labrador Standard Time" => array(210, 0, -60, 0, 11, 0, 1, 0, 1, 0, 0, 0, 3, 0, 2, 0, 1, 0, 0),
+ "North Asia East Standard Time" => array(-480, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "North Asia Standard Time" => array(-420, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Pacific SA Standard Time" => array(240, 0, -60, 0, 3, 6, 2, 23, 59, 59, 999, 0, 10, 6, 2, 23, 59, 59, 999),
+ "Pacific Standard Time" => array(480, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ //"Pacific Standard Time (Mexico)" => array(480, 0, -60, 0, 10, 0, 5, 2, 0, 0, 0, 0, 4, 0, 1, 2, 0, 0, 0),
+ //"Pakistan Standard Time" => array(-300, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Paraguay Standard Time" => array(240, 0, -60, 0, 4, 6, 1, 23, 59, 59, 999, 0, 10, 6, 1, 23, 59, 59, 999),
+ "Romance Standard Time" => array(-60, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "Russian Standard Time" => array(-180, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "SA Eastern Standard Time" => array(180, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "SA Pacific Standard Time" => array(300, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "SA Western Standard Time" => array(240, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Samoa Standard Time" => array(660, 0, -60, 0, 3, 6, 5, 23, 59, 59, 999, 0, 9, 6, 5, 23, 59, 59, 999),
+ "SE Asia Standard Time" => array(-420, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Singapore Standard Time" => array(-480, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "South Africa Standard Time" => array(-120, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Sri Lanka Standard Time" => array(-330, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Syria Standard Time" => array(-120, 0, -60, 0, 10, 4, 5, 23, 59, 59, 999, 0, 4, 4, 1, 23, 59, 59, 999),
+ "Taipei Standard Time" => array(-480, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Tasmania Standard Time" => array(-600, 0, -60, 0, 4, 0, 1, 3, 0, 0, 0, 0, 10, 0, 1, 2, 0, 0, 0),
+ "Tokyo Standard Time" => array(-540, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Tonga Standard Time" => array(-780, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"Ulaanbaatar Standard Time" => array(-480, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "US Eastern Standard Time" => array(300, 0, -60, 0, 11, 0, 1, 2, 0, 0, 0, 0, 3, 0, 2, 2, 0, 0, 0),
+ "US Mountain Standard Time" => array(420, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"UTC" => array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"UTC+12" => array(-720, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"UTC-02" => array(120, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ //"UTC-11" => array(660, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Venezuela Standard Time" => array(270, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Vladivostok Standard Time" => array(-600, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "W Australia Standard Time" => array(-480, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "W Central Africa Standard Time" => array(-60, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "W Europe Standard Time" => array(-60, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ "West Asia Standard Time" => array(-300, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "West Pacific Standard Time" => array(-600, 0, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+ "Yakutsk Standard Time" => array(-540, 0, -60, 0, 10, 0, 5, 3, 0, 0, 0, 0, 3, 0, 5, 2, 0, 0, 0),
+ );
+
+ /**
+ * Generated list of PHP timezones in GMT timezones
+ *
+ * Created at: 01.06.2012
+ */
+ private static $phptimezones = array(
+ // -720 min
+ "Dateline Standard Time" => array(
+ "Etc/GMT+12",
+ ),
+
+ // -660 min
+ "Samoa Standard Time" => array(
+ "Etc/GMT+11",
+ "Pacific/Midway",
+ "Pacific/Niue",
+ "Pacific/Pago_Pago",
+ "Pacific/Samoa",
+ "US/Samoa",
+ ),
+
+ // -600 min
+ "Hawaiian Standard Time" => array(
+ "America/Adak",
+ "America/Atka",
+ "Etc/GMT+10",
+ "HST",
+ "Pacific/Honolulu",
+ "Pacific/Johnston",
+ "Pacific/Rarotonga",
+ "Pacific/Tahiti",
+ "US/Aleutian",
+ "US/Hawaii",
+ ),
+
+ // -570 min
+ "-570" => array(
+ "Pacific/Marquesas",
+ ),
+
+ // -540 min
+ "Alaskan Standard Time" => array(
+ "America/Anchorage",
+ "America/Juneau",
+ "America/Nome",
+ "America/Sitka",
+ "America/Yakutat",
+ "Etc/GMT+9",
+ "Pacific/Gambier",
+ "US/Alaska",
+ ),
+
+ // -480 min
+ "Pacific Standard Time" => array(
+ "America/Dawson",
+ "America/Ensenada",
+ "America/Los_Angeles",
+ "America/Metlakatla",
+ "America/Santa_Isabel",
+ "America/Tijuana",
+ "America/Vancouver",
+ "America/Whitehorse",
+ "Canada/Pacific",
+ "Canada/Yukon",
+ "Etc/GMT+8",
+ "Mexico/BajaNorte",
+ "Pacific/Pitcairn",
+ "PST8PDT",
+ "US/Pacific",
+ "US/Pacific-New",
+ ),
+
+ // -420 min
+ "US Mountain Standard Time" => array(
+ "America/Boise",
+ "America/Cambridge_Bay",
+ "America/Chihuahua",
+ "America/Creston",
+ "America/Dawson_Creek",
+ "America/Denver",
+ "America/Edmonton",
+ "America/Hermosillo",
+ "America/Inuvik",
+ "America/Mazatlan",
+ "America/Ojinaga",
+ "America/Phoenix",
+ "America/Shiprock",
+ "America/Yellowknife",
+ "Canada/Mountain",
+ "Etc/GMT+7",
+ "Mexico/BajaSur",
+ "MST",
+ "MST7MDT",
+ "Navajo",
+ "US/Arizona",
+ "US/Mountain",
+ ),
+
+ // -360 min
+ "Central Standard Time" => array(
+ "America/Chicago",
+ "America/Indiana/Knox",
+ "America/Indiana/Tell_City",
+ "America/Knox_IN",
+ "America/North_Dakota/Beulah",
+ "America/North_Dakota/Center",
+ "America/North_Dakota/New_Salem",
+ "America/Rainy_River",
+ "America/Rankin_Inlet",
+ "America/Regina",
+ "America/Resolute",
+ "America/Swift_Current",
+ "America/Tegucigalpa",
+ "America/Winnipeg",
+ "US/Central",
+ "US/Indiana-Starke",
+ "CST6CDT",
+ "Etc/GMT+6",
+ ),
+ "Canada Central Standard Time" => array(
+ "Canada/Central",
+ "Canada/East-Saskatchewan",
+ "Canada/Saskatchewan",
+ ),
+ "Mexico Standard Time" => array(
+ "America/Mexico_City",
+ "America/Monterrey",
+ "Mexico/General",
+ ),
+ "Central America Standard Time" => array(
+ "America/Bahia_Banderas",
+ "America/Belize",
+ "America/Cancun",
+ "America/Costa_Rica",
+ "America/El_Salvador",
+ "America/Guatemala",
+ "America/Managua",
+ "America/Matamoros",
+ "America/Menominee",
+ "America/Merida",
+ "Chile/EasterIsland",
+ "Pacific/Easter",
+ "Pacific/Galapagos",
+ ),
+
+ // -300 min
+ "US Eastern Standard Time" => array(
+ "America/Detroit",
+ "America/Fort_Wayne",
+ "America/Grand_Turk",
+ "America/Indiana/Indianapolis",
+ "America/Indiana/Marengo",
+ "America/Indiana/Petersburg",
+ "America/Indiana/Vevay",
+ "America/Indiana/Vincennes",
+ "America/Indiana/Winamac",
+ "America/Indianapolis",
+ "America/Jamaica",
+ "America/Kentucky/Louisville",
+ "America/Kentucky/Monticello",
+ "America/Louisville",
+ "America/Montreal",
+ "America/New_York",
+ "America/Thunder_Bay",
+ "America/Toronto",
+ "Canada/Eastern",
+ "Cuba",
+ "EST",
+ "EST5EDT",
+ "Etc/GMT+5",
+ "Jamaica",
+ "US/East-Indiana",
+ "US/Eastern",
+ "US/Michigan",
+ ),
+ "SA Pacific Standard Time" => array(
+ "America/Atikokan",
+ "America/Bogota",
+ "America/Cayman",
+ "America/Coral_Harbour",
+ "America/Guayaquil",
+ "America/Havana",
+ "America/Iqaluit",
+ "America/Lima",
+ "America/Nassau",
+ "America/Nipigon",
+ "America/Panama",
+ "America/Pangnirtung",
+ "America/Port-au-Prince",
+ ),
+
+ // -270 min
+ "Venezuela Standard Time" => array(
+ "America/Caracas",
+ ),
+ // -240 min
+ "Atlantic Standard Time" => array(
+ "America/Barbados",
+ "America/Blanc-Sablon",
+ "America/Glace_Bay",
+ "America/Goose_Bay",
+ "America/Halifax",
+ "America/Lower_Princes",
+ "America/St_Barthelemy",
+ "America/St_Kitts",
+ "America/St_Lucia",
+ "America/St_Thomas",
+ "America/St_Vincent",
+ "America/Thule",
+ "America/Tortola",
+ "America/Virgin",
+ "Atlantic/Bermuda",
+ "Canada/Atlantic",
+ "Etc/GMT+4",
+ ),
+ "SA Western Standard Time" => array(
+ "America/Anguilla",
+ "America/Antigua",
+ "America/Aruba",
+ "America/Asuncion",
+ "America/Boa_Vista",
+ "America/Campo_Grande",
+ "America/Cuiaba",
+ "America/Curacao",
+ "America/Dominica",
+ "America/Eirunepe",
+ "America/Grenada",
+ "America/Guadeloupe",
+ "America/Guyana",
+ "America/Kralendijk",
+ "America/La_Paz",
+ "America/Manaus",
+ "America/Marigot",
+ "America/Martinique",
+ "America/Moncton",
+ "America/Montserrat",
+ "America/Port_of_Spain",
+ "America/Porto_Acre",
+ "America/Porto_Velho",
+ "America/Puerto_Rico",
+ "America/Rio_Branco",
+ "Brazil/Acre",
+ "Brazil/West",
+ ),
+ "Pacific SA Standard Time" => array(
+ "America/Santiago",
+ "America/Santo_Domingo",
+ "Antarctica/Palmer",
+ "Chile/Continental",
+ ),
+
+ // -210 min
+ "Newfoundland and Labrador Standard Time" => array(
+ "America/St_Johns",
+ "Canada/Newfoundland",
+ ),
+
+ // -180 min
+ "E South America Standard Time" => array(
+ "America/Araguaina",
+ "America/Bahia",
+ "America/Belem",
+ "America/Fortaleza",
+ "America/Maceio",
+ "America/Recife",
+ "America/Sao_Paulo",
+ "Brazil/East",
+ "Etc/GMT+3",
+ ),
+ "SA Eastern Standard Time" => array(
+ "America/Argentina/Buenos_Aires",
+ "America/Argentina/Catamarca",
+ "America/Argentina/ComodRivadavia",
+ "America/Argentina/Cordoba",
+ "America/Argentina/Jujuy",
+ "America/Argentina/La_Rioja",
+ "America/Argentina/Mendoza",
+ "America/Argentina/Rio_Gallegos",
+ "America/Argentina/Salta",
+ "America/Argentina/San_Juan",
+ "America/Argentina/San_Luis",
+ "America/Argentina/Tucuman",
+ "America/Argentina/Ushuaia",
+ "America/Buenos_Aires",
+ "America/Catamarca",
+ "America/Cayenne",
+ "America/Cordoba",
+ "America/Godthab",
+ "America/Jujuy",
+ "America/Mendoza",
+ "America/Miquelon",
+ "America/Montevideo",
+ "America/Paramaribo",
+ "America/Rosario",
+ "America/Santarem",
+ ),
+ "Greenland Standard Time" => array(
+ "Antarctica/Rothera",
+ "Atlantic/Stanley",
+ ),
+
+ // -120 min
+ "Mid-Atlantic Standard Time" => array(
+ "America/Noronha",
+ "Atlantic/South_Georgia",
+ "Brazil/DeNoronha",
+ "Etc/GMT+2",
+ ),
+
+ // -60 min
+ "Azores Standard Time" => array(
+ "Atlantic/Azores",
+ "Etc/GMT+1",
+ ),
+ "Cape Verde Standard Time" => array(
+ "America/Scoresbysund",
+ "Atlantic/Cape_Verde",
+ ),
+
+ // 0 min
+ "GMT Standard Time" => array(
+ "Eire",
+ "Etc/GMT",
+ "Etc/GMT+0",
+ "Etc/GMT-0",
+ "Etc/GMT0",
+ "Etc/Greenwich",
+ "Etc/UCT",
+ "Etc/Universal",
+ "Etc/UTC",
+ "Etc/Zulu",
+ "Europe/Belfast",
+ "Europe/Dublin",
+ "Europe/Guernsey",
+ "Europe/Isle_of_Man",
+ "Europe/Jersey",
+ "Europe/Lisbon",
+ "Europe/London",
+ "Factory",
+ "GB",
+ "GB-Eire",
+ "GMT",
+ "GMT+0",
+ "GMT-0",
+ "GMT0",
+ "Greenwich",
+ "Iceland",
+ "Portugal",
+ "UCT",
+ "Universal",
+ "UTC",
+ ),
+ "Greenwich Standard Time" => array(
+ "Africa/Abidjan",
+ "Africa/Accra",
+ "Africa/Bamako",
+ "Africa/Banjul",
+ "Africa/Bissau",
+ "Africa/Casablanca",
+ "Africa/Conakry",
+ "Africa/Dakar",
+ "Africa/El_Aaiun",
+ "Africa/Freetown",
+ "Africa/Lome",
+ "Africa/Monrovia",
+ "Africa/Nouakchott",
+ "Africa/Ouagadougou",
+ "Africa/Sao_Tome",
+ "Africa/Timbuktu",
+ "America/Danmarkshavn",
+ "Atlantic/Canary",
+ "Atlantic/Faeroe",
+ "Atlantic/Faroe",
+ "Atlantic/Madeira",
+ "Atlantic/Reykjavik",
+ "Atlantic/St_Helena",
+ "Zulu",
+ ),
+
+ // +60 min
+ "Central Europe Standard Time" => array(
+ "Europe/Belgrade",
+ "Europe/Bratislava",
+ "Europe/Budapest",
+ "Europe/Ljubljana",
+ "Europe/Prague",
+ "Europe/Vaduz",
+ ),
+ "Central European Standard Time" => array(
+ "Europe/Sarajevo",
+ "Europe/Skopje",
+ "Europe/Warsaw",
+ "Europe/Zagreb",
+ "MET",
+ "Poland",
+ ),
+ "Romance Standard Time" => array(
+ "Europe/Andorra",
+ "Europe/Brussels",
+ "Europe/Copenhagen",
+ "Europe/Gibraltar",
+ "Europe/Madrid",
+ "Europe/Malta",
+ "Europe/Monaco",
+ "Europe/Paris",
+ "Europe/Podgorica",
+ "Europe/San_Marino",
+ "Europe/Tirane",
+ ),
+ "W Europe Standard Time" => array(
+ "Europe/Amsterdam",
+ "Europe/Berlin",
+ "Europe/Luxembourg",
+ "Europe/Vatican",
+ "Europe/Rome",
+ "Europe/Stockholm",
+ "Arctic/Longyearbyen",
+ "Europe/Vienna",
+ "Europe/Zurich",
+ "Europe/Oslo",
+ "WET",
+ "CET",
+ "Etc/GMT-1",
+ ),
+ "W Central Africa Standard Time" => array(
+ "Africa/Algiers",
+ "Africa/Bangui",
+ "Africa/Brazzaville",
+ "Africa/Ceuta",
+ "Africa/Douala",
+ "Africa/Kinshasa",
+ "Africa/Lagos",
+ "Africa/Libreville",
+ "Africa/Luanda",
+ "Africa/Malabo",
+ "Africa/Ndjamena",
+ "Africa/Niamey",
+ "Africa/Porto-Novo",
+ "Africa/Tunis",
+ "Africa/Windhoek",
+ "Atlantic/Jan_Mayen",
+ ),
+
+ // +120 min
+ "E Europe Standard Time" => array(
+ "Europe/Bucharest",
+ "EET",
+ "Etc/GMT-2",
+ "Europe/Chisinau",
+ "Europe/Mariehamn",
+ "Europe/Nicosia",
+ "Europe/Simferopol",
+ "Europe/Tiraspol",
+ "Europe/Uzhgorod",
+ "Europe/Zaporozhye",
+ ),
+ "Egypt Standard Time" => array(
+ "Africa/Cairo",
+ "Africa/Tripoli",
+ "Egypt",
+ "Libya",
+ ),
+ "FLE Standard Time" => array(
+ "Europe/Helsinki",
+ "Europe/Kiev",
+ "Europe/Riga",
+ "Europe/Sofia",
+ "Europe/Tallinn",
+ "Europe/Vilnius",
+ ),
+ "GTB Standard Time" => array(
+ "Asia/Istanbul",
+ "Europe/Athens",
+ "Europe/Istanbul",
+ "Turkey",
+ ),
+ "Israel Standard Time" => array(
+ "Asia/Amman",
+ "Asia/Beirut",
+ "Asia/Damascus",
+ "Asia/Gaza",
+ "Asia/Hebron",
+ "Asia/Nicosia",
+ "Asia/Tel_Aviv",
+ "Asia/Jerusalem",
+ "Israel",
+ ),
+ "South Africa Standard Time" => array(
+ "Africa/Blantyre",
+ "Africa/Bujumbura",
+ "Africa/Gaborone",
+ "Africa/Harare",
+ "Africa/Johannesburg",
+ "Africa/Kigali",
+ "Africa/Lubumbashi",
+ "Africa/Lusaka",
+ "Africa/Maputo",
+ "Africa/Maseru",
+ "Africa/Mbabane",
+ ),
+
+ // +180 min
+ "Russian Standard Time" => array(
+ "Antarctica/Syowa",
+ "Europe/Kaliningrad",
+ "Europe/Minsk",
+ "Etc/GMT-3",
+ ),
+ "Arab Standard Time" => array(
+ "Asia/Qatar",
+ "Asia/Kuwait",
+ "Asia/Riyadh",
+ ),
+ "E Africa Standard Time" => array(
+ "Africa/Addis_Ababa",
+ "Africa/Asmara",
+ "Africa/Asmera",
+ "Africa/Dar_es_Salaam",
+ "Africa/Djibouti",
+ "Africa/Juba",
+ "Africa/Kampala",
+ "Africa/Khartoum",
+ "Africa/Mogadishu",
+ "Africa/Nairobi",
+ ),
+ "Arabic Standard Time" => array(
+ "Asia/Aden",
+ "Asia/Baghdad",
+ "Asia/Bahrain",
+ "Indian/Antananarivo",
+ "Indian/Comoro",
+ "Indian/Mayotte",
+ ),
+
+ // +210 min
+ "Iran Standard Time" => array(
+ "Asia/Tehran",
+ "Iran",
+ ),
+
+ // +240 min
+ "Arabian Standard Time" => array(
+ "Asia/Dubai",
+ "Asia/Muscat",
+ "Indian/Mahe",
+ "Indian/Mauritius",
+ "Indian/Reunion",
+ ),
+ "Caucasus Standard Time" => array(
+ "Asia/Baku",
+ "Asia/Tbilisi",
+ "Asia/Yerevan",
+ "Etc/GMT-4",
+ "Europe/Moscow",
+ "Europe/Samara",
+ "Europe/Volgograd",
+ "W-SU",
+ ),
+
+ // +270 min
+ "Transitional Islamic State of Afghanistan Standard Time" => array(
+ "Asia/Kabul",
+ ),
+
+ // +300 min
+ "Ekaterinburg Standard Time" => array(
+ "Antarctica/Mawson",
+ ),
+ "West Asia Standard Time" => array(
+ "Asia/Aqtau",
+ "Asia/Aqtobe",
+ "Asia/Ashgabat",
+ "Asia/Ashkhabad",
+ "Asia/Dushanbe",
+ "Asia/Karachi",
+ "Asia/Oral",
+ "Asia/Samarkand",
+ "Asia/Tashkent",
+ "Etc/GMT-5",
+ "Indian/Kerguelen",
+ "Indian/Maldives",
+ ),
+
+ // +330 min
+ "India Standard Time" => array(
+ "Asia/Calcutta",
+ "Asia/Colombo",
+ "Asia/Kolkata",
+ ),
+
+ // +345 min
+ "Nepal Standard Time" => array(
+ "Asia/Kathmandu",
+ "Asia/Katmandu",
+ ),
+
+ // +360 min
+ "Central Asia Standard Time" => array(
+ "Asia/Dacca",
+ "Asia/Dhaka",
+ ),
+ "Sri Lanka Standard Time" => array(
+ "Indian/Chagos",
+ ),
+ "N Central Asia Standard Time" => array(
+ "Antarctica/Vostok",
+ "Asia/Almaty",
+ "Asia/Bishkek",
+ "Asia/Qyzylorda",
+ "Asia/Thimbu",
+ "Asia/Thimphu",
+ "Asia/Yekaterinburg",
+ "Etc/GMT-6",
+ ),
+
+ // +390 min
+ "Myanmar Standard Time" => array(
+ "Asia/Rangoon",
+ "Indian/Cocos",
+ ),
+
+ // +420 min
+ "SE Asia Standard Time" => array(
+ "Asia/Bangkok",
+ "Asia/Ho_Chi_Minh",
+ "Asia/Hovd",
+ "Asia/Jakarta",
+ "Asia/Phnom_Penh",
+ "Asia/Saigon",
+ "Indian/Christmas",
+ ),
+ "North Asia Standard Time" => array(
+ "Antarctica/Davis",
+ "Asia/Novokuznetsk",
+ "Asia/Novosibirsk",
+ "Asia/Omsk",
+ "Asia/Pontianak",
+ "Asia/Vientiane",
+ "Etc/GMT-7",
+ ),
+
+ // +480 min
+ "China Standard Time" => array(
+ "Asia/Brunei",
+ "Asia/Choibalsan",
+ "Asia/Chongqing",
+ "Asia/Chungking",
+ "Asia/Harbin",
+ "Asia/Hong_Kong",
+ "Asia/Shanghai",
+ "Asia/Ujung_Pandang",
+ "Asia/Urumqi",
+ "Hongkong",
+ "PRC",
+ "ROC",
+ ),
+ "Singapore Standard Time" => array(
+ "Singapore",
+ "Asia/Singapore",
+ "Asia/Kuala_Lumpur",
+ ),
+ "Taipei Standard Time" => array(
+ "Asia/Taipei",
+ ),
+ "W Australia Standard Time" => array(
+ "Australia/Perth",
+ "Australia/West",
+ ),
+ "North Asia East Standard Time" => array(
+ "Antarctica/Casey",
+ "Asia/Kashgar",
+ "Asia/Krasnoyarsk",
+ "Asia/Kuching",
+ "Asia/Macao",
+ "Asia/Macau",
+ "Asia/Makassar",
+ "Asia/Manila",
+ "Etc/GMT-8",
+ "Asia/Ulaanbaatar",
+ "Asia/Ulan_Bator",
+ ),
+
+ // +525 min
+ "525" => array(
+ "Australia/Eucla",
+ ),
+
+ // +540 min
+ "Korea Standard Time" => array(
+ "Asia/Seoul",
+ "Asia/Pyongyang",
+ "ROK",
+ ),
+ "Tokyo Standard Time" => array(
+ "Asia/Tokyo",
+ "Japan",
+ "Etc/GMT-9",
+ ),
+ "Yakutsk Standard Time" => array(
+ "Asia/Dili",
+ "Asia/Irkutsk",
+ "Asia/Jayapura",
+ "Pacific/Palau",
+ ),
+
+ // +570 min
+ "AUS Central Standard Time" => array(
+ "Australia/Darwin",
+ "Australia/North",
+ ),
+ // DST
+ "Cen Australia Standard Time" => array(
+ "Australia/Adelaide",
+ "Australia/Broken_Hill",
+ "Australia/South",
+ "Australia/Yancowinna",
+ ),
+
+ // +600 min
+ "AUS Eastern Standard Time" => array(
+ "Australia/Canberra",
+ "Australia/Melbourne",
+ "Australia/Sydney",
+ "Australia/Currie",
+ "Australia/ACT",
+ "Australia/NSW",
+ "Australia/Victoria",
+ ),
+ "E Australia Standard Time" => array(
+ "Etc/GMT-10",
+ "Australia/Brisbane",
+ "Australia/Queensland",
+ "Australia/Lindeman",
+ ),
+ "Tasmania Standard Time" => array(
+ "Australia/Hobart",
+ "Australia/Tasmania",
+ ),
+ "Vladivostok Standard Time" => array(
+ "Antarctica/DumontDUrville",
+ ),
+ "West Pacific Standard Time" => array(
+ "Asia/Yakutsk",
+ "Pacific/Chuuk",
+ "Pacific/Guam",
+ "Pacific/Port_Moresby",
+ "Pacific/Saipan",
+ "Pacific/Truk",
+ "Pacific/Yap",
+ ),
+
+ // +630 min
+ "630" => array(
+ "Australia/LHI",
+ "Australia/Lord_Howe",
+ ),
+
+ // +660 min
+ "Central Pacific Standard Time" => array(
+ "Antarctica/Macquarie",
+ "Asia/Sakhalin",
+ "Asia/Vladivostok",
+ "Etc/GMT-11",
+ "Pacific/Efate",
+ "Pacific/Guadalcanal",
+ "Pacific/Kosrae",
+ "Pacific/Noumea",
+ "Pacific/Pohnpei",
+ "Pacific/Ponape",
+ ),
+
+ // 690 min
+ "690" => array(
+ "Pacific/Norfolk",
+ ),
+
+ // +720 min
+ "Fiji Islands Standard Time" => array(
+ "Asia/Anadyr",
+ "Asia/Kamchatka",
+ "Asia/Magadan",
+ "Kwajalein",
+ ),
+ "New Zealand Standard Time" => array(
+ "Antarctica/McMurdo",
+ "Antarctica/South_Pole",
+ "Etc/GMT-12",
+ "NZ",
+ "Pacific/Auckland",
+ "Pacific/Fiji",
+ "Pacific/Funafuti",
+ "Pacific/Kwajalein",
+ "Pacific/Majuro",
+ "Pacific/Nauru",
+ "Pacific/Tarawa",
+ "Pacific/Wake",
+ "Pacific/Wallis",
+ ),
+
+ // +765 min
+ "765" => array(
+ "NZ-CHAT",
+ "Pacific/Chatham",
+ ),
+
+ // +780 min
+ "Tonga Standard Time" => array(
+ "Etc/GMT-13",
+ "Pacific/Apia",
+ "Pacific/Enderbury",
+ "Pacific/Tongatapu",
+ ),
+
+ // +840 min
+ "840" => array(
+ "Etc/GMT-14",
+ "Pacific/Fakaofo",
+ "Pacific/Kiritimati",
+ ),
+ );
+
+ /**
+ * Returns a full timezone array
+ *
+ * @param string $phptimezone (opt) a php timezone string.
+ * If omitted the env. default timezone is used.
+ *
+ * @access public
+ * @return array
+ */
+ static public function GetFullTZ($phptimezone = false) {
+ if ($phptimezone === false)
+ $phptimezone = date_default_timezone_get();
+
+ ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::GetFullTZ() for ". $phptimezone);
+
+ $servertzname = self::guessTZNameFromPHPName($phptimezone);
+ $offset = self::$tzonesoffsets[$servertzname];
+
+ $tz = array(
+ "bias" => $offset[0],
+ "tzname" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)),
+ "dstendyear" => $offset[3],
+ "dstendmonth" => $offset[4],
+ "dstendday" => $offset[6],
+ "dstendweek" => $offset[5],
+ "dstendhour" => $offset[7],
+ "dstendminute" => $offset[8],
+ "dstendsecond" => $offset[9],
+ "dstendmillis" => $offset[10],
+ "stdbias" => $offset[1],
+ "tznamedst" => self::encodeTZName(self::getMSTZnameFromTZName($servertzname)),
+ "dststartyear" => $offset[11],
+ "dststartmonth" => $offset[12],
+ "dststartday" => $offset[14],
+ "dststartweek" => $offset[13],
+ "dststarthour" => $offset[15],
+ "dststartminute" => $offset[16],
+ "dststartsecond" => $offset[17],
+ "dststartmillis" => $offset[18],
+ "dstbias" => $offset[2]
+ );
+
+ return $tz;
+ }
+
+ /**
+ * Sets the timezone name by matching data from the offset (bias etc)
+ *
+ * @param array $offset a z-push timezone array
+ *
+ * @access public
+ * @return array
+ */
+ static public function FillTZNames($tz) {
+ ZLog::Write(LOGLEVEL_DEBUG, "TimezoneUtil::FillTZNames() filling up bias ". $tz["bias"]);
+ if (!isset($tz["bias"]))
+ ZLog::Write(LOGLEVEL_WARN, "TimezoneUtil::FillTZNames() submitted TZ array does not have a bias");
+ else {
+ $tzname = self::guessTZNameFromOffset($tz);
+ $tz['tzname'] = $tz['tznamedst'] = self::encodeTZName(self::getMSTZnameFromTZName($tzname));
+ }
+ return $tz;
+ }
+
+ /**
+ * Tries to find a timezone using the Bias and other offset parameters
+ *
+ * @param array $offset a z-push timezone array
+ *
+ * @access public
+ * @return string
+ */
+ static private function guessTZNameFromOffset($offset) {
+ // try to find a quite exact match
+ foreach (self::$tzonesoffsets as $tzname => $tzoffset) {
+ if ($offset["bias"] == $tzoffset[0] &&
+ isset($offset["dstendmonth"]) && $offset["dstendmonth"] == $tzoffset[4] &&
+ isset($offset["dstendday"]) && $offset["dstendday"] == $tzoffset[6] &&
+ isset($offset["dststartmonth"]) && $offset["dststartmonth"] == $tzoffset[12] &&
+ isset($offset["dststartday"]) && $offset["dststartday"] == $tzoffset[14])
+ return $tzname;
+ }
+
+ // try to find a bias match
+ foreach (self::$tzonesoffsets as $tzname => $tzoffset) {
+ if ($offset["bias"] == $tzoffset[0])
+ return $tzname;
+ }
+
+ // nothing found? return gmt
+ ZLog::Write(LOGLEVEL_WARN, "TimezoneUtil::guessTZNameFromOffset() no timezone found for the data submitted. Returning 'GMT Standard Time'.");
+ return "GMT Standard Time";
+ }
+
+ /**
+ * Tries to find a AS timezone for a php timezone
+ *
+ * @param string $phpname a php timezone name
+ *
+ * @access public
+ * @return string
+ */
+ static private function guessTZNameFromPHPName($phpname) {
+ foreach (self::$phptimezones as $tzn => $phptzs) {
+ if (in_array($phpname, $phptzs)) {
+ $tzname = $tzn;
+ break;
+ }
+ }
+
+ if (!isset($tzname) || is_int($tzname)) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("TimezoneUtil::guessTZNameFromPHPName() no compatible timezone found for '%s'. Returning 'GMT Standard Time'. Please contact the Z-Push dev team.", $phpname));
+ return self::$mstzones["085"][0];
+ }
+
+ return $tzname;
+ }
+
+ /**
+ * Returns an AS compatible tz name
+ *
+ * @param string $name internal timezone name
+ *
+ * @access public
+ * @return string
+ */
+ static private function getMSTZnameFromTZName($name) {
+ foreach (self::$mstzones as $mskey => $msdefs) {
+ if ($name == $msdefs[0])
+ return $msdefs[1];
+ }
+
+ ZLog::Write(LOGLEVEL_WARN, sprintf("TimezoneUtil::getMSTZnameFromTZName() no MS name found for '%s'. Returning '(GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London'", $name));
+ return self::$mstzones["085"][1];
+ }
+
+ /**
+ * Encodes the tz name to UTF-16 compatible with a syncblob
+ *
+ * @param string $name timezone name
+ *
+ * @access public
+ * @return string
+ */
+ static private function encodeTZName($name) {
+ return substr(iconv('UTF-8', 'UTF-16', $name),2,-1);
+ }
+
+ /**
+ * Test to check if $mstzones and $tzonesoffsets can be resolved
+ * in both directions.
+ *
+ * @access public
+ * @return
+ */
+ static public function TZtest() {
+ foreach (self::$mstzones as $mskey => $msdefs) {
+ if (!array_key_exists($msdefs[0], self::$tzonesoffsets))
+ echo "key '". $msdefs[0]. "' not found in tzonesoffsets\n";
+ }
+
+ foreach (self::$tzonesoffsets as $tzname => $offset) {
+ $found = false;
+ foreach (self::$mstzones as $mskey => $msdefs) {
+ if ($tzname == $msdefs[0]) {
+ $found = true;
+ break;
+ }
+ }
+ if (!$found)
+ echo "key '$tzname' NOT FOUND\n";
+ }
+ }
+
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/httpreturncodeexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/httpreturncodeexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/httpreturncodeexception.php (revision 7589)
@@ -0,0 +1,56 @@
+.
+*
+* 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);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/authenticationrequiredexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/authenticationrequiredexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/authenticationrequiredexception.php (revision 7589)
@@ -0,0 +1,52 @@
+.
+*
+* 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;
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/statusexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/statusexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/statusexception.php (revision 7589)
@@ -0,0 +1,48 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class StatusException extends ZPushException {
+ protected $defaultLogLevel = LOGLEVEL_INFO;
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/notimplementedexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/notimplementedexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/notimplementedexception.php (revision 7589)
@@ -0,0 +1,49 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class NotImplementedException extends ZPushException {
+ protected $defaultLogLevel = LOGLEVEL_ERROR;
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/statenotyetavailableexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/statenotyetavailableexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/statenotyetavailableexception.php (revision 7589)
@@ -0,0 +1,46 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class StateNotYetAvailableException extends StatusException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/fatalexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/fatalexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/fatalexception.php (revision 7589)
@@ -0,0 +1,47 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class FatalException extends ZPushException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/provisioningrequiredexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/provisioningrequiredexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/provisioningrequiredexception.php (revision 7589)
@@ -0,0 +1,51 @@
+.
+*
+* 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";
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/stateinvalidexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/stateinvalidexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/stateinvalidexception.php (revision 7589)
@@ -0,0 +1,46 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class StateInvalidException extends StatusException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/fatalmisconfigurationexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/fatalmisconfigurationexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/fatalmisconfigurationexception.php (revision 7589)
@@ -0,0 +1,46 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class FatalMisconfigurationException extends FatalException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/wbxmlexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/wbxmlexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/wbxmlexception.php (revision 7589)
@@ -0,0 +1,46 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class WBXMLException extends FatalNotImplementedException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/nopostrequestexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/nopostrequestexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/nopostrequestexception.php (revision 7589)
@@ -0,0 +1,51 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class NoPostRequestException extends FatalException {
+ const OPTIONS_REQUEST = 1;
+ const GET_REQUEST = 2;
+ protected $defaultLogLevel = LOGLEVEL_DEBUG;
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/fatalnotimplementedexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/fatalnotimplementedexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/fatalnotimplementedexception.php (revision 7589)
@@ -0,0 +1,47 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class FatalNotImplementedException extends FatalException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/statenotfoundexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/statenotfoundexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/statenotfoundexception.php (revision 7589)
@@ -0,0 +1,47 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class StateNotFoundException extends StatusException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/nohierarchycacheavailableexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/nohierarchycacheavailableexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/nohierarchycacheavailableexception.php (revision 7589)
@@ -0,0 +1,46 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class NoHierarchyCacheAvailableException extends StateNotFoundException {}
+
+?>
Index: /trunk/zpush/lib/exceptions/exceptions.php
===================================================================
--- /trunk/zpush/lib/exceptions/exceptions.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/exceptions.php (revision 7589)
@@ -0,0 +1,66 @@
+.
+*
+* 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');
+
+?>
Index: /trunk/zpush/lib/exceptions/syncobjectbrokenexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/syncobjectbrokenexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/syncobjectbrokenexception.php (revision 7589)
@@ -0,0 +1,73 @@
+.
+*
+* 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;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/exceptions/zpushexception.php
===================================================================
--- /trunk/zpush/lib/exceptions/zpushexception.php (revision 7589)
+++ /trunk/zpush/lib/exceptions/zpushexception.php (revision 7589)
@@ -0,0 +1,74 @@
+.
+*
+* 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;
+ }
+}
+?>
Index: /trunk/zpush/lib/default/diffbackend/diffstate.php
===================================================================
--- /trunk/zpush/lib/default/diffbackend/diffstate.php (revision 7589)
+++ /trunk/zpush/lib/default/diffbackend/diffstate.php (revision 7589)
@@ -0,0 +1,280 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class DiffState implements IChanges {
+ protected $syncstate;
+ protected $backend;
+ protected $flags;
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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)
+ }
+ }
+
+}
+
+?>
Index: /trunk/zpush/lib/default/diffbackend/importchangesdiff.php
===================================================================
--- /trunk/zpush/lib/default/diffbackend/importchangesdiff.php (revision 7589)
+++ /trunk/zpush/lib/default/diffbackend/importchangesdiff.php (revision 7589)
@@ -0,0 +1,275 @@
+.
+*
+* 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);
+
+ 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);
+ 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);
+ 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);
+ }
+
+
+ /**
+ * 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;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/default/diffbackend/diffbackend.php
===================================================================
--- /trunk/zpush/lib/default/diffbackend/diffbackend.php (revision 7589)
+++ /trunk/zpush/lib/default/diffbackend/diffbackend.php (revision 7589)
@@ -0,0 +1,369 @@
+.
+*
+* 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;
+
+ 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
+ *
+ * @access public
+ * @return array same return value as StatMessage()
+ * @throws StatusException could throw specific SYNC_STATUS_* exceptions
+ */
+ public abstract function ChangeMessage($folderid, $id, $message);
+
+ /**
+ * 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
+ *
+ * @access public
+ * @return boolean status of the operation
+ * @throws StatusException could throw specific SYNC_STATUS_* exceptions
+ */
+ public abstract function SetReadFlag($folderid, $id, $flags);
+
+ /**
+ * 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
+ *
+ * @access public
+ * @return boolean status of the operation
+ * @throws StatusException could throw specific SYNC_STATUS_* exceptions
+ */
+ public abstract function DeleteMessage($folderid, $id);
+
+ /**
+ * 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
+ *
+ * @access public
+ * @return boolean status of the operation
+ * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions
+ */
+ public abstract function MoveMessage($folderid, $id, $newfolderid);
+
+}
+?>
Index: /trunk/zpush/lib/default/diffbackend/exportchangesdiff.php
===================================================================
--- /trunk/zpush/lib/default/diffbackend/exportchangesdiff.php (revision 7589)
+++ /trunk/zpush/lib/default/diffbackend/exportchangesdiff.php (revision 7589)
@@ -0,0 +1,234 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class ExportChangesDiff extends DiffState implements IExportChanges{
+ private $importer;
+ private $folderid;
+ private $contentparameters;
+ private $cutoffdate;
+ 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;
+ }
+
+ /**
+ * 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());
+ }
+
+ /**
+ * 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;
+ }
+ }
+ }
+}
+
+?>
Index: /trunk/zpush/lib/default/simplemutex.php
===================================================================
--- /trunk/zpush/lib/default/simplemutex.php (revision 7589)
+++ /trunk/zpush/lib/default/simplemutex.php (revision 7589)
@@ -0,0 +1,89 @@
+.
+*
+* 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;
+ }
+}
+?>
Index: /trunk/zpush/lib/default/searchprovider.php
===================================================================
--- /trunk/zpush/lib/default/searchprovider.php (revision 7589)
+++ /trunk/zpush/lib/default/searchprovider.php (revision 7589)
@@ -0,0 +1,125 @@
+.
+*
+* 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;
+ }
+}
+?>
Index: /trunk/zpush/lib/default/filestatemachine.php
===================================================================
--- /trunk/zpush/lib/default/filestatemachine.php (revision 7589)
+++ /trunk/zpush/lib/default/filestatemachine.php (revision 7589)
@@ -0,0 +1,390 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class FileStateMachine implements IStateMachine {
+ private $userfilename;
+
+ /**
+ * 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';
+
+ if (!touch($this->userfilename))
+ throw new FatalMisconfigurationException("Not possible to write to the configured state directory.");
+ }
+
+ /**
+ * 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 array
+ */
+ public function LinkUserDevice($username, $devid) {
+ include_once("simplemutex.php");
+ $mutex = new SimpleMutex();
+
+ // exclusive block
+ if ($mutex->Block()) {
+ $filecontents = @file_get_contents($this->userfilename);
+
+ if ($filecontents)
+ $users = unserialize($filecontents);
+ else
+ $users = array();
+
+ $changed = false;
+
+ // 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();
+ }
+ }
+
+ /**
+ * Unlinks a device from a user
+ *
+ * @param string $username
+ * @param string $devid
+ *
+ * @access public
+ * @return array
+ */
+ public function UnLinkUserDevice($username, $devid) {
+ include_once("simplemutex.php");
+ $mutex = new SimpleMutex();
+
+ // exclusive block
+ if ($mutex->Block()) {
+ $filecontents = @file_get_contents($this->userfilename);
+
+ if ($filecontents)
+ $users = unserialize($filecontents);
+ else
+ $users = array();
+
+ $changed = false;
+
+ // 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();
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+
+ /**----------------------------------------------------------------------------------------------------------
+ * 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;
+ }
+
+}
+?>
Index: /trunk/zpush/lib/default/backend.php
===================================================================
--- /trunk/zpush/lib/default/backend.php (revision 7589)
+++ /trunk/zpush/lib/default/backend.php (revision 7589)
@@ -0,0 +1,279 @@
+.
+*
+* 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;
+ }
+
+
+ /**----------------------------------------------------------------------------------------------------------
+ * 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) { }
+ }
+ }
+
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncsendmail.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncsendmail.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncsendmail.php (revision 7589)
@@ -0,0 +1,86 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncSendMail extends SyncObject {
+ public $clientid;
+ public $saveinsent;
+ public $replacemime;
+ public $accountid;
+ public $source;
+ public $mime;
+ public $replyflag;
+ public $forwardflag;
+
+ function SyncSendMail() {
+ $mapping = array (
+ SYNC_COMPOSEMAIL_CLIENTID => array ( self::STREAMER_VAR => "clientid"),
+
+ SYNC_COMPOSEMAIL_SAVEINSENTITEMS => array ( self::STREAMER_VAR => "saveinsent",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY),
+
+ SYNC_COMPOSEMAIL_REPLACEMIME => array ( self::STREAMER_VAR => "replacemime",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY),
+
+ SYNC_COMPOSEMAIL_ACCOUNTID => array ( self::STREAMER_VAR => "accountid"),
+
+ SYNC_COMPOSEMAIL_SOURCE => array ( self::STREAMER_VAR => "source",
+ self::STREAMER_TYPE => "SyncSendMailSource"),
+
+ SYNC_COMPOSEMAIL_MIME => array ( self::STREAMER_VAR => "mime"),
+
+ SYNC_COMPOSEMAIL_REPLYFLAG => array ( self::STREAMER_VAR => "replyflag",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE),
+
+ SYNC_COMPOSEMAIL_FORWARDFLAG => array ( self::STREAMER_VAR => "forwardflag",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncrecurrence.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncrecurrence.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncrecurrence.php (revision 7589)
@@ -0,0 +1,120 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncRecurrence extends SyncObject {
+ public $type;
+ public $until;
+ public $occurrences;
+ public $interval;
+ public $dayofweek;
+ public $dayofmonth;
+ public $weekofmonth;
+ public $monthofyear;
+
+ function SyncRecurrence() {
+ $mapping = array (
+ // Recurrence type
+ // 0 = Recurs daily
+ // 1 = Recurs weekly
+ // 2 = Recurs monthly
+ // 3 = Recurs monthly on the nth day
+ // 5 = Recurs yearly
+ // 6 = Recurs yearly on the nth day
+ SYNC_POOMCAL_TYPE => array ( self::STREAMER_VAR => "type",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,5,6) )),
+
+ SYNC_POOMCAL_UNTIL => array ( self::STREAMER_VAR => "until",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE),
+
+ SYNC_POOMCAL_OCCURRENCES => array ( self::STREAMER_VAR => "occurrences",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ SYNC_POOMCAL_INTERVAL => array ( self::STREAMER_VAR => "interval",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ // DayOfWeek values
+ // 1 = Sunday
+ // 2 = Monday
+ // 4 = Tuesday
+ // 8 = Wednesday
+ // 16 = Thursday
+ // 32 = Friday
+ // 62 = Weekdays // not in spec: daily weekday recurrence
+ // 64 = Saturday
+ // 127 = The last day of the month. Value valid only in monthly or yearly recurrences.
+ // As this is a bitmask, actually all values 0 > x < 128 are allowed
+ SYNC_POOMCAL_DAYOFWEEK => array ( self::STREAMER_VAR => "dayofweek",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 128 )),
+
+ // DayOfMonth values
+ // 1-31 representing the day
+ SYNC_POOMCAL_DAYOFMONTH => array ( self::STREAMER_VAR => "dayofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 32 )),
+
+ // WeekOfMonth
+ // 1-4 = Y st/nd/rd/th week of month
+ // 5 = last week of month
+ SYNC_POOMCAL_WEEKOFMONTH => array ( self::STREAMER_VAR => "weekofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5) )),
+
+ // MonthOfYear
+ // 1-12 representing the month
+ SYNC_POOMCAL_MONTHOFYEAR => array ( self::STREAMER_VAR => "monthofyear",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncappointment.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncappointment.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncappointment.php (revision 7589)
@@ -0,0 +1,222 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncAppointment extends SyncObject {
+ public $timezone;
+ public $dtstamp;
+ public $starttime;
+ public $subject;
+ public $uid;
+ public $organizername;
+ public $organizeremail;
+ public $location;
+ public $endtime;
+ public $recurrence;
+ public $sensitivity;
+ public $busystatus;
+ public $alldayevent;
+ public $reminder;
+ public $rtf;
+ public $meetingstatus;
+ public $attendees;
+ public $body;
+ public $bodytruncated;
+ public $exception;
+ public $deleted;
+ public $exceptionstarttime;
+ public $categories;
+
+ // AS 12.0 props
+ public $asbody;
+ public $nativebodytype;
+
+ // AS 14.0 props
+ public $disallownewtimeprop;
+ public $responsetype;
+ public $responserequested;
+
+
+ function SyncAppointment() {
+ $mapping = array(
+ SYNC_POOMCAL_TIMEZONE => array ( self::STREAMER_VAR => "timezone"),
+
+ SYNC_POOMCAL_DTSTAMP => array ( self::STREAMER_VAR => "dtstamp",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMCAL_STARTTIME => array ( self::STREAMER_VAR => "starttime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_CMPLOWER => SYNC_POOMCAL_ENDTIME ) ),
+
+
+ SYNC_POOMCAL_SUBJECT => array ( self::STREAMER_VAR => "subject",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)),
+
+ SYNC_POOMCAL_UID => array ( self::STREAMER_VAR => "uid"),
+ SYNC_POOMCAL_ORGANIZERNAME => array ( self::STREAMER_VAR => "organizername"), // verified below
+ SYNC_POOMCAL_ORGANIZEREMAIL => array ( self::STREAMER_VAR => "organizeremail"), // verified below
+ SYNC_POOMCAL_LOCATION => array ( self::STREAMER_VAR => "location"),
+ SYNC_POOMCAL_ENDTIME => array ( self::STREAMER_VAR => "endtime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETONE,
+ self::STREAMER_CHECK_CMPHIGHER => SYNC_POOMCAL_STARTTIME ) ),
+
+ SYNC_POOMCAL_RECURRENCE => array ( self::STREAMER_VAR => "recurrence",
+ self::STREAMER_TYPE => "SyncRecurrence"),
+
+ // Sensitivity values
+ // 0 = Normal
+ // 1 = Personal
+ // 2 = Private
+ // 3 = Confident
+ SYNC_POOMCAL_SENSITIVITY => array ( self::STREAMER_VAR => "sensitivity",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ // Busystatus values
+ // 0 = Free
+ // 1 = Tentative
+ // 2 = Busy
+ // 3 = Out of office
+ SYNC_POOMCAL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ SYNC_POOMCAL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMCAL_REMINDER => array ( self::STREAMER_VAR => "reminder",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1)),
+
+ SYNC_POOMCAL_RTF => array ( self::STREAMER_VAR => "rtf"),
+
+ // Meetingstatus values
+ // 0 = is not a meeting
+ // 1 = is a meeting
+ // 3 = Meeting received
+ // 5 = Meeting is canceled
+ // 7 = Meeting is canceled and received
+ // 9 = as 1
+ // 11 = as 3
+ // 13 = as 5
+ // 15 = as 7
+ SYNC_POOMCAL_MEETINGSTATUS => array ( self::STREAMER_VAR => "meetingstatus",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,3,5,7,9,11,13,15) )),
+
+ SYNC_POOMCAL_ATTENDEES => array ( self::STREAMER_VAR => "attendees",
+ self::STREAMER_TYPE => "SyncAttendee",
+ self::STREAMER_ARRAY => SYNC_POOMCAL_ATTENDEE),
+
+ SYNC_POOMCAL_BODY => array ( self::STREAMER_VAR => "body"),
+ SYNC_POOMCAL_BODYTRUNCATED => array ( self::STREAMER_VAR => "bodytruncated"),
+ SYNC_POOMCAL_EXCEPTIONS => array ( self::STREAMER_VAR => "exceptions",
+ self::STREAMER_TYPE => "SyncAppointmentException",
+ self::STREAMER_ARRAY => SYNC_POOMCAL_EXCEPTION),
+
+ SYNC_POOMCAL_CATEGORIES => array ( self::STREAMER_VAR => "categories",
+ self::STREAMER_ARRAY => SYNC_POOMCAL_CATEGORY),
+ );
+
+ if (Request::GetProtocolVersion() >= 12.0) {
+ $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody",
+ self::STREAMER_TYPE => "SyncBaseBody");
+
+ $mapping[SYNC_AIRSYNCBASE_NATIVEBODYTYPE] = array ( self::STREAMER_VAR => "nativebodytype");
+
+ //unset these properties because airsyncbase body and attachments will be used instead
+ unset($mapping[SYNC_POOMCAL_BODY], $mapping[SYNC_POOMCAL_BODYTRUNCATED]);
+ }
+
+ if(Request::GetProtocolVersion() >= 14.0) {
+ $mapping[SYNC_POOMCAL_DISALLOWNEWTIMEPROPOSAL] = array ( self::STREAMER_VAR => "disallownewtimeprop");
+ $mapping[SYNC_POOMCAL_RESPONSEREQUESTED] = array ( self::STREAMER_VAR => "responserequested");
+ $mapping[SYNC_POOMCAL_RESPONSETYPE] = array ( self::STREAMER_VAR => "responsetype");
+ }
+
+ parent::SyncObject($mapping);
+ }
+
+ /**
+ * Method checks if the object has the minimum of required parameters
+ * and fullfills semantic dependencies
+ *
+ * This overloads the general check() with special checks to be executed
+ * Checks if SYNC_POOMCAL_ORGANIZERNAME and SYNC_POOMCAL_ORGANIZEREMAIL are correctly set
+ *
+ * @param boolean $logAsDebug (opt) default is false, so messages are logged in WARN log level
+ *
+ * @access public
+ * @return boolean
+ */
+ public function Check($logAsDebug = false) {
+ $ret = parent::Check($logAsDebug);
+
+ // semantic checks general "turn off switch"
+ if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false)
+ return $ret;
+
+ if (!$ret)
+ return false;
+
+ if ($this->meetingstatus > 0) {
+ if (!isset($this->organizername) || !isset($this->organizeremail)) {
+ ZLog::Write(LOGLEVEL_WARN, "SyncAppointment->Check(): Parameter 'organizername' and 'organizeremail' should be set for a meeting request");
+ }
+ }
+
+ // do not sync a recurrent appointment without a timezone
+ if (isset($this->recurrence) && !isset($this->timezone)) {
+ ZLog::Write(LOGLEVEL_ERROR, "SyncAppointment->Check(): timezone for a recurring appointment is not set.");
+ return false;
+ }
+
+ return true;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncmailflags.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncmailflags.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncmailflags.php (revision 7589)
@@ -0,0 +1,99 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncMailFlags extends SyncObject {
+ public $subject;
+ public $flagstatus;
+ public $flagtype; //Possible types are clear, complete, active
+ public $datecompleted;
+ public $completetime;
+ public $startdate;
+ public $duedate;
+ public $utcstartdate;
+ public $utcduedate;
+ public $reminderset;
+ public $remindertime;
+ public $ordinaldate;
+ public $subordinaldate;
+
+
+ function SyncMailFlags() {
+ $mapping = array(
+ SYNC_POOMTASKS_SUBJECT => array ( self::STREAMER_VAR => "subject"),
+ SYNC_POOMMAIL_FLAGSTATUS => array ( self::STREAMER_VAR => "flagstatus"),
+ SYNC_POOMMAIL_FLAGTYPE => array ( self::STREAMER_VAR => "flagtype"),
+ SYNC_POOMTASKS_DATECOMPLETED => array ( self::STREAMER_VAR => "datecompleted",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMMAIL_COMPLETETIME => array ( self::STREAMER_VAR => "completetime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_STARTDATE => array ( self::STREAMER_VAR => "startdate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_DUEDATE => array ( self::STREAMER_VAR => "duedate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_UTCSTARTDATE => array ( self::STREAMER_VAR => "utcstartdate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_UTCDUEDATE => array ( self::STREAMER_VAR => "utcduedate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_REMINDERSET => array ( self::STREAMER_VAR => "reminderset"),
+ SYNC_POOMTASKS_REMINDERTIME => array ( self::STREAMER_VAR => "remindertime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_ORDINALDATE => array ( self::STREAMER_VAR => "ordinaldate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_SUBORDINALDATE => array ( self::STREAMER_VAR => "subordinaldate"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncmeetingrequest.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncmeetingrequest.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncmeetingrequest.php (revision 7589)
@@ -0,0 +1,134 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncMeetingRequest extends SyncObject {
+ public $alldayevent;
+ public $starttime;
+ public $dtstamp;
+ public $endtime;
+ public $instancetype;
+ public $location;
+ public $organizer;
+ public $recurrenceid;
+ public $reminder;
+ public $responserequested;
+ public $recurrences;
+ public $sensitivity;
+ public $busystatus;
+ public $timezone;
+ public $globalobjid;
+
+ function SyncMeetingRequest() {
+ $mapping = array (
+ SYNC_POOMMAIL_ALLDAYEVENT => array ( self::STREAMER_VAR => "alldayevent",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMMAIL_STARTTIME => array ( self::STREAMER_VAR => "starttime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_CMPLOWER => SYNC_POOMMAIL_ENDTIME ) ),
+
+ SYNC_POOMMAIL_DTSTAMP => array ( self::STREAMER_VAR => "dtstamp",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO) ),
+
+ SYNC_POOMMAIL_ENDTIME => array ( self::STREAMER_VAR => "endtime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETONE,
+ self::STREAMER_CHECK_CMPHIGHER => SYNC_POOMMAIL_STARTTIME ) ),
+ // Instancetype values
+ // 0 = single appointment
+ // 1 = master recurring appointment
+ // 2 = single instance of recurring appointment
+ // 3 = exception of recurring appointment
+ SYNC_POOMMAIL_INSTANCETYPE => array ( self::STREAMER_VAR => "instancetype",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ SYNC_POOMMAIL_LOCATION => array ( self::STREAMER_VAR => "location"),
+ SYNC_POOMMAIL_ORGANIZER => array ( self::STREAMER_VAR => "organizer",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY ) ),
+
+ SYNC_POOMMAIL_RECURRENCEID => array ( self::STREAMER_VAR => "recurrenceid",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMMAIL_REMINDER => array ( self::STREAMER_VAR => "reminder",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1)),
+
+ SYNC_POOMMAIL_RESPONSEREQUESTED => array ( self::STREAMER_VAR => "responserequested"),
+ SYNC_POOMMAIL_RECURRENCES => array ( self::STREAMER_VAR => "recurrences",
+ self::STREAMER_TYPE => "SyncMeetingRequestRecurrence",
+ self::STREAMER_ARRAY => SYNC_POOMMAIL_RECURRENCE),
+ // Sensitivity values
+ // 0 = Normal
+ // 1 = Personal
+ // 2 = Private
+ // 3 = Confident
+ SYNC_POOMMAIL_SENSITIVITY => array ( self::STREAMER_VAR => "sensitivity",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ // Busystatus values
+ // 0 = Free
+ // 1 = Tentative
+ // 2 = Busy
+ // 3 = Out of office
+ SYNC_POOMMAIL_BUSYSTATUS => array ( self::STREAMER_VAR => "busystatus",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETTWO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ SYNC_POOMMAIL_TIMEZONE => array ( self::STREAMER_VAR => "timezone",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => base64_encode(pack("la64vvvvvvvv"."la64vvvvvvvv"."l",0,"",0,0,0,0,0,0,0,0,0,"",0,0,0,0,0,0,0,0,0)) )),
+
+ SYNC_POOMMAIL_GLOBALOBJID => array ( self::STREAMER_VAR => "globalobjid"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/synctask.php
===================================================================
--- /trunk/zpush/lib/syncobjects/synctask.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/synctask.php (revision 7589)
@@ -0,0 +1,180 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncTask extends SyncObject {
+ public $body;
+ public $complete;
+ public $datecompleted;
+ public $duedate;
+ public $utcduedate;
+ public $importance;
+ public $recurrence;
+ public $regenerate;
+ public $deadoccur;
+ public $reminderset;
+ public $remindertime;
+ public $sensitivity;
+ public $startdate;
+ public $utcstartdate;
+ public $subject;
+ public $rtf;
+ public $categories;
+
+ function SyncTask() {
+ $mapping = array (
+ SYNC_POOMTASKS_BODY => array ( self::STREAMER_VAR => "body"),
+ SYNC_POOMTASKS_COMPLETE => array ( self::STREAMER_VAR => "complete",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO )),
+
+ SYNC_POOMTASKS_DATECOMPLETED => array ( self::STREAMER_VAR => "datecompleted",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_DUEDATE => array ( self::STREAMER_VAR => "duedate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_UTCDUEDATE => array ( self::STREAMER_VAR => "utcduedate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ // Importance values
+ // 0 = Low
+ // 1 = Normal
+ // 2 = High
+ // even the default value 1 is optional, the native android client 2.2 interprets a non-existing value as 0 (low)
+ SYNC_POOMTASKS_IMPORTANCE => array ( self::STREAMER_VAR => "importance",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETONE,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2) )),
+
+ SYNC_POOMTASKS_RECURRENCE => array ( self::STREAMER_VAR => "recurrence",
+ self::STREAMER_TYPE => "SyncTaskRecurrence"),
+
+ SYNC_POOMTASKS_REGENERATE => array ( self::STREAMER_VAR => "regenerate"),
+ SYNC_POOMTASKS_DEADOCCUR => array ( self::STREAMER_VAR => "deadoccur"),
+ SYNC_POOMTASKS_REMINDERSET => array ( self::STREAMER_VAR => "reminderset",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO )),
+
+ SYNC_POOMTASKS_REMINDERTIME => array ( self::STREAMER_VAR => "remindertime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ // Sensitivity values
+ // 0 = Normal
+ // 1 = Personal
+ // 2 = Private
+ // 3 = Confident
+ SYNC_POOMTASKS_SENSITIVITY => array ( self::STREAMER_VAR => "sensitivity",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) )),
+
+ SYNC_POOMTASKS_STARTDATE => array ( self::STREAMER_VAR => "startdate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_UTCSTARTDATE => array ( self::STREAMER_VAR => "utcstartdate",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMTASKS_SUBJECT => array ( self::STREAMER_VAR => "subject"),
+ SYNC_POOMTASKS_RTF => array ( self::STREAMER_VAR => "rtf"),
+ SYNC_POOMTASKS_CATEGORIES => array ( self::STREAMER_VAR => "categories",
+ self::STREAMER_ARRAY => SYNC_POOMTASKS_CATEGORY),
+ );
+
+ if (Request::GetProtocolVersion() >= 12.0) {
+ $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody",
+ self::STREAMER_TYPE => "SyncBaseBody");
+
+ //unset these properties because airsyncbase body and attachments will be used instead
+ unset($mapping[SYNC_POOMTASKS_BODY]);
+ }
+
+ parent::SyncObject($mapping);
+ }
+
+ /**
+ * Method checks if the object has the minimum of required parameters
+ * and fullfills semantic dependencies
+ *
+ * This overloads the general check() with special checks to be executed
+ *
+ * @param boolean $logAsDebug (opt) default is false, so messages are logged in WARN log level
+ *
+ * @access public
+ * @return boolean
+ */
+ public function Check($logAsDebug = false) {
+ $ret = parent::Check($logAsDebug);
+
+ // semantic checks general "turn off switch"
+ if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false)
+ return $ret;
+
+ if (!$ret)
+ return false;
+
+ if (isset($this->startdate) && isset($this->duedate) && $this->duedate < $this->startdate) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter 'startdate' is HIGHER than 'duedate'. Check failed!", get_class($this) ));
+ return false;
+ }
+
+ if (isset($this->utcstartdate) && isset($this->utcduedate) && $this->utcduedate < $this->utcstartdate) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter 'utcstartdate' is HIGHER than 'utcduedate'. Check failed!", get_class($this) ));
+ return false;
+ }
+
+ if (isset($this->duedate) && $this->duedate != Utils::getDayStartOfTimestamp($this->duedate)) {
+ $this->duedate = Utils::getDayStartOfTimestamp($this->duedate);
+ ZLog::Write(LOGLEVEL_DEBUG, "Set the due time to the start of the day");
+ if (isset($this->startdate) && $this->duedate < $this->startdate) {
+ $this->startdate = Utils::getDayStartOfTimestamp($this->startdate);
+ ZLog::Write(LOGLEVEL_DEBUG, "Set the start date to the start of the day");
+ }
+ }
+
+ return true;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncdeviceinformation.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncdeviceinformation.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncdeviceinformation.php (revision 7589)
@@ -0,0 +1,85 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncDeviceInformation extends SyncObject {
+ public $model;
+ public $imei;
+ public $friendlyname;
+ public $os;
+ public $oslanguage;
+ public $phonenumber;
+ public $useragent; //12.1 &14.0
+ public $mobileoperator; //14.0
+ public $enableoutboundsms; //14.0
+ public $Status;
+
+ public function SyncDeviceInformation() {
+ $mapping = array (
+ SYNC_SETTINGS_MODEL => array ( self::STREAMER_VAR => "model"),
+ SYNC_SETTINGS_IMEI => array ( self::STREAMER_VAR => "imei"),
+ SYNC_SETTINGS_FRIENDLYNAME => array ( self::STREAMER_VAR => "friendlyname"),
+ SYNC_SETTINGS_OS => array ( self::STREAMER_VAR => "os"),
+ SYNC_SETTINGS_OSLANGUAGE => array ( self::STREAMER_VAR => "oslanguage"),
+ SYNC_SETTINGS_PHONENUMBER => array ( self::STREAMER_VAR => "phonenumber"),
+
+ SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE)
+ );
+
+ if (Request::GetProtocolVersion() >= 12.1) {
+ $mapping[SYNC_SETTINGS_USERAGENT] = array ( self::STREAMER_VAR => "useragent");
+ }
+
+ if (Request::GetProtocolVersion() >= 14.0) {
+ $mapping[SYNC_SETTINGS_MOBILEOPERATOR] = array ( self::STREAMER_VAR => "mobileoperator");
+ $mapping[SYNC_SETTINGS_ENABLEOUTBOUNDSMS] = array ( self::STREAMER_VAR => "enableoutboundsms");
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncnote.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncnote.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncnote.php (revision 7589)
@@ -0,0 +1,75 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncNote extends SyncObject {
+ public $asbody;
+ public $categories;
+ public $lastmodified;
+ public $messageclass;
+ public $subject;
+
+ function SyncNote() {
+ $mapping = array(
+ SYNC_AIRSYNCBASE_BODY => array ( self::STREAMER_VAR => "asbody",
+ self::STREAMER_TYPE => "SyncBaseBody"),
+
+ SYNC_NOTES_CATEGORIES => array ( self::STREAMER_VAR => "categories",
+ self::STREAMER_ARRAY => SYNC_NOTES_CATEGORY),
+
+ SYNC_NOTES_LASTMODIFIEDDATE => array ( self::STREAMER_VAR => "lastmodified",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE),
+
+ SYNC_NOTES_MESSAGECLASS => array ( self::STREAMER_VAR => "messageclass"),
+
+ SYNC_NOTES_SUBJECT => array ( self::STREAMER_VAR => "subject"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncobject.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncobject.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncobject.php (revision 7589)
@@ -0,0 +1,415 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+abstract class SyncObject extends Streamer {
+ const STREAMER_CHECKS = 6;
+ const STREAMER_CHECK_REQUIRED = 7;
+ const STREAMER_CHECK_ZEROORONE = 8;
+ const STREAMER_CHECK_NOTALLOWED = 9;
+ const STREAMER_CHECK_ONEVALUEOF = 10;
+ const STREAMER_CHECK_SETZERO = "setToValue0";
+ const STREAMER_CHECK_SETONE = "setToValue1";
+ const STREAMER_CHECK_SETTWO = "setToValue2";
+ const STREAMER_CHECK_SETEMPTY = "setToValueEmpty";
+ const STREAMER_CHECK_CMPLOWER = 13;
+ const STREAMER_CHECK_CMPHIGHER = 14;
+ const STREAMER_CHECK_LENGTHMAX = 15;
+ const STREAMER_CHECK_EMAIL = 16;
+
+ protected $unsetVars;
+
+
+ public function SyncObject($mapping) {
+ $this->unsetVars = array();
+ parent::Streamer($mapping);
+ }
+
+ /**
+ * Sets all supported but not transmitted variables
+ * of this SyncObject to an "empty" value, so they are deleted when being saved
+ *
+ * @param array $supportedFields array with all supported fields, if available
+ *
+ * @access public
+ * @return boolean
+ */
+ public function emptySupported($supportedFields) {
+ if ($supportedFields === false || !is_array($supportedFields))
+ return false;
+
+ foreach ($supportedFields as $field) {
+ if (!isset($this->mapping[$field])) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("Field '%s' is supposed to be emptied but is not defined for '%s'", $field, get_class($this)));
+ continue;
+ }
+ $var = $this->mapping[$field][self::STREAMER_VAR];
+ // add var to $this->unsetVars if $var is not set
+ if (!isset($this->$var))
+ $this->unsetVars[] = $var;
+ }
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("Supported variables to be unset: %s", implode(',', $this->unsetVars)));
+ return true;
+ }
+
+
+ /**
+ * Compares this a SyncObject to another.
+ * In case that all available mapped fields are exactly EQUAL, it returns true
+ *
+ * @see SyncObject
+ * @param SyncObject $odo other SyncObject
+ * @return boolean
+ */
+ public function equals($odo, $log = false) {
+ if ($odo === false)
+ return false;
+
+ // check objecttype
+ if (! ($odo instanceof SyncObject)) {
+ ZLog::Write(LOGLEVEL_DEBUG, "SyncObject->equals() the target object is not a SyncObject");
+ return false;
+ }
+
+ // check for mapped fields
+ foreach ($this->mapping as $v) {
+ $val = $v[self::STREAMER_VAR];
+ // array of values?
+ if (isset($v[self::STREAMER_ARRAY])) {
+ // seek for differences in the arrays
+ if (is_array($this->$val) && is_array($odo->$val)) {
+ if (count(array_diff($this->$val, $odo->$val)) + count(array_diff($odo->$val, $this->$val)) > 0) {
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() items in array '%s' differ", $val));
+ return false;
+ }
+ }
+ else {
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() array '%s' is set in one but not the other object", $val));
+ return false;
+ }
+ }
+ else {
+ if (isset($this->$val) && isset($odo->$val)) {
+ if ($this->$val != $odo->$val){
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() false on field '%s': '%s' != '%s'", $val, Utils::PrintAsString($this->$val), Utils::PrintAsString($odo->$val)));
+ return false;
+ }
+ }
+ else if (!isset($this->$val) && !isset($odo->$val)) {
+ continue;
+ }
+ else {
+ ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncObject->equals() false because field '%s' is only defined at one obj: '%s' != '%s'", $val, Utils::PrintAsString(isset($this->$val)), Utils::PrintAsString(isset($odo->$val))));
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * String representation of the object
+ *
+ * @return String
+ */
+ public function __toString() {
+ $str = get_class($this) . " (\n";
+
+ $streamerVars = array();
+ foreach ($this->mapping as $k=>$v)
+ $streamerVars[$v[self::STREAMER_VAR]] = (isset($v[self::STREAMER_TYPE]))?$v[self::STREAMER_TYPE]:false;
+
+ foreach (get_object_vars($this) as $k=>$v) {
+ if ($k == "mapping") continue;
+
+ if (array_key_exists($k, $streamerVars))
+ $strV = "(S) ";
+ else
+ $strV = "";
+
+ // self::STREAMER_ARRAY ?
+ if (is_array($v)) {
+ $str .= "\t". $strV . $k ."(Array) size: " . count($v) ."\n";
+ foreach ($v as $value) $str .= "\t\t". Utils::PrintAsString($value) ."\n";
+ }
+ else if ($v instanceof SyncObject) {
+ $str .= "\t". $strV .$k ." => ". str_replace("\n", "\n\t\t\t", $v->__toString()) . "\n";
+ }
+ else
+ $str .= "\t". $strV .$k ." => " . (isset($this->$k)? Utils::PrintAsString($this->$k) :"null") . "\n";
+ }
+ $str .= ")";
+
+ return $str;
+ }
+
+ /**
+ * Returns the properties which have to be unset on the server
+ *
+ * @access public
+ * @return array
+ */
+ public function getUnsetVars() {
+ return $this->unsetVars;
+ }
+
+ /**
+ * Method checks if the object has the minimum of required parameters
+ * and fullfills semantic dependencies
+ *
+ * General checks:
+ * STREAMER_CHECK_REQUIRED may have as value false (do not fix, ignore object!) or set-to-values: STREAMER_CHECK_SETZERO/ONE/TWO, STREAMER_CHECK_SETEMPTY
+ * STREAMER_CHECK_ZEROORONE may be 0 or 1, if none of these, set-to-values: STREAMER_CHECK_SETZERO or STREAMER_CHECK_SETONE
+ * STREAMER_CHECK_NOTALLOWED fails if is set
+ * STREAMER_CHECK_ONEVALUEOF expects an array with accepted values, fails if value is not in array
+ *
+ * Comparison:
+ * STREAMER_CHECK_CMPLOWER compares if the current parameter is lower as a literal or another parameter of the same object
+ * STREAMER_CHECK_CMPHIGHER compares if the current parameter is higher as a literal or another parameter of the same object
+ *
+ * @param boolean $logAsDebug (opt) default is false, so messages are logged in WARN log level
+ *
+ * @access public
+ * @return boolean
+ */
+ public function Check($logAsDebug = false) {
+ // semantic checks general "turn off switch"
+ if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false) {
+ ZLog::Write(LOGLEVEL_DEBUG, "SyncObject->Check(): semantic checks disabled. Check your config for 'DO_SEMANTIC_CHECKS'.");
+ return true;
+ }
+
+ $defaultLogLevel = LOGLEVEL_WARN;
+
+ // in some cases non-false checks should not provoke a WARN log but only a DEBUG log
+ if ($logAsDebug)
+ $defaultLogLevel = LOGLEVEL_DEBUG;
+
+ $objClass = get_class($this);
+ foreach ($this->mapping as $k=>$v) {
+
+ // check sub-objects recursively
+ if (isset($v[self::STREAMER_TYPE]) && isset($this->$v[self::STREAMER_VAR])) {
+ if ($this->$v[self::STREAMER_VAR] instanceof SyncObject) {
+ if (! $this->$v[self::STREAMER_VAR]->Check($logAsDebug))
+ return false;
+ }
+ else if (is_array($this->$v[self::STREAMER_VAR])) {
+ foreach ($this->$v[self::STREAMER_VAR] as $subobj)
+ if ($subobj instanceof SyncObject && !$subobj->Check($logAsDebug))
+ return false;
+ }
+ }
+
+ if (isset($v[self::STREAMER_CHECKS])) {
+ foreach ($v[self::STREAMER_CHECKS] as $rule => $condition) {
+ // check REQUIRED settings
+ if ($rule === self::STREAMER_CHECK_REQUIRED && (!isset($this->$v[self::STREAMER_VAR]) || $this->$v[self::STREAMER_VAR] === '' ) ) {
+ // parameter is not set but ..
+ // requested to set to 0
+ if ($condition === self::STREAMER_CHECK_SETZERO) {
+ $this->$v[self::STREAMER_VAR] = 0;
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 0", $objClass, $v[self::STREAMER_VAR]));
+ }
+ // requested to be set to 1
+ else if ($condition === self::STREAMER_CHECK_SETONE) {
+ $this->$v[self::STREAMER_VAR] = 1;
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 1", $objClass, $v[self::STREAMER_VAR]));
+ }
+ // requested to be set to 2
+ else if ($condition === self::STREAMER_CHECK_SETTWO) {
+ $this->$v[self::STREAMER_VAR] = 2;
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to 2", $objClass, $v[self::STREAMER_VAR]));
+ }
+ // requested to be set to ''
+ else if ($condition === self::STREAMER_CHECK_SETEMPTY) {
+ if (!isset($this->$v[self::STREAMER_VAR])) {
+ $this->$v[self::STREAMER_VAR] = '';
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to ''", $objClass, $v[self::STREAMER_VAR]));
+ }
+ }
+ // there is another value !== false
+ else if ($condition !== false) {
+ $this->$v[self::STREAMER_VAR] = $condition;
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to '%s'", $objClass, $v[self::STREAMER_VAR], $condition));
+
+ }
+ // no fix available!
+ else {
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' is required but not set. Check failed!", $objClass, $v[self::STREAMER_VAR]));
+ return false;
+ }
+ } // end STREAMER_CHECK_REQUIRED
+
+
+ // check STREAMER_CHECK_ZEROORONE
+ if ($rule === self::STREAMER_CHECK_ZEROORONE && isset($this->$v[self::STREAMER_VAR])) {
+ if ($this->$v[self::STREAMER_VAR] != 0 && $this->$v[self::STREAMER_VAR] != 1) {
+ $newval = $condition === self::STREAMER_CHECK_SETZERO ? 0:1;
+ $this->$v[self::STREAMER_VAR] = $newval;
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): Fixed object from type %s: parameter '%s' is set to '%s' as it was not 0 or 1", $objClass, $v[self::STREAMER_VAR], $newval));
+ }
+ }// end STREAMER_CHECK_ZEROORONE
+
+
+ // check STREAMER_CHECK_ONEVALUEOF
+ if ($rule === self::STREAMER_CHECK_ONEVALUEOF && isset($this->$v[self::STREAMER_VAR])) {
+ if (!in_array($this->$v[self::STREAMER_VAR], $condition)) {
+ ZLog::Write($defaultLogLevel, sprintf("SyncObject->Check(): object from type %s: parameter '%s'->'%s' is not in the range of allowed values.", $objClass, $v[self::STREAMER_VAR], $this->$v[self::STREAMER_VAR]));
+ return false;
+ }
+ }// end STREAMER_CHECK_ONEVALUEOF
+
+
+ // Check value compared to other value or literal
+ if ($rule === self::STREAMER_CHECK_CMPHIGHER || $rule === self::STREAMER_CHECK_CMPLOWER) {
+ if (isset($this->$v[self::STREAMER_VAR])) {
+ $cmp = false;
+ // directly compare against literals
+ if (is_int($condition)) {
+ $cmp = $condition;
+ }
+ // check for invalid compare-to
+ else if (!isset($this->mapping[$condition])) {
+ ZLog::Write(LOGLEVEL_ERROR, sprintf("SyncObject->Check(): Can not compare parameter '%s' against the other value '%s' as it is not defined object from type %s. Please report this! Check skipped!", $objClass, $v[self::STREAMER_VAR], $condition));
+ continue;
+ }
+ else {
+ $cmpPar = $this->mapping[$condition][self::STREAMER_VAR];
+ if (isset($this->$cmpPar))
+ $cmp = $this->$cmpPar;
+ }
+
+ if ($cmp === false) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' can not be compared, as the comparable is not set. Check failed!", $objClass, $v[self::STREAMER_VAR]));
+ return false;
+ }
+ if ( ($rule == self::STREAMER_CHECK_CMPHIGHER && $this->$v[self::STREAMER_VAR] < $cmp) ||
+ ($rule == self::STREAMER_CHECK_CMPLOWER && $this->$v[self::STREAMER_VAR] > $cmp)
+ ) {
+
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter '%s' is %s than '%s'. Check failed!",
+ $objClass,
+ $v[self::STREAMER_VAR],
+ (($rule === self::STREAMER_CHECK_CMPHIGHER)?'LOWER':'HIGHER'),
+ ((isset($cmpPar)?$cmpPar:$condition)) ));
+ return false;
+ }
+ }
+ } // STREAMER_CHECK_CMP*
+
+
+ // check STREAMER_CHECK_LENGTHMAX
+ if ($rule === self::STREAMER_CHECK_LENGTHMAX && isset($this->$v[self::STREAMER_VAR])) {
+
+ if (is_array($this->$v[self::STREAMER_VAR])) {
+ // implosion takes 2bytes, so we just assume ", " here
+ $chkstr = implode(", ", $this->$v[self::STREAMER_VAR]);
+ }
+ else
+ $chkstr = $this->$v[self::STREAMER_VAR];
+
+ if (strlen($chkstr) > $condition) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): object from type %s: parameter '%s' is longer than %d. Check failed", $objClass, $v[self::STREAMER_VAR], $condition));
+ return false;
+ }
+ }// end STREAMER_CHECK_LENGTHMAX
+
+
+ // check STREAMER_CHECK_EMAIL
+ // if $condition is false then the check really fails. Otherwise invalid emails are removed.
+ // if nothing is left (all emails were false), the parameter is set to condition
+ if ($rule === self::STREAMER_CHECK_EMAIL && isset($this->$v[self::STREAMER_VAR])) {
+ if ($condition === false && ( (is_array($this->$v[self::STREAMER_VAR]) && empty($this->$v[self::STREAMER_VAR])) || strlen($this->$v[self::STREAMER_VAR]) == 0) )
+ continue;
+
+ $as_array = false;
+
+ if (is_array($this->$v[self::STREAMER_VAR])) {
+ $mails = $this->$v[self::STREAMER_VAR];
+ $as_array = true;
+ }
+ else {
+ $mails = array( $this->$v[self::STREAMER_VAR] );
+ }
+
+ $output = array();
+ foreach ($mails as $mail) {
+ if (! Utils::CheckEmail($mail)) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): object from type %s: parameter '%s' contains an invalid email address '%s'. Address is removed.", $objClass, $v[self::STREAMER_VAR], $mail));
+ }
+ else
+ $output[] = $mail;
+ }
+ if (count($mails) != count($output)) {
+ if ($condition === false)
+ return false;
+
+ // nothing left, use $condition as new value
+ if (count($output) == 0)
+ $output[] = $condition;
+
+ // if we are allowed to rewrite the attribute, we do that
+ if ($as_array)
+ $this->$v[self::STREAMER_VAR] = $output;
+ else
+ $this->$v[self::STREAMER_VAR] = $output[0];
+ }
+ }// end STREAMER_CHECK_EMAIL
+
+
+ } // foreach CHECKS
+ } // isset CHECKS
+ } // foreach mapping
+
+ return true;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncfolder.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncfolder.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncfolder.php (revision 7589)
@@ -0,0 +1,79 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncFolder extends SyncObject {
+ public $serverid;
+ public $parentid;
+ public $displayname;
+ public $type;
+ public $Store;
+
+ function SyncFolder() {
+ $mapping = array (
+ SYNC_FOLDERHIERARCHY_SERVERENTRYID => array ( self::STREAMER_VAR => "serverid",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => false)),
+
+ SYNC_FOLDERHIERARCHY_PARENTID => array ( self::STREAMER_VAR => "parentid",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_FOLDERHIERARCHY_DISPLAYNAME => array ( self::STREAMER_VAR => "displayname",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)),
+
+ SYNC_FOLDERHIERARCHY_TYPE => array ( self::STREAMER_VAR => "type",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => 18,
+ self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 20 )),
+
+ SYNC_FOLDERHIERARCHY_IGNORE_STORE => array ( self::STREAMER_VAR => "Store",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncitemoperationsattachment.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncitemoperationsattachment.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncitemoperationsattachment.php (revision 7589)
@@ -0,0 +1,62 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncItemOperationsAttachment extends SyncObject {
+ public $contenttype;
+ public $data;
+
+ function SyncItemOperationsAttachment() {
+ $mapping = array(
+ SYNC_AIRSYNCBASE_CONTENTTYPE => array ( self::STREAMER_VAR => "contenttype"),
+ SYNC_ITEMOPERATIONS_DATA => array ( self::STREAMER_VAR => "data",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_STREAM,
+ self::STREAMER_PROP => self::STREAMER_TYPE_MULTIPART),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncsendmailsource.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncsendmailsource.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncsendmailsource.php (revision 7589)
@@ -0,0 +1,67 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncSendMailSource extends SyncObject {
+ public $folderid;
+ public $itemid;
+ public $longid;
+ public $instanceid;
+
+ function SyncSendMailSource() {
+ $mapping = array (
+ SYNC_COMPOSEMAIL_FOLDERID => array ( self::STREAMER_VAR => "folderid"),
+ SYNC_COMPOSEMAIL_ITEMID => array ( self::STREAMER_VAR => "itemid"),
+ SYNC_COMPOSEMAIL_LONGID => array ( self::STREAMER_VAR => "longid"),
+ SYNC_COMPOSEMAIL_INSTANCEID => array ( self::STREAMER_VAR => "instanceid"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncappointmentexception.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncappointmentexception.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncappointmentexception.php (revision 7589)
@@ -0,0 +1,78 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncAppointmentException extends SyncAppointment {
+ public $deleted;
+ public $exceptionstarttime;
+
+ function SyncAppointmentException() {
+ parent::SyncAppointment();
+
+ $this->mapping += array(
+ SYNC_POOMCAL_DELETED => array ( self::STREAMER_VAR => "deleted",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMCAL_EXCEPTIONSTARTTIME => array ( self::STREAMER_VAR => "exceptionstarttime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETONE)),
+ );
+
+ // some parameters are not required in an exception, others are not allowed to be set in SyncAppointmentExceptions
+ $this->mapping[SYNC_POOMCAL_TIMEZONE][self::STREAMER_CHECKS] = array();
+ $this->mapping[SYNC_POOMCAL_DTSTAMP][self::STREAMER_CHECKS] = array();
+ $this->mapping[SYNC_POOMCAL_STARTTIME][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPLOWER => SYNC_POOMCAL_ENDTIME);
+ $this->mapping[SYNC_POOMCAL_SUBJECT][self::STREAMER_CHECKS] = array();
+ $this->mapping[SYNC_POOMCAL_ENDTIME][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPHIGHER => SYNC_POOMCAL_STARTTIME);
+ $this->mapping[SYNC_POOMCAL_BUSYSTATUS][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3) );
+ $this->mapping[SYNC_POOMCAL_REMINDER][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_CMPHIGHER => -1);
+ $this->mapping[SYNC_POOMCAL_EXCEPTIONS][self::STREAMER_CHECKS] = array(self::STREAMER_CHECK_NOTALLOWED => true);
+
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncmeetingrequestrecurrence.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncmeetingrequestrecurrence.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncmeetingrequestrecurrence.php (revision 7589)
@@ -0,0 +1,121 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncMeetingRequestRecurrence extends SyncObject {
+ public $type;
+ public $until;
+ public $occurrences;
+ public $interval;
+ public $dayofweek;
+ public $dayofmonth;
+ public $weekofmonth;
+ public $monthofyear;
+
+ function SyncMeetingRequestRecurrence() {
+ $mapping = array (
+ // Recurrence type
+ // 0 = Recurs daily
+ // 1 = Recurs weekly
+ // 2 = Recurs monthly
+ // 3 = Recurs monthly on the nth day
+ // 5 = Recurs yearly
+ // 6 = Recurs yearly on the nth day
+ SYNC_POOMMAIL_TYPE => array ( self::STREAMER_VAR => "type",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,5,6) )),
+
+ SYNC_POOMMAIL_UNTIL => array ( self::STREAMER_VAR => "until",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE),
+
+ SYNC_POOMMAIL_OCCURRENCES => array ( self::STREAMER_VAR => "occurrences",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ SYNC_POOMMAIL_INTERVAL => array ( self::STREAMER_VAR => "interval",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ // DayOfWeek values
+ // 1 = Sunday
+ // 2 = Monday
+ // 4 = Tuesday
+ // 8 = Wednesday
+ // 16 = Thursday
+ // 32 = Friday
+ // 62 = Weekdays // not in spec: daily weekday recurrence
+ // 64 = Saturday
+ // 127 = The last day of the month. Value valid only in monthly or yearly recurrences.
+ // As this is a bitmask, actually all values 0 > x < 128 are allowed
+ SYNC_POOMMAIL_DAYOFWEEK => array ( self::STREAMER_VAR => "dayofweek",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 128 )),
+
+ // DayOfMonth values
+ // 1-31 representing the day
+ SYNC_POOMMAIL_DAYOFMONTH => array ( self::STREAMER_VAR => "dayofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 32 )),
+
+ // WeekOfMonth
+ // 1-4 = Y st/nd/rd/th week of month
+ // 5 = last week of month
+ SYNC_POOMMAIL_WEEKOFMONTH => array ( self::STREAMER_VAR => "weekofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5) )),
+
+ // MonthOfYear
+ // 1-12 representing the month
+ SYNC_POOMMAIL_MONTHOFYEAR => array ( self::STREAMER_VAR => "monthofyear",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/synctaskrecurrence.php
===================================================================
--- /trunk/zpush/lib/syncobjects/synctaskrecurrence.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/synctaskrecurrence.php (revision 7589)
@@ -0,0 +1,156 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+// Exactly the same as SyncRecurrence, but then with SYNC_POOMTASKS_*
+class SyncTaskRecurrence extends SyncObject {
+ public $start;
+ public $type;
+ public $until;
+ public $occurrences;
+ public $interval;
+ public $dayofweek;
+ public $dayofmonth;
+ public $weekofmonth;
+ public $monthofyear;
+ public $deadoccur;
+
+ function SyncTaskRecurrence() {
+ $mapping = array (
+ SYNC_POOMTASKS_START => array ( self::STREAMER_VAR => "start",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE),
+
+ // Recurrence type
+ // 0 = Recurs daily
+ // 1 = Recurs weekly
+ // 2 = Recurs monthly
+ // 3 = Recurs monthly on the nth day
+ // 5 = Recurs yearly
+ // 6 = Recurs yearly on the nth day
+ SYNC_POOMTASKS_TYPE => array ( self::STREAMER_VAR => "type",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,5,6) )),
+
+ SYNC_POOMTASKS_UNTIL => array ( self::STREAMER_VAR => "until",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE ),
+
+ SYNC_POOMTASKS_OCCURRENCES => array ( self::STREAMER_VAR => "occurrences",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ SYNC_POOMTASKS_INTERVAL => array ( self::STREAMER_VAR => "interval",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 1000 )),
+
+ //TODO: check iOS5 sends deadoccur inside of the recurrence
+ SYNC_POOMTASKS_DEADOCCUR => array ( self::STREAMER_VAR => "deadoccur"),
+
+ // DayOfWeek values
+ // 1 = Sunday
+ // 2 = Monday
+ // 4 = Tuesday
+ // 8 = Wednesday
+ // 16 = Thursday
+ // 32 = Friday
+ // 62 = Weekdays // TODO check: value set by WA with daily weekday recurrence
+ // 64 = Saturday
+ // 127 = The last day of the month. Value valid only in monthly or yearly recurrences.
+ SYNC_POOMTASKS_DAYOFWEEK => array ( self::STREAMER_VAR => "dayofweek",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,4,8,16,32,62,64,127) )),
+
+ // DayOfMonth values
+ // 1-31 representing the day
+ SYNC_POOMTASKS_DAYOFMONTH => array ( self::STREAMER_VAR => "dayofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 32 )),
+
+ // WeekOfMonth
+ // 1-4 = Y st/nd/rd/th week of month
+ // 5 = last week of month
+ SYNC_POOMTASKS_WEEKOFMONTH => array ( self::STREAMER_VAR => "weekofmonth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5) )),
+
+ // MonthOfYear
+ // 1-12 representing the month
+ SYNC_POOMTASKS_MONTHOFYEAR => array ( self::STREAMER_VAR => "monthofyear",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4,5,6,7,8,9,10,11,12) )),
+
+ );
+ parent::SyncObject($mapping);
+ }
+
+ /**
+ * Method checks if the object has the minimum of required parameters
+ * and fullfills semantic dependencies
+ *
+ * This overloads the general check() with special checks to be executed
+ *
+ * @param boolean $logAsDebug (opt) default is false, so messages are logged in WARN log level
+ *
+ * @access public
+ * @return boolean
+ */
+ public function Check($logAsDebug = false) {
+ $ret = parent::Check($logAsDebug);
+
+ // semantic checks general "turn off switch"
+ if (defined("DO_SEMANTIC_CHECKS") && DO_SEMANTIC_CHECKS === false)
+ return $ret;
+
+ if (!$ret)
+ return false;
+
+ if (isset($this->start) && isset($this->until) && $this->until < $this->start) {
+ ZLog::Write(LOGLEVEL_WARN, sprintf("SyncObject->Check(): Unmet condition in object from type %s: parameter 'start' is HIGHER than 'until'. Check failed!", get_class($this) ));
+ return false;
+ }
+
+ return true;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncmail.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncmail.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncmail.php (revision 7589)
@@ -0,0 +1,197 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncMail extends SyncObject {
+ public $to;
+ public $cc;
+ public $from;
+ public $subject;
+ public $threadtopic;
+ public $datereceived;
+ public $displayto;
+ public $importance;
+ public $read;
+ public $attachments;
+ public $mimetruncated;
+ public $mimedata;
+ public $mimesize;
+ public $bodytruncated;
+ public $bodysize;
+ public $body;
+ public $messageclass;
+ public $meetingrequest;
+ public $reply_to;
+
+ // AS 2.5 prop
+ public $internetcpid;
+
+ // AS 12.0 props
+ public $asbody;
+ public $asattachments;
+ public $flag;
+ public $contentclass;
+ public $nativebodytype;
+
+ // AS 14.0 props
+ public $umcallerid;
+ public $umusernotes;
+ public $conversationid;
+ public $conversationindex;
+ public $lastverbexecuted; //possible values unknown, reply to sender, reply to all, forward
+ public $lastverbexectime;
+ public $receivedasbcc;
+ public $sender;
+
+ function SyncMail() {
+ $mapping = array (
+ SYNC_POOMMAIL_TO => array ( self::STREAMER_VAR => "to",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_COMMA_SEPARATED,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_LENGTHMAX => 32768,
+ self::STREAMER_CHECK_EMAIL => "" )),
+
+ SYNC_POOMMAIL_CC => array ( self::STREAMER_VAR => "cc",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_COMMA_SEPARATED,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_LENGTHMAX => 32768,
+ self::STREAMER_CHECK_EMAIL => "" )),
+
+ SYNC_POOMMAIL_FROM => array ( self::STREAMER_VAR => "from",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_LENGTHMAX => 32768,
+ self::STREAMER_CHECK_EMAIL => "broken-from@z-push.local" )),
+
+ SYNC_POOMMAIL_SUBJECT => array ( self::STREAMER_VAR => "subject"),
+ SYNC_POOMMAIL_THREADTOPIC => array ( self::STREAMER_VAR => "threadtopic"),
+ SYNC_POOMMAIL_DATERECEIVED => array ( self::STREAMER_VAR => "datereceived",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_POOMMAIL_DISPLAYTO => array ( self::STREAMER_VAR => "displayto"),
+
+ // Importance values
+ // 0 = Low
+ // 1 = Normal
+ // 2 = High
+ // even the default value 1 is optional, the native android client 2.2 interprets a non-existing value as 0 (low)
+ SYNC_POOMMAIL_IMPORTANCE => array ( self::STREAMER_VAR => "importance",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETONE,
+ self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2) )),
+
+ SYNC_POOMMAIL_READ => array ( self::STREAMER_VAR => "read",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_POOMMAIL_ATTACHMENTS => array ( self::STREAMER_VAR => "attachments",
+ self::STREAMER_TYPE => "SyncAttachment",
+ self::STREAMER_ARRAY => SYNC_POOMMAIL_ATTACHMENT),
+
+ SYNC_POOMMAIL_MIMETRUNCATED => array ( self::STREAMER_VAR => "mimetruncated",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMMAIL_MIMEDATA => array ( self::STREAMER_VAR => "mimedata"), //TODO mimedata should be of a type stream
+
+ SYNC_POOMMAIL_MIMESIZE => array ( self::STREAMER_VAR => "mimesize",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1)),
+
+ SYNC_POOMMAIL_BODYTRUNCATED => array ( self::STREAMER_VAR => "bodytruncated",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ZEROORONE => self::STREAMER_CHECK_SETZERO)),
+
+ SYNC_POOMMAIL_BODYSIZE => array ( self::STREAMER_VAR => "bodysize",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1)),
+
+ SYNC_POOMMAIL_BODY => array ( self::STREAMER_VAR => "body"),
+ SYNC_POOMMAIL_MESSAGECLASS => array ( self::STREAMER_VAR => "messageclass"),
+ SYNC_POOMMAIL_MEETINGREQUEST => array ( self::STREAMER_VAR => "meetingrequest",
+ self::STREAMER_TYPE => "SyncMeetingRequest"),
+
+ SYNC_POOMMAIL_REPLY_TO => array ( self::STREAMER_VAR => "reply_to",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_SEMICOLON_SEPARATED,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_EMAIL => "" )),
+
+ );
+
+ if (Request::GetProtocolVersion() >= 2.5) {
+ $mapping[SYNC_POOMMAIL_INTERNETCPID] = array ( self::STREAMER_VAR => "internetcpid");
+ }
+
+ if (Request::GetProtocolVersion() >= 12.0) {
+ $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody",
+ self::STREAMER_TYPE => "SyncBaseBody");
+
+ $mapping[SYNC_AIRSYNCBASE_ATTACHMENTS] = array ( self::STREAMER_VAR => "asattachments",
+ self::STREAMER_TYPE => "SyncBaseAttachment",
+ self::STREAMER_ARRAY => SYNC_AIRSYNCBASE_ATTACHMENT);
+
+ $mapping[SYNC_POOMMAIL_CONTENTCLASS] = array ( self::STREAMER_VAR => "contentclass",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(DEFAULT_EMAIL_CONTENTCLASS) ));
+
+ $mapping[SYNC_POOMMAIL_FLAG] = array ( self::STREAMER_VAR => "flag",
+ self::STREAMER_TYPE => "SyncMailFlags",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY);
+
+ $mapping[SYNC_AIRSYNCBASE_NATIVEBODYTYPE] = array ( self::STREAMER_VAR => "nativebodytype");
+
+ //unset these properties because airsyncbase body and attachments will be used instead
+ unset($mapping[SYNC_POOMMAIL_BODY], $mapping[SYNC_POOMMAIL_BODYTRUNCATED], $mapping[SYNC_POOMMAIL_ATTACHMENTS]);
+ }
+
+ if (Request::GetProtocolVersion() >= 14.0) {
+ $mapping[SYNC_POOMMAIL2_UMCALLERID] = array ( self::STREAMER_VAR => "umcallerid");
+ $mapping[SYNC_POOMMAIL2_UMUSERNOTES] = array ( self::STREAMER_VAR => "umusernotes");
+ $mapping[SYNC_POOMMAIL2_CONVERSATIONID] = array ( self::STREAMER_VAR => "conversationid");
+ $mapping[SYNC_POOMMAIL2_CONVERSATIONINDEX] = array ( self::STREAMER_VAR => "conversationindex");
+ $mapping[SYNC_POOMMAIL2_LASTVERBEXECUTED] = array ( self::STREAMER_VAR => "lastverbexecuted");
+ $mapping[SYNC_POOMMAIL2_LASTVERBEXECUTIONTIME] = array ( self::STREAMER_VAR => "lastverbexectime");
+ $mapping[SYNC_POOMMAIL2_RECEIVEDASBCC] = array ( self::STREAMER_VAR => "receivedasbcc");
+ $mapping[SYNC_POOMMAIL2_SENDER] = array ( self::STREAMER_VAR => "sender");
+ $mapping[SYNC_POOMMAIL_CATEGORIES] = array ( self::STREAMER_VAR => "categories",
+ self::STREAMER_ARRAY => SYNC_POOMMAIL_CATEGORY);
+ //TODO bodypart, accountid, rightsmanagementlicense
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncdevicepassword.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncdevicepassword.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncdevicepassword.php (revision 7589)
@@ -0,0 +1,63 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncDevicePassword extends SyncObject {
+ public $password;
+ public $Status;
+
+ public function SyncDevicePassword() {
+ $mapping = array (
+ SYNC_SETTINGS_DEVICEPW => array ( self::STREAMER_VAR => "password"),
+
+ SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE)
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncbaseattachment.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncbaseattachment.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncbaseattachment.php (revision 7589)
@@ -0,0 +1,71 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncBaseAttachment extends SyncObject {
+ public $displayname;
+ public $filereference;
+ public $method;
+ public $estimatedDataSize;
+ public $contentid;
+ public $contentlocation;
+ public $isinline;
+
+ function SyncBaseAttachment() {
+ $mapping = array(
+ SYNC_AIRSYNCBASE_DISPLAYNAME => array (self::STREAMER_VAR => "displayname"),
+ SYNC_AIRSYNCBASE_FILEREFERENCE => array (self::STREAMER_VAR => "filereference"),
+ SYNC_AIRSYNCBASE_METHOD => array (self::STREAMER_VAR => "method"),
+ SYNC_AIRSYNCBASE_ESTIMATEDDATASIZE => array (self::STREAMER_VAR => "estimatedDataSize"),
+ SYNC_AIRSYNCBASE_CONTENTID => array (self::STREAMER_VAR => "contentid"),
+ SYNC_AIRSYNCBASE_CONTENTLOCATION => array (self::STREAMER_VAR => "contentlocation"),
+ SYNC_AIRSYNCBASE_ISINLINE => array (self::STREAMER_VAR => "isinline"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncoof.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncoof.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncoof.php (revision 7589)
@@ -0,0 +1,83 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncOOF extends SyncObject {
+ public $oofstate;
+ public $starttime;
+ public $endtime;
+ public $oofmessage = array();
+ public $bodytype;
+ public $Status;
+
+ public function SyncOOF() {
+ $mapping = array (
+ SYNC_SETTINGS_OOFSTATE => array ( self::STREAMER_VAR => "oofstate",
+ self::STREAMER_CHECKS => array( array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2) ))),
+
+ SYNC_SETTINGS_STARTTIME => array ( self::STREAMER_VAR => "starttime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_SETTINGS_ENDTIME => array ( self::STREAMER_VAR => "endtime",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES),
+
+ SYNC_SETTINGS_OOFMESSAGE => array ( self::STREAMER_VAR => "oofmessage",
+ self::STREAMER_TYPE => "SyncOOFMessage",
+ self::STREAMER_PROP => self::STREAMER_TYPE_NO_CONTAINER,
+ self::STREAMER_ARRAY => SYNC_SETTINGS_OOFMESSAGE),
+
+ SYNC_SETTINGS_BODYTYPE => array ( self::STREAMER_VAR => "bodytype",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(SYNC_SETTINGSOOF_BODYTYPE_HTML, ucfirst(strtolower(SYNC_SETTINGSOOF_BODYTYPE_TEXT))) )),
+
+ SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE)
+ );
+
+ parent::SyncObject($mapping);
+ }
+
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncuserinformation.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncuserinformation.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncuserinformation.php (revision 7589)
@@ -0,0 +1,79 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncUserInformation extends SyncObject {
+ public $accountid;
+ public $accountname;
+ public $userdisplayname;
+ public $senddisabled;
+ public $emailaddresses;
+ public $Status;
+
+ public function SyncUserInformation() {
+ $mapping = array (
+ SYNC_SETTINGS_ACCOUNTID => array ( self::STREAMER_VAR => "accountid"),
+ SYNC_SETTINGS_ACCOUNTNAME => array ( self::STREAMER_VAR => "accountname"),
+ SYNC_SETTINGS_EMAILADDRESSES => array ( self::STREAMER_VAR => "emailaddresses",
+ self::STREAMER_ARRAY => SYNC_SETTINGS_SMPTADDRESS),
+
+ SYNC_SETTINGS_PROP_STATUS => array ( self::STREAMER_VAR => "Status",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_IGNORE)
+ );
+
+ if (Request::GetProtocolVersion() >= 12.1) {
+ $mapping[SYNC_SETTINGS_USERDISPLAYNAME] = array ( self::STREAMER_VAR => "userdisplayname");
+ }
+
+ if (Request::GetProtocolVersion() >= 14.0) {
+ $mapping[SYNC_SETTINGS_SENDDISABLED] = array ( self::STREAMER_VAR => "senddisabled");
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncprovisioning.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncprovisioning.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncprovisioning.php (revision 7589)
@@ -0,0 +1,304 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncProvisioning extends SyncObject {
+ //AS 12.0, 12.1 and 14.0 props
+ public $devpwenabled;
+ public $alphanumpwreq;
+ public $devencenabled;
+ public $pwrecoveryenabled;
+ public $docbrowseenabled;
+ public $attenabled;
+ public $mindevpwlenngth;
+ public $maxinacttimedevlock;
+ public $maxdevpwfailedattempts;
+ public $maxattsize;
+ public $allowsimpledevpw;
+ public $devpwexpiration;
+ public $devpwhistory;
+
+ //AS 12.1 and 14.0 props
+ public $allostoragecard;
+ public $allowcam;
+ public $reqdevenc;
+ public $allowunsignedapps;
+ public $allowunsigninstallpacks;
+ public $mindevcomplexchars;
+ public $allowwifi;
+ public $allowtextmessaging;
+ public $allowpopimapemail;
+ public $allowbluetooth;
+ public $allowirda;
+ public $reqmansyncroam;
+ public $allowdesktopsync;
+ public $maxcalagefilter;
+ public $allowhtmlemail;
+ public $maxemailagefilter;
+ public $maxemailbodytruncsize;
+ public $maxemailhtmlbodytruncsize;
+ public $reqsignedsmimemessages;
+ public $reqencsmimemessages;
+ public $reqsignedsmimealgorithm;
+ public $reqencsmimealgorithm;
+ public $allowsmimeencalgneg;
+ public $allowsmimesoftcerts;
+ public $allowbrowser;
+ public $allowconsumeremail;
+ public $allowremotedesk;
+ public $allowinternetsharing;
+ public $unapprovedinromapplist;
+ public $approvedapplist;
+
+ function SyncProvisioning() {
+ $mapping = array (
+ SYNC_PROVISION_DEVPWENABLED => array ( self::STREAMER_VAR => "devpwenabled",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALPHANUMPWREQ => array ( self::STREAMER_VAR => "alphanumpwreq",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_PWRECOVERYENABLED => array ( self::STREAMER_VAR => "pwrecoveryenabled",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_DEVENCENABLED => array ( self::STREAMER_VAR => "devencenabled",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_DOCBROWSEENABLED => array ( self::STREAMER_VAR => "docbrowseenabled"), // depricated
+ SYNC_PROVISION_ATTENABLED => array ( self::STREAMER_VAR => "attenabled",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_MINDEVPWLENGTH => array ( self::STREAMER_VAR => "mindevpwlenngth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 0,
+ self::STREAMER_CHECK_CMPLOWER => 17 )),
+
+ SYNC_PROVISION_MAXINACTTIMEDEVLOCK => array ( self::STREAMER_VAR => "maxinacttimedevlock"),
+ SYNC_PROVISION_MAXDEVPWFAILEDATTEMPTS => array ( self::STREAMER_VAR => "maxdevpwfailedattempts",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => 3,
+ self::STREAMER_CHECK_CMPLOWER => 17 )),
+
+ SYNC_PROVISION_MAXATTSIZE => array ( self::STREAMER_VAR => "maxattsize",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY,
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1 )),
+
+ SYNC_PROVISION_ALLOWSIMPLEDEVPW => array ( self::STREAMER_VAR => "allowsimpledevpw",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_DEVPWEXPIRATION => array ( self::STREAMER_VAR => "devpwexpiration",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1 )),
+
+ SYNC_PROVISION_DEVPWHISTORY => array ( self::STREAMER_VAR => "devpwhistory",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1 )),
+ );
+
+ if(Request::GetProtocolVersion() >= 12.1) {
+ $mapping += array (
+ SYNC_PROVISION_ALLOWSTORAGECARD => array ( self::STREAMER_VAR => "allostoragecard",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWCAM => array ( self::STREAMER_VAR => "allowcam",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_REQDEVENC => array ( self::STREAMER_VAR => "reqdevenc",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWUNSIGNEDAPPS => array ( self::STREAMER_VAR => "allowunsignedapps",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWUNSIGNEDINSTALLATIONPACKAGES => array ( self::STREAMER_VAR => "allowunsigninstallpacks",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_MINDEVPWCOMPLEXCHARS => array ( self::STREAMER_VAR => "mindevcomplexchars",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(1,2,3,4) )),
+
+ SYNC_PROVISION_ALLOWWIFI => array ( self::STREAMER_VAR => "allowwifi",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWTEXTMESSAGING => array ( self::STREAMER_VAR => "allowtextmessaging",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWPOPIMAPEMAIL => array ( self::STREAMER_VAR => "allowpopimapemail",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWBLUETOOTH => array ( self::STREAMER_VAR => "allowbluetooth",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2) )),
+
+ SYNC_PROVISION_ALLOWIRDA => array ( self::STREAMER_VAR => "allowirda",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_REQMANUALSYNCWHENROAM => array ( self::STREAMER_VAR => "reqmansyncroam",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWDESKTOPSYNC => array ( self::STREAMER_VAR => "allowdesktopsync",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_MAXCALAGEFILTER => array ( self::STREAMER_VAR => "maxcalagefilter",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,4,5,6,7) )),
+
+ SYNC_PROVISION_ALLOWHTMLEMAIL => array ( self::STREAMER_VAR => "allowhtmlemail",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_MAXEMAILAGEFILTER => array ( self::STREAMER_VAR => "maxemailagefilter",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -1,
+ self::STREAMER_CHECK_CMPLOWER => 6 )),
+
+ SYNC_PROVISION_MAXEMAILBODYTRUNCSIZE => array ( self::STREAMER_VAR => "maxemailbodytruncsize",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -2 )),
+
+ SYNC_PROVISION_MAXEMAILHTMLBODYTRUNCSIZE => array ( self::STREAMER_VAR => "maxemailhtmlbodytruncsize",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_CMPHIGHER => -2 )),
+
+ SYNC_PROVISION_REQSIGNEDSMIMEMESSAGES => array ( self::STREAMER_VAR => "reqsignedsmimemessages",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_REQENCSMIMEMESSAGES => array ( self::STREAMER_VAR => "reqencsmimemessages",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_REQSIGNEDSMIMEALGORITHM => array ( self::STREAMER_VAR => "reqsignedsmimealgorithm",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_REQENCSMIMEALGORITHM => array ( self::STREAMER_VAR => "reqencsmimealgorithm",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2,3,4) )),
+
+ SYNC_PROVISION_ALLOWSMIMEENCALGORITHNEG => array ( self::STREAMER_VAR => "allowsmimeencalgneg",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1,2) )),
+
+ SYNC_PROVISION_ALLOWSMIMESOFTCERTS => array ( self::STREAMER_VAR => "allowsmimesoftcerts",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWBROWSER => array ( self::STREAMER_VAR => "allowbrowser",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWCONSUMEREMAIL => array ( self::STREAMER_VAR => "allowconsumeremail",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWREMOTEDESKTOP => array ( self::STREAMER_VAR => "allowremotedesk",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_ALLOWINTERNETSHARING => array ( self::STREAMER_VAR => "allowinternetsharing",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(0,1) )),
+
+ SYNC_PROVISION_UNAPPROVEDINROMAPPLIST => array ( self::STREAMER_VAR => "unapprovedinromapplist",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY,
+ self::STREAMER_ARRAY => SYNC_PROVISION_APPNAME), //TODO check
+
+ SYNC_PROVISION_APPROVEDAPPLIST => array ( self::STREAMER_VAR => "approvedapplist",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY,
+ self::STREAMER_ARRAY => SYNC_PROVISION_HASH), //TODO check
+ );
+ }
+
+ parent::SyncObject($mapping);
+ }
+
+ public function Load($policies = array()) {
+ if (empty($policies)) {
+ $this->LoadDefaultPolicies();
+ }
+ else foreach ($policies as $p=>$v) {
+ if (!isset($this->mapping[$p])) {
+ ZLog::Write(LOGLEVEL_INFO, sprintf("Policy '%s' not supported by the device, ignoring", substr($p, strpos($p,':')+1)));
+ continue;
+ }
+ ZLog::Write(LOGLEVEL_INFO, sprintf("Policy '%s' enforced with: %s", substr($p, strpos($p,':')+1), Utils::PrintAsString($v)));
+
+ $var = $this->mapping[$p][self::STREAMER_VAR];
+ $this->$var = $v;
+ }
+ }
+
+ public function LoadDefaultPolicies() {
+ //AS 12.0, 12.1 and 14.0 props
+ $this->devpwenabled = 0;
+ $this->alphanumpwreq = 0;
+ $this->devencenabled = 0;
+ $this->pwrecoveryenabled = 0;
+ $this->docbrowseenabled;
+ $this->attenabled = 1;
+ $this->mindevpwlenngth = 4;
+ $this->maxinacttimedevlock = 900;
+ $this->maxdevpwfailedattempts = 8;
+ $this->maxattsize = '';
+ $this->allowsimpledevpw = 1;
+ $this->devpwexpiration = 0;
+ $this->devpwhistory = 0;
+
+ //AS 12.1 and 14.0 props
+ $this->allostoragecard = 1;
+ $this->allowcam = 1;
+ $this->reqdevenc = 0;
+ $this->allowunsignedapps = 1;
+ $this->allowunsigninstallpacks = 1;
+ $this->mindevcomplexchars = 3;
+ $this->allowwifi = 1;
+ $this->allowtextmessaging = 1;
+ $this->allowpopimapemail = 1;
+ $this->allowbluetooth = 2;
+ $this->allowirda = 1;
+ $this->reqmansyncroam = 0;
+ $this->allowdesktopsync = 1;
+ $this->maxcalagefilter = 0;
+ $this->allowhtmlemail = 1;
+ $this->maxemailagefilter = 0;
+ $this->maxemailbodytruncsize = -1;
+ $this->maxemailhtmlbodytruncsize = -1;
+ $this->reqsignedsmimemessages = 0;
+ $this->reqencsmimemessages = 0;
+ $this->reqsignedsmimealgorithm = 0;
+ $this->reqencsmimealgorithm = 0;
+ $this->allowsmimeencalgneg = 2;
+ $this->allowsmimesoftcerts = 1;
+ $this->allowbrowser = 1;
+ $this->allowconsumeremail = 1;
+ $this->allowremotedesk = 1;
+ $this->allowinternetsharing = 1;
+ $this->unapprovedinromapplist = array();
+ $this->approvedapplist = array();
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/syncbasebody.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncbasebody.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncbasebody.php (revision 7589)
@@ -0,0 +1,68 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncBaseBody extends SyncObject {
+ public $type; //Possible types are plain text, html, rtf and mime
+ public $estimatedDataSize;
+ public $truncated;
+ public $data;
+ public $preview;
+
+ function SyncBaseBody() {
+ $mapping = array(
+ SYNC_AIRSYNCBASE_TYPE => array (self::STREAMER_VAR => "type"),
+ SYNC_AIRSYNCBASE_ESTIMATEDDATASIZE => array (self::STREAMER_VAR => "estimatedDataSize"),
+ SYNC_AIRSYNCBASE_TRUNCATED => array (self::STREAMER_VAR => "truncated"),
+ SYNC_AIRSYNCBASE_DATA => array (self::STREAMER_VAR => "data"),
+ );
+ if(Request::GetProtocolVersion() >= 14.0) {
+ $mapping[SYNC_AIRSYNCBASE_PREVIEW] = array (self::STREAMER_VAR => "preview");
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncattachment.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncattachment.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncattachment.php (revision 7589)
@@ -0,0 +1,77 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncAttachment extends SyncObject {
+ public $attmethod;
+ public $attsize;
+ public $displayname;
+ public $attname;
+ public $attoid;
+ public $attremoved;
+
+ function SyncAttachment() {
+ $mapping = array(
+ SYNC_POOMMAIL_ATTMETHOD => array ( self::STREAMER_VAR => "attmethod"),
+ SYNC_POOMMAIL_ATTSIZE => array ( self::STREAMER_VAR => "attsize",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETZERO,
+ self::STREAMER_CHECK_CMPHIGHER => -1 )),
+
+ SYNC_POOMMAIL_DISPLAYNAME => array ( self::STREAMER_VAR => "displayname",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)),
+
+ SYNC_POOMMAIL_ATTNAME => array ( self::STREAMER_VAR => "attname",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)),
+
+ SYNC_POOMMAIL_ATTOID => array ( self::STREAMER_VAR => "attoid"),
+ SYNC_POOMMAIL_ATTREMOVED => array ( self::STREAMER_VAR => "attremoved"),
+ );
+
+ parent::SyncObject($mapping);
+ }
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncoofmessage.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncoofmessage.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncoofmessage.php (revision 7589)
@@ -0,0 +1,81 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncOOFMessage extends SyncObject {
+ public $appliesToInternal;
+ public $appliesToExternal;
+ public $appliesToExternalUnknown;
+ public $enabled;
+ public $replymessage;
+ public $bodytype;
+
+ public function SyncOOFMessage() {
+ $mapping = array (
+ //only one of the following 3 apply types will be available
+ SYNC_SETTINGS_APPLIESTOINTERVAL => array ( self::STREAMER_VAR => "appliesToInternal",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY),
+
+ SYNC_SETTINGS_APPLIESTOEXTERNALKNOWN => array ( self::STREAMER_VAR => "appliesToExternal",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY),
+
+ SYNC_SETTINGS_APPLIESTOEXTERNALUNKNOWN => array ( self::STREAMER_VAR => "appliesToExternalUnknown",
+ self::STREAMER_PROP => self::STREAMER_TYPE_SEND_EMPTY),
+
+ SYNC_SETTINGS_ENABLED => array ( self::STREAMER_VAR => "enabled"),
+
+ SYNC_SETTINGS_REPLYMESSAGE => array ( self::STREAMER_VAR => "replymessage"),
+
+ SYNC_SETTINGS_BODYTYPE => array ( self::STREAMER_VAR => "bodytype",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_ONEVALUEOF => array(SYNC_SETTINGSOOF_BODYTYPE_HTML, ucfirst(strtolower(SYNC_SETTINGSOOF_BODYTYPE_TEXT))) )),
+
+ );
+
+ parent::SyncObject($mapping);
+ }
+
+}
+?>
Index: /trunk/zpush/lib/syncobjects/syncattendee.php
===================================================================
--- /trunk/zpush/lib/syncobjects/syncattendee.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/syncattendee.php (revision 7589)
@@ -0,0 +1,71 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+
+class SyncAttendee extends SyncObject {
+ public $email;
+ public $name;
+
+ function SyncAttendee() {
+ $mapping = array(
+ SYNC_POOMCAL_EMAIL => array ( self::STREAMER_VAR => "email",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY)),
+
+ SYNC_POOMCAL_NAME => array ( self::STREAMER_VAR => "name",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_REQUIRED => self::STREAMER_CHECK_SETEMPTY) )
+ );
+
+ if (Request::GetProtocolVersion() >= 12.0) {
+ $mapping[SYNC_POOMCAL_ATTENDEESTATUS] = array ( self::STREAMER_VAR => "attendeestatus");
+ $mapping[SYNC_POOMCAL_ATTENDEETYPE] = array ( self::STREAMER_VAR => "attendeetype");
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/syncobjects/synccontact.php
===================================================================
--- /trunk/zpush/lib/syncobjects/synccontact.php (revision 7589)
+++ /trunk/zpush/lib/syncobjects/synccontact.php (revision 7589)
@@ -0,0 +1,208 @@
+.
+*
+* Consult LICENSE file for details
+************************************************/
+
+class SyncContact extends SyncObject {
+ public $anniversary;
+ public $assistantname;
+ public $assistnamephonenumber;
+ public $birthday;
+ public $body;
+ public $bodysize;
+ public $bodytruncated;
+ public $business2phonenumber;
+ public $businesscity;
+ public $businesscountry;
+ public $businesspostalcode;
+ public $businessstate;
+ public $businessstreet;
+ public $businessfaxnumber;
+ public $businessphonenumber;
+ public $carphonenumber;
+ public $children;
+ public $companyname;
+ public $department;
+ public $email1address;
+ public $email2address;
+ public $email3address;
+ public $fileas;
+ public $firstname;
+ public $home2phonenumber;
+ public $homecity;
+ public $homecountry;
+ public $homepostalcode;
+ public $homestate;
+ public $homestreet;
+ public $homefaxnumber;
+ public $homephonenumber;
+ public $jobtitle;
+ public $lastname;
+ public $middlename;
+ public $mobilephonenumber;
+ public $officelocation;
+ public $othercity;
+ public $othercountry;
+ public $otherpostalcode;
+ public $otherstate;
+ public $otherstreet;
+ public $pagernumber;
+ public $radiophonenumber;
+ public $spouse;
+ public $suffix;
+ public $title;
+ public $webpage;
+ public $yomicompanyname;
+ public $yomifirstname;
+ public $yomilastname;
+ public $rtf;
+ public $picture;
+ public $categories;
+
+ // AS 2.5 props
+ public $customerid;
+ public $governmentid;
+ public $imaddress;
+ public $imaddress2;
+ public $imaddress3;
+ public $managername;
+ public $companymainphone;
+ public $accountname;
+ public $nickname;
+ public $mms;
+
+ function SyncContact() {
+ $mapping = array (
+ SYNC_POOMCONTACTS_ANNIVERSARY => array ( self::STREAMER_VAR => "anniversary",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES ),
+
+ SYNC_POOMCONTACTS_ASSISTANTNAME => array ( self::STREAMER_VAR => "assistantname"),
+ SYNC_POOMCONTACTS_ASSISTNAMEPHONENUMBER => array ( self::STREAMER_VAR => "assistnamephonenumber"),
+ SYNC_POOMCONTACTS_BIRTHDAY => array ( self::STREAMER_VAR => "birthday",
+ self::STREAMER_TYPE => self::STREAMER_TYPE_DATE_DASHES ),
+
+ SYNC_POOMCONTACTS_BODY => array ( self::STREAMER_VAR => "body"),
+ SYNC_POOMCONTACTS_BODYSIZE => array ( self::STREAMER_VAR => "bodysize"),
+ SYNC_POOMCONTACTS_BODYTRUNCATED => array ( self::STREAMER_VAR => "bodytruncated"),
+ SYNC_POOMCONTACTS_BUSINESS2PHONENUMBER => array ( self::STREAMER_VAR => "business2phonenumber"),
+ SYNC_POOMCONTACTS_BUSINESSCITY => array ( self::STREAMER_VAR => "businesscity"),
+ SYNC_POOMCONTACTS_BUSINESSCOUNTRY => array ( self::STREAMER_VAR => "businesscountry"),
+ SYNC_POOMCONTACTS_BUSINESSPOSTALCODE => array ( self::STREAMER_VAR => "businesspostalcode"),
+ SYNC_POOMCONTACTS_BUSINESSSTATE => array ( self::STREAMER_VAR => "businessstate"),
+ SYNC_POOMCONTACTS_BUSINESSSTREET => array ( self::STREAMER_VAR => "businessstreet"),
+ SYNC_POOMCONTACTS_BUSINESSFAXNUMBER => array ( self::STREAMER_VAR => "businessfaxnumber"),
+ SYNC_POOMCONTACTS_BUSINESSPHONENUMBER => array ( self::STREAMER_VAR => "businessphonenumber"),
+ SYNC_POOMCONTACTS_CARPHONENUMBER => array ( self::STREAMER_VAR => "carphonenumber"),
+ SYNC_POOMCONTACTS_CHILDREN => array ( self::STREAMER_VAR => "children",
+ self::STREAMER_ARRAY => SYNC_POOMCONTACTS_CHILD ),
+
+ SYNC_POOMCONTACTS_COMPANYNAME => array ( self::STREAMER_VAR => "companyname"),
+ SYNC_POOMCONTACTS_DEPARTMENT => array ( self::STREAMER_VAR => "department"),
+ SYNC_POOMCONTACTS_EMAIL1ADDRESS => array ( self::STREAMER_VAR => "email1address"),
+ SYNC_POOMCONTACTS_EMAIL2ADDRESS => array ( self::STREAMER_VAR => "email2address"),
+ SYNC_POOMCONTACTS_EMAIL3ADDRESS => array ( self::STREAMER_VAR => "email3address"),
+ SYNC_POOMCONTACTS_FILEAS => array ( self::STREAMER_VAR => "fileas"),
+ SYNC_POOMCONTACTS_FIRSTNAME => array ( self::STREAMER_VAR => "firstname"),
+ SYNC_POOMCONTACTS_HOME2PHONENUMBER => array ( self::STREAMER_VAR => "home2phonenumber"),
+ SYNC_POOMCONTACTS_HOMECITY => array ( self::STREAMER_VAR => "homecity"),
+ SYNC_POOMCONTACTS_HOMECOUNTRY => array ( self::STREAMER_VAR => "homecountry"),
+ SYNC_POOMCONTACTS_HOMEPOSTALCODE => array ( self::STREAMER_VAR => "homepostalcode"),
+ SYNC_POOMCONTACTS_HOMESTATE => array ( self::STREAMER_VAR => "homestate"),
+ SYNC_POOMCONTACTS_HOMESTREET => array ( self::STREAMER_VAR => "homestreet"),
+ SYNC_POOMCONTACTS_HOMEFAXNUMBER => array ( self::STREAMER_VAR => "homefaxnumber"),
+ SYNC_POOMCONTACTS_HOMEPHONENUMBER => array ( self::STREAMER_VAR => "homephonenumber"),
+ SYNC_POOMCONTACTS_JOBTITLE => array ( self::STREAMER_VAR => "jobtitle"),
+ SYNC_POOMCONTACTS_LASTNAME => array ( self::STREAMER_VAR => "lastname"),
+ SYNC_POOMCONTACTS_MIDDLENAME => array ( self::STREAMER_VAR => "middlename"),
+ SYNC_POOMCONTACTS_MOBILEPHONENUMBER => array ( self::STREAMER_VAR => "mobilephonenumber"),
+ SYNC_POOMCONTACTS_OFFICELOCATION => array ( self::STREAMER_VAR => "officelocation"),
+ SYNC_POOMCONTACTS_OTHERCITY => array ( self::STREAMER_VAR => "othercity"),
+ SYNC_POOMCONTACTS_OTHERCOUNTRY => array ( self::STREAMER_VAR => "othercountry"),
+ SYNC_POOMCONTACTS_OTHERPOSTALCODE => array ( self::STREAMER_VAR => "otherpostalcode"),
+ SYNC_POOMCONTACTS_OTHERSTATE => array ( self::STREAMER_VAR => "otherstate"),
+ SYNC_POOMCONTACTS_OTHERSTREET => array ( self::STREAMER_VAR => "otherstreet"),
+ SYNC_POOMCONTACTS_PAGERNUMBER => array ( self::STREAMER_VAR => "pagernumber"),
+ SYNC_POOMCONTACTS_RADIOPHONENUMBER => array ( self::STREAMER_VAR => "radiophonenumber"),
+ SYNC_POOMCONTACTS_SPOUSE => array ( self::STREAMER_VAR => "spouse"),
+ SYNC_POOMCONTACTS_SUFFIX => array ( self::STREAMER_VAR => "suffix"),
+ SYNC_POOMCONTACTS_TITLE => array ( self::STREAMER_VAR => "title"),
+ SYNC_POOMCONTACTS_WEBPAGE => array ( self::STREAMER_VAR => "webpage"),
+ SYNC_POOMCONTACTS_YOMICOMPANYNAME => array ( self::STREAMER_VAR => "yomicompanyname"),
+ SYNC_POOMCONTACTS_YOMIFIRSTNAME => array ( self::STREAMER_VAR => "yomifirstname"),
+ SYNC_POOMCONTACTS_YOMILASTNAME => array ( self::STREAMER_VAR => "yomilastname"),
+ SYNC_POOMCONTACTS_RTF => array ( self::STREAMER_VAR => "rtf"),
+ SYNC_POOMCONTACTS_PICTURE => array ( self::STREAMER_VAR => "picture",
+ self::STREAMER_CHECKS => array( self::STREAMER_CHECK_LENGTHMAX => 49152 )),
+
+ SYNC_POOMCONTACTS_CATEGORIES => array ( self::STREAMER_VAR => "categories",
+ self::STREAMER_ARRAY => SYNC_POOMCONTACTS_CATEGORY ),
+ );
+
+ if (Request::GetProtocolVersion() >= 2.5) {
+ $mapping[SYNC_POOMCONTACTS2_CUSTOMERID] = array ( self::STREAMER_VAR => "customerid");
+ $mapping[SYNC_POOMCONTACTS2_GOVERNMENTID] = array ( self::STREAMER_VAR => "governmentid");
+ $mapping[SYNC_POOMCONTACTS2_IMADDRESS] = array ( self::STREAMER_VAR => "imaddress");
+ $mapping[SYNC_POOMCONTACTS2_IMADDRESS2] = array ( self::STREAMER_VAR => "imaddress2");
+ $mapping[SYNC_POOMCONTACTS2_IMADDRESS3] = array ( self::STREAMER_VAR => "imaddress3");
+ $mapping[SYNC_POOMCONTACTS2_MANAGERNAME] = array ( self::STREAMER_VAR => "managername");
+ $mapping[SYNC_POOMCONTACTS2_COMPANYMAINPHONE] = array ( self::STREAMER_VAR => "companymainphone");
+ $mapping[SYNC_POOMCONTACTS2_ACCOUNTNAME] = array ( self::STREAMER_VAR => "accountname");
+ $mapping[SYNC_POOMCONTACTS2_NICKNAME] = array ( self::STREAMER_VAR => "nickname");
+ $mapping[SYNC_POOMCONTACTS2_MMS] = array ( self::STREAMER_VAR => "mms");
+ }
+
+ if (Request::GetProtocolVersion() >= 12.0) {
+ $mapping[SYNC_AIRSYNCBASE_BODY] = array ( self::STREAMER_VAR => "asbody",
+ self::STREAMER_TYPE => "SyncBaseBody");
+
+ //unset these properties because airsyncbase body and attachments will be used instead
+ unset($mapping[SYNC_POOMCONTACTS_BODY], $mapping[SYNC_POOMCONTACTS_BODYTRUNCATED]);
+ }
+
+ parent::SyncObject($mapping);
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/devicemanager.php
===================================================================
--- /trunk/zpush/lib/core/devicemanager.php (revision 7589)
+++ /trunk/zpush/lib/core/devicemanager.php (revision 7589)
@@ -0,0 +1,783 @@
+.
+*
+* 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;
+
+ 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 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
+ $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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/bodypreference.php
===================================================================
--- /trunk/zpush/lib/core/bodypreference.php (revision 7589)
+++ /trunk/zpush/lib/core/bodypreference.php (revision 7589)
@@ -0,0 +1,68 @@
+.
+*
+* 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);
+ }
+}
+?>
Index: /trunk/zpush/lib/core/contentparameters.php
===================================================================
--- /trunk/zpush/lib/core/contentparameters.php (revision 7589)
+++ /trunk/zpush/lib/core/contentparameters.php (revision 7589)
@@ -0,0 +1,141 @@
+.
+*
+* 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;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/syncparameters.php
===================================================================
--- /trunk/zpush/lib/core/syncparameters.php (revision 7589)
+++ /trunk/zpush/lib/core/syncparameters.php (revision 7589)
@@ -0,0 +1,417 @@
+.
+*
+* 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()
+ );
+
+ /**
+ * 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->GetLastestSyncKey(): '%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 = time();
+
+ return true;
+ }
+
+ /**
+ * Called after the StateObject was unserialized
+ *
+ * @access protected
+ * @return boolean
+ */
+ protected function postUnserialize() {
+ // init with default options
+ $this->UseCPO();
+
+ return true;
+ }
+}
+?>
Index: /trunk/zpush/lib/core/changesmemorywrapper.php
===================================================================
--- /trunk/zpush/lib/core/changesmemorywrapper.php (revision 7589)
+++ /trunk/zpush/lib/core/changesmemorywrapper.php (revision 7589)
@@ -0,0 +1,349 @@
+.
+*
+* 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;
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/hierarchycache.php
===================================================================
--- /trunk/zpush/lib/core/hierarchycache.php (revision 7589)
+++ /trunk/zpush/lib/core/hierarchycache.php (revision 7589)
@@ -0,0 +1,216 @@
+.
+*
+* 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");
+ }
+
+}
+
+?>
Index: /trunk/zpush/lib/core/stateobject.php
===================================================================
--- /trunk/zpush/lib/core/stateobject.php (revision 7589)
+++ /trunk/zpush/lib/core/stateobject.php (revision 7589)
@@ -0,0 +1,268 @@
+.
+*
+* 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");
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/loopdetection.php
===================================================================
--- /trunk/zpush/lib/core/loopdetection.php (revision 7589)
+++ /trunk/zpush/lib/core/loopdetection.php (revision 7589)
@@ -0,0 +1,919 @@
+.
+*
+* 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;
+ }
+
+ /**
+ * 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) {
+ // 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 ($current["uuid"] != $uuid) {
+ ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, uuid changed");
+ $obsolete = true;
+ }
+ 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();
+ }
+}
+
+?>
Index: /trunk/zpush/lib/core/zpush.php
===================================================================
--- /trunk/zpush/lib/core/zpush.php (revision 7589)
+++ /trunk/zpush/lib/core/zpush.php (revision 7589)
@@ -0,0 +1,767 @@
+.
+*
+* 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;
+
+ 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 => false),
+ self::COMMAND_VALIDATECERT => array(self::ASV_1, self::REQUESTHANDLER => false),
+ 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),
+ );
+
+
+
+ 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 (!touch(LOGFILE))
+ throw new FatalMisconfigurationException("The configured LOGFILE can not be modified.");
+
+ if (!touch(LOGERRORFILE))
+ throw new FatalMisconfigurationException("The configured LOGERRORFILE can not be modified.");
+
+ // 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.");
+
+ // 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
+ * @return object implementation of IStateMachine
+ * @throws FatalNotImplementedException
+ */
+ 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();
+ }
+ }
+ return ZPush::$stateMachine;
+ }
+
+ /**
+ * 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');
+ 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 = "". $message . "
";
+ if ($additionalMessage)
+ $additionalMessage .= "
";
+
+ header("Content-type: text/html");
+ print <<
+
+
+
+ Z-Push - Open Source ActiveSync
+ Version $zpush_version
+ $message $additionalMessage
+
+ More information about Z-Push can be found at:
+ Z-Push homepage
+ Z-Push download page at BerliOS
+ Z-Push Bugtracker and Roadmap
+
+ All modifications to this sourcecode must be published and returned to the community.
+ Please see AGPLv3 License for details.
+
+
+