From: Hugo Villeneuve Date: Sat, 27 Feb 2021 19:32:51 +0000 (-0500) Subject: Initial import of v9.0.0.1 X-Git-Url: http://gitweb.hugovil.com/?a=commitdiff_plain;h=92b54af1cedbf2a78b4499c61d012c4b9732e985;p=php-addressbook.git Initial import of v9.0.0.1 --- 92b54af1cedbf2a78b4499c61d012c4b9732e985 diff --git a/.htaccess_mobile_sample b/.htaccess_mobile_sample new file mode 100644 index 0000000..e69de29 diff --git a/_LICENSE.txt b/_LICENSE.txt new file mode 100644 index 0000000..9a5e16f --- /dev/null +++ b/_LICENSE.txt @@ -0,0 +1,661 @@ + 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 +. \ No newline at end of file diff --git a/_RELEASE.txt b/_RELEASE.txt new file mode 100644 index 0000000..6c7dba4 --- /dev/null +++ b/_RELEASE.txt @@ -0,0 +1,102 @@ +PHP Addressbook - 8.1.19 +======================== +- 0000103: Add: Translations for new columns in "index.php" (chatelao) - resolved. +- 0000102: Fix: Import LDIF if it starts with "dn::" instead of "dn:" (chatelao) - resolved. +- 0000101: Fix: Search for (phone) numbers without ajax (chatelao) - resolved. +- 0000100: Fix: vCard empty "amonth" in anniversary date - closed. +- 0000099: Fix: Set correct relativ z-push path to logs + states - closed. +- 0000058: Chg: Upgrade z-push from v2.0.3 to v2.0.4 (chatelao) - closed. + +PHP Addressbook - 8.1.14 +======================== + +Fixes only + +- 0000098: Fix: "$use_sso" issue (chatelao) - closed. + +PHP Addressbook - 8.1.13 +======================== + +Fixes, upgrade z-push to v2.0.2 + +- 0000093: Fix: Disable hybridauth if not configured (chatelao) - closed. +- 0000092: Fix: Upgrade z-push to v2.0.2 - closed. + +PHP Addressbook - 8.1.9 +======================= + +Guess birthday and anniversary. Fix bugs. + +- 0000071: Fix: Human hugarian translation (chatelao) - resolved. +- 0000049: Chg: Move "widget" to "include" (chatelao) - resolved. +- 0000012: Add: History- / Domain-Purge scripts (chatelao) - resolved. +- 0000057: Fix: IP based login does not work (chatelao) - resolved. +- 0000054: Add: Guess birthday and anniversary (chatelao) - resolved. +- 0000055: Chg: Lowercase username (chatelao) - resolved. + +PHP Addressbook - 8.1.8 +======================= +- 0000040: Add: Flag "$beta_features" in "config.php" to enable beta-grade features. (chatelao) - closed. +- 0000047: Fix: cfg.zpush.php extraction errors (chatelao) - closed. +- 0000046: Add: Extract config for z-push "cfg.zpush.php" (chatelao) - closed. + +PHP Addressbook - 8.1.7 +======================= + +Fix major z-push defect. + +- 0000044: Fix: Correct log dir name for z-push from "log" to "logs" (chatelao) - closed. +- 0000020: Chg: Autofocus in first field of "Add" page (chatelao) - closed. + +PHP Addressbook - 8.1.6 +======================= + +Fix some bugs. + +- 0000004: Add "nickname" to exchange interface (chatelao) - closed. +- 0000037: Fix: z-push config.php include path should be relativ (chatelao) - closed. + +PHP Addressbook - 8.1.5 +======================= + +Add photo upload support + +- 0000033: Chg: Rescale images after upload (chatelao) - closed. +- 0000034: Add: Show the photos in the main view "index.php" (chatelao) - closed. +- 0000002: Add: Contact photo upload (chatelao) - closed. +- 0000030: Fix: SQL injection issue (chatelao) - closed. + +PHP Addressbook - 8.1.2 +======================= + +Fix more Bugs from versions 8.x + +- 0000029: Add: Affero License (chatelao) - closed. +- 0000031: Fix: "$read_only" config option works again (chatelao) - closed. + +PHP Addressbook - 8.1.1 +======================= + +Fix urgent bugs from 8.x + +- 0000028: Fix: Remove spaces from "cfg.user.php" (chatelao) - closed. + +PHP Addressbook - 8.1.0 +======================= + +Add Exchange/ActiveSync support with a z-push integration + +- 0000026: Add: ActiveSync Support over z-Push (chatelao) - closed. +- 0000027: Fix: "usertable" default value will not override user value. (chatelao) - closed. + +PHP Addressbook - 8.0.0 +======================= + +Add user management in database. + +- 0000001: Fix: Unstable Login on SF demo page (chatelao) - closed. +- 0000007: Chg: Extend hindi translation (chatelao) - closed. +- 0000022: Add: Ukrainian translation (chatelao) - closed. +- 0000023: Fix: Russian translation (chatelao) - closed. +- 0000024: Add: Company + title guessing (chatelao) - closed. +- 0000025: Add: Database table for user login (chatelao) - closed. diff --git a/_USER_GUIDE.pdf b/_USER_GUIDE.pdf new file mode 100644 index 0000000..242377f Binary files /dev/null and b/_USER_GUIDE.pdf differ diff --git a/_howto/PHPAddressbook on GoDaddy.pdf b/_howto/PHPAddressbook on GoDaddy.pdf new file mode 100644 index 0000000..9c32c92 Binary files /dev/null and b/_howto/PHPAddressbook on GoDaddy.pdf differ diff --git a/_howto/_USER_GUIDE.docx b/_howto/_USER_GUIDE.docx new file mode 100644 index 0000000..f7be061 Binary files /dev/null and b/_howto/_USER_GUIDE.docx differ diff --git a/_sql_upgrades/addressbook_upgrade_v12_v20.sql b/_sql_upgrades/addressbook_upgrade_v12_v20.sql new file mode 100644 index 0000000..b04cbfd --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v12_v20.sql @@ -0,0 +1,23 @@ +ALTER TABLE addressbook ADD address2 text NOT NULL; +ALTER TABLE addressbook ADD phone2 text NOT NULL; + +CREATE TABLE month_lookup ( + bmonth varchar(50) NOT NULL default '', + bmonth_short char(3) NOT NULL default '', + bmonth_num int(2) unsigned NOT NULL default '0' +); + + +INSERT INTO month_lookup VALUES ('', '', 0); +INSERT INTO month_lookup VALUES ('January', 'Jan', 1); +INSERT INTO month_lookup VALUES ('February', 'Feb', 2); +INSERT INTO month_lookup VALUES ('March', 'Mar', 3); +INSERT INTO month_lookup VALUES ('April', 'Apr', 4); +INSERT INTO month_lookup VALUES ('May', 'May', 5); +INSERT INTO month_lookup VALUES ('June', 'Jun', 6); +INSERT INTO month_lookup VALUES ('July', 'Jul', 7); +INSERT INTO month_lookup VALUES ('August', 'Aug', 8); +INSERT INTO month_lookup VALUES ('September', 'Sep', 9); +INSERT INTO month_lookup VALUES ('October', 'Oct', 10); +INSERT INTO month_lookup VALUES ('November', 'Nov', 11); +INSERT INTO month_lookup VALUES ('December', 'Dec', 12); \ No newline at end of file diff --git a/_sql_upgrades/addressbook_upgrade_v26_v30.sql b/_sql_upgrades/addressbook_upgrade_v26_v30.sql new file mode 100644 index 0000000..b03fccb --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v26_v30.sql @@ -0,0 +1,24 @@ +-- +-- Structure for table 'groups' - v2.7+ (optional) +-- +CREATE TABLE `group_list` ( + `group_id` int(9) unsigned NOT NULL auto_increment, + `group_name` varchar(255) NOT NULL default '', + `group_header` mediumtext NOT NULL, + PRIMARY KEY (`group_id`) +); + +-- +-- Structure for table `address_in_groups` - v2.7+ (optional) +-- +CREATE TABLE `address_in_groups` ( + `id` int(9) unsigned NOT NULL default '0', + `group_id` int(9) unsigned NOT NULL default '0', + PRIMARY KEY (`group_id`,`id`) +); + + +-- +-- Test group "Rob" (For: Rob M., Autor of version 1.2). +-- +INSERT INTO `group_list` (group_name, group_header) VALUES ('Rob', '\r\n
Rob

Thanks!
\r\n'); diff --git a/_sql_upgrades/addressbook_upgrade_v30_v31.sql b/_sql_upgrades/addressbook_upgrade_v30_v31.sql new file mode 100644 index 0000000..974a42e --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v30_v31.sql @@ -0,0 +1 @@ +ALTER TABLE `group_list` add column `group_footer` mediumtext NOT NULL; diff --git a/_sql_upgrades/addressbook_upgrade_v31_v32.sql b/_sql_upgrades/addressbook_upgrade_v31_v32.sql new file mode 100644 index 0000000..b0d0dd2 --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v31_v32.sql @@ -0,0 +1 @@ +ALTER TABLE `group_list` add column `group_parent_id` int(9) default NULL after group_id; diff --git a/_sql_upgrades/addressbook_upgrade_v32_v33.sql b/_sql_upgrades/addressbook_upgrade_v32_v33.sql new file mode 100644 index 0000000..4f5607d --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v32_v33.sql @@ -0,0 +1,7 @@ +-- +-- Table upgrade to UTF-8 +-- +ALTER TABLE addressbook CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE group_list CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE address_in_groups CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; +ALTER TABLE month_lookup CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci; diff --git a/_sql_upgrades/addressbook_upgrade_v33_v50.sql b/_sql_upgrades/addressbook_upgrade_v33_v50.sql new file mode 100644 index 0000000..b6f2f64 --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v33_v50.sql @@ -0,0 +1,65 @@ +-- +-- Upgrade from 3.3/3.4 to 5.0 +-- +-- Included database extensions: +-- * Notes, company, homepage fields +-- * User login capability +-- * User preferences storage capability +-- * Change logging capability +-- +-- The features inside the .php code will +-- follow later step-by-step. +-- + +-- +-- "Company" field +-- +ALTER TABLE `addressbook` ADD `company` varchar(255) NULL after lastname; +-- +-- "Fax" field +-- +ALTER TABLE `addressbook` ADD `fax` text NULL after work; +-- +-- "Homepage" field +-- +ALTER TABLE `addressbook` ADD `homepage` text NULL after email2; +-- +-- "Notes" field +-- +ALTER TABLE `addressbook` ADD `notes` mediumtext NULL after phone2; +-- +-- Timestamps +-- +ALTER TABLE `addressbook` ADD `created` DATETIME NULL after `notes`; +ALTER TABLE `addressbook` ADD `modified` DATETIME NULL after `created` ; +-- +-- Authentication / Autorisation +-- +ALTER TABLE `addressbook` ADD `password` VARCHAR( 256 ) NULL after modified; +ALTER TABLE `addressbook` ADD `login` DATE NULL after `password`; +ALTER TABLE `addressbook` ADD `role` VARCHAR( 256 ) NULL after login; + +-- +-- Timestamps +-- +ALTER TABLE `group_list` ADD `created` DATETIME NULL after `group_parent_id`; +ALTER TABLE `group_list` ADD `modified` DATETIME NULL after `created` ; + +-- +-- Timestamps +-- +ALTER TABLE `address_in_groups` ADD `created` DATETIME NULL after `group_id`; +ALTER TABLE `address_in_groups` ADD `deleted` DATETIME NULL after `created` ; + +-- +-- Table for user preferences +-- +CREATE TABLE IF NOT EXISTS `user_prefs` ( + `id` int(9) unsigned NOT NULL, + `pref_key` varchar(255) NOT NULL default '', + `pref_value` varchar(255) NOT NULL default '', + `created` datetime default NULL, + `modified` datetime default NULL, + PRIMARY KEY (`id`,`pref_key`), + KEY `fk_id` (`id`) +) DEFAULT CHARSET=utf8; diff --git a/_sql_upgrades/addressbook_upgrade_v50_v60.sql b/_sql_upgrades/addressbook_upgrade_v50_v60.sql new file mode 100644 index 0000000..f218220 --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v50_v60.sql @@ -0,0 +1,26 @@ +-- +-- Upgrade from 5.0 to 6.0 +-- +-- Included database extensions: +-- * Domain ids for multiple customer on the same DB. +-- * Deprecaction date for "remebered" deletion / alternation. +-- +-- The code to implement this features will follow sonner or later. +-- + +-- +-- Add "domain_id" field, to separate more than one domain. +-- +ALTER TABLE `addressbook` ADD `domain_id` int(9) unsigned NOT NULL default 0 FIRST; +ALTER TABLE `group_list` ADD `domain_id` int(9) unsigned NOT NULL default 0 FIRST; +ALTER TABLE `address_in_groups` ADD `domain_id` int(9) unsigned NOT NULL default 0 FIRST; +ALTER TABLE `user_prefs` ADD `domain_id` int(9) unsigned NOT NULL default 0 FIRST; + +-- +-- Add "deprecated" field, to enable deletion recovery and timeline handling. +-- +ALTER TABLE `addressbook` ADD `deprecated` datetime default NULL AFTER `modified`; +ALTER TABLE `group_list` ADD `deprecated` datetime default NULL AFTER `modified`; +ALTER TABLE `address_in_groups` ADD `deprecated` datetime default NULL AFTER `modified`; +ALTER TABLE `user_prefs` ADD `deprecated` datetime default NULL AFTER `modified`; + diff --git a/_sql_upgrades/addressbook_upgrade_v60_v61.sql b/_sql_upgrades/addressbook_upgrade_v60_v61.sql new file mode 100644 index 0000000..58c3ce4 --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v60_v61.sql @@ -0,0 +1,13 @@ +-- +-- Upgrade from 6.0 to 6.1 +-- + +-- +-- Change the primary key, to enable the timeline handling. +-- +ALTER TABLE `addressbook` CHANGE `id` `id` INT( 9 ) UNSIGNED NOT NULL; +ALTER TABLE `addressbook` DROP PRIMARY KEY; +ALTER TABLE `addressbook` ADD PRIMARY KEY ( `id` , `deprecated` ); + +ALTER TABLE `address_in_groups` DROP PRIMARY KEY; +ALTER TABLE `address_in_groups` ADD PRIMARY KEY ( `group_id`, `id`, `deprecated` ); diff --git a/_sql_upgrades/addressbook_upgrade_v61_v62.sql b/_sql_upgrades/addressbook_upgrade_v61_v62.sql new file mode 100644 index 0000000..7b1313c --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v61_v62.sql @@ -0,0 +1,12 @@ +-- +-- Upgrade from 6.1.x to 6.2.x +-- + +-- +-- Add long, lat, photo and vcard column +-- +ALTER TABLE `addressbook` ADD `addr_long` text default NULL AFTER `address`; +ALTER TABLE `addressbook` ADD `addr_lat` text default NULL AFTER `addr_long`; +ALTER TABLE `addressbook` ADD `addr_status` text default NULL AFTER `addr_lat`; +ALTER TABLE `addressbook` ADD `photo` mediumtext default NULL AFTER `notes`; +ALTER TABLE `addressbook` ADD `x_vcard` mediumtext default NULL AFTER `photo`; diff --git a/_sql_upgrades/addressbook_upgrade_v62_v70.sql b/_sql_upgrades/addressbook_upgrade_v62_v70.sql new file mode 100644 index 0000000..745068d --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v62_v70.sql @@ -0,0 +1,23 @@ +-- +-- Upgrade from 6.2.x to 7.0.x +-- + +-- +-- Add anniversary-fields (aday, amonth, ayear), email3, im, im2, im3, x_activesync +-- +ALTER TABLE `addressbook` ADD `nickname` varchar(255) default NULL AFTER `lastname`; +ALTER TABLE `addressbook` ADD `company` varchar(255) default NULL AFTER `nickname`; +ALTER TABLE `addressbook` ADD `title` varchar(255) default NULL AFTER `company`; + +ALTER TABLE `addressbook` ADD `aday` tinyint(2) default NULL AFTER `byear`; +ALTER TABLE `addressbook` ADD `amonth` varchar(50) default NULL AFTER `aday`; +ALTER TABLE `addressbook` ADD `ayear` varchar(4) default NULL AFTER `amonth`; + +ALTER TABLE `addressbook` ADD `email3` text default NULL AFTER `email2`; + +ALTER TABLE `addressbook` ADD `im` text default NULL AFTER `email3`; +ALTER TABLE `addressbook` ADD `im2` text default NULL AFTER `im`; +ALTER TABLE `addressbook` ADD `im3` text default NULL AFTER `im2`; + +ALTER TABLE `addressbook` DROP PRIMARY KEY; +ALTER TABLE `addressbook` ADD PRIMARY KEY (id,deprecated,domain_id); diff --git a/_sql_upgrades/addressbook_upgrade_v70_v80.sql b/_sql_upgrades/addressbook_upgrade_v70_v80.sql new file mode 100644 index 0000000..fc945ea --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v70_v80.sql @@ -0,0 +1,34 @@ +-- +-- Upgrade from 7.0.x to 8.0.x +-- + +-- +-- Add user management table +-- +CREATE TABLE `user` ( + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `domain_id` int(9) unsigned NOT NULL DEFAULT '0', + `username` char(128) NOT NULL, + `md5_pass` char(128) NOT NULL, + `password_hint` varchar(255) NOT NULL DEFAULT '', + `sso_facebook_uid` varchar(255) DEFAULT NULL, + `sso_google_uid` varchar(255) DEFAULT NULL, + `sso_live_uid` varchar(255) DEFAULT NULL, + `sso_yahoo_uid` varchar(255) DEFAULT NULL, + `lastname` varchar(50) NOT NULL DEFAULT '', + `firstname` varchar(50) NOT NULL DEFAULT '', + `email` varchar(100) NOT NULL DEFAULT '', + `phone` varchar(50) NOT NULL DEFAULT '', + `address1` varchar(100) NOT NULL DEFAULT '', + `address2` varchar(100) NOT NULL DEFAULT '', + `city` varchar(80) NOT NULL DEFAULT '', + `state` varchar(20) NOT NULL DEFAULT '', + `zip` varchar(20) NOT NULL DEFAULT '', + `country` varchar(50) NOT NULL DEFAULT '', + `master_code` char(128) NOT NULL, + `confirmation_code` char(128) DEFAULT NULL, + `pass_reset_code` char(128) DEFAULT NULL, + `status` char(128) NOT NULL DEFAULT 'NEW' COMMENT 'New, Ready, Blocked', + `trials` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`user_id`) +) DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/_sql_upgrades/addressbook_upgrade_v80_v82.sql b/_sql_upgrades/addressbook_upgrade_v80_v82.sql new file mode 100644 index 0000000..a0829ba --- /dev/null +++ b/_sql_upgrades/addressbook_upgrade_v80_v82.sql @@ -0,0 +1,8 @@ + +ALTER TABLE `addressbook` ADD `middlename` varchar(255) default NULL AFTER `firstname`; + +ALTER TABLE `users` ADD `created` DATETIME NULL after `trials`; +ALTER TABLE `users` ADD `modified` DATETIME NULL after `created` ; +ALTER TABLE `users` ADD `deprecated` DATETIME NULL after `modified` ; +UPDATE `users` set created = now(), modified= now(); +UPDATE `users` set created = now(), modified= now(); \ No newline at end of file diff --git a/_sql_utilities/addressbook_constraints.sql b/_sql_utilities/addressbook_constraints.sql new file mode 100644 index 0000000..d1dd713 --- /dev/null +++ b/_sql_utilities/addressbook_constraints.sql @@ -0,0 +1,15 @@ +-- +-- Convert all table to transactional. +-- +alter table addressbook type=innodb; +alter table address_in_groups type=innodb; +alter table group_list type=innodb; +alter table month_lookup type=innodb; + +-- +-- Add foreign keys +-- +ALTER TABLE address_in_groups ADD FOREIGN KEY addr_of_group (domain_id, group_id) + REFERENCES group_list (domain_id, group_id); +ALTER TABLE address_in_groups ADD FOREIGN KEY addr_of_group (domain_id, id) + REFERENCES addressbook (domain_id, id); diff --git a/_sql_utilities/addressbook_vw.sql b/_sql_utilities/addressbook_vw.sql new file mode 100644 index 0000000..226eff5 --- /dev/null +++ b/_sql_utilities/addressbook_vw.sql @@ -0,0 +1,56 @@ +-- +-- Some (optional) views to select the current addresses. +-- +CREATE VIEW vw_address AS select * from addressbook where isnull(deprecated); + +-- +-- Show all deleted fields +-- * May be used to recover "lost" records +-- +CREATE VIEW vw_address_deleted AS +select * from addressbook +where (id,deprecated) in (select max(id), deprecated from addressbook + where deprecated > 0 group by id) + and (id) not in (select id from addressbook + where deprecated is null +group by id) +order by deprecated desc; + + +-- +-- Select records having very similar names in two domains +-- * May be used to merge new fields from imports in. +-- +CREATE VIEW vw_address_match AS +SELECT dst.id AS dst_id + , dst.firstname AS dst_first + , dst.lastname AS dst_last + , dst.domain_id AS dst_domain + , src.id AS src_id,src.firstname AS src_first + , src.lastname AS src_last + , src.domain_id AS src_domain + FROM (vw_address dst join vw_address src) + WHERE ( ( (concat(' ',lcase(replace(replace(src.lastname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(dst.lastname,'-',' '),',',' ')),' %')) + or (concat(' ',lcase(replace(replace(dst.lastname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(src.lastname,'-',' '),',',' ')),' %')) + or (concat(' ',lcase(replace(replace(dst.lastname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(src.firstname,'-',' '),',',' ')),' %'))) + and (src.lastname <> '') and (dst.lastname <> '') + and ( (concat(' ',lcase(replace(replace(src.firstname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(dst.firstname,'-',' '),',',' ')),' %')) + or (concat(' ',lcase(replace(replace(dst.firstname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(src.firstname,'-',' '),',',' ')),' %')) + or (concat(' ',lcase(replace(replace(dst.firstname,'-',' '),',',' ')),' ') like concat('% ',lcase(replace(replace(src.lastname,'-',' '),',',' ')),' %'))) + and (src.firstname <> '') + and (dst.firstname <> '') + and (src.id <> dst.id) + ); + +-- +-- Select records having very similar names in two domains +-- * May be used to merge new fields from imports in. +-- +CREATE VIEW vw_address_restore AS +SELECT max(deprecated) AS deprecated + , id + , firstname + , lastname + , domain_id + FROM vw_address_deleted +GROUP BY id,domain_id order by domain_id,id; diff --git a/_sql_utilities/clean_up_addressbook_sf.sql b/_sql_utilities/clean_up_addressbook_sf.sql new file mode 100644 index 0000000..d74ca66 --- /dev/null +++ b/_sql_utilities/clean_up_addressbook_sf.sql @@ -0,0 +1,19 @@ +delete FROM `addressbook` +WHERE lower(firstname) like '%test%' + OR lower(lastname) like '%test%' + OR lower(firstname) like '%sdf%' + OR lower(lastname) like '%sdf%' + OR lower(firstname) like '%miller%' + OR lower(lastname) like '%miller%' + OR lower(firstname) like '%müller%' + OR lower(lastname) like '%müller%' + OR ( (email = '' or email is null) + AND (email2 = '' or email2 is null)); + +DELETE + FROM address_in_groups + WHERE id not in (select id from addressbook); + +DELETE + FROM group_list + WHERE group_id not in (select group_id from address_in_groups) ; \ No newline at end of file diff --git a/_sql_utilities/clean_up_deleted.sql b/_sql_utilities/clean_up_deleted.sql new file mode 100644 index 0000000..57e4897 --- /dev/null +++ b/_sql_utilities/clean_up_deleted.sql @@ -0,0 +1 @@ +DELETE FROM addressbook where deprecated > 0 \ No newline at end of file diff --git a/_sql_utilities/delete_imported_groups.sql b/_sql_utilities/delete_imported_groups.sql new file mode 100644 index 0000000..f072f5e --- /dev/null +++ b/_sql_utilities/delete_imported_groups.sql @@ -0,0 +1,3 @@ +DELETE FROM `addr_addressbook` WHERE id IN ( SELECT id FROM addr_address_in_groups WHERE group_id IN ( SELECT group_id FROM addr_group_list WHERE group_name LIKE '@IMP%' ) ); +DELETE FROM addr_address_in_groups WHERE group_id IN ( SELECT group_id FROM addr_group_list WHERE group_name LIKE '@IMP%' ); +DELETE FROM addr_group_list WHERE group_name LIKE '@IMP%'; \ No newline at end of file diff --git a/_sql_utilities/drop_addressbook.sql b/_sql_utilities/drop_addressbook.sql new file mode 100644 index 0000000..03aae19 --- /dev/null +++ b/_sql_utilities/drop_addressbook.sql @@ -0,0 +1,8 @@ +DROP TABLE IF EXISTS addressbook ; +DROP TABLE IF EXISTS group_list ; +DROP TABLE IF EXISTS address_in_groups ; +DROP TABLE IF EXISTS user_prefs ; +DROP TABLE IF EXISTS month_lookup ; +DROP VIEW IF EXISTS vw_address ; +DROP VIEW IF EXISTS vw_address_deleted; + diff --git a/_sql_utilities/find_corrupt_address_in_groups.sql b/_sql_utilities/find_corrupt_address_in_groups.sql new file mode 100644 index 0000000..075ec71 --- /dev/null +++ b/_sql_utilities/find_corrupt_address_in_groups.sql @@ -0,0 +1,3 @@ +select * from address_in_groups +where id not in (select id from `addressbook`) + or group_id not in (select group_id from group_list) \ No newline at end of file diff --git a/_sql_utilities/find_double_by_name.sql b/_sql_utilities/find_double_by_name.sql new file mode 100644 index 0000000..534fd25 --- /dev/null +++ b/_sql_utilities/find_double_by_name.sql @@ -0,0 +1,4 @@ +select * from +( +SELECT `firstname`, `lastname`,count(*) cnt FROM `addressbook` group by `firstname`, `lastname` +) abc where cnt > 1 \ No newline at end of file diff --git a/_sql_utilities/lowercase_email.sql b/_sql_utilities/lowercase_email.sql new file mode 100644 index 0000000..2ad4095 --- /dev/null +++ b/_sql_utilities/lowercase_email.sql @@ -0,0 +1,17 @@ + +-- +-- 0. BACKUP YOUR DATABASE FIRST +-- 1. Replace 0 by your domain_id in this file +-- 2. View the number of rows affected. +-- +select * + from addr_addressbook +where domain_id = 0; +-- +-- 3. Execute the deletion if you are REALLY sure +-- +update addr_addressbook + set email = lower(email) + , email2 = lower(email2) + , email3 = lower(email3) +where domain_id = 0; diff --git a/_sql_utilities/purge_domain.sql b/_sql_utilities/purge_domain.sql new file mode 100644 index 0000000..3ec7ec0 --- /dev/null +++ b/_sql_utilities/purge_domain.sql @@ -0,0 +1,20 @@ + +-- +-- 0. BACKUP YOUR DATABASE FIRST +-- 1. Replace 0 by your domain_id in this file +-- 2. View the number of rows affected. +-- +select 'Nr of total ids', count(*) from addr_addressbook where domain_id = 0 +union +select 'Nr of active ids', count(*) from addr_addressbook where domain_id = 0 and deprecated is null +union +select 'Nr of purgable records', count(*) from addr_addressbook where domain_id = 0 and deprecated is not null + +-- +-- 3. Execute the deletion if you are REALLY sure +-- +delete + from addr_addressbook + where deprecated is not null + and deprecated > 0 + and domain_id = 0; diff --git a/_sql_utilities/reset_domain.sql b/_sql_utilities/reset_domain.sql new file mode 100644 index 0000000..b2e1855 --- /dev/null +++ b/_sql_utilities/reset_domain.sql @@ -0,0 +1,18 @@ + +-- +-- 0. BACKUP YOUR DATABASE FIRST +-- 1. Replace 189 by your domain_id in this file +-- 2. View the number of rows affected. +-- +select 'Nr of addresses', count(*) from addr_addressbook where domain_id = 189 +union +select 'Nr of address in groups', count(*) from addr_address_in_groups where domain_id = 189 +union +select 'Nr of groups', count(*) from addr_group_list where domain_id = 189; + +-- +-- 3. Execute the deletion if you are REALLY sure +-- +delete from addr_addressbook where domain_id = 189; +delete from addr_address_in_groups where domain_id = 189; +delete from addr_group_list where domain_id = 189; diff --git a/_sql_utilities/use_innodb.sql.sql b/_sql_utilities/use_innodb.sql.sql new file mode 100644 index 0000000..852d54d --- /dev/null +++ b/_sql_utilities/use_innodb.sql.sql @@ -0,0 +1,4 @@ + +ALTER TABLE addressbook ENGINE = InnoDB; +ALTER TABLE group_list ENGINE = InnoDB; +ALTER TABLE address_in_groups ENGINE = InnoDB; diff --git a/_sql_utilities/utf-8_samples.sql b/_sql_utilities/utf-8_samples.sql new file mode 100644 index 0000000..ab05eed --- /dev/null +++ b/_sql_utilities/utf-8_samples.sql @@ -0,0 +1 @@ +INSERT INTO `addressbook` VALUES(0, 7373, 'حسن علی ', 'جعفری', 'پشمک', '', '', '', '', '', 'حسن علی .جعفری@پشمک', '', '', 0, '-', '', '', '', '', '2010-06-13 18:22:03', '2010-06-13 18:22:03', NULL, NULL, NULL, NULL); diff --git a/addressbook.sql b/addressbook.sql new file mode 100644 index 0000000..d451615 --- /dev/null +++ b/addressbook.sql @@ -0,0 +1,131 @@ +-- +-- Creation script with sample data for "php-addressbook" +-- +-- * You may add table prefixes, if the "$table_prefix" +-- parameter is set in "config.php". +-- +-- $LastChangedDate$ +-- $Rev$ +-- +-- + +CREATE TABLE addressbook ( + domain_id int(9) unsigned NOT NULL default 0, + id int(9) unsigned NOT NULL, + firstname varchar(255) NOT NULL, + middlename varchar(255) NOT NULL, + lastname varchar(255) NOT NULL, + nickname varchar(255) NOT NULL, + company varchar(255) NOT NULL, + title varchar(255) NOT NULL, + address text NOT NULL, + addr_long text, + addr_lat text, + addr_status text, + home text NOT NULL, + mobile text NOT NULL, + work text NOT NULL, + fax text NOT NULL, + email text NOT NULL, + email2 text NOT NULL, + email3 text NOT NULL, + im text NOT NULL, + im2 text NOT NULL, + im3 text NOT NULL, + homepage text NOT NULL, + bday tinyint(2) NOT NULL, + bmonth varchar(50) NOT NULL, + byear varchar(4) NOT NULL, + aday tinyint(2) NOT NULL, + amonth varchar(50) NOT NULL, + ayear varchar(4) NOT NULL, + address2 text NOT NULL, + phone2 text NOT NULL, + notes text NOT NULL, + photo mediumtext, + x_vcard mediumtext, + x_activesync mediumtext, + created datetime default NULL, + modified datetime default NULL, + deprecated datetime default NULL, + password varchar(256) default NULL, + login date default NULL, + role varchar(256) default NULL, + PRIMARY KEY (id,deprecated,domain_id), + KEY deprecated_domain_id_idx (deprecated,domain_id) +) DEFAULT CHARSET=utf8; + +CREATE TABLE group_list ( + `domain_id` int(9) unsigned NOT NULL default 0, + `group_id` int(9) unsigned NOT NULL auto_increment, + `group_parent_id` int(9) default NULL, + `created` datetime default NULL, + `modified` datetime default NULL, + `deprecated` datetime default NULL, + `group_name` varchar(255) NOT NULL default '', + `group_header` mediumtext NOT NULL, + `group_footer` mediumtext NOT NULL, + PRIMARY KEY (group_id,deprecated,domain_id) +) DEFAULT CHARSET=utf8; + +CREATE TABLE address_in_groups ( + `domain_id` int(9) unsigned NOT NULL default 0, + `id` int(9) unsigned NOT NULL default 0, + `group_id` int(9) unsigned NOT NULL default 0, + `created` datetime default NULL, + `modified` datetime default NULL, + `deprecated` datetime default NULL, + PRIMARY KEY (`group_id`,`id`, deprecated) +) DEFAULT CHARSET=utf8; + +CREATE TABLE month_lookup ( + `bmonth` varchar(50) NOT NULL default '', + `bmonth_short` char(3) NOT NULL default '', + `bmonth_num` int(2) unsigned NOT NULL default 0, + PRIMARY KEY (bmonth_num) +) DEFAULT CHARSET=utf8; + +INSERT INTO `month_lookup` VALUES ('', '', 0); +INSERT INTO `month_lookup` VALUES ('January', 'Jan', 1); +INSERT INTO `month_lookup` VALUES ('February', 'Feb', 2); +INSERT INTO `month_lookup` VALUES ('March', 'Mar', 3); +INSERT INTO `month_lookup` VALUES ('April', 'Apr', 4); +INSERT INTO `month_lookup` VALUES ('May', 'May', 5); +INSERT INTO `month_lookup` VALUES ('June', 'Jun', 6); +INSERT INTO `month_lookup` VALUES ('July', 'Jul', 7); +INSERT INTO `month_lookup` VALUES ('August', 'Aug', 8); +INSERT INTO `month_lookup` VALUES ('September', 'Sep', 9); +INSERT INTO `month_lookup` VALUES ('October', 'Oct', 10); +INSERT INTO `month_lookup` VALUES ('November', 'Nov', 11); +INSERT INTO `month_lookup` VALUES ('December', 'Dec', 12); + +CREATE TABLE users ( + `user_id` int(11) NOT NULL AUTO_INCREMENT, + `domain_id` int(9) unsigned NOT NULL DEFAULT '0', + `username` char(128) NOT NULL, + `md5_pass` char(128) NOT NULL, + `password_hint` varchar(255) NOT NULL DEFAULT '', + `sso_facebook_uid` varchar(255) DEFAULT NULL, + `sso_google_uid` varchar(255) DEFAULT NULL, + `sso_live_uid` varchar(255) DEFAULT NULL, + `sso_yahoo_uid` varchar(255) DEFAULT NULL, + `lastname` varchar(50) NOT NULL DEFAULT '', + `firstname` varchar(50) NOT NULL DEFAULT '', + `email` varchar(100) NOT NULL DEFAULT '', + `phone` varchar(50) NOT NULL DEFAULT '', + `address1` varchar(100) NOT NULL DEFAULT '', + `address2` varchar(100) NOT NULL DEFAULT '', + `city` varchar(80) NOT NULL DEFAULT '', + `state` varchar(20) NOT NULL DEFAULT '', + `zip` varchar(20) NOT NULL DEFAULT '', + `country` varchar(50) NOT NULL DEFAULT '', + `master_code` char(128) NOT NULL, + `confirmation_code` char(128) DEFAULT NULL, + `pass_reset_code` char(128) DEFAULT NULL, + `status` char(128) NOT NULL DEFAULT 'NEW' COMMENT 'New, Ready, Blocked', + `trials` int(11) NOT NULL DEFAULT '0', + created datetime default NULL, + modified datetime default NULL, + deprecated datetime default NULL, + PRIMARY KEY (`user_id`) +) DEFAULT CHARSET=utf8; diff --git a/b64img.php b/b64img.php new file mode 100644 index 0000000..ad1b6c7 --- /dev/null +++ b/b64img.php @@ -0,0 +1,50 @@ + + +*/ + +header("Content-Type: image/png"); + +// get amounts and titles from session. +$text = base64_decode($_GET['text']); + +// calculate required width and height of image +$pic_width = strlen($text)*6; +$pic_height = 12; + +// create image +$pic = ImageCreate($pic_width+1,$pic_height+1); + +// allocate colours +$white = ImageColorAllocate($pic,255,255,255); +$grey = ImageColorAllocate($pic,200,200,200); +$lt_grey = ImageColorAllocate($pic,210,210,210); +$black = ImageColorAllocate($pic,0,0,0); +$trans_temp = ImageColorAllocate($pic,254,254,254); +$transparent = ImageColorTransparent($pic,$trans_temp); + +// using isset not !empty, as values could=0, therefore "empty" +if(isset($_GET['r']) && isset($_GET['g']) && isset($_GET['b'])) +{ + $user = ImageColorAllocate($pic,intval($_GET['r']),intval($_GET['g']),intval($_GET['b'])); +} else { + $user = $black; +} + +// transparent fill for background +ImageFilledRectangle($pic,0,0,$pic_width,$pic_height,$trans_temp); + +// draw text +ImageString($pic,2,0,0,$text,$user); + +// output image +ImagePNG($pic); + +// remove image from memory +ImageDestroy($pic); +?> \ No newline at end of file diff --git a/birthdays.php b/birthdays.php new file mode 100644 index 0000000..4758bf0 --- /dev/null +++ b/birthdays.php @@ -0,0 +1,216 @@ + +<?php echo ucfmsg("ADDRESS_BOOK").($group_name != "" ? " ($group_name)":""); ?> + + +

+"; +} + $tablespace = 0; + + $alternate = "2"; + + include ("include/guess.inc.php"); + + function Birthday2vCal($date, $age) { + + global $id, $firstname, $middlename, $lastname, $email, $email2, $email3, $home, $mobile, $work, $byear; + + echo "BEGIN:VEVENT\r\n"; + echo "UID:".date('Y', $date).$id."@php-addressbook.sourceforge.net\r\n"; + echo "DTSTART;VALUE=DATE:".date("Ymd", $date)."\r\n"; + echo "DTEND;VALUE=DATE:".date("Ymd", $date+(24*3600))."\r\n"; + echo "DTSTAMP:".date("Ymd\THi00\Z")."\r\n"; + echo "CREATED:".date("Ymd\THi00\Z")."\r\n"; + echo "LAST-MODIFIED:".date("Ymd\THi00\Z")."\r\n"; + echo "LOCATION:\r\n"; + echo "STATUS:CONFIRMED\r\n"; + + if($age == -1) { + $age = ""; + } else { + $age = "(".$age.")"; + } + echo "SUMMARY:".ucfmsg("BIRTHDAY")." ".trim($firstname.(isset($middlename) ? " ".$middlename:"")." ".$lastname) + ." ".$age."\r\n"; + echo "DESCRIPTION:Mail:\\n- ".$email + ."\\n- ".$email2 + ."\\n- ".$email3 + ."\\n\\n".ucfmsg("TELEPHONE") + .($home != "" ? "\\n- ".$home : "") + .($mobile != "" ? "\\n- ".$mobile : "") + .($work != "" ? "\\n- ".$work : "") + ."\r\n"; + echo "CLASS:PRIVATE\r\n"; + echo "END:VEVENT\r\n"; + } + + $lastmonth = ''; + +$sql=" +SELECT DISTINCT $table.*, $month_lookup.* , +IF ($month_lookup.bmonth_num < MONTH( CURDATE( ) ) + OR $month_lookup.bmonth_num = MONTH( CURDATE( ) ) + AND $table.bday < DAYOFMONTH( CURDATE( ) ) , CONCAT( ' ', YEAR( CURDATE( ) ) +1 ) , '' +) display_year, +IF ( +$month_lookup.bmonth_num < MONTH( CURDATE( ) ) +OR $month_lookup.bmonth_num = MONTH( CURDATE( ) ) +AND $table.bday < DAYOFMONTH( CURDATE( ) ) , $month_lookup.bmonth_num+12, $month_lookup.bmonth_num +)*32+bday prio +FROM $month_lookup, +$base_from_where AND $table.bmonth = $month_lookup.bmonth AND $table.bday > 0 +ORDER BY prio ASC;"; + + $result = mysqli_query($db,$sql); + $resultsnumber = mysqli_num_rows($result); + + while ($myrow = mysqli_fetch_array($result)) + { + $firstname = $myrow["firstname"]; + $id = $myrow["id"]; + $lastname = $myrow["lastname"]; + $middlename = $myrow["middlename"]; + + $email = ($myrow["email"] != "" ? $myrow["email"] : ($myrow["email2"] != "" ? $myrow["email2"] : "")); + $email2 = $myrow["email2"]; + + $home = $myrow["home"]; + $mobile = $myrow["mobile"]; + $work = $myrow["work"]; + + $homepage = $myrow["homepage"]; + + // Phone order home->mobile->work + $phone = ($myrow["home"] != "" ? $myrow["home"] + : ($myrow["mobile"] != "" ? $myrow["mobile"] + : $myrow["work"])); + $phone = str_replace("'", "", + str_replace('/', "", + str_replace(" ", "", + str_replace(".", "", $phone)))); + + $bday = $myrow["bday"]; + $bmonth = $myrow["bmonth"]; + $bmonth_num = $myrow["bmonth_num"]; + $byear = $myrow["byear"]; + $display_year = $myrow["display_year"]; + + // Current year + + $addr = new Address($myrow); + + if($use_ics) { + + // Last year + //* -- commented to reduce traffic + $date = gmmktime(0,0,0,$bmonth_num,$bday,date('Y')-1,0); + Birthday2vCal($date, $addr->getBirthday()->getAge()); + //*/ + + $date = gmmktime(0,0,0,$bmonth_num,$bday,date('Y'),0); + Birthday2vCal($date, $addr->getBirthday()->getAge(1)); + + // Next year + $date = gmmktime(0,0,0,$bmonth_num,$bday,date('Y')+1); + Birthday2vCal($date, $addr->getBirthday()->getAge(2)); + + } else { + + if($lastmonth != $bmonth) { + + $lastmonth = $bmonth; + + if ($tablespace >=1) { + echo "
"; + } else {} + + echo "".ucfmsg(strtoupper($myrow["bmonth"])).$myrow["display_year"].""; + $alternate = "0"; + } + + if ($alternate == "1") { + $color = "even"; + $alternate = "2"; + } else { + $color = "odd"; + $alternate = "1"; + } + + echo ""; + echo "$bday."; + if (!empty($middlename)) { + echo "$middlename $lastname"; + } else { + echo "$lastname"; + } + echo "$firstname"; + $age = $addr->getBirthday()->getAge(1); + echo "$age"; + echo "$email"; + echo "$phone"; + echo "".ucfmsg("; + if(! $read_only) + echo "".ucfmsg("; + echo "vCard"; + + if( substr($phone, 0, 1) == "0" || substr($phone, 0, 3) == "+41") + { + $country = "Switzerland"; + } + else $country = ""; + + if($map_guess) { + if($myrow["address"] != "") + echo " + vCard"; + else echo ""; + } + + if($homepage != "") { + $homepage = (strcasecmp(substr($homepage, 0, strlen("http")),"http")== 0 + ? $homepage + : "http://".$homepage); + echo "$homepage"; + } elseif(($homepage = guessHomepage($email, $email2)) != "") { + echo "".ucfmsg("GUESSED_HOMEPAGE")." ($homepage)"; + } else { + echo ""; + } + + echo "\n"; + $tablespace++; + } + } + +if($use_ics) { + echo "END:VCALENDAR\r\n"; +} else { + echo ""; + include ("include/footer.inc.php"); +} +?> \ No newline at end of file diff --git a/config/cfg.db.php b/config/cfg.db.php new file mode 100644 index 0000000..9afd683 --- /dev/null +++ b/config/cfg.db.php @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/config/cfg.guess.php b/config/cfg.guess.php new file mode 100644 index 0000000..8a204ce --- /dev/null +++ b/config/cfg.guess.php @@ -0,0 +1,158 @@ + array("name" => "pagesblanches.fr" + ,"url" => "http://www.pagesjaunes.fr/pagesblanches/rechercheInverse.do?portail=PJ&numeroTelephone=") + , "+39" => array("name" => "paginebianche.it" + ,"url" => "http://www.paginebianche.it/execute.cgi?btt=1&ts=106&rk=&qs=") + , "+41" => array("name" => "local.ch" + ,"url" => "http://www.local.ch/de/q/?what=") + , "+43" => array("name" => "herold.at" + ,"url" => "http://www.herold.at/servlet/at.herold.sp.servlet.SPWPSearchServlet?searchterm=") + , "+49" => array("name" => "dastelefonbuch.de" + ,"url" => "http://www1.dastelefonbuch.de/Rueckwaerts-Suche.html?cmd=search&kw=") + ); + + // + // Companies postfixes + // + $company_exts = array( "INC" + , "LTD" + , "PLC" + , "HOTEL" + , "MOTEL" + , "REST." + , "RESTAURANT" + + , "SA" + , "SARL" + + , "AB" + + , "AG" + , "AMT " + , "DEPARTEMENT" + , "GMBH" ); + + // + // Title + // + $title_exts = array( + // + // English + // + "ADVISOR" + , "ANALYST" + , "ARCHITECT" + , "ASSISTANT" + , "ASSOCIATE" + , "C\w?O" + , "CHAIR(WO)?MAN" + , "CHIEF" + , "CONSULTANT" + , "COUNSEL" + , "COUNSELOR" + , "\\w*DIRECTOR" + , "\\w*ENGINEER" + , "\\w*EXECUTIVE" + , "\\w*EXPERT" + , "FOUNDER" + , "HEAD" + , "\\w*LEAD(ER)?" + , "\\w*MANAGER" + , "MEMBER" + , "METHODOLOGIST" + , "\\w*OFFICER" + , "OWNER" + , "PRESIDENT" + , "PROPRIETOR" + , "SPECIALIST" + , "SURGEON" + , "WRITER" + + // + // French + // + , "ADJOINT" + , "DÉVELOPPEUR" + , "INTÉGRATEUR" + + // + // German + // + , "\\w*ADMINISTRATOR(IN)" + , "\\w*(ANTWALT|ANTWÄTIN)" + , "\\w*ASSISTENT(IN)?" + , "\\w*ARZT|\\w*ÄRZTIN" + , "\\w*ARCHITEKT(IN)?" + , "\\w*BERATER(IN)?" + , "\\w*BETRIEBS(RAT|RÄTIN)?" + , "\\w*BETREUER(IN)?" + , "\\w*DIREKTOR(IN)" + , "\\w*DOZENT(IN)" + , "\\w*ENTWICKLER(IN)?" + , "\\w*FÜHRER(IN)?" + , "\\w*FÜRSPRECHER(IN)?" + , "\\w*INFORMATIKER(IN)?" + , "\\w*INGENIEUR(IN)?" + , "\\w*INHABER(IN)?" + , "\\w*KURIER(IN)" + , "\\w*LEITER(IN)?" + , "\\w*MITGLIED" + , "\\w*MODERATOR" + , "\\w*PARTNER(IN)?" + , "\\w*PRÄSIDENT(IN)?" + , "\\w*PROFESSOR(IN)" + , "\\w*SEKRETÄR(IN)?" + , "\\w*SPEZIALIST(IN)" + , "\\w*UNTERNEHMER(IN)" + , "\\w*VERANTWORTLICHE(R)?" + , "\\w*VERWALTUNGS(RAT|RÄTIN)" + , "\\w*VORSTEHER(IN)?" + , "\\w*VORSITZENDE(R)?" + + ); + + // List of excluded sites in "Homepage guessing" + $free_mailers = array( "a3.epfl.ch" + , "acm.org" + , "aol.com" + , "bigfoot.com" + , "bluewin.ch" + , "bluemail.ch" + , "email.ch" + , "eml.cc" + , "freesurf.ch" + , "freenet.de" + , "gmail.com" + , "googlemail.com" + , "gmx." + , "hispeed.ch" + , "hotmail." + , "ieee.org" + , "intergga.ch" + , "msn." + , "pobox.com" + , "swissonline.ch" + , "spectraweb.ch" + , "tiscalinet.ch" + , "t-online.de" + , "web.de" + , "yahoo." + ); +?> \ No newline at end of file diff --git a/config/cfg.mail.php b/config/cfg.mail.php new file mode 100644 index 0000000..309b486 --- /dev/null +++ b/config/cfg.mail.php @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/config/cfg.sso.php b/config/cfg.sso.php new file mode 100644 index 0000000..cc0f632 --- /dev/null +++ b/config/cfg.sso.php @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/config/cfg.user.php b/config/cfg.user.php new file mode 100644 index 0000000..e593d5d --- /dev/null +++ b/config/cfg.user.php @@ -0,0 +1,54 @@ + \ No newline at end of file diff --git a/config/cfg.zpush.php b/config/cfg.zpush.php new file mode 100644 index 0000000..62668b7 --- /dev/null +++ b/config/cfg.zpush.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..454d78e --- /dev/null +++ b/config/config.php @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/config/mod_rewrite.sample/.htaccess b/config/mod_rewrite.sample/.htaccess new file mode 100644 index 0000000..b4db3c1 --- /dev/null +++ b/config/mod_rewrite.sample/.htaccess @@ -0,0 +1,10 @@ +Options +FollowSymlinks +RewriteEngine on +RewriteBase / + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^([^/]+)$ $1/ [R] + +RewriteRule ^(all)/(.*) addressbook/$2?%{QUERY_STRING} +RewriteRule ^(test)/(.*) addressbook/$2?%{QUERY_STRING}&fixgroup&group=$1 +RewriteRule ^(readonly-test)/(.*) addressbook/$2?%{QUERY_STRING}&fixgroup&readonly&group=test diff --git a/csv.php b/csv.php new file mode 100644 index 0000000..5378fe0 --- /dev/null +++ b/csv.php @@ -0,0 +1,151 @@ + 0 ? "$day. ":"").($month != null ? $month : "")." $year"); + } else { + $month = $myrow["bmonth_num"]; + add( ($day > 0 ? "$day.":"").($month != null ? "$month." : "")."$year"); + } + + # Home contact + if($zip_pattern != "") + { + $address = ""; + $zip = ""; + $city = ""; + preg_match( "/(.*)(\b".$zip_pattern."\b)(.*)/m" + , str_replace("\r", "", str_replace("\n", ", ", trim($myrow["address"]))), $matches); + if(count($matches) > 1) + $address = preg_replace("/,$/", "", trim($matches[1])); + if(count($matches) > 2) + $zip = $matches[2]; + if(count($matches) > 3) + $city = preg_replace("/^,/", "", trim($matches[3])); + + add($address); + add($zip); + add($city); + } + else add($myrow["address"]); + + # Privat contact + add($myrow["home"]); + add($myrow["mobile"]); + add($myrow["email"]); + + + # Work contact + add($myrow["work"]); + add($myrow["fax"]); + add($myrow["email2"]); + + # 2nd contact + add($myrow["address2"]); + add($myrow["phone2"]); + + if($use_utf_16LE) + print mb_convert_encoding( "\n", 'UTF-16LE', 'UTF-8'); + else + echo "\r\n"; + } + +?> \ No newline at end of file diff --git a/delete.php b/delete.php new file mode 100644 index 0000000..db40e29 --- /dev/null +++ b/delete.php @@ -0,0 +1,30 @@ + + +Delete"; + include ("include/header.inc.php"); + + echo '

Delete record

'; + + if(! $read_only) + { + $sql="SELECT * FROM $base_from_where AND ".$part_sql; + $result = mysqli_query($db,$sql); + $resultsnumber = mysqli_num_rows($result); + + if(! deleteAddresses($part_sql)) { + echo "
Invalid record, sorry but the record no longer exsists
return to home page
"; + } else { + echo "
Record successful deleted
"; + } + } else { + echo "
Deleting is disabled.
\n"; + } + + include ("include/footer.inc.php"); +?> diff --git a/diag.php b/diag.php new file mode 100644 index 0000000..bd1fc24 --- /dev/null +++ b/diag.php @@ -0,0 +1,59 @@ +hasRole("root")) { + echo "Login in with root permissions"; + print_r($user); + exit; + } +?> +<?php echo ucfmsg("ADDRESS_BOOK").($group_name != "" ? " ($group_name)":""); ?> + + +
+
+
+

Self-Checks

+ + + + + + + + + + + + +
TestExpectedResult
DB-Connection
Tables
+ + + +
+

Statistics

+ + +" + ."" + .""; + } +?> +
IDNameCount
".$myrow['domain_id']."".$domain[$myrow['domain_id']]['name']."".$myrow['cnt']."
+ \ No newline at end of file diff --git a/edit.php b/edit.php new file mode 100644 index 0000000..3ea4545 --- /dev/null +++ b/edit.php @@ -0,0 +1,729 @@ + + +Address book <?php echo ($group_name != "" ? "($group_name)":""); ?><?php echo $r["firstname"].(isset($r['middlename']) ? " ".$r['middlename']:"")." ".$r["lastname"]." ".($group_name != "" ? "($group_name)":"")."\n"; ?>'; + // echo ''; + } +} +?> +

+ 0 + || preg_match('/\D{3,}/', $mobile) > 0) { + exit; + } + if( strlen($home) > 15 + || strlen($mobile) > 15) { + exit; + } + } + + $addr['firstname'] = $firstname; + $addr['middlename']= $middlename; + $addr['lastname'] = $lastname; + $addr['nickname'] = $nickname; + $addr['title'] = $title; + $addr['company'] = $company; + $addr['address'] = $address; + $addr['home'] = $home; + $addr['mobile'] = $mobile; + $addr['work'] = $work; + $addr['fax'] = $fax; + $addr['email'] = $email; + $addr['email2'] = $email2; + $addr['email3'] = $email3; + $addr['homepage'] = $homepage; + $addr['bday'] = $bday; + $addr['bmonth'] = $bmonth; + $addr['byear'] = $byear; + $addr['aday'] = $aday; + $addr['amonth'] = $amonth; + $addr['ayear'] = $ayear; + $addr['address2'] = $address2; + $addr['phone2'] = $phone2; + $addr['notes'] = $notes; + + if (isset($_FILES["photo"]) && $_FILES["photo"]["error"] <= 0) { + + $file_tmp_name = $_FILES["photo"]["tmp_name"]; + $file_name = $_FILES["photo"]["name"]; + $photo = new Photo($file_tmp_name); + $photo->scaleToMaxSide(150); + $addr['photo'] = $photo->getBase64(); + + } + + if(isset($table_groups) and $table_groups != "" ) { + if( !$is_fix_group ) { + $g_name = $new_group; + } else { + $g_name = $group_name; + } + saveAddress($addr, $g_name); + + echo "
Information entered into address book."; + echo "
add next or return to home page.
"; + } + +} else + echo "
Editing is disabled.
\n"; + +} +else if($update) +{ + if(! $read_only) + { + $addr['id'] = $id; + $addr['firstname'] = $firstname; + $addr['middlename']= $middlename; + $addr['lastname'] = $lastname; + $addr['nickname'] = $nickname; + $addr['title'] = $title; + $addr['company'] = $company; + $addr['address'] = $address; + $addr['home'] = $home; + $addr['mobile'] = $mobile; + $addr['work'] = $work; + $addr['fax'] = $fax; + $addr['email'] = $email; + $addr['email2'] = $email2; + $addr['email3'] = $email3; + $addr['homepage'] = $homepage; + $addr['bday'] = $bday; + $addr['bmonth'] = $bmonth; + $addr['byear'] = $byear; + $addr['aday'] = $aday; + $addr['amonth'] = $amonth; + $addr['ayear'] = $ayear; + $addr['address2'] = $address2; + $addr['phone2'] = $phone2; + $addr['notes'] = $notes; + + $keep_photo = true; + if(isset($delete_photo)) { + $keep_photo = !$delete_photo; + } + + if(isset($_FILES["photo"]) + && $_FILES["photo"]["error"] <= 0) { + + $file_tmp_name = $_FILES["photo"]["tmp_name"]; + $file_name = $_FILES["photo"]["name"]; + $photo = new Photo($file_tmp_name); + $photo->scaleToMaxSide(150); + $addr['photo'] = $photo->getBase64(); + $keep_photo = false; + } else { + $addr['photo'] = ''; + } + + + if(updateAddress($addr, $keep_photo)) { + echo "
".ucfmsg('ADDRESS_BOOK')." ".msg('UPDATED')."
return to home page
"; + } else { + echo "
".ucfmsg('INVALID')." ID.
return to home page
"; + echo ""; + } + } else + echo "
Editing is disabled.
\n"; +} +else if($id) +{ + if(! $read_only) + { +$result = mysqli_query($db,"SELECT * FROM $base_from_where AND $table.id=$id"); +$myrow = mysqli_fetch_array($result); +?> + +
+ +
+ + + +
+ + +

+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +

+ + +
+ + +
+ + +
+ + +
+ +

+ + +
+ + +
+ + +
+ + +
+ + + + +
+ + + + +
+ +: + + + +
+ */ ?> +
+

+ + +
+ + +
+ + +

+ + +
+
+ + +
+
Editing is disabled.
"; + } + else if( !(isset($_POST['quickskip']) || isset($_POST['quickadd'])) + && (isset($_GET['quickadd']) || isset($_POST['quickadd']) || $quickadd)) + { +?> +
+

+ + +

+
+
+ + + + + +
+ +

+ + + +
+ + +

+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +

+ + +
+ + +
+ + +
+ + +
+ +

+ + +
+ + +
+ + +
+ + +
+ + + + +
+ + + + +
+ + + + +
+ + +
+

+ + +
+ + +
+ + +

+ + +
+ +
Editing is disabled.
"; +} + +include ("include/footer.inc.php"); ?> \ No newline at end of file diff --git a/email_in.php b/email_in.php new file mode 100644 index 0000000..392679e --- /dev/null +++ b/email_in.php @@ -0,0 +1,193 @@ +]*?>.*?@siu', + '@]*?>.*?@siu' + ), + array( + ' ', ' '), + $text ); + + return $result; +} +function br2nl($string){ + $result = $string; + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + $result = str_replace('
', "\r\n", $result); + return $result; +} + +function flattenParts($messageParts, $flattenedParts = array(), $prefix = '', $index = 1, $fullPrefix = true) { + + foreach($messageParts as $part) { + $flattenedParts[$prefix.$index] = $part; + if(isset($part->parts)) { + if($part->type == 2) { + $flattenedParts = flattenParts($part->parts, $flattenedParts, $prefix.$index.'.', 0, false); + } + elseif($fullPrefix) { + $flattenedParts = flattenParts($part->parts, $flattenedParts, $prefix.$index.'.'); + } + else { + $flattenedParts = flattenParts($part->parts, $flattenedParts, $prefix); + } + unset($flattenedParts[$prefix.$index]->parts); + } + $index++; + } + + return $flattenedParts; +} + +function getPart($connection, $messageNumber, $partNumber, $encoding) { + + $data = imap_fetchbody($connection, $messageNumber, $partNumber); + + switch($encoding) { + case 3: return base64_decode($data); // BASE64 + case 4: return imap_qprint($data); // QUOTED_PRINTABLE + default: return $data; // 0: 7BIT, 1: 8BIT, 2: BINARY, 5: OTHER + } +} + +$connection = imap_open ( $mail_box , $mail_user , $mail_pass, !OP_SECURE ) or die("Error: " . imap_last_error()); +$MC = imap_check($connection); +$messageNumber = $MC->Nmsgs; +if($messageNumber == 0) die; + +// +// Check last mail +// +$hds = imap_headerinfo($connection, $messageNumber); +echo nl2br(print_r($hds, true)); + +// Check if mail is authorized +$to = $hds->from[0]->mailbox."@".$hds->from[0]->host; +if(count($mail_accept) > 0 && !in_array($to, $mail_accept) { + + // delete without warning + imap_delete ($connection, "$messageNumber"); + + // process next mail + die; +} + +// +// Process last mail +// +$structure = imap_fetchstructure($connection, $messageNumber); + +if(isset($structure->parts)) { + $flattenedParts = flattenParts($structure->parts); +} else { + $flattenedParts[1] = $structure; +} + +foreach($flattenedParts as $partNumber => $part) { + + switch($part->type) { + case 0: + $message = getPart($connection, $messageNumber, $partNumber, $part->encoding); + + + foreach($part->parameters as $parameter) { + if(strtoupper($parameter->attribute) == "CHARSET") { + $charset = $parameter->value; + echo "
$charset
"; + if(function_exists('mb_convert_encoding')) { + $message = mb_convert_encoding ($message, "UTF-8", strtoupper($charset)); + } else { + $message = utf8_encode($message); + } + } + } + + if($part->subtype == "HTML") { + $message = br2nl($message); + // ToDo: strip " + + + + + + + + + + \ No newline at end of file diff --git a/include/group.class.php b/include/group.class.php new file mode 100644 index 0000000..3b20479 --- /dev/null +++ b/include/group.class.php @@ -0,0 +1,20 @@ +group = $data; + } + +} +?> \ No newline at end of file diff --git a/include/guess.inc.php b/include/guess.inc.php new file mode 100644 index 0000000..14c5001 --- /dev/null +++ b/include/guess.inc.php @@ -0,0 +1,337 @@ + 0) + { + return $homepage; + } + } + + if($email2 != "") { + $homepage = guessOneHomepage($email2); + if(strlen($homepage) > 0) + { + return $homepage; + } + } + + return ""; + +} + +function guessAddressFields($address) { + + global $company_exts, $title_exts, $name_of_months_langs, $name_of_months; + + if(!isset($company_exts)) $company_exts = array(); + if(!isset($title_exts)) $title_exts = array(); + + $new_addr_list = array(); + + $address = preg_replace("/--(-)*/", "", $address); + $address = preg_replace("/__(_)*/", "", $address); + $address = preg_replace("/==(=)*/", "", $address); + // + // Preprocess: + // * Revert "mysqli_real_escape" + // * Split into block (newline, pipe, colon) + // * Remove whitespaces & empty lines + // + $address = stripslashes($address); + $sign_delims = "�*|,-"; + $addr_list = preg_split("/(\n|\s?[".$sign_delims."]\s)/", $address); + for($i = 0; $i < count($addr_list); $i++) { + $addr_line = $addr_list[$i]; + $addr_line = trim($addr_line); + if($addr_line != "" && $addr_line != null) { + $new_addr_list[] = $addr_line; + } + } + $addr_list = $new_addr_list; + + $is_phone = false; + $is_url = false; + + $unparsed_addr_lines = array(); + $phones = array(); + $mails = array(); + // $company = ""; + // $homepage = ""; + + $firstname = ""; + $middlename = ""; + $lastname = ""; + $has_name = false; + + $has_birthday = false; + + for($i = 0; $i < count($addr_list); $i++) { + + $addr_line = $addr_list[$i]; + $addr_line = trim($addr_line); + // $addr_line = preg_replace('/^\p{Z}+|\p{Z}+$/u','',$addr_line); + $addr_line = str_replace("(at)", "@", $addr_line); + $addr_line = str_replace("{at}", "@", $addr_line); + $addr_line = str_replace("'at'", "@", $addr_line); + $addr_line = str_replace("'dot'", ".", $addr_line); + + $keep_line = true; + + if(function_exists("mb_strtoupper")) { + $addr_line_upper = mb_strtoupper($addr_line); + } else { + $addr_line_upper = strtoupper($addr_line); + } + + $guessed_date = date_parse(str_replace($name_of_months_langs, $name_of_months, strtoupper($addr_line))); + + $guessed_year = $guessed_date['year']; + $guessed_month = $guessed_date['month']; + $guessed_day = $guessed_date['day']; + + if( $guessed_month != false + && $guessed_day != false) { + + if(!$has_birthday) { + + $bday = ($guessed_day == false ? '' : $guessed_day); + $bmonth = ($guessed_month == false ? '' : $guessed_month); + $byear = ($guessed_year == false ? '' : $guessed_year); + + $has_birthday = true; + }else { + $aday = ($guessed_day == false ? '' : $guessed_day); + $amonth = ($guessed_month == false ? '' : $guessed_month); + $ayear = ($guessed_year == false ? '' : $guessed_year); + } + $keep_line = false; + + } elseif((!isset($company) || $company == "") + && function_exists("mb_ereg_match") + && mb_ereg_match("(^|.* )(".implode("|", $company_exts).")(\W|$)", $addr_line_upper)) { + $company = $addr_line; + $keep_line = false; + } elseif((!isset($title) || $title == "") + && function_exists("mb_ereg_match") + && mb_ereg_match("(^|.* |.*-)(".implode("|", $title_exts).")(\W|$)", $addr_line_upper)) { + $title = $addr_line; + $keep_line = false; + } else + + // + // fistname Lastname + // + if(! $has_name) { + $names = explode(" ", $addr_line, 2); + $firstname = $names[0]; + $lastname = ""; + $count_names = count($names); + if($count_names > 1) { + if($count_names = 2) { + $lastname = $names[1]; + } else { + $lastname = $names[$count_names - 1]; + for ($i = 1; $i < ($count_names - 1); $i++) { + $middlename .= " " . $names[$i]; + } + $middlename = trim($middlename); + } + } + $keep_line = false; + $has_name = true; + } + + // + // Mail addresses + // + elseif(preg_match("/([A-Za-z0-9\.\-_])*\@([A-Za-z0-9\.\-_])*\.([A-Za-z]){2,3}/", $addr_line, $matches) > 0) { + $mails[] = $matches[0]; + $keep_line = false; + } + + // + // websites: + // * http:// + // * https:// + // * www. + // + elseif(( !isset($homepage) + || $homepage == "") + && preg_match("/(http(s)?:\/\/|www\.)([A-Za-z\.\-_])*\.([A-Za-z]){2,3}([\/A-Za-z\.\-_])*/", $addr_line, $matches) > 0) { + $homepage = $matches[0]; + $homepage = str_replace("http://", "", $homepage); + $keep_line = false; + } + + // + // phone nummers + // + elseif(preg_match("/(\+)?([0-9\(\)])+([0-9\(\) -\/'])*/", $addr_line, $matches) > 0) { + $phone_number = $matches[0]; + + $phone_type = ""; + + // Telefon, Fon, Privat(e), Home + $phone_exts = array("TEL P", "P", "PRIVATE" + ,"TEL H", "H", "HOME" + // , "T", "TEL", "TELEFON", "TELEPHON", "TELEPHONE", "PHONE" + // , "FON" + ); + if(preg_match("/^(".implode("|", $phone_exts).")(\.)?(:)?(\s)+/", $addr_line_upper, $matches) > 0) { + $phone_type = "HOME"; + } + + // Mobil(e), Natel, Cell + $phone_exts = array("TEL M", "M", "MOBIL", "MOBILE", "N", "NATEL", "C", "CELL"); + if(preg_match("/^(".implode("|", $phone_exts).")(\.)?(:)?(\s)+/", $addr_line_upper, $matches) > 0) { + $phone_type = "CELL"; + } + + // Gesch�ft, Business + $phone_exts = array("TEL G", "G", "GESCH�FT", "B", "BUSINESS"); + if(preg_match("/^(".implode("|", $phone_exts).")(\.)?(:)?(\s)+/", $addr_line_upper, $matches) > 0) { + $phone_type = "WORK"; + } + + // Fax, Facsmile + $phone_exts = array("F", "FAX", "TELEFAX"); + if(preg_match("/^(".implode("|", $phone_exts).")(\.)?(:)?(\s)+/", $addr_line_upper, $matches) > 0) { + $phone_type = "FAX"; + } + + if(strlen($phone_number) > 6) { + $phones[$phone_type][] = $phone_number; + $keep_line = false; + } + } + if($keep_line) { + $unparsed_addr_lines[] = $addr_line; + } + } + + // + // Redistribute the phone numbers + // + $phone_types = array('WORK', 'FAX', 'HOME', 'CELL'); + $phone_targets = array('work', 'fax', 'home', 'mobile', 'phone2'); + + for($i = 0; $i < count($phone_types); $i++) { + $phone_type = $phone_types[$i]; + $phone_target = $phone_targets[$i]; + if(isset($phones[$phone_type])) { + $$phone_target = $phones[$phone_type][0]; + } + } + + // Better distribution of "neutral" phone types + // * If fax OR company is set + Business empty => business phone + // Else: Privat or Phone2 + if(isset($phones['']) && count($phones['']) > 0) { + if(isset($phones['FAX']) && count($phones['FAX']) > 0 && !isset($phones['WORK'])) { + $work = $phones[''][0]; + } elseif(!isset($phones['HOME'])) { + $home = $phones[''][0]; + } else { + $phone2 = $phones[''][0]; + } + + $phone_type = ''; + for($j = 1; $j < count($phones[$phone_type]); $j++) { + foreach($phone_targets as $phone_target) { + if(!isset($$phone_target) || $$phone_target == "") { + $$phone_target = $phones[$phone_type][$j]; + break; + } + } + } + } + + for($i = 0; $i < count($phone_types); $i++) { + $phone_type = $phone_types[$i]; + $phone_target = $phone_targets[$i]; + if(isset($phones[$phone_type]) && count($phones[$phone_type]) > 1) { + for($j = 1; $j < count($phones[$phone_type]); $j++) { + foreach($phone_targets as $phone_target) { + if(!isset($$phone_target) || $$phone_target == "") { + $$phone_target = $phones[$phone_type][$j]; + break; + } + } + } + } + } + + if(isset($firstname)) $result['firstname'] = $firstname; + if(isset($middlename)) $result['middlename'] = $middlename; + if(isset($lastname)) $result['lastname'] = $lastname; + if(isset($company)) $result['company'] = $company; + if(isset($title)) $result['title'] = $title; + + $result['address'] = implode("\n", $unparsed_addr_lines); + + if(isset($home)) $result['home'] = $home; + if(isset($mobile)) $result['mobile'] = $mobile; + if(isset($work)) $result['work'] = $work; + if(isset($fax)) $result['fax'] = $fax; + if(isset($phone2)) $result['phone2'] = $phone2; + + if(isset($mails[0])) $result['email'] = $mails[0]; + if(isset($mails[1])) $result['email2'] = $mails[1]; + if(isset($mails[2])) $result['email3'] = $mails[2]; + + if(isset($aday)) $result['aday'] = $aday; + if(isset($amonth)) $result['amonth'] = $name_of_months[$amonth-1]; + if(isset($ayear)) $result['ayear'] = $ayear; + + if(isset($bday)) $result['bday'] = $bday; + if(isset($bmonth)) $result['bmonth'] = $name_of_months[$bmonth-1]; + if(isset($byear)) $result['byear'] = $byear; + + if(isset($homepage)) $result['homepage'] = $homepage; + + return $result; +} + +?> \ No newline at end of file diff --git a/include/header.inc.php b/include/header.inc.php new file mode 100644 index 0000000..b7ebfbb --- /dev/null +++ b/include/header.inc.php @@ -0,0 +1,35 @@ + + + +
+
+ + +
+ +(".$username.") "; ?> + +
+ +(".$_SERVER['REMOTE_ADDR'].")"; ?> + +
+ + +
+ + diff --git a/include/import.common.php b/include/import.common.php new file mode 100644 index 0000000..bdd428e --- /dev/null +++ b/include/import.common.php @@ -0,0 +1,111 @@ += 4) { + + // BOM detection + if (substr($line, 0, 4) == "\0\0\xFE\xFF") $encoding = 'UTF-32BE'; // Big Endian + if (substr($line, 0, 4) == "\xFF\xFE\0\0") $encoding = 'UTF-32LE'; // Little Endian + if (substr($line, 0, 2) == "\xFE\xFF") $encoding = 'UTF-16BE'; // Big Endian + if (substr($line, 0, 2) == "\xFF\xFE") $encoding = 'UTF-16LE'; // Little Endian + if (substr($line, 0, 3) == "\xEF\xBB\xBF") $encoding = 'UTF-8'; + + // Heuristic UTF-16 detection + if ((substr($line, 0, 4) & "\xFF\x00\xFF\x00") == "\0\0\0\0") $encoding = 'UTF-16BE'; // Big Endian + if ((substr($line, 0, 4) & "\x00\xFF\x00\xFF") == "\0\0\0\0") $encoding = 'UTF-16LE'; // Little Endian + } + + $file_lines[$i] = mb_convert_encoding($line, 'UTF-8', $encoding); + } + + // + // Load into memory + // + if(preg_match( "/^dn:/", $file_lines[0] )) { // Is a LDIF-File + $import_type = "LDIF"; + include_once ("import.ldif.php"); + + } elseif(preg_match( "/^BEGIN:VCARD/", $file_lines[0] )) { // Is a vCard-File + $import_type = "VCARD"; + include_once ("import.vcard.php"); + $ivc = new ImportVCards($file_lines); + $ab = $ivc->getResult(); + + } elseif( substr_count($file_lines[0], ';') > 5 // Is a CSV-File + || substr_count($file_lines[0], ',') > 5 + || substr_count($file_lines[0], "\t") > 5) { + $import_type = "CSV"; + include_once ("import.csv.php"); + $icsv = new ImportCsv($file_lines); + $ab = $icsv->getResult(); + + } elseif( strtolower(pathinfo($file_name, PATHINFO_EXTENSION)) == "xls") { + $import_type = "EXCEL"; + include_once ("import.xls.php"); + $ixls = new ImportXls($file_tmp_name); + $ab = $ixls->getResult(); + + } else { + $import_type = "UNKNOWN"; + $ab = array(); + } + +?> \ No newline at end of file diff --git a/include/import.csv.map-nokia.php b/include/import.csv.map-nokia.php new file mode 100644 index 0000000..8d4de5a --- /dev/null +++ b/include/import.csv.map-nokia.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/include/import.csv.map-outlook.php b/include/import.csv.map-outlook.php new file mode 100644 index 0000000..8a9a91d --- /dev/null +++ b/include/import.csv.map-outlook.php @@ -0,0 +1,36 @@ + \ No newline at end of file diff --git a/include/import.csv.map-phpaddr.php b/include/import.csv.map-phpaddr.php new file mode 100644 index 0000000..b45a76a --- /dev/null +++ b/include/import.csv.map-phpaddr.php @@ -0,0 +1,29 @@ + \ No newline at end of file diff --git a/include/import.csv.php b/include/import.csv.php new file mode 100644 index 0000000..2cd271c --- /dev/null +++ b/include/import.csv.php @@ -0,0 +1 @@ + 0 ? $chars[0] : " "); foreach($chars as $char) { $new_count = substr_count($testString, $char); if($new_count > $max_count) { $max_count = $new_count; $the_char = $char; } } return $the_char; } $test_line = $file_lines[0]; // // Detect the most probable delimiter. // $delim = maxChar($delims, $test_line); $quote = maxChar($quotes, $test_line); // // Re-Conncat the file-lines // $input = implode("\n", $file_lines)."\n"; // // Setup and run the parser // include "lib/parsecsv.lib.php"; $csv = new parseCSV(); $csv->delimiter = $delim; $csv->enclosure = $quote; $csv->file_data = &$input; $this->data = $csv->parse_string(); // // Convert the array to addresses // $this->convertToAddresses(); } } ?> \ No newline at end of file diff --git a/include/import.del.php b/include/import.del.php new file mode 100644 index 0000000..f750f1b --- /dev/null +++ b/include/import.del.php @@ -0,0 +1,43 @@ +data as $rec) { + + $count++; + if($count <= $this->col_offset) + continue; + + $keys = array_keys($rec); + if(count($rec) > 0 && !is_int($keys[0])) { + $val = array_values($rec); + $rec = array_merge($rec, $val); + } + + $off = $this->col_offset; + + if(in_array($del_format, array("outlook", "phpaddr", "nokia"))) { + $mapping = $del_format; + } else { + $mapping = "phpaddr"; + } + include "import.csv.map-".$mapping.".php"; + + $this->ab[] = $addr; + } + } + + function getResult() { + return $this->ab; + } +} +?> \ No newline at end of file diff --git a/include/import.ldif.php b/include/import.ldif.php new file mode 100644 index 0000000..e17d2d9 --- /dev/null +++ b/include/import.ldif.php @@ -0,0 +1,110 @@ + \ No newline at end of file diff --git a/include/import.vcard.php b/include/import.vcard.php new file mode 100644 index 0000000..62be002 --- /dev/null +++ b/include/import.vcard.php @@ -0,0 +1,324 @@ + 1) { + foreach(explode(',',$subkey[1]) as $subtype) { + $addr_line['SUBTYPE'][] = $subtype; + } + } + } + } + + // + // Value Analyzer: Replace escape values + // + if( isset($addr_line["ENCODING"]) + && count($addr_line["ENCODING"]) == 1 + && $addr_line["ENCODING"][0] == "QUOTED-PRINTABLE") { + $val = utf8_encode(quoted_printable_decode($val)); + } + $val = str_replace("=0D", "\r", $val); + $val = str_replace("=0A", "\n", $val); + $val = str_replace("\\r", "\r", $val); + $val = str_replace("\\n", "\n", $val); + + $addr_line['VALUE'] = $val; + if(count(explode(';', $val)) > 1) { + $addr_line['SEMI-COLON'] = explode(';', $val); + } + + $res['pkey'] = $pkey; + $res['value'] = $addr_line; + } + return $res; + } + + + function __construct($file_lines) { + +// +// Concat multi-line records (e.g.: photos) +// +$concated_lines = array(); +foreach($file_lines as $file_line) { + $file_line = str_replace("\n", "", $file_line); + $file_line = str_replace("\r", "", $file_line); + if(preg_match('/^ /', $file_line)) { + $concated_lines[count($concated_lines)-1] .= preg_replace('/^ /','', $file_line); + } else { + $concated_lines[] = $file_line; + } +} +$file_lines = $concated_lines; + +// +// Split every line to semi-structured records +// +$addresses = array(); +foreach($file_lines as $vcards_line) { + + // Parse and add a field to the addess. + $res = self::parseLine($vcards_line); + + if(isset($res['pkey'])) { + $address[$res['pkey']][] = $res['value']; + } + + // Init the new address + if($res['key'] == "BEGIN") { + $address = array(); + } + + // Add the address to the list + if($res['key'] == "END") { + $addresses[] = $address; + } +} + +foreach($addresses as $address) { + + $dest_addr = array(); + + foreach($address as $type => $entries) { + + // + // "N" Type, X.520 based, delimiter ";" (5 fields) + // + // Family Name;Given Name;Additional Names;Honorific Prefixes;Honorific Suffixes + // + if($type == "N") { + $dest_addr['lastname'] = $entries[0]['SEMI-COLON'][0]; + $dest_addr['firstname'] = $entries[0]['SEMI-COLON'][1]; + } + + // + // "ADR" Type, delimiter ";" (7 fields) + // + // post office box; the extended address; the street + // address; the locality (e.g., city); the region (e.g., state or + // province); the postal code; the country name + // + if($type == "ADR") { + + foreach($entries as $entry) { + $street = $entry['SEMI-COLON'][2]; + $city = $entry['SEMI-COLON'][3]; + $postal_code = $entry['SEMI-COLON'][5]; + $country = $entry['SEMI-COLON'][6]; + + $dest_address = trim($street."\n".$postal_code." ".$city."\n".$country); + + if(strlen($dest_address) > 0) { + if(self::checkType($entry, 'HOME')) { + $dest_addr['address'] = $dest_address; + } else { + $dest_addr['address2'] = $dest_address; + } + } + } + } + + // + // "EMAIL" e-Mail addresses + // + if($type == "EMAIL") { + + $dest_addr['email'] = $entries[0]['VALUE']; + if(isset($entries[1]['VALUE'])) { + $dest_addr['email2'] = $entries[1]['VALUE']; + } + if(isset($entries[2]['VALUE'])) { + $dest_addr['email3'] = $entries[2]['VALUE']; + } + } + + // + // "URL" homepage + // + if($type == "URL") { + + foreach($entries as $entry) { + if(self::checkType($entry, 'HOME')) { + $dest_addr['homepage'] = $entry['VALUE']; + } + elseif(!isset($dest_addr['homepage'])) + { + $dest_addr['homepage'] = $entry['VALUE']; + } + } + } + + // + // "TEL" Type, X.500 Telephone Number attribute + // + if($type == "TEL") { + + foreach($entries as $entry) { + + // Mapping: + // * Paste value in correct field. + if(self::checkType($entry, 'HOME')) { $dest_addr['home'] = $entry['VALUE']; + } elseif(self::checkType($entry, 'FAX')) { $dest_addr['fax'] = $entry['VALUE']; + } elseif(self::checkType($entry, 'WORK')) { $dest_addr['work'] = $entry['VALUE']; + } elseif(self::checkType($entry, 'CELL')) { $dest_addr['mobile'] = $entry['VALUE']; + } else { $dest_addr['phone2'] = $entry['VALUE']; + } + + } + } + + // + // "BDAY" Type, Birthday + // + // Examples + // + // - BDAY:19960415 + // - BDAY:1996-04-15 + // - BDAY:1953-10-15T23:10:00Z + // - BDAY:1987-09-27T08:30:00-06:00 + // + if($type == "BDAY") { + if(strlen($entries[0]['VALUE']) == 8) { + + $dest_addr['bday'] = ltrim(substr($entries[0]['VALUE'], 6, 2),"0"); + $dest_addr['bmonth'] = MonthToName(ltrim(substr($entries[0]['VALUE'], 4, 2),"0")); + $dest_addr['byear'] = substr($entries[0]['VALUE'], 0, 4); + + } elseif(strlen($entries[0]['VALUE']) >= 10) { + + $date = substr($entries[0]['VALUE'], 0, 10); + $date_parts = explode("-",$date); + + $dest_addr['bday'] = ltrim($date_parts[2],"0"); + $dest_addr['byear'] = $date_parts[0]; + + $dest_addr['bmonth'] = MonthToName($date_parts[1]); + } + } + + if($type == "X-ANNIVERSARY") { + if(strlen($entries[0]['VALUE']) == 8) { + + $dest_addr['aday'] = ltrim(substr($entries[0]['VALUE'], 6, 2),"0"); + $dest_addr['amonth'] = MonthToName(ltrim(substr($entries[0]['VALUE'], 4, 2),"0")); + $dest_addr['ayear'] = substr($entries[0]['VALUE'], 0, 4); + + } elseif(strlen($entries[0]['VALUE']) >= 10) { + + $date = substr($entries[0]['VALUE'], 0, 10); + $date_parts = explode("-",$date); + + $dest_addr['aday'] = ltrim($date_parts[2],"0"); + $dest_addr['ayear'] = $date_parts[0]; + + $dest_addr['amonth'] = MonthToName($date_parts[1]); + } + } + + // + // "ORG" Type, the Company + // + if($type == "ORG") { + $dest_addr['company'] = $entries[0]['VALUE']; + } + + // + // "TITLE" Type, the title + // + if($type == "TITLE") { + $dest_addr['title'] = $entries[0]['VALUE']; + } + + // + // "NOTE" Type, just Notes + // + if($type == "NOTE") { + $dest_addr['notes'] = $entries[0]['VALUE']; + } + + // + // "PHOTO" Type, the photo + // + if($type == "PHOTO") { + $dest_addr['photo'] = $entries[0]['VALUE']; + } + } + $this->ab[] = $dest_addr; + } + } + + function getResult() { + return $this->ab; + } +} +?> \ No newline at end of file diff --git a/include/import.xls.php b/include/import.xls.php new file mode 100644 index 0000000..ee6a730 --- /dev/null +++ b/include/import.xls.php @@ -0,0 +1,30 @@ +data = $xls->sheets[0]['cells']; + + // + // Load the array to address records + // + $this->row_offset = 1; // Skip header + $this->col_offset = 1; // Excel vs. CSV + $this->convertToAddresses(); + } + + function getResult() { + return $this->ab; + } +} +?> \ No newline at end of file diff --git a/include/install.php b/include/install.php new file mode 100644 index 0000000..1fb1ac0 --- /dev/null +++ b/include/install.php @@ -0,0 +1,95 @@ +"; + + // + // Check DB connection + // + $level = error_reporting(); + error_reporting(E_ERROR); + $db = mysqli_connect($_POST['db_host'], $_POST['db_user'], $_POST['db_pass']); + error_reporting($level); + if($db) { + // Write valid values to "cfg.db.php" + file_put_contents("config/cfg.db.php", $cfg); + } else { + contine; + } + mysqli_select_db($db, $_POST['db_name']); + + $sql_ddl_arr = file("addressbook.sql"); + + $search_arr = array("TABLE addressbook" + ,"TABLE month_lookup" + ,"TABLE group_list" + ,"TABLE address_in_groups" + ,"TABLE users" + ); + $replac_arr = array("TABLE ".$_POST['db_tbl_prefix']."addressbook" + ,"TABLE ".$_POST['db_tbl_prefix']."month_lookup" + ,"TABLE ".$_POST['db_tbl_prefix']."group_list" + ,"TABLE ".$_POST['db_tbl_prefix']."address_in_groups" + ,"TABLE ".$_POST['db_tbl_prefix']."users" + ); + + $sql_ddl_arr_pfx = str_replace($search_arr, $replac_arr, $sql_ddl_arr); + $sql_ddl = implode($sql_ddl_arr_pfx); + $sql_ddl_arr = explode(";", $sql_ddl); + + foreach($sql_ddl_arr as $sql) { + mysqli_query($db,$sql); + }; + + // Keep the variables for the first run :-) + $dbserver = $_POST['db_host']; + $dbname = $_POST['db_name']; + $dbuser = $_POST['db_user']; + $dbpass = $_POST['db_pass']; + $table_prefix = $_POST['db_tbl_prefix']; + $keep_history = !($_POST['db_hist'] == ""); + +} +if(!$db) { + include ("include/format.inc.php"); + echo "".ucfmsg("ADDRESS_BOOK").""; +?> + + +
+
+ + +
+
+ '>
+ '>
+ '>
+ '>
+ '>
+ '>
+ +
+ \ No newline at end of file diff --git a/include/login.inc.html b/include/login.inc.html new file mode 100644 index 0000000..56a785b --- /dev/null +++ b/include/login.inc.html @@ -0,0 +1,54 @@ + + +
+
+ + +
+
+
+
+

+
+ + + + +

+
+
+ +

+ +
+ + +
+ + + + + + +


+ + + New features + + + + + +
\ No newline at end of file diff --git a/include/login.inc.php b/include/login.inc.php new file mode 100644 index 0000000..76f0e75 --- /dev/null +++ b/include/login.inc.php @@ -0,0 +1,536 @@ +hasRoles(array($role)); +} + + +// +//---------------- Implementations --------------- +// +class AuthUserConfig implements AuthUser { + + private $name; + private $config; + + function __construct($username, $config) { + $this->name = $username; + $this->config = $config; + } + + function getConfig() { + return $this->config; + } + + function hasRole($rolename) { + + $config = $this->config; + + if( isset($this->config['role']) + && $rolename == $this->config['role']) { + return true; + } + if( isset($this->config['roles']) + && in_array($rolename, $this->config['roles'])) { + return true; + } + return false; + } + + function getDomain() { + if(isset($this->config['domain'])) { + return $this->config['domain']; + } else { + return 0; // the default domain + } + } + function getName() { + return $this->name; + } + + function getGroup() { + if(isset($this->config['group'])) { + return $this->config['group']; + } else { + return ""; // no group + } + } +} + +// +// Login implementations +// +class AuthLoginFactory { + + + static function getBestLogin($required_roles = array()) { + + global $iplist, $blacklist, $userlist, $db, $usertable, $use_sso; + +// if((!isset($login) || !$login->hasRoles()) && isset($userlist)) { // Workaround with PHP 7.1, cause (yet) unknown + if(!isset($login) && isset($userlist)) { + $login = new AuthLoginUserList($userlist); + } + if((!isset($login) || !$login->hasRoles()) && isset($usertable)) { + $login = new AuthLoginDb($db, $usertable); + } + if($use_sso && (!isset($login) || !$login->hasRoles()) && is_dir('hybridauth') + && !(isset($_POST['logout']) && $_POST['logout'] == "yes")) { + $login = new AuthHybrid($db, $usertable); + } + if( (!isset($login) || !$login->hasRoles()) + && isset($iplist) && !(isset($_POST['logout']) && $_POST['logout'] == "yes")) { + if(isset($blacklist)) { + $login = new AuthLoginIP($iplist, $blacklist); + } else { + $login = new AuthLoginIP($iplist); + } + } + if(!isset($iplist) && !isset($userlist)) { + $login = new AuthLoginAlways(); + } + + return $login; + } +} + +abstract class AuthLoginImpl implements AuthLogin { + + protected $user_id; + + function __construct() { + $this->user_id = -1; + } + + public function hasValidUserPass() { + return $this->user_id != -1; + } +} + +class AuthLoginAlways extends AuthLoginImpl { + + function __construct() { + parent::__construct(); + } + + function hasValidUserPass() { + return true; + } + + public function hasRoles($roles = array()) { + return (count($roles) == 0); + } + + public function getUser() { + return new AuthUserConfig("", array()); + } + + public function hasLogout() { + return false; + } +} + +class AuthLoginIP extends AuthLoginImpl { + + private $whitelist; + private $blacklist; + private $ip; + + function __construct($whitelist, $blacklist = array()) { + + parent::__construct(); + + $this->ip = $_SERVER['REMOTE_ADDR']; + $this->whitelist = $whitelist; + $this->blacklist = $blacklist; + } + + function calcMin($sub_range) { + + $sub_range_elements = explode('-',$sub_range); + if(count($sub_range_elements) == 2) { + return $sub_range_elements[0]; + } elseif($sub_range == "*") { + return 0; + } else { + return $sub_range; + } + } + + function calcMax($sub_range) { + + $sub_range_elements = explode('-',$sub_range); + if(count($sub_range_elements) == 2) { + return $sub_range_elements[1]; + } elseif($sub_range == "*") { + return 255; + } else { + return $sub_range; + } + } + + function getIpValue() { + + $result = 0; + $sub_ranges = explode(".", $this->ip); + foreach($sub_ranges as $sub_range) { + $result *= 256; + $result += $sub_range; + } + return $result; + } + + function isInIpRange($range) { + + $sub_ranges = explode(".", $range); + $min = 0; + $max = 0; + foreach($sub_ranges as $sub_range) { + $min = $min * 256; + $min = $min + $this->calcMin($sub_range); + $max = $max * 256; + $max = $max + $this->calcMax($sub_range); + } + return ($this->getIpValue() >= $min) && ($this->getIpValue() <= $max); + } + + function isInIpRanges($ranges) { + + $result = false; + foreach($ranges as $range => $config) { + $result = $this->isInIpRange($range) || $result; + } + return $result; + } + + function getConfigFromIpRange($ranges) { + + $result = false; + foreach($ranges as $range => $config) { + if($this->isInIpRange($range)) { + return $config; + } + } + return $result; + } + + function hasValidUserPass() { + return $this->isInIpRanges($this->whitelist) + && !$this->isInIpRanges($this->blacklist); + } + + function hasRoles($roles = array()) { + if(count($roles) == 0) { + return $this->hasValidUserPass(); + } + } + + public function getUser() { + return new AuthUserConfig($this->ip, $this->getConfigFromIpRange($this->whitelist)); + } + + public function hasLogout() { + return false; + } +} + +abstract class AuthLoginUserPass extends AuthLoginImpl { + + // Authentication stuff + private $ip_date; + private $uin; + protected $username; + protected $md5_pass; + protected $user_cfg; + + function __construct() { + + parent::__construct(); + + if(isset($_SERVER['HTTP_USER_AGENT'])) { + $this->ip_date = $_SERVER['HTTP_USER_AGENT'].date('Y-m'); + } else { + // SimpleText does not send any default user agent + $this->ip_date = $_SERVER['REMOTE_ADDR'].date('Y-m'); + } + $this->uin = (isset($_COOKIE['uin']) ? $_COOKIE['uin'] : ""); + + // + // Handle the logout + // + if(isset($_POST['logout'])) { + setcookie("uin", "logged-out", 0); + setcookie("PHPSESSID", "", 0); + $this->uin = "logged-out"; + } + } + + function finishConstruct() { + $this->uin = $this->genUIN($this->username, $this->md5_pass); + setcookie("uin", $this->getUIN(), 0); + } + + // Create a locally unique, monthly changing cookie value. + function genUIN($username, $md5_pass) { + return md5($username.$md5_pass.$this->ip_date); + } + function getUIN() { + return $this->uin; + } + + function getM5P() { + return $this->md5_pass; + } + + function getIpDate() { + return $this->ip_date; + } + + public function getUserName() { + $username = (isset($_POST['user']) ? $_POST['user'] + : (isset($_GET['user']) ? $_GET['user'] + : (isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] + : ""))); + + return strtolower($username); + } + + public function getPassWord() { + + $password = (isset($_POST['pass']) ? $_POST['pass'] + : (isset($_GET['pass']) ? $_GET['pass'] + : (isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] + : ""))); + + return $password; + } + + public function hasLogout() { + return true; + } + + public function hasRoles($roles = array()) { + + if($this->hasValidUserPass()) { + if(count($roles) == 0) { + return true; + } elseif(isset($this->user_cfg['role'])) { + return in_array($this->user_cfg['role'], $roles); + } elseif(isset($this->user_cfg['roles'])) { + return in_array($this->user_cfg['roles'], $roles); + } else { + return false; + } + } else { + return false; + } + } + + function getUser() { + + if(isset($this->user_cfg)) { + return new AuthUserConfig($this->username, $this->user_cfg); + } else { + return ""; + } + } +} + +class AuthLoginUserList extends AuthLoginUserPass { + + private $userlist; + + function __construct($userlist) { + parent::__construct(); + + $this->userlist = $userlist; + + // + // Search with UIN + // + if($this->getUIN() != "") { + foreach($this->userlist as $username => $config) { + if( array_key_exists('pass', $config) + && $this->genUIN($username, md5($config['pass'])) == $this->getUIN()) { + $this->user_id = $username; + } + } + } + + // + // Check the new user/pass + // + $username = $this->getUserName(); + if(!$this->hasValidUserPass() && $username != "") { + if(array_key_exists($username, $this->userlist) + && $this->userlist[$username]['pass'] == $this->getPassWord()) { + $this->user_id = $username; + } + } + + if($this->user_id != -1) { + $this->user_cfg = $this->userlist[$this->user_id]; + $this->username = $this->user_id; + $this->md5_pass = md5($this->user_cfg['pass']); + } + + $this->finishConstruct(); + } +} + +class AuthLoginDb extends AuthLoginUserPass { + + // return md5($username.$md5_pass.$this->ip_date); + + function __construct($db_conn, $table) { + + parent::__construct(); + + // + // Check if UIN is valid in DB. + // + $cnt = 0; + if($this->getUIN() != "") { + $uin = $this->getUIN(); + $sql = "select * from ".$table + ." where md5(concat(username,md5_pass,'".$this->getIpDate()."'))" + ." = '".mysqli_real_escape_string($db, $uin)."'"; + + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $cnt = mysqli_num_rows($result); + } + + // + // Check if user is valid in DB. + // + if($cnt == 0 && $this->getUserName() != "") { + $username = $this->getUserName(); + $username_lower = strtolower($this->getUserName()); + $md5_pass = md5($this->getPassWord()); + $md5_pass_lower = md5(strtolower($this->getPassWord())); + + $sql = "select user_id, domain_id, username, md5_pass from ".$table + ." where username in ('".mysqli_real_escape_string($username)."','" + .mysqli_real_escape_string($username_lower)."')" + ." and md5_pass in ('".$md5_pass."','".$md5_pass_lower."');"; + + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $cnt = mysqli_num_rows($result); + } + + if($cnt == 1) { + $this->user_id = $rec['user_id']; + $this->username = $rec['username']; + $this->md5_pass = $rec['md5_pass']; + $this->user_cfg = array('domain' => $rec['domain_id']); + } + + $this->finishConstruct(); + } +} + +class AuthHybrid extends AuthLoginDb { + + // return md5($username.$md5_pass.$this->ip_date); + + function __construct($db_conn, $table) { + + parent::__construct($db_conn, $table); + + // + // Check if user is valid in DB. + // + $hybrid_types = array("facebook", "google", "yahoo", "live"); + $provider = $this->getUserName(); + + // create an instance for Hybridauth with the configuration file path as parameter + $hybridauth_config = "hybridauth".DIRECTORY_SEPARATOR."config.php"; + require_once( "hybridauth".DIRECTORY_SEPARATOR."Hybrid".DIRECTORY_SEPARATOR."Auth.php" ); + + $hybridauth = new Hybrid_Auth( $hybridauth_config ); + $loaded_providers = Hybrid_Auth::getConnectedProviders(); + + if($provider == "" && count($loaded_providers) > 0) { + $provider = strtolower($loaded_providers[0]); + } + + if($provider != "" && in_array($provider, $hybrid_types)) { + + try{ + + // try to authenticate the selected $provider + $adapter = $hybridauth->authenticate( $provider ); + + // grab the user profile + $user_profile = $adapter->getUserProfile(); + + // a) Does user with "xxx" = identifier exist? + // -> Yes, then login as user + + // b) Does email of user exist? + // -> No, then create new user + + // c) Does email of user exist? + // -> Yes, ask for regular login. Preset email = login + + $provider_uid = $user_profile->identifier; + $email = $user_profile->email; + + // + // Check if user is valid in DB. + // + $sql = "select user_id, domain_id, username, md5_pass from ".$table + ." where sso_".strtolower($provider)."_uid = '".$provider_uid."';"; + + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $cnt = mysqli_num_rows($result); + + if($cnt == 1) { + $this->user_id = $rec['user_id']; + $this->username = $rec['username']; + $this->md5_pass = $rec['md5_pass']; + $this->user_cfg = array('domain' => $rec['domain_id']); + } + + } catch( Exception $e ){ + // Display the recived error + switch( $e->getCode() ){ + case 0 : $error = "Unspecified error."; break; + case 1 : $error = "Hybriauth configuration error."; break; + case 2 : $error = "Provider not properly configured."; break; + case 3 : $error = "Unknown or disabled provider."; break; + case 4 : $error = "Missing provider application credentials."; break; + case 5 : $error = "Authentification failed. The user has canceled the authentication or the provider refused the connection."; break; + case 6 : $error = "User profile request failed. Most likely the user is not connected to the provider and he should to authenticate again."; + $adapter->logout(); + break; + case 7 : $error = "User not connected to the provider."; + $adapter->logout(); + break; + } + echo $error; + } + } + + $this->finishConstruct(); + } +} +?> \ No newline at end of file diff --git a/include/mailer.inc.php b/include/mailer.inc.php new file mode 100644 index 0000000..f90dcdc --- /dev/null +++ b/include/mailer.inc.php @@ -0,0 +1,35 @@ + \ No newline at end of file diff --git a/include/nav.inc.php b/include/nav.inc.php new file mode 100644 index 0000000..f9221e5 --- /dev/null +++ b/include/nav.inc.php @@ -0,0 +1,48 @@ + +
    +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • +
  • + +
  • +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
  • + +
diff --git a/include/phone.intl_prefixes.php b/include/phone.intl_prefixes.php new file mode 100644 index 0000000..5ac740c --- /dev/null +++ b/include/phone.intl_prefixes.php @@ -0,0 +1,213 @@ + \ No newline at end of file diff --git a/include/photo.class.php b/include/photo.class.php new file mode 100644 index 0000000..4a36fd7 --- /dev/null +++ b/include/photo.class.php @@ -0,0 +1,165 @@ += 3) { + $base64 = $base64[2]; + $base64 = explode(":", $base64); + if(count($base64) >= 2) { + return str_replace(" ", "", $base64[1]); + } + } + return ""; + +} + +function embeddedImg($photo_b64) { + + $base64 = extractImg($photo_b64); + return ($base64 != "" ? 'Embedded Image
' : ""); + +} + +function binaryImg($photo_b64) { + + return base64_decode(extractImg($photo_b64)); + +} + +class Photo { + + var $filename; + var $image; + var $image_type; + + function __construct($filename) { + $this->filename = $filename; + $this->load($filename); + } + + function scaleToMaxSide($max_side) { + if($this->getWidth() > $this->getHeight()) { + $this->resizeToWidth($max_side); + } else { + $this->resizeToHeight($max_side); + } + } + + /* + function setBase64($data) { + $filename = ""; + $this->image_type = IMAGETYPE_JPEG; + $this->image = fread(fopen($filename, "r"), filesize($filename)); + } + */ + + function getBase64() { + $filename = $this->filename; + $this->save($filename); // Save as JPG + $data = fread(fopen($filename, "r"), filesize($filename)); + return 'PHOTO;ENCODING=BASE64;TYPE=JPEG:' + .base64_encode($data); + } + + function load($filename) { + + $image_info = getimagesize($filename); + $this->image_type = $image_info[2]; + if( $this->image_type == IMAGETYPE_JPEG ) { + + $this->image = imagecreatefromjpeg($filename); + } elseif( $this->image_type == IMAGETYPE_GIF ) { + + $this->image = imagecreatefromgif($filename); + } elseif( $this->image_type == IMAGETYPE_PNG ) { + + $this->image = imagecreatefrompng($filename); + } + } + + function save($filename, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) { + + if( $image_type == IMAGETYPE_JPEG ) { + imagejpeg($this->image,$filename,$compression); + } elseif( $image_type == IMAGETYPE_GIF ) { + + imagegif($this->image,$filename); + } elseif( $image_type == IMAGETYPE_PNG ) { + + imagepng($this->image,$filename); + } + if( $permissions != null) { + + chmod($filename,$permissions); + } + } + + function output($image_type=IMAGETYPE_JPEG) { + + if( $image_type == IMAGETYPE_JPEG ) { + imagejpeg($this->image); + } elseif( $image_type == IMAGETYPE_GIF ) { + + imagegif($this->image); + } elseif( $image_type == IMAGETYPE_PNG ) { + + imagepng($this->image); + } + } + + function getWidth() { + + return imagesx($this->image); + } + function getHeight() { + + return imagesy($this->image); + } + function resizeToHeight($height) { + + $ratio = $height / $this->getHeight(); + $width = $this->getWidth() * $ratio; + $this->resize($width,$height); + } + + function resizeToWidth($width) { + $ratio = $width / $this->getWidth(); + $height = $this->getheight() * $ratio; + $this->resize($width,$height); + } + + function scale($scale) { + $width = $this->getWidth() * $scale/100; + $height = $this->getheight() * $scale/100; + $this->resize($width,$height); + } + + function resize($width,$height) { + $new_image = imagecreatetruecolor($width, $height); + imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); + $this->image = $new_image; + } + +} +?> \ No newline at end of file diff --git a/include/prefs.inc.php b/include/prefs.inc.php new file mode 100644 index 0000000..f519d67 --- /dev/null +++ b/include/prefs.inc.php @@ -0,0 +1,40 @@ + \ No newline at end of file diff --git a/include/translations.inc.php b/include/translations.inc.php new file mode 100644 index 0000000..04366c1 --- /dev/null +++ b/include/translations.inc.php @@ -0,0 +1,80 @@ +getDefaultLang(); +$supported_langs = $trans->getSupportedLangs(); +$right_to_left_languages = array('ar', 'fa', 'he'); + +// +// Handle language choice +// +$choose_lang = false; +if(getPref('lang') != NULL) { + $lang = getPref('lang'); +} else { + if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $lang = $trans->getBestAcceptLang($_SERVER['HTTP_ACCEPT_LANGUAGE']); + } else { + $lang = $trans->getBestAcceptLang(array()); + } +} +$trans->setDefaultLang($lang); + +// +// Return if a language is writte from +// right-to-left +// - Default: false +// +function is_right_to_left($language) { + global $trans; + return $trans->isRTL($language); +} + +function msg($value) { + global $trans; + return $trans->msg($value); +} + +function ucfmsg($value) { + global $trans; + return $trans->ucfmsg($value); +} + +// +// Try the best to convert UTF-8 to latin1. +// +function utf8_to_latin1($text) { + + if(function_exists('iconv')) { + setlocale(LC_CTYPE, 'cs_CZ'); + return iconv("UTF-8", "ISO-8859-1//TRANSLIT", $text); + + } else { + return utf8_decode($text); + } +} + +function translateTags($text) { + + global $messages; + + foreach($messages as $key => $translations) { + $text = str_replace("%".$key."%", msg($key), $text); + } + return $text; +} + +?> \ No newline at end of file diff --git a/include/translator.class.php b/include/translator.class.php new file mode 100644 index 0000000..264ac8a --- /dev/null +++ b/include/translator.class.php @@ -0,0 +1,208 @@ +has_mb_strtoupper = function_exists('mb_strtoupper'); + } + + function getSupportedLangs() { + return $this->supported_langs; + } + + function isSupportedLang($lang) { + return in_array($lang, $this->supported_langs); + } + + function getDefaultLang() { + return $this->default_lang; + } + + function setDefaultLang($lang) { + if($this->isSupportedLang($lang)) { + return $this->default_lang = $lang; + } + return $this->default_lang; + } + + function getLangFromLocale($locale) { + return substr($accept_lang, 0, 2); + } + + // + // Find the best accepted languages: + // - Usually from $_SERVER["HTTP_ACCEPT_LANGUAGE"] + // + function getBestAcceptLang($accepted_languages) { + + // Extract all available locales + $accept_languages = explode(',', strtolower($accepted_languages)); + $accepted_languages = array(); + + // Extract foreach locale the "affection" + foreach($accept_languages as $accept_lang) { + $lang_weight = explode("=", $accept_lang); + $lang_weight = (isset($lang_weight[1]) && $lang_weight[1] != "" ? $lang_weight[1] : 1.0); + $lang_name = substr($accept_lang, 0, 2); + + // Memorize the highst acceptance for the language (e.g.: en_us=0.8,en_uk=0.6) + if( !isset($accepted_languages[$lang_name]) + || $accepted_languages[$lang_name] < $lang_weight) { + $accepted_languages[$lang_name] = $lang_weight; + } + } + + // Sort by priorities + arsort($accepted_languages); + + // Return the best matching language + foreach($accepted_languages as $curr_lang => $curr_weight) { + if(in_array($curr_lang, $this->getSupportedLangs())) { + return $curr_lang; + } + } + return $this->getDefaultLang(); + } + + function getLang($lang = "") { + if(in_array($lang, $this->getSupportedLangs())) { + return $lang; + } + return $this->getDefaultLang(); + } + + function isRTL($lang = "") { + return in_array( $this->getLang($lang) + , $this->right_to_left_languages); + } + + abstract function msg($msgid, $lang = ""); + + // + // Uppercase the first character with UTF-8 if possible, + // else try to use "ucfirst". + // + function ucfmsg($value, $lang = "") { + + $lang = $this->getLang($lang); + if(isset($this->ucf_messages[$lang][$value])) { // check cache + $msg = $this->ucf_messages[$lang][$value]; + + } else { // calculate get the best uppercase + + $msg = $this->msg($value, $lang); + + // Multibyte "ucfirst" function + if( $this->has_mb_strtoupper ) { + mb_internal_encoding("UTF-8"); + $msg = mb_strtoupper(mb_substr($msg, 0,1),"UTF-8").mb_substr($msg, 1); + + } else { // Backward compatiblity + $msg = ucfirst($msg); + } + $ucf_messages[$value] = $msg; // write to cache + } + + return $msg; + } +} + +// +// Implement a one-word translator to verify the tests +// +class QuestionTranslator extends Translator { + + function __construct() { + parent::__construct(); + + $this->supported_langs = array("de", "en", "ar"); + $this->setDefaultLang("de"); + } + + // Just translate "address" (en) to "Adresse" (de). + function msg($msgid, $lang = "") { + if($msgid == "ADDRESS") { + if($this->getLang($lang) == "de") { + return "Adresse"; + } elseif($this->getLang($lang) == "ar") { + return "عنوان"; + } else { + return "address"; + } + } else + return $msgid; + } +} + +/* +class ExcelTranslator extends Translator { + +} + + +// Prepare "native gettext" setup +T_setlocale(LC_ALL, $locale); +T_bindtextdomain($domain, $directory); +T_textdomain($domain); +T_bind_textdomain_codeset($domain, 'UTF-8'); +*/ +// Prepare "php-gettext" +require_once('lib/gettext/gettext.inc'); + +class GetTextTranslator extends Translator { + + protected $directory; + protected $domain = 'php-addressbook'; + + function __construct() { + parent::__construct(); + + $this->directory = dirname(__FILE__).'/../translations/LOCALES'; + + // + // Read all supported languages from the directory + // + $d = dir($this->directory); + while (false !== ($entry = $d->read())) { + if(strlen($entry) == 2 && $entry != "..") { + $this->supported_langs[] = $entry; + } + } + $d->close(); + } + + + function msg($msgid, $lang = "") { + + T_setlocale(LC_ALL, $this->getLang($lang)); + T_bindtextdomain( $this->domain + , $this->directory); + T_textdomain($this->domain); + T_bind_textdomain_codeset($this->domain, 'UTF-8'); + + if($msgid == "") { + return ""; + } else { + return T_gettext($msgid); + } + } +} + +?> \ No newline at end of file diff --git a/include/version.inc.php b/include/version.inc.php new file mode 100644 index 0000000..7759e22 --- /dev/null +++ b/include/version.inc.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/include/view.w.php b/include/view.w.php new file mode 100644 index 0000000..80bbf21 --- /dev/null +++ b/include/view.w.php @@ -0,0 +1,201 @@ +"; + if($prefix != "") { + $value = $prefix." ".$value; + } + } + return $value; +} + +function addPhone($phone, $prefix = "") { + + global $default_provider, $providers; + + if($phone != "" && !isset($_GET["print"]) && isset($providers)) { + + $links = array(); + foreach($providers as $pprefix => $provider) { + $pos = strpos ($phone, $pprefix); + $use_default = preg_match('/^0[1-9]/', $phone) == 1; + + if(($pos !== FALSE && $pos == 0) || ($use_default && $pprefix == $default_provider)) { + $links[] = "".$provider['name'].""; + $urls[] = ""; + } + } + if(count($links) == 1) { + $phone = $urls[0].$phone.""; + } + if(count($links) > 1) { + $phone .= " (".implode(", ", $links).")"; + } + } + return add($phone, $prefix); +} + +function addEmail($email) { + + if($email != "") { + + // Add Mailerspecific link + $result = "".$email.""; + + // Add a link to the guess homepage + $homepage = guessOneHomepage($email); + if( !isset($_GET["print"]) && $homepage != "") { + $result .= " (".$homepage.")"; + } + return add($result); + } else return ""; +} + +function addHomepage($homepage) { + + if($homepage != "") { + + // Keep the protocol-prefixs (http/https) + $url = ( strcasecmp(substr($homepage, 0, strlen("http")), "http") == 0 + ? $homepage + : "http://".$homepage); + + // Display the homepage without protocol-prefixs (http/https) + $result = "".str_replace("http://", "", + str_replace("https://", "", $url)).""; + return add($result); + } else return ""; +} + +function addBirthday($bday, $bmonth, $byear, $prefix) { + + // Add the birthday + if($bday != 0 || $bmonth != "-" || $byear != "") { + $month = ucfmsg(strtoupper($bmonth)); + $result = ($bday > 0 ? $bday.". " : "") + .($month != '-' ? $month : "") + .($byear != "" ? " ".$byear : ""); + + // Add the age + $birthday = new Birthday($bday, $bmonth, $byear); + $result .= ( $birthday->getAge() != -1 + ? " (".$birthday->getAge().")" + : ""); + + return add($result, $prefix); + } else return ""; +} + +function addGroup($r, $members, $title = "") { + + $has_members = false; + $result = ""; + foreach($members as $member) { + $has_members = $has_members||($r[$member] != ""); + } + if($has_members) { + $result .= add(" "); + if($title != "") { + $result .= add($title); + } + } + + return $result; +} + +function showOneEntry($r, $only_phone = false) { + + global $db, $table, $table_grp_adr, $table_groups, $print, $is_fix_group, $mail_as_image, $page_ext_qry; + + $view = ""; + $view .= add("".$r['firstname'].(!empty($r['middlename']) ? " ".$r['middlename'] : "")." ".$r['lastname'].""); + $view .= add($r['nickname']); + + $b64 = explode(";", $r['photo']); + if(count($b64) >= 3 && ! $only_phone) { + $b64 = $b64[2]; + $b64 = explode(":", $b64); + if(count($b64) >= 2) { + $b64 = str_replace(" ", "", $b64[1]); + $view .= ($r['photo'] != "" ? 'Embedded Image
' : ""); + } + } + + if(! $only_phone) { + $view .= ($r['title'] != ""?"":"").add($r['title']).($r['title'] != ""?"":""); + $view .= add($r['company']); +// $view .= addGroup($r, array('address')); + $view .= add(str_replace("\n", "
", trim($r["address"]))); + $view .= addGroup($r, array('home','mobile','work','fax')); + } + $view .= addPhone($r['home'], ucfmsg('H:')); + $view .= addPhone($r['mobile'], ucfmsg('M:')); + $view .= addPhone($r['work'], ucfmsg('W:')); + $view .= addPhone($r['fax'], ucfmsg('F:')); + if(! $only_phone) { + + $view .= addGroup($r, array('email','email2','email3','homepage')); + if($mail_as_image) { // B64IMG: Thanks to NelloD + $view .= ($r['email'] != "" ? "
" : ""); + $view .= ($r['email2'] != "" ? "
" : ""); + $view .= ($r['email3'] != "" ? "
" : ""); + } else { + $view .= addEmail($r['email']); + $view .= addEmail($r['email2']); + $view .= addEmail($r['email3']); + } + $view .= addHomepage($r['homepage']); + + $view .= addGroup($r, array('bday','bmonth','byear')); + $view .= addBirthday($r['bday'], $r['bmonth'], $r['byear'], ucfmsg('BIRTHDAY')); + $view .= addBirthday($r['aday'], $r['amonth'], $r['ayear'], ucfmsg('ANNIVERSARY')); + + $view .= addGroup($r, array('address2','phone2')); + $view .= add(str_replace("\n", "
", trim($r['address2']))); + $view .= addGroup($r, array('phone2')); + } + $view .= add($r['phone2'], ucfmsg('P:')); + + if(! $only_phone) { + + // Detect URLs (http://*, www.*) and show as link. + // + // $text = "Hello, http://www.google.com"; + // $new = preg_replace("/(http:\/\/[^\s]+)/", "$1", $test); + // + $view .= ($r['notes'] != "" ? "
".str_replace("\n", "
", trim($r['notes']))."

" : ""); + } + echo $view."\n"; + + if( !isset($print) and !$is_fix_group) { + + $sql = "SELECT DISTINCT $table_groups.group_id, group_name + FROM $table_grp_adr, $table_groups, $table + WHERE $table.id = $table_grp_adr.id + AND $table.id = ".$r['id']." + AND $table_grp_adr.group_id = $table_groups.group_id"; + + $result = mysqli_query($sql, $db); + + $first = true; + while($result && $g = mysqli_fetch_array($result)) { + if($first) + echo "
".ucfmsg('MEMBER_OF').": "; + else + echo ", "; + echo "".$g['group_name'].""; + $first = false; + } + if($first != true) + echo ""; + /* + echo "

"; + echo ucfmsg('MODIFIED') . ": ".$r['modified']; + echo "(".ucfmsg('CREATED') . ": ".$r['created'].")
"; + */ + } +} +?> diff --git a/index.html b/index.html new file mode 100644 index 0000000..0f1f52f --- /dev/null +++ b/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..864b83a --- /dev/null +++ b/index.php @@ -0,0 +1,479 @@ + +<?php echo ucfmsg("ADDRESS_BOOK").($group_name != "" ? " ($group_name)":""); ?> + + +

+
+ +
+ + +
+A | B | C | D | E | F | G | H | I | J | K | L | M | N | O | P | Q | R | S | T | U | V | W | X | Y | Z | ".ucfmsg('ALL')."
" ; +} else { +?> + + + + + +
+ PHP-Addressbook + iPhone-Contacts-Synchronization
+ + www.swiss-addressbook.com +
+ Advertisment: 2 months for free, then just 2$ per month!
+
+
+ + +
+ + onkeyup="filterResults(this)"/> + +
+ + +

+
+getResults(); + $resultsnumber = mysqli_num_rows($result); + + // TBD: Pagination + // http://php.about.com/od/phpwithmysql/ss/php_pagination.htm + + echo ""; + +if(isset($table_groups) and $table_groups != "" and !$is_fix_group) { ?> + + + +

+ +
+ + + +".ucfmsg(strtoupper($col)).""; + } elseif(in_array($col, array("home", "work", "mobile"))) { + echo ""; + } else { + echo ""; + if($col == "edit" && !$read_only) { // row for edit + echo ""; + } + if($col == "details") { + echo ""; + echo ""; + } + } + } +?> + +getData(); + + foreach($myrow as $mycol => $mycolval) { + ${$mycol} = $mycolval; + } + + $email = $addr->firstEMail(); + if($email != "" && $email != $myrow['email2']) { + $email2 = $myrow['email2']; + } else { + $email2 = ""; + } + + // Special value for short phone + $row = ($row == "telephone" ? "phone" : $row); + + if($row == "phone") { + if($full_phone) { + $phone = $addr->firstPhone(); + } else { + $phone = $addr->shortPhone(); + } + } + + switch ($row) { + case "select": + $emails = implode(getMailerDelim(), $addr->getEMails()); + echo ""; + break; + case "first_last": + echo ""; + break; + case "last_first": + echo ""; + break; + case "photo": +// echo ""; +/* + if($photo != "") { + echo ""; + } else { + echo ""; + } +//*/ + echo ""; + break; + case "email": + case "email2": + echo ""; + break; + case "all_phones": + $phones = $addr->shortPhones(); + echo ""; + break; + case "all_emails": + $emails = $addr->getEMails(); + $amails = array(); + foreach($emails as $amail) { + $amails[] = "$amail"; + } + echo ""; + break; + case "address": + echo ""; + break; + case "edit": + echo ""; + if(! $read_only) { + echo ""; + } + break; + case "vcard": + echo ""; + break; + case "map": + if($map_guess) { + if($myrow["address"] != "") + echo ""; + else echo ""; + } elseif($homepage_guess && ($homepage = guessHomepage($email, $email2)) != "") { + echo ""; + } else { + echo ""; + if($map_guess) { + if($myrow["address"] != "") + echo ""; + else echo ""; + } elseif($homepage_guess && ($homepage = guessHomepage($email, $email2)) != "") { + echo ""; + } else { + echo ""; + } +} + + while ($addr = $addresses->nextAddress()) { + + $color = ($alternate++ % 2) ? "odd" : ""; + echo ""; + + if($is_mobile) { + // addRow("select"); + addRow("lastname"); + addRow("firstname"); + // addRow("first_last"); + // addRow("all_phones"); + // addRow("email"); + addRow("edit"); + } else { + foreach($disp_cols as $col) { + addRow($col); + } + } + + echo "\n"; + } + + echo "
".ucfmsg("PHONE_".strtoupper($col))."
$firstname ".(!empty($middlename) ? $middlename." " : "")."$lastname".(!empty($middlename) ? $middlename." " : "")."$lastname $firstname".embeddedImg($photo).""; + echo $addr->getPhoto(); + echo "${$row}".implode("
", $phones)."
".implode("
", $amails)."
".str_replace("\n", "
", $address)."
".ucfmsg(".ucfmsg(vCard + vCard"; + } + break; + case "homepage": + if($homepage != "") { + $homepage = (strcasecmp(substr($homepage, 0, strlen("http")),"http")== 0 + ? $homepage + : "http://".$homepage); + echo "$homepage".ucfmsg("GUESSED_HOMEPAGE")." ($homepage)"; + } + break; + case "details": + echo "vCard + vCard"; + } + + if($homepage != "") { + $homepage = (strcasecmp(substr($homepage, 0, strlen("http")),"http")== 0 + ? $homepage + : "http://".$homepage); + echo "$homepage".ucfmsg("GUESSED_HOMEPAGE")." ($homepage)"; + } + break; + default: // firstname, lastname, home, mobile, work, fax, phone2 + echo "${$row}
"; + echo "  ".ucfmsg("SELECT_ALL")."

"; + if($use_doodle) { + echo "
"; + } + echo "
"; + if(! $read_only) { + echo "
"; + + if(isset($table_groups) and $table_groups != "" and !$is_fix_group) + { + + // -- Remove from group -- + if($group_name != "" and $group_name != "[none]") + { + echo "
"; + } else + echo "
"; + + // -- Add to a group -- + echo "
-"; + echo ""; + + echo "
"; + } + } + echo "
"; + + // Show group footer + if($group_name != "" and $group_myrow['group_footer'] != "") + { + echo "
"; + echo $group_myrow['group_footer']; + echo "
"; + } +?> + + + \ No newline at end of file diff --git a/iphone.css b/iphone.css new file mode 100644 index 0000000..e11f0e7 --- /dev/null +++ b/iphone.css @@ -0,0 +1,17 @@ +body {background-image:none;margin:0;} +#container {width:100%;} +#top {margin:0;height:10px;background:#739fce;padding:4px 8px 0;} +#top a {font-size:75%;} +#header {height:35px;background:#739fce;padding-top:5px;} +#header h1 {margin:0;display:inline;padding:4px;} +#header h1 a {color:#fff;font-size:160%;text-decoration:none;} +#logo, .export {display:none;} + +#nav {margin:0 0 20px;height:25px;width:100%;float:left;border:0;display:inline;padding-top:4px;background:#3b65a1;} +#nav ul li a {color:#fff;padding:0 4px;font-size:75%;} + +#content {margin:0;padding:0 4px;width:98.6%;font-size:85%;} +table th {font-size:95%;} +.msgbox {margin:0 auto;width:80%;} +#footer {margin:25px 0 0;padding:10px 4px;} +#footer li a {font-size:75%;} diff --git a/js/SOURCE b/js/SOURCE new file mode 100644 index 0000000..84f657b --- /dev/null +++ b/js/SOURCE @@ -0,0 +1,3 @@ +See: +* http://www.frequency-decoder.com/2007/11/15/unobtrusive-table-actions-script +* http://www.frequency-decoder.com/2006/09/16/unobtrusive-table-sort-script-revisited/ \ No newline at end of file diff --git a/js/jquery-1.8.2.min.js b/js/jquery-1.8.2.min.js new file mode 100644 index 0000000..bc3fbc8 --- /dev/null +++ b/js/jquery-1.8.2.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
t
",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
","
"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/js/jquery.dataTables.min.js b/js/jquery.dataTables.min.js new file mode 100644 index 0000000..02694a4 --- /dev/null +++ b/js/jquery.dataTables.min.js @@ -0,0 +1,155 @@ +/* + * File: jquery.dataTables.min.js + * Version: 1.9.4 + * Author: Allan Jardine (www.sprymedia.co.uk) + * Info: www.datatables.net + * + * Copyright 2008-2012 Allan Jardine, all rights reserved. + * + * This source file is free software, under either the GPL v2 license or a + * BSD style license, available at: + * http://datatables.net/license_gpl2 + * http://datatables.net/license_bsd + * + * This source file 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 license files for details. + */ +(function(X,l,n){var L=function(h){var j=function(e){function o(a,b){var c=j.defaults.columns,d=a.aoColumns.length,c=h.extend({},j.models.oColumn,c,{sSortingClass:a.oClasses.sSortable,sSortingClassJUI:a.oClasses.sSortJUI,nTh:b?b:l.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.oDefaults:d});a.aoColumns.push(c);if(a.aoPreSearchCols[d]===n||null===a.aoPreSearchCols[d])a.aoPreSearchCols[d]=h.extend({},j.models.oSearch);else if(c=a.aoPreSearchCols[d], +c.bRegex===n&&(c.bRegex=!0),c.bSmart===n&&(c.bSmart=!0),c.bCaseInsensitive===n)c.bCaseInsensitive=!0;m(a,d,null)}function m(a,b,c){var d=a.aoColumns[b];c!==n&&null!==c&&(c.mDataProp&&!c.mData&&(c.mData=c.mDataProp),c.sType!==n&&(d.sType=c.sType,d._bAutoType=!1),h.extend(d,c),p(d,c,"sWidth","sWidthOrig"),c.iDataSort!==n&&(d.aDataSort=[c.iDataSort]),p(d,c,"aDataSort"));var i=d.mRender?Q(d.mRender):null,f=Q(d.mData);d.fnGetData=function(a,b){var c=f(a,b);return d.mRender&&b&&""!==b?i(c,b,a):c};d.fnSetData= +L(d.mData);a.oFeatures.bSort||(d.bSortable=!1);!d.bSortable||-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableNone,d.sSortingClassJUI=""):-1==h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortable,d.sSortingClassJUI=a.oClasses.sSortJUI):-1!=h.inArray("asc",d.asSorting)&&-1==h.inArray("desc",d.asSorting)?(d.sSortingClass=a.oClasses.sSortableAsc,d.sSortingClassJUI=a.oClasses.sSortJUIAscAllowed):-1== +h.inArray("asc",d.asSorting)&&-1!=h.inArray("desc",d.asSorting)&&(d.sSortingClass=a.oClasses.sSortableDesc,d.sSortingClassJUI=a.oClasses.sSortJUIDescAllowed)}function k(a){if(!1===a.oFeatures.bAutoWidth)return!1;da(a);for(var b=0,c=a.aoColumns.length;bj[f])d(a.aoColumns.length+j[f],b[i]);else if("string"===typeof j[f]){e=0;for(w=a.aoColumns.length;eb&&a[d]--; -1!=c&&a.splice(c,1)}function S(a,b,c){var d=a.aoColumns[c];return d.fnRender({iDataRow:b,iDataColumn:c,oSettings:a,aData:a.aoData[b]._aData,mDataProp:d.mData},v(a,b,c,"display"))}function ea(a,b){var c=a.aoData[b],d;if(null===c.nTr){c.nTr=l.createElement("tr");c.nTr._DT_RowIndex=b;c._aData.DT_RowId&&(c.nTr.id=c._aData.DT_RowId);c._aData.DT_RowClass&& +(c.nTr.className=c._aData.DT_RowClass);for(var i=0,f=a.aoColumns.length;i=a.fnRecordsDisplay()?0:a.iInitDisplayStart,a.iInitDisplayStart=-1,y(a));if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++;else if(a.oFeatures.bServerSide){if(!a.bDestroying&&!wa(a))return}else a.iDraw++;if(0!==a.aiDisplay.length){var g= +a._iDisplayStart;d=a._iDisplayEnd;a.oFeatures.bServerSide&&(g=0,d=a.aoData.length);for(;g
")[0];a.nTable.parentNode.insertBefore(b,a.nTable);a.nTableWrapper=h('
')[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var c=a.nTableWrapper,d=a.sDom.split(""),i,f,g,e,w,o,k,m=0;m")[0];w=d[m+ +1];if("'"==w||'"'==w){o="";for(k=2;d[m+k]!=w;)o+=d[m+k],k++;"H"==o?o=a.oClasses.sJUIHeader:"F"==o&&(o=a.oClasses.sJUIFooter);-1!=o.indexOf(".")?(w=o.split("."),e.id=w[0].substr(1,w[0].length-1),e.className=w[1]):"#"==o.charAt(0)?e.id=o.substr(1,o.length-1):e.className=o;m+=k}c.appendChild(e);c=e}else if(">"==g)c=c.parentNode;else if("l"==g&&a.oFeatures.bPaginate&&a.oFeatures.bLengthChange)i=ya(a),f=1;else if("f"==g&&a.oFeatures.bFilter)i=za(a),f=1;else if("r"==g&&a.oFeatures.bProcessing)i=Aa(a),f= +1;else if("t"==g)i=Ba(a),f=1;else if("i"==g&&a.oFeatures.bInfo)i=Ca(a),f=1;else if("p"==g&&a.oFeatures.bPaginate)i=Da(a),f=1;else if(0!==j.ext.aoFeatures.length){e=j.ext.aoFeatures;k=0;for(w=e.length;k'):""===c?'':c+' ',d=l.createElement("div");d.className=a.oClasses.sFilter;d.innerHTML="";a.aanFeatures.f||(d.id=a.sTableId+"_filter");c=h('input[type="text"]',d);d._DT_Input=c[0];c.val(b.sSearch.replace('"',"""));c.bind("keyup.DT",function(){for(var c=a.aanFeatures.f,d=this.value===""?"":this.value, +g=0,e=c.length;g=b.length)a.aiDisplay.splice(0,a.aiDisplay.length),a.aiDisplay=a.aiDisplayMaster.slice();else if(a.aiDisplay.length==a.aiDisplayMaster.length||i.sSearch.length>b.length||1==c||0!==b.indexOf(i.sSearch)){a.aiDisplay.splice(0, +a.aiDisplay.length);la(a,1);for(b=0;b").html(c).text()); +return c.replace(/[\n\r]/g," ")}function ma(a,b,c,d){if(c)return a=b?a.split(" "):oa(a).split(" "),a="^(?=.*?"+a.join(")(?=.*?")+").*$",RegExp(a,d?"i":"");a=b?a:oa(a);return RegExp(a,d?"i":"")}function Ja(a,b){return"function"===typeof j.ext.ofnSearch[b]?j.ext.ofnSearch[b](a):null===a?"":"html"==b?a.replace(/[\r\n]/g," ").replace(/<.*?>/g,""):"string"===typeof a?a.replace(/[\r\n]/g," "):a}function oa(a){return a.replace(RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"), +"\\$1")}function Ca(a){var b=l.createElement("div");b.className=a.oClasses.sInfo;a.aanFeatures.i||(a.aoDrawCallback.push({fn:Ka,sName:"information"}),b.id=a.sTableId+"_info");a.nTable.setAttribute("aria-describedby",a.sTableId+"_info");return b}function Ka(a){if(a.oFeatures.bInfo&&0!==a.aanFeatures.i.length){var b=a.oLanguage,c=a._iDisplayStart+1,d=a.fnDisplayEnd(),i=a.fnRecordsTotal(),f=a.fnRecordsDisplay(),g;g=0===f?b.sInfoEmpty:b.sInfo;f!=i&&(g+=" "+b.sInfoFiltered);g+=b.sInfoPostFix;g=ja(a,g); +null!==b.fnInfoCallback&&(g=b.fnInfoCallback.call(a.oInstance,a,c,d,i,f,g));a=a.aanFeatures.i;b=0;for(c=a.length;b",c,d,i=a.aLengthMenu;if(2==i.length&&"object"===typeof i[0]&&"object"===typeof i[1]){c=0;for(d=i[0].length;c'+i[1][c]+""}else{c=0;for(d=i.length;c'+i[c]+""}b+="";i=l.createElement("div");a.aanFeatures.l|| +(i.id=a.sTableId+"_length");i.className=a.oClasses.sLength;i.innerHTML="";h('select option[value="'+a._iDisplayLength+'"]',i).attr("selected",!0);h("select",i).bind("change.DT",function(){var b=h(this).val(),i=a.aanFeatures.l;c=0;for(d=i.length;ca.aiDisplay.length||-1==a._iDisplayLength?a.aiDisplay.length:a._iDisplayStart+a._iDisplayLength}function Da(a){if(a.oScroll.bInfinite)return null;var b=l.createElement("div");b.className=a.oClasses.sPaging+a.sPaginationType;j.ext.oPagination[a.sPaginationType].fnInit(a, +b,function(a){y(a);x(a)});a.aanFeatures.p||a.aoDrawCallback.push({fn:function(a){j.ext.oPagination[a.sPaginationType].fnUpdate(a,function(a){y(a);x(a)})},sName:"pagination"});return b}function qa(a,b){var c=a._iDisplayStart;if("number"===typeof b)a._iDisplayStart=b*a._iDisplayLength,a._iDisplayStart>a.fnRecordsDisplay()&&(a._iDisplayStart=0);else if("first"==b)a._iDisplayStart=0;else if("previous"==b)a._iDisplayStart=0<=a._iDisplayLength?a._iDisplayStart-a._iDisplayLength:0,0>a._iDisplayStart&&(a._iDisplayStart= +0);else if("next"==b)0<=a._iDisplayLength?a._iDisplayStart+a._iDisplayLengthh(a.nTable).height()-a.oScroll.iLoadGap&&a.fnDisplayEnd()d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(h(a.nTable).outerWidth()-a.oScroll.iBarWidth)}else""!==a.oScroll.sXInner?a.nTable.style.width= +q(a.oScroll.sXInner):i==h(d).width()&&h(d).height()i-a.oScroll.iBarWidth&&(a.nTable.style.width=q(i))):a.nTable.style.width=q(i);i=h(a.nTable).outerWidth();C(s,e);C(function(a){p.push(q(h(a).width()))},e);C(function(a,b){a.style.width=p[b]},g);h(e).height(0);null!==a.nTFoot&&(C(s,j),C(function(a){n.push(q(h(a).width()))},j),C(function(a,b){a.style.width=n[b]},o),h(j).height(0));C(function(a,b){a.innerHTML= +"";a.style.width=p[b]},e);null!==a.nTFoot&&C(function(a,b){a.innerHTML="";a.style.width=n[b]},j);if(h(a.nTable).outerWidth()d.offsetHeight||"scroll"==h(d).css("overflow-y")?i+a.oScroll.iBarWidth:i;if(r&&(d.scrollHeight>d.offsetHeight||"scroll"==h(d).css("overflow-y")))a.nTable.style.width=q(g-a.oScroll.iBarWidth);d.style.width=q(g);a.nScrollHead.style.width=q(g);null!==a.nTFoot&&(a.nScrollFoot.style.width=q(g));""===a.oScroll.sX?D(a,1,"The table cannot fit into the current element which will cause column misalignment. The table has been drawn at its minimum possible width."): +""!==a.oScroll.sXInner&&D(a,1,"The table cannot fit into the current element which will cause column misalignment. Increase the sScrollXInner value or remove it to allow automatic calculation")}else d.style.width=q("100%"),a.nScrollHead.style.width=q("100%"),null!==a.nTFoot&&(a.nScrollFoot.style.width=q("100%"));""===a.oScroll.sY&&r&&(d.style.height=q(a.nTable.offsetHeight+a.oScroll.iBarWidth));""!==a.oScroll.sY&&a.oScroll.bCollapse&&(d.style.height=q(a.oScroll.sY),r=""!==a.oScroll.sX&&a.nTable.offsetWidth> +d.offsetWidth?a.oScroll.iBarWidth:0,a.nTable.offsetHeightd.clientHeight||"scroll"==h(d).css("overflow-y");b.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px";null!==a.nTFoot&&(R.style.width=q(r),l.style.width=q(r),l.style.paddingRight=c?a.oScroll.iBarWidth+"px":"0px");h(d).scroll();if(a.bSorted||a.bFiltered)d.scrollTop=0}function C(a,b,c){for(var d= +0,i=0,f=b.length,g,e;itd",b));j=N(a,f);for(f=d=0;fc)return null;if(null===a.aoData[c].nTr){var d=l.createElement("td");d.innerHTML=v(a,c,b,"");return d}return J(a,c)[b]}function Pa(a,b){for(var c=-1,d=-1,i=0;i/g,"");e.length>c&&(c=e.length,d=i)}return d}function q(a){if(null===a)return"0px";if("number"==typeof a)return 0>a?"0px":a+"px";var b=a.charCodeAt(a.length-1); +return 48>b||57/g,""),i=q[c].nTh,i.removeAttribute("aria-sort"),i.removeAttribute("aria-label"),q[c].bSortable?0d&&d++;f=RegExp(f+"[123]");var o;b=0;for(c=a.length;b
')[0];l.body.appendChild(b);a.oBrowser.bScrollOversize= +100===h("#DT_BrowserTest",b)[0].offsetWidth?!0:!1;l.body.removeChild(b)}function Va(a){return function(){var b=[s(this[j.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return j.ext.oApi[a].apply(this,b)}}var U=/\[.*?\]$/,Wa=X.JSON?JSON.stringify:function(a){var b=typeof a;if("object"!==b||null===a)return"string"===b&&(a='"'+a+'"'),a+"";var c,d,e=[],f=h.isArray(a);for(c in a)d=a[c],b=typeof d,"string"===b?d='"'+d+'"':"object"===b&&null!==d&&(d=Wa(d)),e.push((f?"":'"'+c+'":')+d);return(f? +"[":"{")+e+(f?"]":"}")};this.$=function(a,b){var c,d,e=[],f;d=s(this[j.ext.iApiIndex]);var g=d.aoData,o=d.aiDisplay,k=d.aiDisplayMaster;b||(b={});b=h.extend({},{filter:"none",order:"current",page:"all"},b);if("current"==b.page){c=d._iDisplayStart;for(d=d.fnDisplayEnd();c=d.fnRecordsDisplay()&&(d._iDisplayStart-=d._iDisplayLength,0>d._iDisplayStart&&(d._iDisplayStart=0));if(c===n||c)y(d),x(d);return g};this.fnDestroy=function(a){var b=s(this[j.ext.iApiIndex]),c=b.nTableWrapper.parentNode,d=b.nTBody,i,f,a=a===n?!1:a;b.bDestroying=!0;A(b,"aoDestroyCallback","destroy",[b]);if(!a){i=0;for(f=b.aoColumns.length;itr>td."+b.oClasses.sRowEmpty,b.nTable).parent().remove();b.nTable!=b.nTHead.parentNode&&(h(b.nTable).children("thead").remove(),b.nTable.appendChild(b.nTHead));b.nTFoot&&b.nTable!=b.nTFoot.parentNode&&(h(b.nTable).children("tfoot").remove(),b.nTable.appendChild(b.nTFoot));b.nTable.parentNode.removeChild(b.nTable);h(b.nTableWrapper).remove();b.aaSorting=[];b.aaSortingFixed=[];P(b);h(T(b)).removeClass(b.asStripeClasses.join(" "));h("th, td",b.nTHead).removeClass([b.oClasses.sSortable,b.oClasses.sSortableAsc, +b.oClasses.sSortableDesc,b.oClasses.sSortableNone].join(" "));b.bJUI&&(h("th span."+b.oClasses.sSortIcon+", td span."+b.oClasses.sSortIcon,b.nTHead).remove(),h("th, td",b.nTHead).each(function(){var a=h("div."+b.oClasses.sSortJUIWrapper,this),c=a.contents();h(this).append(c);a.remove()}));!a&&b.nTableReinsertBefore?c.insertBefore(b.nTable,b.nTableReinsertBefore):a||c.appendChild(b.nTable);i=0;for(f=b.aoData.length;i=t(d);if(!m)for(e=a;et<"F"ip>')):h.extend(g.oClasses,j.ext.oStdClasses);h(this).addClass(g.oClasses.sTable);if(""!==g.oScroll.sX||""!==g.oScroll.sY)g.oScroll.iBarWidth=Qa();g.iInitDisplayStart===n&&(g.iInitDisplayStart=e.iDisplayStart, +g._iDisplayStart=e.iDisplayStart);e.bStateSave&&(g.oFeatures.bStateSave=!0,Sa(g,e),z(g,"aoDrawCallback",ra,"state_save"));null!==e.iDeferLoading&&(g.bDeferLoading=!0,a=h.isArray(e.iDeferLoading),g._iRecordsDisplay=a?e.iDeferLoading[0]:e.iDeferLoading,g._iRecordsTotal=a?e.iDeferLoading[1]:e.iDeferLoading);null!==e.aaData&&(f=!0);""!==e.oLanguage.sUrl?(g.oLanguage.sUrl=e.oLanguage.sUrl,h.getJSON(g.oLanguage.sUrl,null,function(a){pa(a);h.extend(true,g.oLanguage,e.oLanguage,a);ba(g)}),i=!0):h.extend(!0, +g.oLanguage,e.oLanguage);null===e.asStripeClasses&&(g.asStripeClasses=[g.oClasses.sStripeOdd,g.oClasses.sStripeEven]);b=g.asStripeClasses.length;g.asDestroyStripes=[];if(b){c=!1;d=h(this).children("tbody").children("tr:lt("+b+")");for(a=0;a=g.aoColumns.length&&(g.aaSorting[a][0]=0);var k=g.aoColumns[g.aaSorting[a][0]];g.aaSorting[a][2]===n&&(g.aaSorting[a][2]=0);e.aaSorting===n&&g.saved_aaSorting===n&&(g.aaSorting[a][1]= +k.asSorting[0]);c=0;for(d=k.asSorting.length;c=parseInt(n,10)};j.fnIsDataTable=function(e){for(var h=j.settings,m=0;me)return e;for(var h=e+"",e=h.split(""),j="",h=h.length,k=0;k'+k.sPrevious+''+k.sNext+"":'';h(j).append(k);var l=h("a",j), +k=l[0],l=l[1];e.oApi._fnBindAction(k,{action:"previous"},n);e.oApi._fnBindAction(l,{action:"next"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_previous",l.id=e.sTableId+"_next",k.setAttribute("aria-controls",e.sTableId),l.setAttribute("aria-controls",e.sTableId))},fnUpdate:function(e){if(e.aanFeatures.p)for(var h=e.oClasses,j=e.aanFeatures.p,k,l=0,n=j.length;l'+k.sFirst+''+k.sPrevious+''+k.sNext+''+k.sLast+"");var t=h("a",j),k=t[0],l=t[1],r=t[2],t=t[3];e.oApi._fnBindAction(k,{action:"first"},n);e.oApi._fnBindAction(l,{action:"previous"},n);e.oApi._fnBindAction(r,{action:"next"},n);e.oApi._fnBindAction(t,{action:"last"},n);e.aanFeatures.p||(j.id=e.sTableId+"_paginate",k.id=e.sTableId+"_first",l.id=e.sTableId+"_previous",r.id=e.sTableId+"_next",t.id=e.sTableId+"_last")}, +fnUpdate:function(e,o){if(e.aanFeatures.p){var m=j.ext.oPagination.iFullNumbersShowPages,k=Math.floor(m/2),l=Math.ceil(e.fnRecordsDisplay()/e._iDisplayLength),n=Math.ceil(e._iDisplayStart/e._iDisplayLength)+1,t="",r,B=e.oClasses,u,M=e.aanFeatures.p,L=function(h){e.oApi._fnBindAction(this,{page:h+r-1},function(h){e.oApi._fnPageChange(e,h.data.page);o(e);h.preventDefault()})};-1===e._iDisplayLength?n=k=r=1:l=l-k?(r=l-m+1,k=l):(r=n-Math.ceil(m/2)+1,k=r+m-1);for(m=r;m<=k;m++)t+= +n!==m?''+e.fnFormatNumber(m)+"":''+e.fnFormatNumber(m)+"";m=0;for(k=M.length;mh?1:0},"string-desc":function(e,h){return eh?-1:0},"html-pre":function(e){return e.replace(/<.*?>/g,"").toLowerCase()},"html-asc":function(e,h){return eh?1:0},"html-desc":function(e,h){return e< +h?1:e>h?-1:0},"date-pre":function(e){e=Date.parse(e);if(isNaN(e)||""===e)e=Date.parse("01/01/1970 00:00:00");return e},"date-asc":function(e,h){return e-h},"date-desc":function(e,h){return h-e},"numeric-pre":function(e){return"-"==e||""===e?0:1*e},"numeric-asc":function(e,h){return e-h},"numeric-desc":function(e,h){return h-e}});h.extend(j.ext.aTypes,[function(e){if("number"===typeof e)return"numeric";if("string"!==typeof e)return null;var h,j=!1;h=e.charAt(0);if(-1=="0123456789-".indexOf(h))return null; +for(var k=1;k")?"html":null}]);h.fn.DataTable=j;h.fn.dataTable=j;h.fn.dataTableSettings=j.settings;h.fn.dataTableExt=j.ext};"function"===typeof define&&define.amd?define(["jquery"],L):jQuery&&!jQuery.fn.dataTable&& +L(jQuery)})(window,document); diff --git a/js/jscalendar/doc_nogray_date.html b/js/jscalendar/doc_nogray_date.html new file mode 100644 index 0000000..7d1bbd6 --- /dev/null +++ b/js/jscalendar/doc_nogray_date.html @@ -0,0 +1,340 @@ + + + +Date Object Extension + + + +
+ + +Your Ad Here + +
+

+

Date Object Extension:

+For the latest version of the Date Object Extension, please visit The NoGray.com +

+we thrive on your donations +

+nogray_date.js will extend the native JavaScript Date object and implement a few functions to allow easier date generation and formatting. You'll need the latest version on mootools Class.Extras file. +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionArgumentsReturn Value
languageAn object that contain the language to be used in formatting + the output from the print functionN/AN/A
+ Example: +
alert(new Date().language.days.short[1]); // will alert Mo (short for Monday)
+
daysInMonthReturns the number of days in the monthN/ANumber of days
Example: +
alert(new Date(2007, 8, 1).daysInMonth());	// will alert 30 (30 days in Septembar)
+
isLeapYearCheck if the year is a leap year or notN/ABoolean; true if the year is leap, false otherwise.
Example: +
alert(new Date(2008,0,1).isLeapYear());	// will alert true
+alert(new Date(2009,0,1).isLeapYear()); // will alert false
fromStringCreate a date object from a stringstr: see belowA new date object
+ str: the string variable for the date. If the date can't be parsed using Date.parse() a list of custom values are provided below. +
+ All custom values are relative to the date object. +

+ Possible values: +
    +
  • Any string that can be parsed by Date.parse() +
      +
    • Short dates can use either a "/", "\" or "-" date separator, but must follow the month/day/year format, for example "7/20/96".
      + month are 1 for January 12 for December
    • +
    • Long dates of the form "July 10 1995" can be given with the year, month, and day in any order, and the year in 2-digit or 4-digit form. If you use the 2-digit form, the year must be greater than or equal to 70.
    • +
    • Month and day names must have two or more characters. Two character names that are not unique are resolved as the last match. For example, "Ju" is resolved as July, not June.
    • +
    • Handles all standard time zones, as well as Universal Coordinated Time (UTC) and Greenwich Mean Time (GMT).
    • +
    • Colons separate hours, minutes, and seconds, although all need not be specified. "10:", "10:11", and "10:11:12" are all valid.
    • +
    • If the 24-hour clock is used, it is an error to specify "PM" for times later than 12 noon. For example, "23:15 PM" is an error.
    • +
    • A string containing an invalid date is an error. For example, a string containing two years or two months is an error.
    • +
    +
  • "yesterday" a day before relative to the date object
  • +
  • "tomorrow" a day after relative to the date object
  • +
  • "today+[n]" [n] is any number
  • + add the [n] number of days to the date +
  • "today-[n]" [n] is any number
    + subtract the [n] number of days from the date
  • +
  • "last month" a month before relative to the date object
  • +
  • "next month" a month after relative to the date object
  • +
  • "month+[n]" [n] is any number
    + add the [n] number of months to the date
  • +
  • "month-[n]" [n] is any number
    + subtract the [n] number of days from the date
  • +
  • "last year"
  • +
  • "next year"
  • +
  • "year+[n]"
  • +
  • "year-[n]"
  • +
+
Example: +
alert(new Date().fromString("yesterday"));	// will alert yesterday's day
fromObjectCreate a date from an objectdate_obj: see belowA new date object
+ date_obj: is a javascript object (key and value object) and + should have the following attributes + +
    +
  • date: can be either a number or a string
    + if string, the following values are allowed +
      +
    • "[1st | 2nd | 3rd | 4th | 5th | last] sunday" either the 1st, 2nd, or 3rd, etc... sunday of the month.
      + will return the date for the [nth] or last sunday of the month
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] monday" same as above but for monday
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] tuesday"
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] wednesday"
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] thursday"
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] friday"
    • +
    • "[1st | 2nd | 3rd | 4th | 5th | last] saturday"
    • +
    +
  • month: a numerical value for the month
    + 0 for January 11 for December
  • +
  • year: a numerical value for the year (for digits format)
  • +
  • hour: a numerical value for the hour
  • +
  • minute: a numerical value for the minute
  • +
  • second: a numerical value for the second
  • +
  • millisecond: a numerical value for the millisecond
  • +
+ + if any of the values above is not defined, the current date value will be used. +
Example: +
alert(new Date(2007,8,1).fromObject({'date':'last friday'}));	// will alert the date for Sep 28th 2007
+alert(new Date(2007,8,1).fromObject({'date':'3rd Monday'}));	// will alert the date for Sep 17th 2007
printReturns a formatted date string similar to PHP date function.
+ Please visit http://us.php.net/manual/en/function.date.php for more details + on the possible values. The only exception is the Y and o will return the same value
format: the format of the date. +
+ lang: a language object if using a different language than the date object
String of the formatted date
alert(new Date(2007,8,12).print("D, d M Y")); // will alert Wed, 12 Sep 2007
getWeekInYearReturns the number of week in the yearN/ANumber of weeks
Example: +
alert(new Date(2007,8,12).getWeekInYear());	// will alert 36
+
getDayInYearReturns the number of day in the yearN/ANumber of days
Example: +
alert(new Date(2007,8,12).getDayInYear());	// will alert 253
+
getHourInYear~~~~~~~N/ANumber of hours
getMinuteInYear~~~~~~~N/ANumber of minutes
getSecondInYear~~~~~~~N/ANumber of seconds
getMillisecondInYear~~~~~~~N/ANumber of milliseconds
getWeekSinceReturns the number of week since a given datedate: a JavaScript DateNumber of weeks
Example: +
alert(new Date(2007,8,12).getWeekSince(new Date(2006,2,14)));	// will alert 78
+
getDaySinceReturns the number of day in the yeardate: a JavaScript DateNumber of days
Example: +
alert(new Date(2007,8,12).getDaySince(new Date(2006,2,14)));	// will alert 547
+
getHourSince~~~~~~~~~~~~~~Number of hours
getMinuteSince~~~~~~~~~~~~~~Number of minutes
getSecondSince~~~~~~~~~~~~~~Number of seconds
getMillisecondSince~~~~~~~~~~~~~~Number of milliseconds
timeDifferenceReturns the time difference in milliseconds between the date and the arguments datedate: a JavaScript DateDifference in milliseconds
Example: +
alert(new Date(2007,8,12).timeDifference(new Date(2006,2,14))); // will alert 47260800000
+
toSwatchInternetTimeReturns the number of beats as a string (including the @ sign) + This function assume the browser handles the time zone and day time saving.
+ For more details about the Swatch Internet Time, please visit http://www.swatch.com/internettime/
N/Abeats
Example: +
alert(new Date(2007,8,12,8,52,0).toSwatchInternetTime()); // will alert @702 (based on timezone)
+
fromSwatchInternetTimeReturns a JavaScript Date object (approximate time) from the given bean.
+ This function assume the browser handles the time zone and day time saving.
+ For more details about the Swatch Internet Time, please visit http://www.swatch.com/internettime/
beat: Swatch Internet Time beatA new date object
Example: +
alert(new Date().fromSwatchInternetTime(354)); // will alert the time at 10:47:31 (based on timezone)
+
+ +

+
+ + +Your Ad Here + +
+ + \ No newline at end of file diff --git a/js/jscalendar/examples_calendar.html b/js/jscalendar/examples_calendar.html new file mode 100644 index 0000000..56113ca --- /dev/null +++ b/js/jscalendar/examples_calendar.html @@ -0,0 +1,692 @@ + + + +NoGray Calendar Component + + + + + + + + + +
+ + +Your Ad Here + +
+

+

The NoGray Calendar Component:

+This file contains a few examples to help you start using the calendar component, for the latest version please visit The NoGray.com + + +Requirements:
+The NoGray Calendar requires an XHTML doctype, which means your <html> tag should look like +
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
+

+Create It

+To create a calendar, you'll need to call the calendar class with two elements and a set of options. +
    +
  • holdingElement: the element that will hold the calendar parts.
  • +
  • toggler: the element that will toggle the calendar on and off (if not visible). Can be null if no toggler is required, just use the openCalendar and closeCalendar functions to open and close the calendar.
  • +
  • options
  • +
+Example: +
HTML:
+<input type="text" id="date3" name="date3">
+<a href="#" id="cal3_toggler">Open Calendar</a>
+<div id="calendar3"></div>
+...
+later
+...
+Script:
+var calender3 = new Calendar("calendar3", "cal3_toggler", {inputField:'date3',
+                                                                                       numMonths:3,
+                                                                                       multiSelection:true,
+                                                                                       maxSelection:5,
+                                                                                       forceSelections:[
+                                                                                             {date:'last Saturday'}
+                                                                                       ],
+                                                                                       dateFormat:'D, d M Y ::  ',
+                                                                                       idPrefix:'cal3'});
+
+Try It: +
+
+Simple Calendar with a text field input:
+ +Open Calendar +
+ +


+Four months calendar with events handling and select drop downs fields:
+ + + +Open Calendar +
+
+The 14th of this month will have a blue background and when you click on the 16th an alert will display the date of the 16th + +


+Multi date selection with a textarea (text or select drop downs can be used):
+Select up to 5 dates:
+ +Open Calendar +
+ +


+Visible Menu with no input and not selectable (larger display and holidays have a different background color using CSS):
+
+ +

+Configurations:

+ +You can customized the look and feel of the calendar component by changing the nogray_calendar_vs1.css file or adding your own custom classes. Each calendar can have a class and id prefix which make it easier to have more than one calendar in the same page. + +

+Options objects are a key: value object with the following syntax +
{option: value,
+option: {option: value,
+		option: value},
+option: [value1, value2]}
+

+Date objects for the calendar can be one of the following +
+String: a date in a string format or special value (check nogray_date.js)
+Object: an object that include the date values (check nogray_date.js)
+JavaScript Date
+Number: JavaScript time value (number of milliseconds since 1970) +

+Events objects for the calendar are objects where the key is the event (without the on) and the value is the function to execute.
+for example an onclick event will be something like this
+
{click:function(){
+	...
+	do something
+	...
+	}
+)
+
+ +Skinning:

+The Calendar Component use CSS to skin the different parts of the calendar. The gray border around the calendar is the style for the holding element (div in this case). +The following images illustrate the different parts with their class names, ids and HTML values. +
+NoGray Calendar Outer Parts +
+NoGray Calendar Inner Parts +

+ +Options + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OptionDescriptionDefault Value
visibleIndicates weather the calendar will be visible when loaded or notfalse
+ Example: +
var cal = new Calendar('holdingElement', 'toggeler', {visible:false});
offsetThe calendar by default will show at the lower left corner of the input field. + offset allows for moving the offsetting the calendar position{x:0, y:0}
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {offset:{x:50, y:50}});
dateFormatThe format of the date output (for text field).
+ check nogray_date.js for details
+ check http://us3.php.net/manual/en/function.date.php for formatting details
D, d M Y
+ Example: +
var cal = new Calendar('holdingElement', 'toggeler', {dateFormat:"Y-m-d"});
+
numMonthsThe number of months per calendar.
+ Warnings: Firefox is slow when handling date objects. If using datesoff or forcedSelection + try to use 1 month per calendar
1
+ Example: +
var cal = new Calendar('holdingElement', 'toggeler', {numMonths:4});
classPrefixThe CSS class prefix, check the skinning section for instructionsng-
+ Example: +
var cal = new Calendar('holdingElement', 'toggeler', {classPrefix:'new_calendar'});
idPrefixThe id prefix for the dates, use this if using more than one calendar.ng-
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {idPrefix:'cal_1'});
startDayThe first day of the week. 0 for Sunday, 1 for Monday ... 6 for Saturday0
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {startDay:1});
startDateThe first selectable date, use a date objecttoday
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {startDate:new Date(2007,0,1)});
endDateThe last selectable date, use a date objectyear+10
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {endDate:new Date(2017,0,1)});
inputTypeThe type of input fields. It can be one of the following values: +
  • text (for text of textarea fields)
  • select (for drop down select menus)
  • none (no input field)
text
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {inputType:'select'});
inputFieldThe input field to be used. If the inputType is text it can be either an HTML input object or the object ID. If the inputType is select it must be an object with the date, month and year HTML objectnull
Example: +
HTML:
+	<input type="text" name="date" id="date" />
+...
+later
+...
+Script:
+	var cal = new Calendar('holdingElement', 'toggeler', {inputField:'date'});
+
+HTML:
+	<select name="date" id="date"></select>
+	<select name="month" id="month"></select>
+	<select name="year" id="year"></select>
+...
+later
+...
+Script:
+	var cal = new Calendar('holdingElement', 'toggeler', {inputField:{date:'date',
+					month:'month',
+					year:'year'}});
allowSelectionAllows the user to select a datetrue
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {allowSelection:false});
multiSelectionAllows the user to select more than one datefalse
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {multiSelection:true});
maxSelectionThe maximum number of dates the user can select if multiSelection is true. + If 0, unlimited number of dates is selectable.0
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {maxSelection:3});
selectedDateThe initially selected date, use a date objectnull
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {selectedDate:'today'});
datesOffAn array of date objects for the holidays. Holidays are unselectable unless allowDatesOffSelection is true.[]
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {datesOff:{date:25, month:11}});
allowDatesOffSelectionAllows the user to select the dates off.false
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {allowDatesOffSelection:true});
daysOffAn array of numbers (0 for Sunday, 1 for Monday ... 6 for Saturday) for the days off in the week (beside weekends). daysOff are unselectable unless allowDaysOffSelection is true.[]
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {daysOff:[4]});
allowDaysOffSelectionAllows the user to select the days off.false
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {allowDaysOffSelection:true});
weekendAn array of numbers (0 for Sunday, 1 for Monday ... 6 for Saturday) for the weekend. Weekend are unselectable unless allowWeekendSelection is true.[]
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {weekend:[4,5]});
allowWeekendSelectionAllows the user to select the weekend.false
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {allowDaysOffSelection:true});
forceSelectionsan Array of date objects that are forced to be selectable regardless where they fall + (e.g. if the user can select the last Saturday of the month but not other weekend days)[]
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {forceSelections:[{date:'last Saturday'}, 'today']});
formmaterA function that will return the HTML for the table cell and takes the Date as an argument. Default return is the date’s getDate() value.function (date){
     return date.getDate();
}
Example: +
var cal = new Calendar('holdingElement', 'toggeler', {
+     formmater: function(date){
+          if (date.getDate() == 25) return "It's the 25th";
+          else return date.getDate();
+     }
+});
outOfRangeFormmaterSame as formmater but for out of range dates~~~~~~
weekendFormmaterSame as formmater but for the weekend dates~~~~~~
daysOffFormmaterSame as formmater but for days off dates~~~~~~
datesOffFormmaterSame as formmater but for dates off dates (holidays)~~~~~~
selectedDateFormmaterSame as formmater but for the selected date~~~~~~
languagea language object (check nogray_date.js for details). Used for translation. null (for English)
daysTextThe days format to be used on the calendar. The key for the language.days object.mid
monthsTextThe months format to be used on the calendar. The key for the language.months object.long
preTdHTMLThe HTML for the previous months arrow&laquo;
preTdHTMLOffThe HTML for the previous months arrow when the calendar at the first month.&nbsp;
nexTdHTMLThe HTML for the next months arrow&raquo;
nexTdHTMLOffThe HTML for the next months arrow when the calendar at the last month.&nbsp;
closeLinkHTMLThe HTML for the close link.Close
clearLinkHTMLThe HTML for the clear link (only visible when multiSelection is true).Clear
calEventsan Events object for all the selectable table cell.
+ Arguments passed to the function: +
    +
  • this: the calendar object.
  • +
  • td: the table cell (TD) HTML object.
  • +
  • date_str: the date in a string format.
  • +
mouseover: add a mouse-over class to the table cell
+ mouseleave: remove the mouse-over class from the table cell
tdEventsAn array of Events objects to attach events to a specific table cells. + The array key must be the date in the following format + month-date-year (8 for August)
+ Arguments passed to the function: +
    +
  • this: the calendar object.
  • +
  • td: the table cell (TD) HTML object.
  • +
  • date_str: the date in a string format.
  • +
[]
Example: +
var events_arr = [];
+events_arr['8-15-2007'] = {click: function(td, date_str){
+          alert(td.innerHTML+" "+new Date().fromString(date_str));
+     }
+};
+var cal = new Calendar('holdingElement', 'toggeler', {
+     tdEvents: events_arr
+});
dateOnAvailableAn array of functions to run when a specific date is rendered. + The array key must be the date in the following format + month-date-year (8 for August)
+ Arguments passed to the function: +
    +
  • this: the calendar object.
  • +
  • td: the table cell (TD) HTML object.
  • +
  • date_str: the date in a string format.
  • +
[]
Example: +
var dates_func_arr = [];
+dates_func_arr['8-15-2007'] = function(td, date_str){td.setHTML(date_str);}
+var cal = new Calendar('holdingElement', 'toggeler', {
+     dateOnAvailable: dates_func_arr
+});
speedFireFoxOption to speed the calendar creation in Firefox, + since firefox is slow when handling date objects, this option will illuminate + the datesOff (holidays) daysOff and forcedSelection for better performance.false
closeOpenCalendarsClose all the open calendars when the calendar openstrue
+ +

+Events + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EventWhen Fired
onSelectWhen a date is selected.
onUnSelectWhen a date is unselected.
onCalendarLoadWhen the calendar is completely rendered.
onOpenWhen the calendar is opened.
onCloseWhen the calendar is closed.
onClearWhen the calendar is cleared.
+

+
+ + +Your Ad Here + +
+ + \ No newline at end of file diff --git a/js/jscalendar/license.html b/js/jscalendar/license.html new file mode 100644 index 0000000..17b72c9 --- /dev/null +++ b/js/jscalendar/license.html @@ -0,0 +1,20 @@ + + + + +Licensing + + + + + +
+ If you are not redirected in 2 seconds, please Click Here +
+ + diff --git a/js/jscalendar/mootools.v1.11.js b/js/jscalendar/mootools.v1.11.js new file mode 100644 index 0000000..7f1d9d3 --- /dev/null +++ b/js/jscalendar/mootools.v1.11.js @@ -0,0 +1,3 @@ +//MooTools, My Object Oriented Javascript Tools. Copyright (c) 2006 Valerio Proietti, , MIT Style License. + +eval(function(p,a,c,k,e,d){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--){d[e(c)]=k[c]||e(c)}k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--){if(k[c]){p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c])}}return p}('o ci={cj:\'1.11\'};k $77(N){m(N!=9N)};k $F(N){B(!$77(N))m O;B(N.5i)m\'G\';o F=7c N;B(F==\'2I\'&&N.ch){22(N.84){Y 1:m\'G\';Y 3:m(/\\S/).2v(N.ax)?\'cg\':\'cd\'}}B(F==\'2I\'||F==\'k\'){22(N.9C){Y 2t:m\'1z\';Y 7y:m\'5C\';Y 18:m\'4R\'}B(7c N.V==\'4M\'){B(N.3r)m\'ce\';B(N.8t)m\'1b\'}}m F};k $2a(){o 54={};M(o i=0;i<1b.V;i++){M(o K 1a 1b[i]){o ap=1b[i][K];o 6d=54[K];B(6d&&$F(ap)==\'2I\'&&$F(6d)==\'2I\')54[K]=$2a(6d,ap);14 54[K]=ap}}m 54};o $R=k(){o 1p=1b;B(!1p[1])1p=[c,1p[0]];M(o K 1a 1p[1])1p[0][K]=1p[1][K];m 1p[0]};o $5e=k(){M(o i=0,l=1b.V;i-1:c.3k(2z)>-1},b5:k(){m c.3g(/([.*+?^${}()|[\\]\\/\\\\])/g,\'\\\\$1\')}});2t.R({5E:k(1z){B(c.V<3)m O;B(c.V==4&&c[3]==0&&!1z)m\'c2\';o 3C=[];M(o i=0;i<3;i++){o 5d=(c[i]-0).4l(16);3C.1k((5d.V==1)?\'0\'+5d:5d)}m 1z?3C:\'#\'+3C.2c(\'\')},5G:k(1z){B(c.V!=3)m O;o 1s=[];M(o i=0;i<3;i++){1s.1k(5O((c[i].V==1)?c[i]+c[i]:c[i],16))}m 1z?1s:\'1s(\'+1s.2c(\',\')+\')\'}});7Z.R({3a:k(C){o fn=c;C=$2a({\'W\':fn,\'I\':O,\'1b\':1n,\'2g\':O,\'4f\':O,\'6f\':O},C);B($2A(C.1b)&&$F(C.1b)!=\'1z\')C.1b=[C.1b];m k(I){o 1p;B(C.I){I=I||U.I;1p=[(C.I===1e)?I:L C.I(I)];B(C.1b)1p.R(C.1b)}14 1p=C.1b||1b;o 3N=k(){m fn.4j($4T(C.W,fn),1p)};B(C.2g)m 9M(3N,C.2g);B(C.4f)m c3(3N,C.4f);B(C.6f)5j{m 3N()}5c(c9){m O};m 3N()}},bT:k(1p,W){m c.3a({\'1b\':1p,\'W\':W})},6f:k(1p,W){m c.3a({\'1b\':1p,\'W\':W,\'6f\':1e})()},W:k(W,1p){m c.3a({\'W\':W,\'1b\':1p})},c8:k(W,1p){m c.3a({\'W\':W,\'I\':1e,\'1b\':1p})},2g:k(2g,W,1p){m c.3a({\'2g\':2g,\'W\':W,\'1b\':1p})()},4f:k(aV,W,1p){m c.3a({\'4f\':aV,\'W\':W,\'1b\':1p})()}});aN.R({3d:k(){m 5O(c)},aH:k(){m 66(c)},1F:k(3s,1D){m 1c.3s(1D,1c.1D(3s,c))},2q:k(5Y){5Y=1c.3w(10,5Y||0);m 1c.2q(c*5Y)/5Y},c7:k(fn){M(o i=0;i\'}el=Q.aJ(el)}el=$(el);m(!1U||!el)?el:el.2j(1U)}});o 26=L 18({1i:k(T){m(T)?$R(T,c):c}});26.R=k(1U){M(o 1V 1a 1U){c.1L[1V]=1U[1V];c[1V]=$5e.6x(1V)}};k $(el){B(!el)m 1n;B(el.5i)m 2F.52(el);B([U,Q].1j(el))m el;o F=$F(el);B(F==\'2z\'){el=Q.6W(el);F=(el)?\'G\':O}B(F!=\'G\')m 1n;B(el.5i)m 2F.52(el);B([\'2I\',\'c4\'].1j(el.6S.5L()))m el;$R(el,P.1L);el.5i=k(){};m 2F.52(el)};Q.6Y=Q.33;k $$(){o T=[];M(o i=0,j=1b.V;i0&&6Q<13)c.1t=\'f\'+6Q}c.1t=c.1t||6i.bA(c.6O).5L()}14 B(c.F.2v(/(6h|3m|bw)/)){c.1Y={\'x\':I.8E||I.9f+Q.2Z.5V,\'y\':I.8w||I.at+Q.2Z.63};c.9B={\'x\':I.8E?I.8E-U.99:I.9f,\'y\':I.8w?I.8w-U.9i:I.at};c.bR=(I.9K==3)||(I.bv==2);22(c.F){Y\'90\':c.2o=I.2o||I.ca;1C;Y\'8Y\':c.2o=I.2o||I.8A}c.aU()}m c},1R:k(){m c.6U().6X()},6U:k(){B(c.I.6U)c.I.6U();14 c.I.db=1e;m c},6X:k(){B(c.I.6X)c.I.6X();14 c.I.eK=O;m c}});2X.6m={2o:k(){B(c.2o&&c.2o.84==3)c.2o=c.2o.3n},aD:k(){5j{2X.6m.2o.1X(c)}5c(e){c.2o=c.3v}}};2X.1L.aU=(U.8r)?2X.6m.aD:2X.6m.2o;2X.1O=L 3M({\'eL\':13,\'6P\':38,\'eJ\':40,\'1u\':37,\'4n\':39,\'eI\':27,\'eF\':32,\'eG\':8,\'eH\':9,\'57\':46});P.2H.2p={1B:k(F,fn){c.$19=c.$19||{};c.$19[F]=c.$19[F]||{\'1O\':[],\'1I\':[]};B(c.$19[F].1O.1j(fn))m c;c.$19[F].1O.1k(fn);o 76=F;o 2w=P.2p[F];B(2w){B(2w.7F)2w.7F.1X(c,fn);B(2w.2D)fn=2w.2D;B(2w.F)76=2w.F}B(!c.8j)fn=fn.3a({\'W\':c,\'I\':1e});c.$19[F].1I.1k(fn);m(P.8V.1j(76))?c.2C(76,fn):c},4C:k(F,fn){B(!c.$19||!c.$19[F])m c;o 1m=c.$19[F].1O.3k(fn);B(1m==-1)m c;o 1t=c.$19[F].1O.74(1m,1)[0];o J=c.$19[F].1I.74(1m,1)[0];o 2w=P.2p[F];B(2w){B(2w.2K)2w.2K.1X(c,fn);B(2w.F)F=2w.F}m(P.8V.1j(F))?c.3h(F,J):c},6j:k(1Z){m P.72(c,\'1B\',1Z)},78:k(F){B(!c.$19)m c;B(!F){M(o 6g 1a c.$19)c.78(6g);c.$19=1n}14 B(c.$19[F]){c.$19[F].1O.1q(k(fn){c.4C(F,fn)},c);c.$19[F]=1n}m c},1h:k(F,1p,2g){B(c.$19&&c.$19[F]){c.$19[F].1O.1q(k(fn){fn.3a({\'W\':c,\'2g\':2g,\'1b\':1p})()},c)}m c},au:k(15,F){B(!15.$19)m c;B(!F){M(o 6g 1a 15.$19)c.au(15,6g)}14 B(15.$19[F]){15.$19[F].1O.1q(k(fn){c.1B(F,fn)},c)}m c}};U.R(P.2H.2p);Q.R(P.2H.2p);P.R(P.2H.2p);P.2p=L 3M({\'8N\':{F:\'90\',2D:k(I){I=L 2X(I);B(I.2o!=c&&!c.8o(I.2o))c.1h(\'8N\',I)}},\'8P\':{F:\'8Y\',2D:k(I){I=L 2X(I);B(I.2o!=c&&!c.8o(I.2o))c.1h(\'8P\',I)}},\'5a\':{F:(U.8r)?\'8b\':\'5a\'}});P.8V=[\'6h\',\'eM\',\'5z\',\'5n\',\'5a\',\'8b\',\'90\',\'8Y\',\'2M\',\'9X\',\'eN\',\'eS\',\'4e\',\'7v\',\'9t\',\'eT\',\'5o\',\'eR\',\'eQ\',\'3F\',\'eO\',\'eP\',\'48\',\'aE\',\'8s\',\'eE\',\'2G\'];7Z.R({3e:k(W,1p){m c.3a({\'W\':W,\'1b\':1p,\'I\':2X})}});26.R({eV:k(3q){m L 26(c.36(k(el){m(P.4D(el)==3q)}))},a8:k(1A,2J){o T=c.36(k(el){m(el.1A&&el.1A.1j(1A,\' \'))});m(2J)?T:L 26(T)},a2:k(4u,2J){o T=c.36(k(el){m(el.4u==4u)});m(2J)?T:L 26(T)},a9:k(1w,82,J,2J){o T=c.36(k(el){o 2i=P.5R(el,1w);B(!2i)m O;B(!82)m 1e;22(82){Y\'=\':m(2i==J);Y\'*=\':m(2i.1j(J));Y\'^=\':m(2i.6K(0,J.V)==J);Y\'$=\':m(2i.6K(2i.V-J.V)==J);Y\'!=\':m(2i!=J);Y\'~=\':m 2i.1j(J,\' \')}m O});m(2J)?T:L 26(T)}});k $E(1S,36){m($(36)||Q).9P(1S)};k $et(1S,36){m($(36)||Q).6Y(1S)};$$.3B={\'5C\':/^(\\w*|\\*)(?:#([\\w-]+)|\\.([\\w-]+))?(?:\\[(\\w+)(?:([!*^$]?=)["\']?([^"\'\\]]*)["\']?)?])?$/,\'4a\':{7L:k(1x,3b,1d,i){o 2r=[3b.eu?\'7N:\':\'\',1d[1]];B(1d[2])2r.1k(\'[@4u="\',1d[2],\'"]\');B(1d[3])2r.1k(\'[1j(7P(" ", @4R, " "), " \',1d[3],\' ")]\');B(1d[4]){B(1d[5]&&1d[6]){22(1d[5]){Y\'*=\':2r.1k(\'[1j(@\',1d[4],\', "\',1d[6],\'")]\');1C;Y\'^=\':2r.1k(\'[es-er(@\',1d[4],\', "\',1d[6],\'")]\');1C;Y\'$=\':2r.1k(\'[eo(@\',1d[4],\', 2z-V(@\',1d[4],\') - \',1d[6].V,\' + 1) = "\',1d[6],\'"]\');1C;Y\'=\':2r.1k(\'[@\',1d[4],\'="\',1d[6],\'"]\');1C;Y\'!=\':2r.1k(\'[@\',1d[4],\'!="\',1d[6],\'"]\')}}14{2r.1k(\'[@\',1d[4],\']\')}}1x.1k(2r.2c(\'\'));m 1x},7O:k(1x,3b,2J){o T=[];o 4a=Q.5r(\'.//\'+1x.2c(\'//\'),3b,$$.3B.ac,ep.eq,1n);M(o i=0,j=4a.ev;i<\\/2s>\');$(\'7I\').7i=k(){B(c.5m==\'8p\')5X()}}}14{U.2C("4e",5X);Q.2C("fe",5X)}}};U.fm=k(fn){m c.1B(\'7S\',fn)};U.R({8m:k(){B(c.5x)m c.fl;B(c.9a)m Q.4B.9c;m Q.2Z.9c},8n:k(){B(c.5x)m c.fo;B(c.9a)m Q.4B.9d;m Q.2Z.9d},93:k(){B(c.2P)m 1c.1D(Q.2Z.4b,Q.2Z.71);B(c.4x)m Q.4B.71;m Q.2Z.71},92:k(){B(c.2P)m 1c.1D(Q.2Z.3R,Q.2Z.5P);B(c.4x)m Q.4B.5P;m Q.2Z.5P},8u:k(){m c.99||Q.2Z.5V},8v:k(){m c.9i||Q.2Z.63},7g:k(){m{\'3l\':{\'x\':c.8m(),\'y\':c.8n()},\'7h\':{\'x\':c.93(),\'y\':c.92()},\'2G\':{\'x\':c.8u(),\'y\':c.8v()}}},3p:k(){m{\'x\':0,\'y\':0}}});o 1f={};1f.2T=L 18({C:{3X:18.1l,1Q:18.1l,7w:18.1l,2f:k(p){m-(1c.av(1c.7W*p)-1)/2},49:fb,2x:\'4W\',3T:1e,98:50},1i:k(C){c.G=c.G||1n;c.2Y(C);B(c.C.1i)c.C.1i.1X(c)},2n:k(){o 3A=$3A();B(3A=(7-4*a)/11){J=-1c.3w((11-6*a-11*p)/4,2)+b*b;1C}}m J},ds:k(p,x){m 1c.3w(2,10*--p)*1c.av(20*p*1c.7W*(x[0]||1)/3)}});[\'dt\',\'dz\',\'dA\',\'dG\'].1q(k(2f,i){1f.3o[2f]=L 1f.7U(k(p){m 1c.3w(p,[i+2])});1f.3o.7X(2f)});o 4g={};4g.2T=L 18({C:{3J:O,2x:\'4W\',3X:18.1l,al:18.1l,1Q:18.1l,as:18.1l,8S:18.1l,1F:O,3E:{x:\'1u\',y:\'1o\'},4P:O,6M:6},1i:k(el,C){c.2Y(C);c.G=$(el);c.3J=$(c.C.3J)||c.G;c.3m={\'12\':{},\'1m\':{}};c.J={\'1g\':{},\'12\':{}};c.1G={\'1g\':c.1g.3e(c),\'4i\':c.4i.3e(c),\'3D\':c.3D.3e(c),\'1R\':c.1R.W(c)};c.6V();B(c.C.1i)c.C.1i.1X(c)},6V:k(){c.3J.1B(\'5n\',c.1G.1g);m c},9F:k(){c.3J.4C(\'5n\',c.1G.1g);m c},1g:k(I){c.1h(\'al\',c.G);c.3m.1g=I.1Y;o 1F=c.C.1F;c.1F={\'x\':[],\'y\':[]};M(o z 1a c.C.3E){B(!c.C.3E[z])6l;c.J.12[z]=c.G.2h(c.C.3E[z]).3d();c.3m.1m[z]=I.1Y[z]-c.J.12[z];B(1F&&1F[z]){M(o i=0;i<2;i++){B($2A(1F[z][i]))c.1F[z][i]=($F(1F[z][i])==\'k\')?1F[z][i]():1F[z][i]}}}B($F(c.C.4P)==\'4M\')c.C.4P={\'x\':c.C.4P,\'y\':c.C.4P};Q.2C(\'2M\',c.1G.4i);Q.2C(\'5z\',c.1G.1R);c.1h(\'3X\',c.G);I.1R()},4i:k(I){o ao=1c.2q(1c.dH(1c.3w(I.1Y.x-c.3m.1g.x,2)+1c.3w(I.1Y.y-c.3m.1g.y,2)));B(ao>c.C.6M){Q.3h(\'2M\',c.1G.4i);Q.2C(\'2M\',c.1G.3D);c.3D(I);c.1h(\'as\',c.G)}I.1R()},3D:k(I){c.69=O;c.3m.12=I.1Y;M(o z 1a c.C.3E){B(!c.C.3E[z])6l;c.J.12[z]=c.3m.12[z]-c.3m.1m[z];B(c.1F[z]){B($2A(c.1F[z][1])&&(c.J.12[z]>c.1F[z][1])){c.J.12[z]=c.1F[z][1];c.69=1e}14 B($2A(c.1F[z][0])&&(c.J.12[z]el.1u&&12.xel.1o)},1R:k(){B(c.3f&&!c.69)c.3f.1h(\'dC\',[c.G,c]);14 c.G.1h(\'dD\',c);c.1r();m c}});P.R({dq:k(C){m L 4g.aM(c,C)}});o 6n=L 18({C:{23:\'59\',be:1e,9g:18.1l,5h:18.1l,6w:18.1l,aG:1e,5J:\'dp-8\',aZ:O,4J:{}},7q:k(){c.2u=(U.6C)?L 6C():(U.2P?L 9o(\'en.dc\'):O);m c},1i:k(C){c.7q().2Y(C);c.C.5D=c.C.5D||c.5D;c.4J={};B(c.C.aG&&c.C.23==\'59\'){o 5J=(c.C.5J)?\'; dd=\'+c.C.5J:\'\';c.5l(\'9R-F\',\'9J/x-aS-da-d9\'+5J)}B(c.C.1i)c.C.1i.1X(c)},9s:k(){B(c.2u.5m!=4||!c.4Q)m;c.4Q=O;o 4I=0;5j{4I=c.2u.4I}5c(e){};B(c.C.5D.1X(c,4I))c.5h();14 c.6w();c.2u.7i=18.1l},5D:k(4I){m((4I>=d6)&&(4I]*>([\\s\\S]*?)<\\/2s>/dJ;6Z((2s=5C.e9(c.3L.1K)))3y.1k(2s[1]);3y=3y.2c(\'\\n\')}B(3y)(U.9O)?U.9O(3y):U.9M(3y,0)},af:k(1w){5j{m c.2u.ea(1w)}5c(e){};m 1n}});8X.5A=k(1Z){o 5f=[];M(o K 1a 1Z)5f.1k(7e(K)+\'=\'+7e(1Z[K]));m 5f.2c(\'&\')};P.R({6a:k(C){m L 9b(c.5R(\'eb\'),$2a({1T:c.5A()},C,{23:\'59\'})).9h()}});o 3H=L 3M({C:{7o:O,7k:O,49:O,5g:O},2j:k(1t,J,C){C=$2a(c.C,C);J=7e(J);B(C.7o)J+=\'; 7o=\'+C.7o;B(C.7k)J+=\'; 7k=\'+C.7k;B(C.49){o 6k=L 96();6k.e8(6k.9w()+C.49*24*60*60*bd);J+=\'; e7=\'+6k.e4()}B(C.5g)J+=\'; 5g\';Q.4K=1t+\'=\'+J;m $R(C,{\'1t\':1t,\'J\':J})},5q:k(1t){o J=Q.4K.31(\'(?:^|;)\\\\s*\'+1t.b5()+\'=([^;]*)\');m J?e5(J[1]):O},2K:k(4K,C){B($F(4K)==\'2I\')c.2j(4K.1t,\'\',$2a(4K,{49:-1}));14 c.2j(4K,\'\',$2a(C,{49:-1}))}});o 3I={4l:k(N){22($F(N)){Y\'2z\':m\'"\'+N.3g(/(["\\\\])/g,\'\\\\$1\')+\'"\';Y\'1z\':m\'[\'+N.2D(3I.4l).2c(\',\')+\']\';Y\'2I\':o 2z=[];M(o K 1a N)2z.1k(3I.4l(K)+\':\'+3I.4l(N[K]));m\'{\'+2z.2c(\',\')+\'}\';Y\'4M\':B(e6(N))1C;Y O:m\'1n\'}m 6i(N)},5r:k(4H,5g){m(($F(4H)!=\'2z\')||(5g&&!4H.2v(/^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+ec-u \\n\\r\\t])+?$/)))?1n:ed(\'(\'+4H+\')\')}};3I.ej=6n.R({1i:k(2L,C){c.2L=2L;c.1B(\'5h\',c.1Q);c.1r(C);c.5l(\'X-ek\',\'ei\')},6a:k(N){m c.1r(c.2L,\'eh=\'+3I.4l(N))},1Q:k(){c.1h(\'1Q\',[3I.5r(c.3L.1K,c.C.5g)])}});o ar=L 3M({8q:k(1Z,1J){1J=$2a({\'5N\':18.1l},1J);o 2s=L P(\'2s\',{\'4s\':1Z}).6j({\'4e\':1J.5N,\'ee\':k(){B(c.5m==\'8p\')c.1h(\'4e\')}});57 1J.5N;m 2s.6o(1J).28(Q.6e)},1y:k(1Z,1J){m L P(\'4y\',$2a({\'a1\':\'ef\',\'eg\':\'e3\',\'F\':\'1K/1y\',\'4N\':1Z},1J)).28(Q.6e)},4S:k(1Z,1J){1J=$2a({\'5N\':18.1l,\'e2\':18.1l,\'dP\':18.1l},1J);o 4S=L dQ();4S.4s=1Z;o G=L P(\'8x\',{\'4s\':1Z});[\'4e\',\'8s\',\'aE\'].1q(k(F){o I=1J[\'67\'+F];57 1J[\'67\'+F];G.1B(F,k(){c.4C(F,1b.8t);I.1X(c)})});B(4S.2y&&4S.2N)G.1h(\'4e\',G,1);m G.6o(1J)},6s:k(58,C){C=$2a({1Q:18.1l,an:18.1l},C);B(!58.1k)58=[58];o 6s=[];o 6q=0;58.1q(k(1Z){o 8x=L ar.4S(1Z,{\'5N\':k(){C.an.1X(c,6q);6q++;B(6q==58.V)C.1Q()}});6s.1k(8x)});m L 26(6s)}});o 3O=L 18({V:0,1i:k(2I){c.N=2I||{};c.5K()},5q:k(1t){m(c.6t(1t))?c.N[1t]:1n},6t:k(1t){m(1t 1a c.N)},2j:k(1t,J){B(!c.6t(1t))c.V++;c.N[1t]=J;m c},5K:k(){c.V=0;M(o p 1a c.N)c.V++;m c},2K:k(1t){B(c.6t(1t)){57 c.N[1t];c.V--}m c},1q:k(fn,W){$1q(c.N,fn,W)},R:k(N){$R(c.N,N);m c.5K()},2a:k(){c.N=$2a.4j(1n,[c.N].R(1b));m c.5K()},1l:k(){c.N={};c.V=0;m c},1O:k(){o 1O=[];M(o K 1a c.N)1O.1k(K);m 1O},1I:k(){o 1I=[];M(o K 1a c.N)1I.1k(c.N[K]);m 1I}});k $H(N){m L 3O(N)};3O.3H=3O.R({1i:k(1w,C){c.1w=1w;c.C=$R({\'aw\':1e},C||{});c.4e()},aX:k(){B(c.V==0){3H.2K(c.1w,c.C);m 1e}o 4H=3I.4l(c.N);B(4H.V>dR)m O;3H.2j(c.1w,4H,c.C);m 1e},4e:k(){c.N=3I.5r(3H.5q(c.1w),1e)||{};c.5K()}});3O.3H.2H={};[\'R\',\'2j\',\'2a\',\'1l\',\'2K\'].1q(k(23){3O.3H.2H[23]=k(){3O.1L[23].4j(c,1b);B(c.C.aw)c.aX();m c}});3O.3H.3i(3O.3H.2H);o 2Q=L 18({1i:k(2E,F){F=F||(2E.1k?\'1s\':\'3C\');o 1s,2m;22(F){Y\'1s\':1s=2E;2m=1s.8h();1C;Y\'2m\':1s=2E.b9();2m=2E;1C;62:1s=2E.5G(1e);2m=1s.8h()}1s.2m=2m;1s.3C=1s.5E();m $R(1s,2Q.1L)},54:k(){o 5I=$A(1b);o 7d=($F(5I[5I.V-1])==\'4M\')?5I.dO():50;o 1s=c.8e();5I.1q(k(2E){2E=L 2Q(2E);M(o i=0;i<3;i++)1s[i]=1c.2q((1s[i]/ 35 * (35 - 7d)) + (2E[i] /35*7d))});m L 2Q(1s,\'1s\')},dN:k(){m L 2Q(c.2D(k(J){m 51-J}))},dK:k(J){m L 2Q([J,c.2m[1],c.2m[2]],\'2m\')},dL:k(7a){m L 2Q([c.2m[0],7a,c.2m[2]],\'2m\')},dM:k(7a){m L 2Q([c.2m[0],c.2m[1],7a],\'2m\')}});k $dS(r,g,b){m L 2Q([r,g,b],\'1s\')};k $dT(h,s,b){m L 2Q([h,s,b],\'2m\')};2t.R({8h:k(){o 5W=c[0],65=c[1],75=c[2];o 2W,6y,8k;o 1D=1c.1D(5W,65,75),3s=1c.3s(5W,65,75);o 4p=1D-3s;8k=1D/51;6y=(1D!=0)?4p/1D:0;B(6y==0){2W=0}14{o 8l=(1D-5W)/4p;o 8W=(1D-65)/4p;o br=(1D-75)/4p;B(5W==1D)2W=br-8W;14 B(65==1D)2W=2+8l-br;14 2W=4+8W-8l;2W/=6;B(2W<0)2W++}m[1c.2q(2W*bc),1c.2q(6y*35),1c.2q(8k*35)]},b9:k(){o br=1c.2q(c[2]/35*51);B(c[1]==0){m[br,br,br]}14{o 2W=c[0]%bc;o f=2W%60;o p=1c.2q((c[2]*(35-c[1]))/dZ*51);o q=1c.2q((c[2]*(b7-c[1]*f))/bm*51);o t=1c.2q((c[2]*(b7-c[1]*(60-f)))/bm*51);22(1c.9q(2W/60)){Y 0:m[br,t,p];Y 1:m[q,br,p];Y 2:m[p,br,t];Y 3:m[p,q,br];Y 4:m[t,p,br];Y 5:m[br,p,q]}}m O}});o 9x=L 18({C:{6b:20,8O:1,6F:k(x,y){c.G.3G(x,y)}},1i:k(G,C){c.2Y(C);c.G=$(G);c.8y=([U,Q].1j(G))?$(Q.4B):c.G},1g:k(){c.8z=c.9A.3e(c);c.8y.2C(\'2M\',c.8z)},1R:k(){c.8y.3h(\'2M\',c.8z);c.1H=$55(c.1H)},9A:k(I){c.1Y=(c.G==U)?I.9B:I.1Y;B(!c.1H)c.1H=c.2G.4f(50,c)},2G:k(){o el=c.G.7g();o 1m=c.G.3p();o 3F={\'x\':0,\'y\':0};M(o z 1a c.1Y){B(c.1Y[z]<(c.C.6b+1m[z])&&el.2G[z]!=0)3F[z]=(c.1Y[z]-c.C.6b-1m[z])*c.C.8O;14 B(c.1Y[z]+c.C.6b>(el.3l[z]+1m[z])&&el.2G[z]+el.3l[z]!=el.7h[z])3F[z]=(c.1Y[z]-el.3l[z]+c.C.6b-1m[z])*c.C.8O}B(3F.y||3F.x)c.1h(\'6F\',[el.2G.x+3F.x,el.2G.y+3F.y])}});9x.3i(L 2p,L 43);o 8B=L 18({C:{6F:18.1l,1Q:18.1l,8L:k(1m){c.4h.1P(c.p,1m)},2b:\'8M\',6E:35,1E:0},1i:k(el,4h,C){c.G=$(el);c.4h=$(4h);c.2Y(C);c.8K=-1;c.8D=-1;c.2n=-1;c.G.1B(\'5n\',c.9D.3e(c));o 6H,1E;22(c.C.2b){Y\'8M\':c.z=\'x\';c.p=\'1u\';6H={\'x\':\'1u\',\'y\':O};1E=\'4b\';1C;Y\'8Q\':c.z=\'y\';c.p=\'1o\';6H={\'x\':O,\'y\':\'1o\'};1E=\'3R\'}c.1D=c.G[1E]-c.4h[1E]+(c.C.1E*2);c.a5=c.4h[1E]/2;c.ai=c.G[\'5q\'+c.p.8R()].W(c.G);c.4h.1P(\'1v\',\'70\').1P(c.p,-c.C.1E);o 8U={};8U[c.z]=[-c.C.1E,c.1D-c.C.1E];c.3D=L 4g.2T(c.4h,{1F:8U,3E:6H,6M:0,3X:k(){c.6L()}.W(c),8S:k(){c.6L()}.W(c),1Q:k(){c.6L();c.29()}.W(c)});B(c.C.1i)c.C.1i.1X(c)},2j:k(2n){c.2n=2n.1F(0,c.C.6E);c.6G();c.29();c.1h(\'8L\',c.a0(c.2n));m c},9D:k(I){o 1v=I.1Y[c.z]-c.ai()-c.a5;1v=1v.1F(-c.C.1E,c.1D-c.C.1E);c.2n=c.8C(1v);c.6G();c.29();c.1h(\'8L\',1v)},6L:k(){c.2n=c.8C(c.3D.J.12[c.z]);c.6G()},6G:k(){B(c.8K!=c.2n){c.8K=c.2n;c.1h(\'6F\',c.2n)}},29:k(){B(c.8D!==c.2n){c.8D=c.2n;c.1h(\'1Q\',c.2n+\'\')}},8C:k(1v){m 1c.2q((1v+c.C.1E)/c.1D*c.C.6E)},a0:k(2n){m c.1D*2n/c.C.6E}});8B.3i(L 2p);8B.3i(L 43);o e0=1f.ah.R({1i:k(C){c.1r(U,C);c.5w=(c.C.5w)?$$(c.C.5w):$$(Q.5w);o 5k=U.5k.4N.31(/^[^#]*/)[0]+\'#\';c.5w.1q(k(4y){B(4y.4N.3k(5k)!=0)m;o 3K=4y.4N.6K(5k.V);B(3K&&$(3K))c.9L(4y,3K)},c);B(!U.5x)c.1B(\'1Q\',k(){U.5k.e1=c.3K})},9L:k(4y,3K){4y.1B(\'6h\',k(I){c.3K=3K;c.8A(3K);I.1R()}.3e(c))}});o 9S=L 18({C:{4L:O,3X:18.1l,1Q:18.1l,2S:1e,6M:3,9H:k(G,2S){2S.1P(\'21\',0.7);G.1P(\'21\',0.7)},9e:k(G,2S){G.1P(\'21\',1);2S.2K();c.3V.2K()}},1i:k(5p,C){c.2Y(C);c.5p=$(5p);c.T=c.5p.8H();c.4L=(c.C.4L)?$$(c.C.4L):c.T;c.1G={\'1g\':[],\'5y\':c.5y.3e(c)};M(o i=0,l=c.4L.V;i0);o 6T=c.4G.9W();o 3x=c.4G.8I();B(6T&&6P&&12<6T.4E().3P)c.4G.7Y(6T);B(3x&&!6P&&12>3x.4E().1o)c.4G.6v(3x);c.2l=12},dY:k(9Q){m c.5p.8H().2D(9Q||k(el){m c.T.3k(el)},c)},29:k(){c.2l=1n;Q.3h(\'2M\',c.1G.5o);Q.3h(\'5z\',c.1G.29);B(c.C.2S){Q.3h(\'2M\',c.1G.5y);c.1h(\'9e\',[c.4G,c.2S])}c.1h(\'1Q\',c.4G)}});9S.3i(L 2p,L 43);o aI=L 18({C:{aT:k(3W){3W.1P(\'4z\',\'8G\')},aW:k(3W){3W.1P(\'4z\',\'4O\')},8T:30,bp:35,bt:35,1A:\'dX\',5F:{\'x\':16,\'y\':16},4V:O},1i:k(T,C){c.2Y(C);c.45=L P(\'4Z\',{\'4R\':c.C.1A+\'-3W\',\'8J\':{\'1v\':\'3Y\',\'1o\':\'0\',\'1u\':\'0\',\'4z\':\'4O\'}}).28(Q.4B);c.3c=L P(\'4Z\').28(c.45);$$(T).1q(c.9I,c);B(c.C.1i)c.C.1i.1X(c)},9I:k(el){el.$1W.42=(el.4N&&el.4D()==\'a\')?el.4N.3g(\'9Y://\',\'\'):(el.a1||O);B(el.53){o 6z=el.53.68(\'::\');B(6z.V>1){el.$1W.42=6z[0].5T();el.$1W.5u=6z[1].5T()}14{el.$1W.5u=el.53}el.a7(\'53\')}14{el.$1W.5u=O}B(el.$1W.42&&el.$1W.42.V>c.C.8T)el.$1W.42=el.$1W.42.6K(0,c.C.8T-1)+"&dU;";el.1B(\'8N\',k(I){c.1g(el);B(!c.C.4V)c.8f(I);14 c.1v(el)}.W(c));B(!c.C.4V)el.1B(\'2M\',c.8f.3e(c));o 29=c.29.W(c);el.1B(\'8P\',29);el.1B(\'3V\',29)},1g:k(el){c.3c.1l();B(el.$1W.42){c.53=L P(\'b0\').28(L P(\'4Z\',{\'4R\':c.C.1A+\'-53\'}).28(c.3c)).5s(el.$1W.42)}B(el.$1W.5u){c.1K=L P(\'b0\').28(L P(\'4Z\',{\'4R\':c.C.1A+\'-1K\'}).28(c.3c)).5s(el.$1W.5u)}$55(c.1H);c.1H=c.4d.2g(c.C.bp,c)},29:k(I){$55(c.1H);c.1H=c.3Z.2g(c.C.bt,c)},1v:k(G){o 1m=G.3p();c.45.4A({\'1u\':1m.x+c.C.5F.x,\'1o\':1m.y+c.C.5F.y})},8f:k(I){o am={\'x\':U.8m(),\'y\':U.8n()};o 2G={\'x\':U.8u(),\'y\':U.8v()};o 3W={\'x\':c.45.4b,\'y\':c.45.3R};o 1V={\'x\':\'1u\',\'y\':\'1o\'};M(o z 1a 1V){o 1m=I.1Y[z]+c.C.5F[z];B((1m+3W[z]-2G[z])>am[z])1m=I.1Y[z]-c.C.5F[z]-3W[z];c.45.1P(1V[z],1m)}},4d:k(){B(c.C.aq)c.1H=c.3Z.2g(c.C.aq,c);c.1h(\'aT\',[c.45])},3Z:k(){c.1h(\'aW\',[c.45])}});aI.3i(L 2p,L 43);o dV=L 18({1i:k(){c.6D=$A(1b);c.19={};c.4U={}},1B:k(F,fn){c.4U[F]=c.4U[F]||{};c.19[F]=c.19[F]||[];B(c.19[F].1j(fn))m O;14 c.19[F].1k(fn);c.6D.1q(k(5v,i){5v.1B(F,c.4i.W(c,[F,5v,i]))},c);m c},4i:k(F,5v,i){c.4U[F][i]=1e;o 4F=c.6D.4F(k(2i,j){m c.4U[F][j]||O},c);B(!4F)m;c.4U[F]={};c.19[F].1q(k(I){I.1X(c,c.6D,5v)},c)}});o 7t=1f.26.R({C:{7K:18.1l,aa:18.1l,3Q:0,4d:O,2N:1e,2y:O,21:1e,7f:O,7n:O,3T:O,6I:O},1i:k(){o C,2B,T,2d;$1q(1b,k(4t,i){22($F(4t)){Y\'2I\':C=4t;1C;Y\'G\':2d=$(4t);1C;62:o 2r=$$(4t);B(!2B)2B=2r;14 T=2r}});c.2B=2B||[];c.T=T||[];c.2d=$(2d);c.2Y(C);c.2l=-1;B(c.C.6I)c.C.3T=1e;B($2A(c.C.4d)){c.C.3Q=O;c.2l=c.C.4d}B(c.C.1g){c.C.3Q=O;c.C.4d=O}c.3U={};B(c.C.21)c.3U.21=\'b8\';B(c.C.2y)c.3U.2y=c.C.7n?\'aj\':\'4b\';B(c.C.2N)c.3U.2N=c.C.7f?\'9n\':\'5P\';M(o i=0,l=c.2B.V;i0));c.1h(3Z?\'aa\':\'7K\',[c.2B[i],el]);M(o 2O 1a c.3U)N[i][2O]=3Z?0:el[c.3U[2O]]},c);m c.1g(N)},dW:k(25){m c.3Q(25)}});1f.7t=7t;',62,956,'||||||||||||this||||||||function||return||var|||||||||||||if|options|||type|element||event|value|property|new|for|obj|false|Element|document|extend||elements|window|length|bind||case||||now||else|from||to|Class|events|in|arguments|Math|param|true|Fx|start|fireEvent|initialize|contains|push|empty|pos|null|top|args|each|parent|rgb|key|left|position|name|items|css|array|className|addEvent|break|max|offset|limit|bound|timer|values|properties|text|prototype|result|style|keys|setStyle|onComplete|stop|selector|data|props|prop|tmp|call|page|source||opacity|switch|method||index|Elements||inject|end|merge|mode|join|container|parsed|transition|delay|getStyle|current|set|overflown|previous|hsb|step|relatedTarget|Events|round|temp|script|Array|transport|test|custom|unit|width|string|chk|togglers|addListener|map|color|Garbage|scroll|Methods|object|nocash|remove|url|mousemove|height|fx|ie|Color|border|ghost|Base|params|parse|hue|Event|setOptions|documentElement||match||getElementsByTagName|margin|100|filter||||create|context|wrapper|toInt|bindWithEvent|overed|replace|removeListener|implement|toggler|indexOf|size|mouse|parentNode|Transitions|getPosition|tag|item|min|CSS|iCss|target|pow|next|scripts|option|time|shared|hex|drag|modifiers|change|scrollTo|Cookie|Json|handle|anchor|response|Abstract|returns|Hash|bottom|display|offsetHeight|len|wait|effects|trash|tip|onStart|absolute|hide||iterable|myTitle|Options|getValue|toolTip||iTo|select|duration|xpath|offsetWidth|Styles|show|load|periodical|Drag|knob|check|apply|increase|toString|val|right|compute|delta|setNow|cont|src|argument|id|chains|padding|webkit|link|visibility|setStyles|body|removeEvent|getTag|getCoordinates|every|active|str|status|headers|cookie|handles|number|href|hidden|grid|running|class|image|pick|checker|fixed|px|open|results|div||255|collect|title|mix|clear|getNow|delete|sources|post|mousewheel|fromTo|catch|bit|native|queryString|secure|onSuccess|htmlElement|try|location|setHeader|readyState|mousedown|move|list|get|evaluate|setHTML|xml|myText|instance|links|webkit419|moveGhost|mouseup|toQueryString|HTMLElement|regexp|isSuccess|rgbToHex|offsets|hexToRgb|_method|colors|encoding|setLength|toLowerCase|unique|onload|parseInt|scrollHeight|iFrom|getProperty|include|trim|iNow|scrollLeft|red|domReady|precision|klass||walk|default|scrollTop|layout|green|parseFloat|on|split|out|send|area|droppables|mp|head|attempt|evType|click|String|addEvents|date|continue|fix|XHR|setProperties|currentStyle|counter|included|images|hasKey|brother|injectAfter|onFailure|generic|saturation|dual|Properties|loaded|XMLHttpRequest|instances|steps|onChange|checkStep|mod|alwaysHide|Listeners|substr|draggedKnob|snap|getElements|code|up|fKey|evalScripts|tagName|prev|stopPropagation|attach|getElementById|preventDefault|getElementsBySelector|while|relative|scrollWidth|setMany|Multi|splice|blue|realType|defined|removeEvents|regex|percent|forEach|typeof|alpha|encodeURIComponent|fixedHeight|getSize|scrollSize|onreadystatechange|none|path|setProperty|proto|fixedWidth|domain|evalResponse|setTransport|clean|hasClass|Accordion|Chain|unload|onCancel|update|RegExp|callChain|toUpperCase|transitions|qs|disabled|checked|add|pairs|getMany|ie_ready|multiple|onActive|getParam|found|xhtml|getItems|concat|selected|Dom|domready|Style|Transition|flag|PI|compat|injectBefore|Function|getLast|node|operator|innerText|nodeType|iProps|appendChild|cssText|firstChild|easeType|camelCase|DOMMouseScroll|random|charAt|copy|locate|newArray|rgbToHsb|merged|addEventListener|brightness|rr|getWidth|getHeight|hasChild|complete|javascript|gecko|abort|callee|getScrollLeft|getScrollTop|pageY|img|mousemover|coord|toElement|Slider|toStep|previousEnd|pageX|coordinates|visible|getChildren|getNext|styles|previousChange|onTick|horizontal|mouseenter|velocity|mouseleave|vertical|capitalize|onDrag|maxTitleChars|lim|NativeEvents|gr|Object|mouseout|first|mouseover|insertBefore|getScrollHeight|getScrollWidth|after|cancel|Date|borderShort|fps|pageXOffset|opera|Ajax|clientWidth|clientHeight|onDragComplete|clientX|onRequest|request|pageYOffset|Single|before|Merge|pp|fullHeight|ActiveXObject|wheelDelta|floor|wheelStops|onStateChange|beforeunload|iParsed|direction|getTime|Scroller|overflow|addClass|getCoords|client|constructor|clickedElement|removeClass|detach|clone|onDragStart|build|application|which|useLink|setTimeout|undefined|execScript|getElement|converter|Content|Sortables|normal|sel|contents|getPrevious|keydown|http|prefix|toPosition|rel|filterById|PropertiesIFlag|removeEventListener|half|Left|removeAttribute|filterByClass|filterByAttribute|onBackground|input|resolver|textarea|getFormElements|getHeader|ie6|Scroll|getPos|fullWidth|zoom|onBeforeStart|win|onProgress|distance||timeout|Asset|onSnap|clientY|cloneEvents|cos|autoSave|nodeValue|where|Bottom|idx|elementsProperty|childNodes|relatedTargetGecko|error|defaultView|urlEncoded|toFloat|Tips|createElement|shift|hyphenate|Move|Number|checkAgainst|getLeft|getTop|addSection|www|onShow|fixRelatedTarget|interval|onHide|save|picked|autoCancel|span|textContent|adopt|innerHTML|styleSheet|escapeRegExp|fixStyle|6000|fullOpacity|hsbToRgb|slideIn|slideOut|360|1000|async|Width|getStyles|slice|Top|sin|setOpacity|removeChild|600000|appendText|0px|showDelay|extended||Right|hideDelay|full|button|menu|shiftKey|metaKey|altKey|fromCharCode|frameborder|ctrlKey|attachEvent|detail|srcElement|control|CollectGarbage|readonly|frameBorder|alt|keyCode|111|readOnly|meta|detachEvent|120|rightClick|wheel|pass|some|associate|getRandom|clearChain|chain|DOMElement|execCommand|BackgroundImageCache|transparent|setInterval|embed|boolean|injectInside|times|bindAsEventListener|err|fromElement|iframe|khtml|whitespace|collection|clearTimeout|textnode|nodeName|MooTools|version|clearInterval|Window|taintEnabled|webkit420|getBoxObjectFor|navigator|all|Document|ie7|injectTop|cloneNode|borderStyle|borderColor|htmlFor|borderWidth|getText|getProperties|setAttribute|setText|colspan|colSpan|tabindex|tabIndex|maxlength|accessKey|accesskey|rowspan|rowSpan|removeProperty|attributes|float|styleFloat|cssFloat|toggleClass|createTextNode|replaceWith|replaceChild|zIndex|hasLayout|lastChild|getParent|getAttribute|getFirst|Sibling|getComputedStyle|getPropertyValue|maxLength|overrideMimeType|200|300|responseText|urlencoded|form|cancelBubble|XMLHTTP|charset|responseXML|Connection|Accept|html|ecma|With|Requested|close|setRequestHeader|postBody||utf|makeDraggable|Bounce|Elastic|Quad|618|Back|Circ|acos|Sine|Cubic|Quart|over|drop|emptydrop|leave|makeResizable|Quint|sqrt|java|gi|setHue|setSaturation|setBrightness|invert|pop|onerror|Image|4096|RGB|HSB|hellip|Group|showThisHideOpen|tool|serialize|10000|SmoothScroll|hash|onabort|screen|toGMTString|decodeURIComponent|isFinite|expires|setTime|exec|getResponseHeader|action|Eaeflnr|eval|readystatechange|stylesheet|media|json|JSON|Remote|Request||Expo|Microsoft|substring|XPathResult|UNORDERED_NODE_SNAPSHOT_TYPE|with|starts|ES|namespaceURI|snapshotLength|snapshotItem|checkbox|radio|getElementsByClassName|1999|w3|org|Pow|contextmenu|space|backspace|tab|esc|down|returnValue|enter|dblclick|keypress|submit|reset|blur|focus|keyup|resize|password|filterByTag|toLeft|toRight|Slide|toBottom|toTop|clearTimer|effect|toggle|easeIn|InOut|ease|Out|In|easeOut|easeInOut|500|linear|defer|DOMContentLoaded|protocol|write|offsetTop|https|offsetParent|void|innerWidth|onDomReady||innerHeight|offsetLeft'.split('|'),0,{})) diff --git a/js/jscalendar/ng_cal_inner_parts.gif b/js/jscalendar/ng_cal_inner_parts.gif new file mode 100644 index 0000000..a9dcbd7 Binary files /dev/null and b/js/jscalendar/ng_cal_inner_parts.gif differ diff --git a/js/jscalendar/ng_cal_outer_parts.gif b/js/jscalendar/ng_cal_outer_parts.gif new file mode 100644 index 0000000..92184f8 Binary files /dev/null and b/js/jscalendar/ng_cal_outer_parts.gif differ diff --git a/js/jscalendar/nogray_calendar_vs1.css b/js/jscalendar/nogray_calendar_vs1.css new file mode 100644 index 0000000..a8c932b --- /dev/null +++ b/js/jscalendar/nogray_calendar_vs1.css @@ -0,0 +1,78 @@ +/* +Default style for the nogray calendar +the default class prefix is ng- + +made by Wesam Saif +http://www.nogray.com +support@nogray.com + +http://www.nogray.com/license.php + +*/ + +/* the main header table */ +.ng-cal-header-table {margin:5px; + border-collapse:collapse; + background:#efefef; + border:solid #cccccc 1px;} + +/* the previous and next table cells */ +.ng-cal-previous-td, .ng-cal-next-td {width:20px; + height:20px; + color:#336699; + border:solid #cccccc 1px; + text-align:center;} + +/* the calendar header table cell (the area where the month and year is displayed) */ +.ng-cal-header-td {text-align:center; + font-weight:bold; + padding:3px;} + +/* the main month table */ +.ng-cal {border:solid #5fd7d6 1px; + border-collapse:collapse; + margin:5px; + margin-top:0px; + background-color:#FFFFFF;} + +.ng-cal * {font-size:8pt;} +.ng-cal td {padding:3px; + border:solid #9eefee 1px;} + +/* the days name table cells */ +.ng-cal .ng-days-name-td {background:#9eefee; + border:solid #5fd7d6 1px;} + +/* the month name table cell */ +.ng-month-name-th {background:#2f99b4; + font-weight:bold; + padding:3px; + color:#ffffff; + text-align:center;} + +/* weekends, days off, dates off (holidayes) */ +.ng-weekend, .ng-dayOff, .ng-dateOff {color:#999999;} + +/* out of range days (previous or next month) */ +.ng-outOfRange {color:#999999; + background:#efefef; + text-decoration:line-through;} + +/* the style for the table cell when the mouse is over it */ +.ng-mouse-over {font-weight:bold; + background:#faedd6;} + +/* selected day */ +.ng-selected-day {background:#ffbb45; + font-weight:bold;} + +/* the close and clear buttons in the bottom of the calendar */ +.ng-close-link, .ng-clear-link {padding:3px; + text-align:center; + color:#666666; + text-decoration:none; + font-size:8pt; + background:#efefef; + border:solid #cccccc 1px; + float:right; + margin-right:5px;} \ No newline at end of file diff --git a/js/jscalendar/nogray_calendar_vs1.js b/js/jscalendar/nogray_calendar_vs1.js new file mode 100644 index 0000000..6149fc2 --- /dev/null +++ b/js/jscalendar/nogray_calendar_vs1.js @@ -0,0 +1,2898 @@ +/* + +Script: nogray_calendar_vs1.js + + Calendar class (see below) + + + +License: + + http://www.nogray.com/license.php + + + +provided by the NoGray.com + +by Wesam Saif + +http://www.nogray.com + +support: support@nogray.com + +*/ + +/* + +Class: + + Calendar + + The calendar class will create a calendar component and attach it to + + an input field or drop down select menus. It uses the nogray_date.js for + + easier formatting and date processing. + + + + features: + + create any numbers of months per calendar + + + + set the weekend, days off, holidays (dates off), start day of the week + + + + start and end date + + + + multiselection and limits + + + + and much more.... + + + + + + date object: + + The calendar can process any format of date when supplying options or arguments + + The date object can be one of the following + + - JavaScript Date + + - a Date in a string format (check nogray_date.js for details) + + - a Date in an object format (check nogray_date.js for details) + + - a number (JavaScript time value- number of milliseconds) + + + + + + + +Options: + + visible: indicates weather the calendar will be visible when loaded or not + + default is false + + + + offset: The calender by default will show at the lower left corner of the input field. + + offset allows for moving the offsetting the calendar position + + default x:0 y:0 + + + + dateFormat: The format of the date output (for text field). + + check nogray_date.js for details + + check http://us3.php.net/manual/en/function.date.php for formatting details + + default D, d M Y + + + + numMonths: the number of months per calendar + + Warrning: Firefox is a little slow when handling date objects. If using datesoff or forcedSelection + + try to use 1 month per calendar + + default 1 + + + + classPrefix: the CSS class prefix, check the skining manual for instructions + + default ng- + + + + idPrefix: the id prefix for the dates, use this if using more than one calendar + + default ng- + + + + startDay: the first day of the week 0 for Sunday 6 for Saturday + + default 0 + + + + startDate: the first selectable date, use a date object (check above). + + default today + + + + endDate: the last selectable date, use a date object (check above). + + default year+10 + + + + inputType: the type of input fields can be one of the following + + - text (default) + + - select + + - none + + + + inputField: the input field to be used + + if the inputType is text it can be either an HTML input object or the object ID + + Example: + + HTML: + + later .... + + Script: + + inputField:'date' + + if the inputType is select it must be an object with the date, month and year HTML object + + Example: + + HTML: + + + + + + later .... + + Script: + + inputField:{date:'date', + + month:'month', + + year:'year'} + + + + + + allowSelection: allow the user to select a date + + default true + + + + + + multiSelection: allow the user to select more than one date + + default false + + + + maxSelection: the maximum number of dates the user can select if multiSelection is true + + set to 0 for unlimited number of dates + + default 0 + + + + selectedDate: the initially selected date + + default null + + + + datesOff: an Array of date objects for the holidays. Holidays are unselectable unless allowDatesOffSelection is true + + default [] + + + + allowDatesOffSelection: allow the user to select the dates off + + default false + + + + daysOff: an Array of numbers for the days off in the week (beside weekends). daysOff are unselectable unless allowDaysOffSelection is true + + default [] + + + + allowDaysOffSelection: allow the user to select days off + + default false + + + + weekend: an Array of numbers for the weekend. weekend is unselectable unless allowWeekendSelection is true + + + + allowWeekendSelection: allow the user to select the weekend + + default false + + + + forceSelections: an Array of date objects that are forced to be selectable regardless where they fall + + (e.g. if the user can select the last Saturday of the month but not other weekend days) + + default [] + + + + + + formatter: a function that will return the HTML for the table cell. + + arguments JavaScript Date + + default returns the date + + + + outOfRangeFormatter: a function that will return the HTML for out of range date table cell. + + arguments JavaScript Date + + default returns the date + + + + weekendFormatter: a function that will return the HTML for the weekend table cell. + + arguments JavaScript Date + + default returns the date + + + + + + daysOffFormatter: a function that will return the HTML for the days off table cell. + + arguments JavaScript Date + + default returns the date + + + + datesOffFormatter: a function that will return the HTML for dates off table cell. + + arguments JavaScript Date + + default returns the date + + + + selectedDateFormatter: a function that will return the HTML for the selected date table cell. + + arguments JavaScript Date + + default returns the date + + + + language: a language object (check nogray_date.js for details) for translation + + default null (English will be used) + + + + daysText: the days format to be used on the calendar + + default mid + + + + monthsText: the month format to be used on the calendar + + default long + + + + preTdHTML: the HTML for the previous months arrow + + defualt « + + + + preTdHTMLOff: the HTML for the previous months arrow when the calendar at the very first mont + + default   + + + + nexTdHTML: the HTML for the previous months arrow + + default » + + + + nexTdHTMLOff: the HTML for the previous months arrow when the calendar at the very last mont + + default:   + + + + closeLinkHTML: the HTML for the close link + + default Close + + + + clearLinkHTML: the HTML for the clear link + + default: Clear + + + + calEvents: an Events object for all the selectable table cell + + Events objects are objects that the key is a javascript event (without the on, e.g. click, mouseover, mouseout) + + and the value is the function to execute when the event occure. + + default: mouseover add a mouse-over class to the table cell + + mouseout remove the mouse-over class from the table cell + + + + + + tdEvents: an array of Events objects to attach events to sepecific table cells + + the array key must be the date in the following format + + month-date-year + + for example events_arr['8-15-2007'] = {click:function(td, date_str){alert(td.innerHTML+" "+new Date().fromString(date_str));}}; + + arguments passed td (the table cell) + + date_str (the date in a string format) + + default [] + + + + dateOnAvailable: an array of functions to run when a specific date is rendered + + the array key must be the date in the following format + + month-date-year + + + + for example date_on_arr['8-15-2007'] = function(td, date_str){td.setHTML(date_str);} + + arguments passed td (the table cell) + + date_str (the date in a string format) + + default [] + + + + + + speedFireFox: option to speed the calendar creation in Firefox, + + since firefox is slow when handling date objects, this option will illimenate + + the datesOff (holidays) daysOff and forcedSelection for better performance. + + default false + + + + closeOpenCalendars: close all the open calendars when the calendar opens + + default true + + + +Events: + + onSelect: an event that will be fired everytime a date is selected + + + + onUnSelect: an event that will be fired everytime a date is unselected + + + + onCalendarLoad: an event that will be fired once all the tables for the calendar are created. + + + + onOpen: an event that will be fired when the calendar is opened + + + + onClose: an event that will be fired when the calendar is closed + + + + onClear: an event that will be fired when all the selected dates are cleared + + + +Object Variables: + + visibleMonth: + + an array that hold all the visible months values in the key in the following format + + month-year + + Example: + + alert(this.visibleMonth[8-2007]); // will alert true if september is visible + + + + selectedDates: + + an array of all the selected dates in time value (milliseconds) + + + +*/ + + + +var Calendar = new Class({ + + options:{ + + visible: false, + + + + offset:{x:0, + + y:0}, + + + + dateFormat:'D, d M Y', + + + + numMonths: 1, + + + + classPrefix: 'ng-', + + idPrefix: 'ng-', + + + + startDay:0, + + + + startDate:'today', + + endDate:'year+10', + + + + inputType:'text', + + + + inputField:null, + + + + allowSelection: true, + + multiSelection: false, + + maxSelection: 0, + + + + selectedDate:null, + + + + datesOff:[], + + + + allowDatesOffSelection:false, + + + + daysOff:[], + + + + allowDaysOffSelection:false, + + + + weekend:[0,6], + + + + allowWeekendSelection:false, + + + + forceSelections:[], + + + + onSelect: function(){ + + return; + + }, + + + + onUnSelect: function(){ + + return; + + }, + + + + onCalendarLoad: function(){ + + return; + + }, + + + + onOpen: function(){ + + return; + + }, + + + + onClose: function(){ + + return; + + }, + + + + onClear: function(){ + + return; + + }, + + + + formatter: function(dt){ + + return dt.getDate(); + + }, + + outOfRangeFormatter: function(dt){ + + return dt.getDate(); + + }, + + weekendFormatter: function(dt){ + + return dt.getDate(); + + }, + + daysOffFormatter: function(dt){ + + return dt.getDate(); + + }, + + datesOffFormatter: function(dt){ + + return dt.getDate(); + + }, + + selectedDateFormatter:function(dt){ + + return dt.getDate(); + + }, + + + + language: null, + + daysText : 'mid', + + monthsText : 'long', + + + + preTdHTML: "«", + + preTdHTMLOff: " ", + + nexTdHTML: "»", + + nexTdHTMLOff: " ", + + closeLinkHTML: "Close", + + clearLinkHTML: "Clear", + + + + calEvents: {mouseenter:function(td){ + + if (!td.hasClass(this.options.classPrefix+"selected-day")) td.addClass(this.options.classPrefix+"mouse-over"); + + }, + + mouseleave:function(td){ + + td.removeClass(this.options.classPrefix+"mouse-over"); + + }}, + + + + tdEvents: [], + + + + dateOnAvailable: [], + + + + speedFireFox: false, + + + + closeOpenCalendars:true + + + + }, + + + + initialize: function(el, toggler, options){ + + this.element = $(el); + + if ($defined(toggler)) + + this.toggler = $(toggler); + + else + + this.toggler = null; + + + + this.visibleMonth = []; + + this.manageTDs = []; + + this.selectedDates = []; + + + + this.setOptions(options); + + + + this.options.inputType = this.options.inputType.toLowerCase(); + + + + this.date = new Date(); + + this.date = new Date(this.date.getFullYear(), this.date.getMonth(), this.date.getDate()); + + + + if ($defined(this.options.language)) + + this.date.language = this.options.language; + + + + this.options.startDate = this.processDates(this.options.startDate); + + this.options.endDate = this.processDates(this.options.endDate); + + + + if (!$defined(this.options.startDate)) + + this.options.startDate = this.date.fromObject({date:"year-10"}); + + else + + this.date = this.processDates(new Date(this.options.startDate.getTime())); + + + + if (!$defined(this.options.endDate)) + + this.options.endDate = this.date.fromObject({date:"year+10"}); + + + + + + this.options.selectedDate = this.processDates(this.options.selectedDate); + + if (!this.isSelectable(this.options.selectedDate)) + + this.options.selectedDate = null; + + + + this.options.selectedDate2 = this.processDates(this.options.selectedDate2); + + if (!this.isSelectable(this.options.selectedDate2)) + + this.options.selectedDate2 = null; + + + + + + if (!this.options.visible){ + + if ((window.ie) && (!window.ie7)){ + + this.iframe = new Element("iframe", {'src':'about:Blank', + + 'styles':{ + + 'position':'absolute', + + 'z-index':20000, + + 'opacity':0, + + 'background-color':'#ffffff' + + }, + + 'frameborder':0 + + }); + + + + document.body.appendChild(this.iframe); + + } + + + + this.element.setStyles({ + + 'position':'absolute', + + 'z-index':25000, + + 'opacity':0 + + }); + + + + if ($defined(this.toggler)){ + + this.toggler.addEvent("click", function(e){ + + var e = new Event(e); + + if (this.element.getStyle('opacity') == 0) this.openCalendar(); + + else this.closeCalendar(); + + e.stop(); + + }.bind(this)); + + } + + + + } + + + + if (this.options.numMonths > 1){ + + this.loading_div = new Element("div", {'styles':{ + + 'position':'absolute', + + 'z-index':26000, + + 'opacity':0, + + 'background':'#FFFFFF' + + }}); + + + + this.element.adopt(this.loading_div); + + } + + + + var ch = new Element("table", {'class':this.options.classPrefix+'cal-header-table'}); + + var tbody = new Element("tbody"); + + ch.adopt(tbody); + + + + var tr = new Element("tr"); + + + + this.preTD = new Element("td", {'class':this.options.classPrefix+'cal-previous-td'}); + + this.preTD.addEvent("click", function(){ + + var i=this.options.numMonths; + + var new_dt; + + while(i > 0){ + + new_dt = this.date.fromString("month-"+i); + + new_dt.setDate(new_dt.daysInMonth()); + + if (!this.isOutOfRange(new_dt)){ + + new_dt.fromString("month-1"); + + break; + + } + + i--; + + } + + if (i > 0) this.updateCalendar(new_dt); + + }.bind(this)); + + + + tr.adopt(this.preTD); + + + + this.headerTD = new Element("td", {'class':this.options.classPrefix+'cal-header-td'}); + + tr.adopt(this.headerTD); + + + + this.nexTD = new Element("td", {'class':this.options.classPrefix+'cal-next-td'}); + + this.nexTD.addEvent("click", function(){ + + var i=this.options.numMonths; + + var new_dt; + + while(i > 0){ + + new_dt = this.date.fromString("month+"+i); + + new_dt.setDate(1); + + if (!this.isOutOfRange(new_dt)){ + + new_dt.fromString("month-1"); + + break; + + } + + i--; + + } + + if (i > 0) this.updateCalendar(new_dt); + + }.bind(this)); + + + + tr.adopt(this.nexTD); + + + + this.updateHeader(); + + + + tbody.adopt(tr); + + + + this.element.adopt(ch); + + + + this.calendarHolder = new Element("div"); + + this.element.adopt(this.calendarHolder); + + + + var footer = new Element("div", {'styles':{'clear':'both'}}); + + this.element.adopt(footer); + + + + var clear_float = new Element("div", {'styles':{'clear':'both', + + 'height':1, + + 'font-size':'1px'}}); + + clear_float.setHTML(" "); + + this.element.adopt(clear_float); + + + + if (!this.options.visible){ + + var close_link = new Element("a", {'class':this.options.classPrefix+'close-link', + + 'href':'#'}); + + close_link.addEvent("click", function(e){ + + var e = new Event(e); + + e.preventDefault(); + + this.closeCalendar(); + + + + }.bind(this)); + + + + close_link.setHTML(this.options.closeLinkHTML); + + + + footer.adopt(close_link); + + } + + + + if (this.options.multiSelection){ + + var clear_link = new Element("a", {'class':this.options.classPrefix+'clear-link', + + 'href':'#'}); + + clear_link.addEvent("click", function(e){ + + var e = new Event(e); + + e.preventDefault(); + + this.unselectAll(); + + + + }.bind(this)); + + + + clear_link.setHTML(this.options.clearLinkHTML); + + + + footer.adopt(clear_link); + + } + + + + this.populateCalendar(); + + + + if (this.options.allowSelection){ + + if (this.options.inputType == "select") { + + this.options.inputField.year = $(this.options.inputField.year); + + this.options.inputField.month = $(this.options.inputField.month); + + this.options.inputField.date = $(this.options.inputField.date); + + + + this.options.inputField.year.addEvent("change", function(){ + + if (this.options.inputField.year.options[this.options.inputField.year.selectedIndex].value != ""){ + + if ($defined(this.options.selectedDate)) + + var use_dt = new Date(this.options.selectedDate.getTime()); + + else + + var use_dt = new Date(this.date.getTime()); + + use_dt.setYear(this.options.inputField.year.options[this.options.inputField.year.selectedIndex].value); + + if (!this.options.multiSelection) + + this.selectDate(use_dt); + + this.updateCalendar(use_dt); + + this.populateMonthSelect(); + + } + + }.bind(this)); + + + + + + this.options.inputField.month.addEvent("change", function(){ + + if (this.options.inputField.month.options[this.options.inputField.month.selectedIndex].value != ""){ + + if ($defined(this.options.selectedDate)) + + var use_dt = new Date(this.options.selectedDate.getTime()); + + else + + var use_dt = new Date(this.date.getTime()); + + + + var temp_dt = use_dt.getDate(); + + use_dt.setDate(1); + + use_dt.setMonth(this.options.inputField.month.options[this.options.inputField.month.selectedIndex].value.toInt()-1); + + if (use_dt.daysInMonth() > temp_dt) use_dt.setDate(temp_dt); + + else use_dt.setDate(use_dt.daysInMonth()); + + + + if (!this.options.multiSelection) + + this.selectDate(use_dt); + + + + if (!$defined(this.visibleMonth[use_dt.getMonth()+"-"+use_dt.getFullYear()])) + + this.updateCalendar(use_dt); + + + + this.populateDateSelect(this.options.inputField); + + } + + }.bind(this)); + + + + + + this.options.inputField.date.addEvent("change", function(){ + + if ($defined(this.options.selectedDate)) + + var use_dt = new Date(this.options.selectedDate.getTime()); + + else + + var use_dt = new Date(this.date.getTime()); + + + + use_dt.setDate(this.options.inputField.date.options[this.options.inputField.date.selectedIndex].value); + + this.selectDate(use_dt); + + }.bind(this)); + + + + + + this.populateSelect(); + + } + + else if (this.options.inputType == "text") { + + this.options.inputField = $(this.options.inputField); + + this.options.inputField.addEvent("focus", function(){ + + this.openCalendar(); + + }.bind(this)); + + this.options.inputField.addEvent("keydown", function(e){ + + var e = new Event(e); + + if ((e.key.length == 1) || (e.key == "space")) e.stop(); + + }); + + } + + } + + + + if ($defined(this.options.selectedDate)){ + + if ((window.ie6) && (this.options.inputType == "select")){ + + (function(){ + + this.selectDate(this.options.selectedDate); + + this.updateCalendar(this.options.selectedDate);}).delay(100, this); + + } + + else { + + this.selectDate(this.options.selectedDate); + + this.updateCalendar(this.options.selectedDate); + + } + + } + + + + _all_page_calendars.push(this); + + }, + + + + /* + + Function: + + populateSelect + + fill in the select menu values + + + + */ + + populateSelect: function(){ + + if (this.options.inputType != "select") return; + + + + this.options.inputField.year.empty(); + + + + var opt = new Element("option"); + + this.options.inputField.year.adopt(opt); + + var i=0; + + for (i=this.options.startDate.getFullYear(); i<=this.options.endDate.getFullYear(); i++){ + + opt = new Element("option", {'value':i}); + + opt.setText(i); + + if (($defined(this.options.selectedDate))&&(this.options.selectedDate.getFullYear() == i)) opt.selected = "selected"; + + this.options.inputField.year.adopt(opt); + + } + + this.populateMonthSelect(); + + + + }, + + + + /* + + Function: + + populateMonthSelect + + fill in the months select menu values + + + + */ + + populateMonthSelect: function(){ + + if (this.options.inputType != "select") return; + + + + var st_mn = 0; + + if (this.options.startDate.getFullYear() == this.date.getFullYear()) + + st_mn = this.options.startDate.getMonth(); + + + + var en_mn = 11; + + if (this.options.endDate.getFullYear() == this.date.getFullYear()) + + en_mn = this.options.endDate.getMonth(); + + + + this.options.inputField.month.empty(); + + + + opt = new Element("option"); + + this.options.inputField.month.adopt(opt); + + + + for (i=st_mn; i<=en_mn; i++){ + + opt = new Element("option", {'value':(i+1)}); + + opt.setText(this.date.language.months[this.options.monthsText][i]); + + if (($defined(this.options.selectedDate))&&(this.options.selectedDate.getMonth() == i)) opt.selected = "selected"; + + this.options.inputField.month.adopt(opt); + + } + + + + this.populateDateSelect(); + + }, + + + + /* + + Function: + + populateDateSelect + + fill in the date select menu values + + + + */ + + populateDateSelect: function(){ + + if (this.options.inputType != "select") return; + + + + if ((this.options.inputField.year.options[this.options.inputField.year.selectedIndex].value != "") + + && (this.options.inputField.month.options[this.options.inputField.month.selectedIndex].value != "")) + + var use_dt = new Date(this.options.inputField.year.options[this.options.inputField.year.selectedIndex].value, this.options.inputField.month.options[this.options.inputField.month.selectedIndex].value-1, 1); + + else if ($defined(this.options.selectedDate)) + + var use_dt = this.options.selectedDate; + + else + + var use_dt = this.date; + + + + var en_dy = use_dt.daysInMonth(); + + + + this.options.inputField.date.empty(); + + + + opt = new Element("option"); + + this.options.inputField.date.adopt(opt); + + var seletable; + + for (i=1; i<=en_dy; i++){ + + opt = new Element("option", {'value':i}); + + opt.setText(i); + + if (!this.isSelectable(new Date(use_dt.getFullYear(), use_dt.getMonth(), i))){ + + opt.disabled = true; + + opt.setStyles({'color':'#cccccc'}); + + } + + else + + if (($defined(this.options.selectedDate))&&(this.options.selectedDate.getDate() == i) + + && (this.options.selectedDate.getMonth() == this.options.inputField.month.options[this.options.inputField.month.selectedIndex].value-1) + + && (this.options.selectedDate.getFullYear() == this.options.inputField.year.options[this.options.inputField.year.selectedIndex].value)) opt.selected = "selected"; + + this.options.inputField.date.adopt(opt); + + } + + }, + + + + /* backward compaitiblity typo */ + + populateCalender: function(){ + + return this.populateCalendar(); + + }, + + + + + + /* + + Function: + + populateCalendar + + populate the calendar table from the calendar date + + + + */ + + + + populateCalendar: function(){ + + var func = function(mn, yr){ + + var i=0; + + this.visibleMonth = []; + + + + this.calendarHolder.setHTML(""); + + for(i=0; i 11){ + + mn = 0; + + yr++; + + } + + } + + + + if (this.options.numMonths > 1){ + + this.loading_div.setOpacity(0); + + } + + + + this.processTdEvents(); + + + + if ($defined(this.iframe)){ + + this.iframe.setStyles({ + + 'width':this.element.getStyle('width'), + + 'height':this.element.getStyle('height') + + }); + + } + + + + this.fireEvent("onCalendarLoad"); + + }; + + + + if (this.options.numMonths > 1){ + + if (this.element.getStyle('opacity') > 0){ + + this.loading_div.setStyles({'opacity':0.5, + + 'height':this.element.getStyle('height'), + + 'width':this.element.getStyle('width')}); + + } + + func.delay(1, this, [this.date.getMonth(), this.date.getFullYear()]); + + } + + else { + + this.visibleMonth = []; + + + + this.calendarHolder.innerHTML = this.createCalenderTable(this.date.getMonth(), this.date.getFullYear()); + + this.visibleMonth[this.date.getMonth()+"-"+this.date.getFullYear()] = true; + + + + this.processTdEvents(); + + + + if ($defined(this.iframe)){ + + this.iframe.width = this.element.getStyle('width'); + + this.iframe.height = this.element.getStyle('height'); + + } + + + + this.fireEvent("onCalendarLoad"); + + } + + }, + + + + + + /* + + Function: + + createCalenderTable + + create the month table for the given month and year + + + + Argumesnt: + + mn: month + + yr: year + + + + Returns: + + the table HTML + + + + */ + + createCalenderTable: function (mn, yr){ + + var t = new Array(); + + t[t.length] = '\ + + '; + + + + + + var i=0; + + var wd = 0; + + for(i=0; i<7; i++){ + + wd = (i+this.options.startDay)%7; + + t[t.length] = ''; + + } + + + + t[t.length] = ''; + + + + var date = new Date(yr, mn, 1); + + + + date.setDate(date.getDate()-(date.getDay()-this.options.startDay)); + + + + if ((date.getDate() <= 7) && (date.getDate() != 1)){ + + date.setDate(date.getDate() - 7); + + } + + + + var i, j, className, title, html, id_str, selectable; + + + + var loop = 7; + + for (i=1; i'+this.options.outOfRangeFormatter(date)+''; + + } + + else { + + selectable = this.isSelectable(date, true); + + + + if (selectable[1] == "outOfRange"){ + + className = this.options.classPrefix+"outOfRange"; + + html = this.options.outOfRangeFormatter(date); + + } + + else if (selectable[1] == "weekend"){ + + className = this.options.classPrefix+"weekend"; + + html = this.options.weekendFormatter(date); + + } + + else if (selectable[1] == "dayOff"){ + + className = this.options.classPrefix+"dayOff"; + + html = this.options.daysOffFormatter(date); + + } + + else if (selectable[1] == "dateOff"){ + + className = this.options.classPrefix+"dateOff"; + + html = this.options.datesOffFormatter(date); + + } + + else { + + html = this.options.formatter(date); + + } + + + + if (selectable[0]){ + + if (this.isSelected(date)){ + + className +=" "+this.options.classPrefix+"selected-day"; + + html = this.options.selectedDateFormatter(date); + + } + + } + + + + + + id_str = (date.getMonth()+1)+'-'+date.getDate()+'-'+date.getFullYear(); + + this.manageTDs[id_str] = []; + + t[t.length] = ''; + + if (selectable[0]) + + this.manageTDs[id_str]['click'] = true; + + + + if ($defined(this.options.tdEvents[id_str])){ + + if (!$defined(this.manageTDs[id_str]['event'])) + + this.manageTDs[id_str]['event'] = []; + + for (e in this.options.tdEvents[id_str]){ + + this.manageTDs[id_str]['event'][e] = this.options.tdEvents[id_str][e]; + + } + + } + + + + if ($defined(this.options.dateOnAvailable[id_str])){ + + if (!$defined(this.manageTDs[id_str]['dateOnAvailable'])) + + this.manageTDs[id_str]['dateOnAvailable'] = []; + + this.manageTDs[id_str]['dateOnAvailable'].push(this.options.dateOnAvailable[id_str]); + + } + + + + } + + + + date.setDate(date.getDate() + 1); + + } + + t[t.length] = ''; + + if ((date.getMonth() > mn) && (this.options.numMonths == 1)) loop = 6; + + } + + + + t[t.length] = '
'+this.date.language.months[this.options.monthsText][mn]+" "+yr+'
'+this.date.language.days[this.options.daysText][wd]+'
'+html+'
'; + + + + return t.join(""); + + }, + + + + /* + + Function: + + processTdEvents + + assign custom events to the table cells + + */ + + processTdEvents: function(){ + + var td, obj; + + for(p in this.manageTDs){ + + obj = this.manageTDs[p]; + + td = $(this.options.idPrefix+'date-'+p); + + var date_str = p; + + + + if ($defined(obj['click'])){ + + td.addEvent("click", function(td, date_str){ + + var date = new Date().fromString(date_str); + + if (this.isSelected(date)) + + this.unselectDate(date); + + else + + this.selectDate(date); + + + + }.bind(this, [td, date_str])); + + + + td.setStyle("cursor", "pointer"); + + + + for (e in this.options.calEvents) + + td.addEvent(e, this.options.calEvents[e].bind(this, td)); + + } + + + + if ($defined(obj['event'])){ + + for (ep in obj['event']){ + + if ($type(obj['event'][ep]) == "function"){ + + td.addEvent(ep, obj['event'][ep].bind(this, [td, date_str])); + + } + + } + + } + + + + if ($defined(obj['dateOnAvailable'])){ + + obj['dateOnAvailable'].each(function(func){ + + func.attempt([td, date_str], this); + + }, this); + + } + + } + + + + this.manageTDs = []; + + }, + + + + /* + + Function: + + isWeekend + + check if the day is a weekend in the calendar + + + + Arguments: + + weekDay: a number for the weekday. 0 for Sunday 6 for Saturday + + + + Returns: + + true or false + + */ + + isWeekend: function (weekDay){ + + return this.options.weekend.contains(weekDay); + + }, + + + + + + /* + + Function: + + isDayOff + + check if the day is a day off in the calendar + + + + Arguments: + + weekDay: a number for the weekday. 0 for Sunday 6 for Saturday + + + + Returns: + + true or false + + */ + + isDayOff: function (weekDay){ + + if ((this.options.speedFireFox) && (window.gecko)) return false; + + return this.options.daysOff.contains(weekDay); + + }, + + + + + + /* + + Function: + + isDateOff + + check if the date is a date off (holiday) in the calendar + + + + Arguments: + + date: date object (check above) + + + + Returns: + + true or false + + */ + + isDateOff: function (date){ + + if ((this.options.speedFireFox) && (window.gecko)) return false; + + var date = this.processDates(date); + + if (!$defined(date)) return false; + + + + var j=0; + + var loop = this.options.datesOff.length; + + for(j=0; j this.options.endDate.getTime())); + + }, + + + + /* + + Function: + + isSelectable + + check if the date is selectable + + + + Arguments: + + date: date object (check above) + + arr: returns true or false if null + + returns an array with the first element true or false + + the second element is either the reason for false or the date + + possible reasons + + outOfRange + + dayOff + + weekend + + dateOff + + + + Returns: + + check Arguments + + */ + + isSelectable: function (date, arr){ + + var date = this.processDates(date); + + if (!$defined(date)) return false; + + + + if ((!$defined(arr)) && (!this.options.allowSelection)) return false; + + + + var re = [true,date]; + + if (this.isOutOfRange(date)) + + re = [false,'outOfRange']; + + else if (this.isDayOff(date.getDay())) + + re = [this.options.allowDaysOffSelection,'dayOff']; + + else if (this.isWeekend(date.getDay())) + + re = [this.options.allowWeekendSelection,'weekend']; + + else if (this.isDateOff(date)) + + re = [this.options.allowDatesOffSelection,'dateOff']; + + + + if ((!this.options.allowSelection) && (re[0])) re=[false,'noSelection']; + + + + if (!re[0]) + + if (this.isForcedSelection(date)) re = [true,date]; + + if ($defined(arr)) + + return re; + + else + + return re[0]; + + + + + + return [false,date] + + + + }, + + + + /* + + Function: + + isForcedSelection + + check if the date is forced to be selectable + + + + Arguments: + + date: date object (check above) + + + + Returns: + + true or false + + */ + + + + isForcedSelection: function(date){ + + if ((this.options.speedFireFox) && (window.gecko)) return false; + + var i=0; + + var loop = this.options.forceSelections.length; + + var dt; + + for (i=0; i 0) && (this.selectedDates.length >= this.options.maxSelection)) return false; + + if (this.isSelectable(date)){ + + if (this.options.inputType == "select"){ + + var update_year = true; + + var update_month = true; + + if ($defined(this.options.selectedDate)){ + + if (date.getFullYear() == this.options.selectedDate.getFullYear()) + + update_year = false; + + else if (date.getMonth() == this.options.selectedDate.getMonth()) + + update_month = false; + + } + + } + + + + if (!this.options.multiSelection) + + this.unselectDate(this.options.selectedDate); + + + + this.options.selectedDate = this.date = date; + + this.selectedDates.push(date.getTime()); + + + + var id_str = this.options.idPrefix+'date-'+(date.getMonth()+1)+'-'+date.getDate()+'-'+date.getFullYear(); + + if ($defined($(id_str))){ + + $(id_str).removeClass(this.options.classPrefix+"mouse-over"); + + $(id_str).addClass(this.options.classPrefix+"selected-day"); + + $(id_str).setHTML(this.options.selectedDateFormatter(this.options.selectedDate)); + + } + + + + if(this.options.inputType == "select"){ + + if (update_year) this.populateMonthSelect(); + + else if (update_month) this.populateDateSelect(); + + this.selectSelectMenu(); + + } + + else if (this.options.inputType == "text") + + this.fillInputField(); + + + + this.fireEvent("onSelect"); + + return true; + + } + + else { + + if (this.options.inputType == "select"){ + + this.options.inputField.date.selectedIndex = 0; + + return false; + + } + + } + + + + this.updateHeader(); + + }, + + + + + + /* + + Function: + + unselectDate + + unselect the given date + + + + Arguments: + + date: date object (check above) + + + + */ + + + + unselectDate: function(date){ + + if (!this.options.allowSelection) return false; + + + + var date = this.processDates(date); + + if (!$defined(date)) return false; + + + + this.selectedDates.remove(date.getTime()); + + + + var id_str = this.options.idPrefix+'date-'+(date.getMonth()+1)+'-'+date.getDate()+'-'+date.getFullYear(); + + + + if ($defined($(id_str))) + + $(id_str).removeClass(this.options.classPrefix+"selected-day"); + + + + if (($defined(this.options.selectedDate)) && (this.options.multiSelection)){ + + if (this.options.selectedDate.getTime() == date.getTime()){ + + if ($defined(this.selectedDates.getLast())){ + + this.options.selectedDate = new Date(this.selectedDates.getLast()); + + } + + else + + this.options.selectedDate = null; + + } + + } + + else + + this.options.selectedDate = null; + + + + if (this.options.inputType == "select"){ + + var update_year = true; + + var update_month = true; + + if ($defined(this.options.selectedDate)){ + + if (date.getFullYear() == this.options.selectedDate.getFullYear()) + + update_year = false; + + else if (date.getMonth() == this.options.selectedDate.getMonth()) + + update_month = false; + + } + + + + if (update_year) this.populateMonthSelect(); + + else if (update_month) this.populateDateSelect(); + + this.selectSelectMenu(); + + } + + else if (this.options.inputType == "text") + + this.options.inputField.value = this.options.inputField.value.replace(date.print(this.options.dateFormat, this.options.language), ""); + + + + this.fireEvent("onUnSelect", date); + + }, + + + + /* + + Function: + + unselectAll + + unselect all the selected dates + + + + */ + + unselectAll: function(){ + + if (!this.options.allowSelection) return false; + + + + var arr = this.selectedDates.copy(); + + arr.each(function (dt){ + + this.unselectDate(new Date(dt)); + + }, this); + + + + this.fireEvent("onClear"); + + }, + + + + + + /* + + Function: + + selectSelectMenu + + select the drop down menu if the user click on the a selectable date in the calendar + + + + */ + + selectSelectMenu: function (){ + + if (this.options.inputType != "select") return; + + if (!$defined(this.options.selectedDate)){ + + this.options.inputField.date.selectedIndex = 0; + + this.options.inputField.month.selectedIndex = 0; + + this.options.inputField.year.selectedIndex = 0; + + return; + + } + + + + this.options.inputField.date.selectedIndex = this.options.selectedDate.getDate(); + + + + var loop; + + var i=0; + + + + var opts = $(this.options.inputField.month).getElements("option"); + + loop = opts.length; + + for(i=0; i35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'([2-489q-wA-CE-LN-XZ]|[1-3]\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('8 2Q=t Class({3:{1M:A,1E:{x:0,y:0},2c:\'D, d M Y\',1f:1,E:\'ng-\',1g:\'ng-\',2d:0,12:\'today\',18:\'G+10\',P:\'1N\',9:1c,1m:R,1n:A,2e:0,u:1c,2f:[],2S:A,2T:[],2U:A,1F:[0,6],2V:A,2g:[],2W:r(){s},2X:r(){s},2h:r(){s},2Y:r(){s},2Z:r(){s},30:r(){s},31:r(a){s a.H()},2i:r(a){s a.H()},32:r(a){s a.H()},33:r(a){s a.H()},34:r(a){s a.H()},2j:r(a){s a.H()},X:1c,35:\'mid\',1O:\'long\',36:"«",37:"&2k;",38:"»",39:"&2k;",3a:"Close",3b:"Clear",2l:{mouseenter:r(a){4(!a.hasClass(2.3.E+"19-1P"))a.3c(2.3.E+"2m-2n")},mouseleave:r(a){a.2o(2.3.E+"2m-2n")}},1Q:[],1h:[],1R:A,3d:R},initialize:r(c,d,h){2.Q=$(c);4($v(d))2.1S=$(d);w 2.1S=1c;2.1x=[];2.13=[];2.1i=[];2.setOptions(h);2.3.P=2.3.P.toLowerCase();2.q=t L();2.q=t L(2.q.B(),2.q.F(),2.q.H());4($v(2.3.X))2.q.X=2.3.X;2.3.12=2.S(2.3.12);2.3.18=2.S(2.3.18);4(!$v(2.3.12))2.3.12=2.q.2p({q:"G-10"});w 2.q=2.S(t L(2.3.12.I()));4(!$v(2.3.18))2.3.18=2.q.2p({q:"G+10"});2.3.u=2.S(2.3.u);4(!2.1y(2.3.u))2.3.u=1c;2.3.1T=2.S(2.3.1T);4(!2.1y(2.3.1T))2.3.1T=1c;4(!2.3.1M){4((1z.ie)&&(!1z.ie7)){2.14=t K("14",{\'src\':\'about:Blank\',\'1U\':{\'2q\':\'2r\',\'z-2s\':20000,\'1a\':0,\'3e-3f\':\'#ffffff\'},\'frameborder\':0});document.body.appendChild(2.14)}2.Q.1A({\'2q\':\'2r\',\'z-2s\':25000,\'1a\':0});4($v(2.1S)){2.1S.Z("1j",r(a){8 a=t 1V(a);4(2.Q.1k(\'1a\')==0)2.2t();w 2.1W();a.3g()}.15(2))}}4(2.3.1f>1){2.1X=t K("1Y",{\'1U\':{\'2q\':\'2r\',\'z-2s\':26000,\'1a\':0,\'3e\':\'#FFFFFF\'}});2.Q.N(2.1X)}8 f=t K("1G",{\'16\':2.3.E+\'1H-3h-1G\'});8 m=t K("tbody");f.N(m);8 j=t K("tr");2.1p=t K("U",{\'16\':2.3.E+\'1H-previous-U\'});2.1p.Z("1j",r(){8 a=2.3.1f;8 b;3i(a>0){b=2.q.1B("C-"+a);b.1d(b.1Z());4(!2.1q(b)){b.1B("C-1");20}a--}4(a>0)2.1r(b)}.15(2));j.N(2.1p);2.2u=t K("U",{\'16\':2.3.E+\'1H-3h-U\'});j.N(2.2u);2.1s=t K("U",{\'16\':2.3.E+\'1H-next-U\'});2.1s.Z("1j",r(){8 a=2.3.1f;8 b;3i(a>0){b=2.q.1B("C+"+a);b.1d(1);4(!2.1q(b)){b.1B("C-1");20}a--}4(a>0)2.1r(b)}.15(2));j.N(2.1s);2.21();m.N(j);2.Q.N(f);2.1I=t K("1Y");2.Q.N(2.1I);8 n=t K("1Y",{\'1U\':{\'2v\':\'3j\'}});2.Q.N(n);8 k=t K("1Y",{\'1U\':{\'2v\':\'3j\',\'1e\':1,\'font-size\':\'1px\'}});k.1b("&2k;");2.Q.N(k);4(!2.3.1M){8 g=t K("a",{\'16\':2.3.E+\'close-3k\',\'3l\':\'#\'});g.Z("1j",r(a){8 a=t 1V(a);a.3m();2.1W()}.15(2));g.1b(2.3.3a);n.N(g)}4(2.3.1n){8 l=t K("a",{\'16\':2.3.E+\'2v-3k\',\'3l\':\'#\'});l.Z("1j",r(a){8 a=t 1V(a);a.3m();2.3n()}.15(2));l.1b(2.3.3b);n.N(l)}2.22();4(2.3.1m){4(2.3.P=="17"){2.3.9.G=$(2.3.9.G);2.3.9.C=$(2.3.9.C);2.3.9.q=$(2.3.9.q);2.3.9.G.Z("2w",r(){4(2.3.9.G.3[2.3.9.G.O].J!=""){4($v(2.3.u))8 a=t L(2.3.u.I());w 8 a=t L(2.q.I());a.setYear(2.3.9.G.3[2.3.9.G.O].J);4(!2.3.1n)2.1t(a);2.1r(a);2.1J()}}.15(2));2.3.9.C.Z("2w",r(){4(2.3.9.C.3[2.3.9.C.O].J!=""){4($v(2.3.u))8 a=t L(2.3.u.I());w 8 a=t L(2.q.I());8 b=a.H();a.1d(1);a.setMonth(2.3.9.C.3[2.3.9.C.O].J.2x()-1);4(a.1Z()>b)a.1d(b);w a.1d(a.1Z());4(!2.3.1n)2.1t(a);4(!$v(2.1x[a.F()+"-"+a.B()]))2.1r(a);2.1K(2.3.9)}}.15(2));2.3.9.q.Z("2w",r(){4($v(2.3.u))8 a=t L(2.3.u.I());w 8 a=t L(2.q.I());a.1d(2.3.9.q.3[2.3.9.q.O].J);2.1t(a)}.15(2));2.3o()}w 4(2.3.P=="1N"){2.3.9=$(2.3.9);2.3.9.Z("focus",r(){2.2t()}.15(2));2.3.9.Z("keydown",r(a){8 a=t 1V(a);4((a.3p.V==1)||(a.3p=="space"))a.3g()})}}4($v(2.3.u)){4((1z.ie6)&&(2.3.P=="17")){(r(){2.1t(2.3.u);2.1r(2.3.u)}).3q(100,2)}w{2.1t(2.3.u);2.1r(2.3.u)}}_0.2z(2)},3o:r(){4(2.3.P!="17")s;2.3.9.G.2A();8 a=t K("1l");2.3.9.G.N(a);8 b=0;T(b=2.3.12.B();b<=2.3.18.B();b++){a=t K("1l",{\'J\':b});a.2B(b);4(($v(2.3.u))&&(2.3.u.B()==b))a.19="19";2.3.9.G.N(a)}2.1J()},1J:r(){4(2.3.P!="17")s;8 a=0;4(2.3.12.B()==2.q.B())a=2.3.12.F();8 b=11;4(2.3.18.B()==2.q.B())b=2.3.18.F();2.3.9.C.2A();W=t K("1l");2.3.9.C.N(W);T(i=a;i<=b;i++){W=t K("1l",{\'J\':(i+1)});W.2B(2.q.X.2C[2.3.1O][i]);4(($v(2.3.u))&&(2.3.u.F()==i))W.19="19";2.3.9.C.N(W)}2.1K()},1K:r(){4(2.3.P!="17")s;4((2.3.9.G.3[2.3.9.G.O].J!="")&&(2.3.9.C.3[2.3.9.C.O].J!=""))8 a=t L(2.3.9.G.3[2.3.9.G.O].J,2.3.9.C.3[2.3.9.C.O].J-1,1);w 4($v(2.3.u))8 a=2.3.u;w 8 a=2.q;8 b=a.1Z();2.3.9.q.2A();W=t K("1l");2.3.9.q.N(W);8 c;T(i=1;i<=b;i++){W=t K("1l",{\'J\':i});W.2B(i);4(!2.1y(t L(a.B(),a.F(),i))){W.disabled=R;W.1A({\'3f\':\'#cccccc\'})}w 4(($v(2.3.u))&&(2.3.u.H()==i)&&(2.3.u.F()==2.3.9.C.3[2.3.9.C.O].J-1)&&(2.3.u.B()==2.3.9.G.3[2.3.9.G.O].J))W.19="19";2.3.9.q.N(W)}},populateCalender:r(){s 2.22()},22:r(){8 d=r(a,b){8 c=0;2.1x=[];2.1I.1b("");T(c=0;c<2.3.1f;c++){2.1I.3r+=2.2D(a,b);2.1x[a+"-"+b]=R;a++;4(a>11){a=0;b++}}4(2.3.1f>1){2.1X.setOpacity(0)}2.2E();4($v(2.14)){2.14.1A({\'1C\':2.Q.1k(\'1C\'),\'1e\':2.Q.1k(\'1e\')})}2.1u("2h")};4(2.3.1f>1){4(2.Q.1k(\'1a\')>0){2.1X.1A({\'1a\':0.5,\'1e\':2.Q.1k(\'1e\'),\'1C\':2.Q.1k(\'1C\')})}d.3q(1,2,[2.q.F(),2.q.B()])}w{2.1x=[];2.1I.3r=2.2D(2.q.F(),2.q.B());2.1x[2.q.F()+"-"+2.q.B()]=R;2.2E();4($v(2.14)){2.14.1C=2.Q.1k(\'1C\');2.14.1e=2.Q.1k(\'1e\')}2.1u("2h")}},2D:r(a,b){8 c=t Array();c[c.V]=\'<1G 16="\'+2.3.E+\'1H" id="\'+2.3.1g+\'C-\'+(a+1)+\'-\'+b+\'-1G"> \'+2.q.X.2C[2.3.1O][a]+" "+b+\'\';8 d=0;8 h=0;T(d=0;d<7;d++){h=(d+2.3.2d)%7;c[c.V]=\'\'+2.q.X.2F[2.3.35][h]+\'\'}c[c.V]=\'\';8 f=t L(b,a,1);f.1d(f.H()-(f.2G()-2.3.2d));4((f.H()<=7)&&(f.H()!=1)){f.1d(f.H()-7)}8 d,m,j,n,k,g,l;8 o=7;T(d=1;d\';T(m=1;m<=7;m++){j="";k="";g="";4(f.F()!=a){g=(f.F()+1)+\'-\'+f.H()+\'-\'+f.B();c[c.V]=\'\'+2.3.2i(f)+\'\'}w{l=2.1y(f,R);4(l[1]=="26"){j=2.3.E+"26";k=2.3.2i(f)}w 4(l[1]=="1F"){j=2.3.E+"1F";k=2.3.32(f)}w 4(l[1]=="2H"){j=2.3.E+"2H";k=2.3.33(f)}w 4(l[1]=="2I"){j=2.3.E+"2I";k=2.3.34(f)}w{k=2.3.31(f)}4(l[0]){4(2.2J(f)){j+=" "+2.3.E+"19-1P";k=2.3.2j(f)}}g=(f.F()+1)+\'-\'+f.H()+\'-\'+f.B();2.13[g]=[];c[c.V]=\'\'+k+\'\';4(l[0])2.13[g][\'1j\']=R;4($v(2.3.1Q[g])){4(!$v(2.13[g][\'1v\']))2.13[g][\'1v\']=[];T(e in 2.3.1Q[g]){2.13[g][\'1v\'][e]=2.3.1Q[g][e]}}4($v(2.3.1h[g])){4(!$v(2.13[g][\'1h\']))2.13[g][\'1h\']=[];2.13[g][\'1h\'].2z(2.3.1h[g])}}f.1d(f.H()+1)}c[c.V]=\'\';4((f.F()>a)&&(2.3.1f==1))o=6}c[c.V]=\'\';s c.join("")},2E:r(){8 d,h;T(p in 2.13){h=2.13[p];d=$(2.3.1g+\'q-\'+p);8 f=p;4($v(h[\'1j\'])){d.Z("1j",r(a,b){8 c=t L().1B(b);4(2.2J(c))2.28(c);w 2.1t(c)}.15(2,[d,f]));d.1w("1L","2K");T(e in 2.3.2l)d.Z(e,2.3.2l[e].15(2,d))}4($v(h[\'1v\'])){T(ep in h[\'1v\']){4($1D(h[\'1v\'][ep])=="r"){d.Z(ep,h[\'1v\'][ep].15(2,[d,f]))}}}4($v(h[\'1h\'])){h[\'1h\'].2L(r(a){a.attempt([d,f],2)},2)}}2.13=[]},3s:r(a){s 2.3.1F.2M(a)},3t:r(a){4((2.3.1R)&&(1z.2N))s A;s 2.3.2T.2M(a)},3u:r(a){4((2.3.1R)&&(1z.2N))s A;8 a=2.S(a);4(!$v(a))s A;8 b=0;8 c=2.3.2f.V;T(b=0;b2.3.18.I()))},1y:r(a,b){8 a=2.S(a);4(!$v(a))s A;4((!$v(b))&&(!2.3.1m))s A;8 c=[R,a];4(2.1q(a))c=[A,\'26\'];w 4(2.3t(a.2G()))c=[2.3.2U,\'2H\'];w 4(2.3s(a.2G()))c=[2.3.2V,\'1F\'];w 4(2.3u(a))c=[2.3.2S,\'2I\'];4((!2.3.1m)&&(c[0]))c=[A,\'noSelection\'];4(!c[0])4(2.3v(a))c=[R,a];4($v(b))s c;w s c[0];s[A,a]},3v:r(a){4((2.3.1R)&&(1z.2N))s A;8 b=0;8 c=2.3.2g.V;8 d;T(b=0;b0)&&(2.1i.V>=2.3.2e))s A;4(2.1y(a)){4(2.3.P=="17"){8 b=R;8 c=R;4($v(2.3.u)){4(a.B()==2.3.u.B())b=A;w 4(a.F()==2.3.u.F())c=A}}4(!2.3.1n)2.28(2.3.u);2.3.u=2.q=a;2.1i.2z(a.I());8 d=2.3.1g+\'q-\'+(a.F()+1)+\'-\'+a.H()+\'-\'+a.B();4($v($(d))){$(d).2o(2.3.E+"2m-2n");$(d).3c(2.3.E+"19-1P");$(d).1b(2.3.2j(2.3.u))}4(2.3.P=="17"){4(b)2.1J();w 4(c)2.1K();2.2P()}w 4(2.3.P=="1N")2.3w();2.1u("2W");s R}w{4(2.3.P=="17"){2.3.9.q.O=0;s A}}2.21()},28:r(a){4(!2.3.1m)s A;8 a=2.S(a);4(!$v(a))s A;2.1i.remove(a.I());8 b=2.3.1g+\'q-\'+(a.F()+1)+\'-\'+a.H()+\'-\'+a.B();4($v($(b)))$(b).2o(2.3.E+"19-1P");4(($v(2.3.u))&&(2.3.1n)){4(2.3.u.I()==a.I()){4($v(2.1i.3x())){2.3.u=t L(2.1i.3x())}w 2.3.u=1c}}w 2.3.u=1c;4(2.3.P=="17"){8 c=R;8 d=R;4($v(2.3.u)){4(a.B()==2.3.u.B())c=A;w 4(a.F()==2.3.u.F())d=A}4(c)2.1J();w 4(d)2.1K();2.2P()}w 4(2.3.P=="1N")2.3.9.J=2.3.9.J.replace(a.3y(2.3.2c,2.3.X),"");2.1u("2X",a)},3n:r(){4(!2.3.1m)s A;8 b=2.1i.copy();b.2L(r(a){2.28(t L(a))},2);2.1u("30")},2P:r(){4(2.3.P!="17")s;4(!$v(2.3.u)){2.3.9.q.O=0;2.3.9.C.O=0;2.3.9.G.O=0;s}2.3.9.q.O=2.3.u.H();8 a;8 b=0;8 c=$(2.3.9.C).3z("1l");a=c.V;T(b=0;b= 0) + + d = d+str.replace("today+","").toInt(); + + else if(str.indexOf("today-") >= 0) + + d = d-str.replace("today-","").toInt(); + + else if(str.indexOf("month+") >= 0){ + + m = m+str.replace("month+","").toInt(); + + var mx_dys = new Date(y, m, 1).daysInMonth(); + + if (d > mx_dys) d = mx_dys; + + } + + else if(str.indexOf("month-") >= 0){ + + m = this.getMonth()-str.replace("month-","").toInt(); + + var mx_dys = new Date(y, m, 1).daysInMonth(); + + if (d > mx_dys) d = mx_dys; + + } + + else if(str.indexOf("year+") >= 0){ + + y = y+str.replace("year+","").toInt(); + + var mx_dys = new Date(y, m, 1).daysInMonth(); + + if (d > mx_dys) d = mx_dys; + + } + + else if(str.indexOf("year-") >= 0){ + + y = this.getFullYear()-str.replace("year-","").toInt(); + + var mx_dys = new Date(y, m, 1).daysInMonth(); + + if (d > mx_dys) d = mx_dys; + + } + + + + var dt = new Date(y, m, d, this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds()); + + } + + else { + + var dt = new Date(prs_dt); + + } + + + + return dt; + + }, + + + + /* + + Function: + + fromObject + + + + Returns: + + a date from the object provided + + + + Arguments: + + date_obj: an object that hold the date values + + the date_obj should have the following attributes + + + + - date: can be either a number or a string + + if string, the following values are allowed + + * "[1st | 2nd | 3rd | 4th | 5th | last] sunday" either the 1st, 2nd, or 3rd, etc... sunday of the month. + + will return the date for the [nth] or last sunday of the month + + * "[1st | 2nd | 3rd | 4th | 5th | last] monday" same as above but for monday + + * "[1st | 2nd | 3rd | 4th | 5th | last] tuesday" + + * "[1st | 2nd | 3rd | 4th | 5th | last] wednesday" + + * "[1st | 2nd | 3rd | 4th | 5th | last] thursday" + + * "[1st | 2nd | 3rd | 4th | 5th | last] friday" + + * "[1st | 2nd | 3rd | 4th | 5th | last] saturday" + + - month: a numerical value for the month + + 0 for January 11 for December + + - year: a numerical value for the year (for digits format) + + - hour: a numerical value for the hour + + - minute: a numerical value for the minute + + - second: a numerical value for the second + + - millisecond: a numerical value for the millisecond + + + + if any of the values above is not defined, the current date value will be used. + + + + Example: + + alert(new Date(2007,8,1).fromObject({'date':'last friday'})); // will alert the date for Sep 28th 2007 + + alert(new Date(2007,8,1).fromObject({'date':'3rd Monday'})); // will alert the date for Sep 17th 2007 + + + + */ + + fromObject: function(date_obj){ + + var obj = {}; + + var p; + + for (p in date_obj) + + obj[p] = date_obj[p]; + + + + + + if (!$defined(obj.date)) obj.date = this.getDate(); + + if (!$defined(obj.month)) obj.month = this.getMonth(); + + if (!$defined(obj.year)) obj.year = this.getFullYear(); + + if (!$defined(obj.hour)) obj.hour = this.getHours(); + + if (!$defined(obj.minute)) obj.minute = this.getMinutes(); + + if (!$defined(obj.second)) obj.second = this.getSeconds(); + + if (!$defined(obj.millisecond)) obj.millisecond = this.getMilliseconds(); + + + + if ($type(obj.date) != "string"){ + + var dt = new Date(obj.year, obj.month ,obj.date, obj.hour, obj.minute, obj.second, obj.millisecond); + + return dt; + + } + + + + obj.date = obj.date.toLowerCase(); + + var date = new Date(obj.year, obj.month, 1); + + + + var cur_dy; + + if (obj.date.indexOf("sunday") != -1) + + cur_dy = 0; + + else if (obj.date.indexOf("monday") != -1) + + cur_dy = 1; + + else if (obj.date.indexOf("tuesday") != -1) + + cur_dy = 2; + + else if (obj.date.indexOf("wednesday") != -1) + + cur_dy = 3; + + else if (obj.date.indexOf("thursday") != -1) + + cur_dy = 4; + + else if (obj.date.indexOf("friday") != -1) + + cur_dy = 5; + + else if (obj.date.indexOf("saturday") != -1) + + cur_dy = 6; + + + + + + if (date.getDay() > cur_dy) + + var fd = (7 - date.getDay()) + cur_dy + 1; + + else if (date.getDay() < cur_dy) + + var fd = cur_dy - date.getDay() + 1; + + else + + var fd = 1; + + + + var rp_arr = ["1st","2nd","3rd","4th","5th"]; + + var c=5; + + var dnm = date.daysInMonth(); + + while(obj.date.indexOf("last") != -1){ + + if ((fd+(c*7)) <= dnm) + + obj.date = obj.date.replace("last",rp_arr[c]); + + c--; + + if (c < 0) + + obj.date = obj.date.replace("last","1st"); + + } + + + + var after_dys; + + if (obj.date.indexOf("1st") != -1) + + after_dys = 0; + + else if (obj.date.indexOf("2nd") != -1) + + after_dys = 1; + + else if (obj.date.indexOf("3rd") != -1) + + after_dys = 2; + + else if (obj.date.indexOf("4th") != -1) + + after_dys = 3; + + else if (obj.date.indexOf("5th") != -1) + + after_dys = 4; + + + + var dt = new Date(obj.year, obj.month, fd+(after_dys*7), obj.hour, obj.minute, obj.second, obj.millisecond); + + return dt; + + }, + + + + /* + + Function: + + print + + + + Returns: + + a formatted date string similar to PHP date function + + + + Arguments: + + format: a string to format the return value from the date + + Please visit http://us.php.net/manual/en/function.date.php for more details + + on the possible values + + + + the only exception is the Y and o will return the same value + + + + lang: + + a language object if using a different language than the date object + + + + Example: + + alert(new Date(2007,8,12).print("D, d M Y")); // will alert Wed, 12 Sep 2007 + + + + */ + + print: function(format, lang){ + + if (!$defined(lang)) lang = this.language; + + else { + + if ($defined(lang.days)){ + + if (!$defined(lang.days['char'])) lang.day['char'] = this.language.days['char']; + + if (!$defined(lang.days['short'])) lang.day['short'] = this.language.days['short']; + + if (!$defined(lang.days['mid'])) lang.day['mid'] = this.language.days['mid']; + + if (!$defined(lang.days['long'])) lang.day['long'] = this.language.days['long']; + + } + + else + + lang.days = this.language.days; + + + + if ($defined(lang.months)){ + + if (!$defined(lang.months['short'])) lang.months['short'] = this.language.months['short']; + + if (!$defined(lang.months['long'])) lang.months['long'] = this.language.months['long']; + + } + + else + + lang.months = this.language.months; + + + + if ($defined(lang.am_pm)){ + + if (!$defined(lang.am_pm['lowerCase'])) lang.am_pm['lowerCase'] = this.language.am_pm['lowerCase']; + + if (!$defined(lang.am_pm['upperCase'])) lang.am_pm['upperCase'] = this.language.am_pm['upperCase']; + + } + + else + + lang.am_pm = this.language.am_pm; + + } + + var i=0; + + var re = ""; + + var ch = ""; + + + + for (i=0; i -10) && (hr < 0)) hr = "-0"+Math.abs(hr); + + else if ((hr < 10) && (hr > 0)) hr = "0"+hr; + + else hr = hr.toString(); + + if (hr > 0) re += "+"; + + if (mn < 10) mn = "0"+mn; + + else mn = mn.toString(); + + if (ch == "P") var sep = ":"; + + else var sep = ""; + + re += hr+sep+mn; + + } + + else if (ch == "Z") + + re += this.getTimezoneOffset(); + + else if (ch == "c"){ + + format = format.substr(0, i-0)+"Y-m-dTH:i:sP"+format.substr(0, i); + + i--; + + } + + else if (ch == "r"){ + + format = format.substr(0, i-0)+"D, d M Y H:i:s O"+format.substr(0, i); + + i--; + + } + + else if (ch == "U") + + re += Math.floor(this.timeDifference(new Date(1970,0,1))/1000); + + else + + re += ch; + + + + } + + + + return re; + + }, + + + + + + /* + + Function: + + getWeekInYear + + + + Returns: + + the number of week in the year + + + + Example: + + alert(new Date(2007,8,12).getWeekInYear()); // will alert 36 + + */ + + getWeekInYear: function(){ + + return Math.floor(this.getDayInYear()/7); + + }, + + + + /* + + Function: + + getDayInYear + + + + Returns: + + the number of day in the year + + + + Example: + + alert(new Date(2007,8,12).getDayInYear()); // will alert 253 + + */ + + + + getDayInYear: function(){ + + return Math.floor(this.getHourInYear()/24); + + }, + + + + getHourInYear: function(){ + + return Math.floor(this.getMinuteInYear()/60); + + }, + + + + getMinuteInYear: function(){ + + return Math.floor(this.getSecondInYear()/60); + + }, + + + + getSecondInYear: function(){ + + return Math.floor(this.getMillisecondInYear()/1000); + + }, + + + + + + getMillisecondInYear: function(){ + + return this.timeDifference(new Date(this.getFullYear(), 0, 1)); + + }, + + + + /* + + Function: + + getWeekSince + + + + Returns: + + the number of week since a given date + + + + Arguments: + + date: a javascript date object + + + + Example: + + alert(new Date(2007,8,12).getWeekSince(new Date(2006,2,14))); // will alert 78 + + */ + + + + getWeekSince: function(date){ + + return Math.floor(this.getDaySince(date)/7); + + }, + + + + + + /* + + Function: + + getDaySince + + + + Returns: + + the number of days since a given date + + + + Arguments: + + date: a javascript date object + + + + Example: + + alert(new Date(2007,8,12).getDaySince(new Date(2006,2,14))); // will alert 547 + + */ + + + + getDaySince: function(date){ + + return Math.floor(this.getHourSince(date)/24); + + }, + + + + getHourSince: function(date){ + + return Math.floor(this.getMinuteSince(date)/60); + + }, + + + + getMinuteSince: function(date){ + + return Math.floor(this.getSecondSince(date)/60); + + }, + + + + getSecondSince: function(date){ + + return Math.floor(this.getMillisecondSince(date)/1000); + + }, + + + + getMillisecondSince: function(date){ + + return this.timeDifference(date); + + }, + + + + + + /* + + Function: + + timeDifference + + + + Returns: + + return the time difference in milliseconds between the date and the arguments date + + + + Arguments: + + date: javascript date + + + + Example: + + alert(new Date(2007,8,12).timeDifference(new Date(2006,2,14))); // will alert 47260800000 + + */ + + timeDifference: function(date){ + + return this.getTime() - date.getTime(); + + }, + + + + /* + + Function: + + toSwatchInternetTime + + for more details about the Swatch Internet Time, please visit http://www.swatch.com/internettime/ + + + + Returns: + + the number of beats as a string (including the @ sign) + + This function assume the browser handles the time zone and day time saving + + + + Example: + + alert(new Date(2007,8,12,8,52,0).toSwatchInternetTime()); // will alert @702 (based on timezone) + + + + */ + + + + + + toSwatchInternetTime: function(){ + + var sec = (this.getHours() * 3600) + (this.getMinutes() * 60) + this.getSeconds() + ((this.getTimezoneOffset() + 60)*60); + + var beat = Math.floor(sec/86.4); + + return ("@"+beat); + + }, + + + + /* + + Function: + + fromSwatchInternetTime + + for more details about the Swatch Internet Time, please visit http://www.swatch.com/internettime/ + + + + Returns: + + a javascript date object (approximate time) + + This function assume the browser handles the time zone and day time saving + + + + Arguments: + + beat: the beat value + + + + Example: + + alert(new Date().fromSwatchInternetTime(354)); // will alert the time at 10:47:31 (based on timezone) + + */ + + + + fromSwatchInternetTime: function (beat){ + + if ($type(beat) == "string") beat = beat.replace("@","").toInt(); + + var sec = Math.floor(beat*86.4) - ((this.getTimezoneOffset() + 60)*60); + + + + var dt = new Date(this.getFullYear(), this.getMonth(), this.getDate()); + + dt.setTime(dt.getTime()+(sec*1000)); + + + + return dt; + + } + + + + + +}; + + + +try { + + $native(Date); + + Date.extend(_ng_date_object); + +} + +catch(e) { + + Native.implement([Date], _ng_date_object); + +} + + + +delete _ng_date_object; \ No newline at end of file diff --git a/js/jscalendar/nogray_date_calendar_vs1_min.js b/js/jscalendar/nogray_date_calendar_vs1_min.js new file mode 100644 index 0000000..dfae8bd --- /dev/null +++ b/js/jscalendar/nogray_date_calendar_vs1_min.js @@ -0,0 +1,3 @@ +//http://www.nogray.com/license.php +eval(function(p,a,c,k,e,r){e=function(c){return(c<62?'':e(parseInt(c/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'([89vCEI-KQRVX]|[1-4]\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('C _0={1a:{\'1k\':{\'2F\':[\'S\',\'M\',\'T\',\'W\',\'T\',\'F\',\'S\'],\'1N\':[\'Su\',\'Mo\',\'Tu\',\'We\',\'Th\',\'Fr\',\'Sa\'],\'2i\':[\'Sun\',\'Mon\',\'Tue\',\'Wed\',\'Thu\',\'Fri\',\'Sat\'],\'1B\':[\'Sunday\',\'Monday\',\'Tuesday\',\'Wednesday\',\'Thursday\',\'Friday\',\'Saturday\']},\'1q\':{\'1N\':[\'Jan\',\'Feb\',\'Mar\',\'Apr\',\'3U\',\'Jun\',\'Jul\',\'Aug\',\'Sep\',\'Oct\',\'Nov\',\'Dec\'],\'1B\':[\'January\',\'February\',\'March\',\'April\',\'3U\',\'June\',\'July\',\'August\',\'September\',\'October\',\'November\',\'December\']},\'1t\':{\'2j\':[\'am\',\'pm\'],\'2k\':[\'AM\',\'PM\']}},1C:J(){C a=V 15(8.16(),8.14(),28);C b=28;1g(b=28;b<=32;b++){a.1G(b);v(a.14()!=8.14())K(b-1)}},3V:J(){C a=V 15(8.16(),1,29);K(a.14()==1)},1Z:J(a){C b=15.parse(a.1h(/[-|\\\\]/g,"/"));v(isNaN(b)){a=a.3c();a=a.1h(/(\\s)*([\\+|-])(\\s)*/g,"$2");C c=8.16();C d=8.14();C f=8.18();a=a.1h("yesterday","20-1").1h("tomorrow","20+1").1h("2q 13","13-1").1h("3d 13","13+1").1h("2q 17","17-1").1h("3d 17","17+1");v(a.1e("20+")>=0)f=f+a.1h("20+","").1H();E v(a.1e("20-")>=0)f=f-a.1h("20-","").1H();E v(a.1e("13+")>=0){d=d+a.1h("13+","").1H();C g=V 15(c,d,1).1C();v(f>g)f=g}E v(a.1e("13-")>=0){d=8.14()-a.1h("13-","").1H();C g=V 15(c,d,1).1C();v(f>g)f=g}E v(a.1e("17+")>=0){c=c+a.1h("17+","").1H();C g=V 15(c,d,1).1C();v(f>g)f=g}E v(a.1e("17-")>=0){c=8.16()-a.1h("17-","").1H();C g=V 15(c,d,1).1C();v(f>g)f=g}C j=V 15(c,d,f,8.1I(),8.2r(),8.2s(),8.3e())}E{C j=V 15(b)}K j},2G:J(a){C b={};C c;1g(c in a)b[c]=a[c];v(!$Q(b.I))b.I=8.18();v(!$Q(b.13))b.13=8.14();v(!$Q(b.17))b.17=8.16();v(!$Q(b.2H))b.2H=8.1I();v(!$Q(b.2I))b.2I=8.2r();v(!$Q(b.2J))b.2J=8.2s();v(!$Q(b.2K))b.2K=8.3e();v($1R(b.I)!="3f"){C d=V 15(b.17,b.13,b.I,b.2H,b.2I,b.2J,b.2K);K d}b.I=b.I.3c();C f=V 15(b.17,b.13,1);C g;v(b.I.1e("sunday")!=-1)g=0;E v(b.I.1e("monday")!=-1)g=1;E v(b.I.1e("tuesday")!=-1)g=2;E v(b.I.1e("wednesday")!=-1)g=3;E v(b.I.1e("thursday")!=-1)g=4;E v(b.I.1e("friday")!=-1)g=5;E v(b.I.1e("saturday")!=-1)g=6;v(f.1D()>g)C j=(7-f.1D())+g+1;E v(f.1D()-10)&&(j<0))j="-0"+1l.abs(j);E v((j<10)&&(j>0))j="0"+j;E j=j.3k();v(j>0)d+="+";v(l<10)l="0"+l;E l=l.3k();v(f=="P")C n=":";E C n="";d+=j+n+l}E v(f=="Z")d+=8.2L();E v(f=="c"){a=a.2v(0,c-0)+"Y-m-dTH:i:sP"+a.2v(0,c);c--}E v(f=="r"){a=a.2v(0,c-0)+"D, d M Y H:i:s O"+a.2v(0,c);c--}E v(f=="U")d+=1l.1o(8.2M(V 15(1970,0,1))/2N);E d+=f}K d},getWeekInYear:J(){K 1l.1o(8.3j()/7)},3j:J(){K 1l.1o(8.41()/24)},41:J(){K 1l.1o(8.42()/60)},42:J(){K 1l.1o(8.43()/60)},43:J(){K 1l.1o(8.44()/2N)},44:J(){K 8.2M(V 15(8.16(),0,1))},getWeekSince:J(a){K 1l.1o(8.45(a)/7)},45:J(a){K 1l.1o(8.46(a)/24)},46:J(a){K 1l.1o(8.47(a)/60)},47:J(a){K 1l.1o(8.48(a)/60)},48:J(a){K 1l.1o(8.49(a)/2N)},49:J(a){K 8.2M(a)},2M:J(a){K 8.1c()-a.1c()},40:J(){C a=(8.1I()*3600)+(8.2r()*60)+8.2s()+((8.2L()+60)*60);C b=1l.1o(a/86.4);K("@"+b)},fromSwatchInternetTime:J(a){v($1R(a)=="3f")a=a.1h("@","").1H();C b=1l.1o(a*86.4)-((8.2L()+60)*60);C c=V 15(8.16(),8.14(),8.18());c.setTime(c.1c()+(b*2N));K c}};try{$native(15);15.extend(_0)}catch(e){Native.4b([15],_0)}delete _0;C 4c=V Class({9:{2O:19,2w:{x:0,y:0},3l:\'D, d M Y\',1S:1,1b:\'ng-\',1T:\'ng-\',3m:0,1x:\'20\',1J:\'17+10\',1m:\'2P\',R:1P,25:1p,26:19,3n:0,X:1P,3o:[],4e:19,4f:[],4g:19,2x:[0,6],4h:19,3p:[],4i:J(){K},4j:J(){K},3q:J(){K},4k:J(){K},4l:J(){K},4m:J(){K},4n:J(a){K a.18()},3r:J(a){K a.18()},4o:J(a){K a.18()},4p:J(a){K a.18()},4q:J(a){K a.18()},3s:J(a){K a.18()},1a:1P,4r:\'2i\',2Q:\'1B\',4s:"«",4t:"&3t;",4u:"»",4v:"&3t;",4w:"Close",4x:"Clear",3u:{mouseenter:J(a){v(!a.hasClass(8.9.1b+"1K-1O"))a.4y(8.9.1b+"3v-3w")},mouseleave:J(a){a.3x(8.9.1b+"3v-3w")}},2R:[],1U:[],2S:19,4z:1p},initialize:J(c,d,f){8.1n=$(c);v($Q(d))8.2T=$(d);E 8.2T=1P;8.2l=[];8.1y=[];8.1V=[];8.setOptions(f);8.9.1m=8.9.1m.3c();8.I=V 15();8.I=V 15(8.I.16(),8.I.14(),8.I.18());v($Q(8.9.1a))8.I.1a=8.9.1a;8.9.1x=8.1s(8.9.1x);8.9.1J=8.1s(8.9.1J);v(!$Q(8.9.1x))8.9.1x=8.I.2G({I:"17-10"});E 8.I=8.1s(V 15(8.9.1x.1c()));v(!$Q(8.9.1J))8.9.1J=8.I.2G({I:"17+10"});8.9.X=8.1s(8.9.X);v(!8.2m(8.9.X))8.9.X=1P;8.9.2U=8.1s(8.9.2U);v(!8.2m(8.9.2U))8.9.2U=1P;v(!8.9.2O){v((2n.ie)&&(!2n.ie7)){8.1z=V 1f("1z",{\'src\':\'about:Blank\',\'2V\':{\'3y\':\'3z\',\'z-3A\':20000,\'1L\':0,\'4A-4B\':\'#ffffff\'},\'frameborder\':0});document.body.appendChild(8.1z)}8.1n.2o({\'3y\':\'3z\',\'z-3A\':25000,\'1L\':0});v($Q(8.2T)){8.2T.1w("1W",J(a){C a=V 2W(a);v(8.1n.1X(\'1L\')==0)8.3B();E 8.2X();a.4C()}.1A(8))}}v(8.9.1S>1){8.2Y=V 1f("2Z",{\'2V\':{\'3y\':\'3z\',\'z-3A\':26000,\'1L\':0,\'4A\':\'#FFFFFF\'}});8.1n.1i(8.2Y)}C g=V 1f("2y",{\'1E\':8.9.1b+\'2z-4D-2y\'});C j=V 1f("tbody");g.1i(j);C l=V 1f("tr");8.2a=V 1f("td",{\'1E\':8.9.1b+\'2z-previous-td\'});8.2a.1w("1W",J(){C a=8.9.1S;C b;3h(a>0){b=8.I.1Z("13-"+a);b.1G(b.1C());v(!8.2b(b)){b.1Z("13-1");30}a--}v(a>0)8.2c(b)}.1A(8));l.1i(8.2a);8.3C=V 1f("td",{\'1E\':8.9.1b+\'2z-4D-td\'});l.1i(8.3C);8.2d=V 1f("td",{\'1E\':8.9.1b+\'2z-3d-td\'});8.2d.1w("1W",J(){C a=8.9.1S;C b;3h(a>0){b=8.I.1Z("13+"+a);b.1G(1);v(!8.2b(b)){b.1Z("13-1");30}a--}v(a>0)8.2c(b)}.1A(8));l.1i(8.2d);8.33();j.1i(l);8.1n.1i(g);8.2A=V 1f("2Z");8.1n.1i(8.2A);C n=V 1f("2Z",{\'2V\':{\'3D\':\'4E\'}});8.1n.1i(n);C m=V 1f("2Z",{\'2V\':{\'3D\':\'4E\',\'1Q\':1,\'font-size\':\'1px\'}});m.1M("&3t;");8.1n.1i(m);v(!8.9.2O){C k=V 1f("a",{\'1E\':8.9.1b+\'close-4F\',\'4G\':\'#\'});k.1w("1W",J(a){C a=V 2W(a);a.4H();8.2X()}.1A(8));k.1M(8.9.4w);n.1i(k)}v(8.9.26){C o=V 1f("a",{\'1E\':8.9.1b+\'3D-4F\',\'4G\':\'#\'});o.1w("1W",J(a){C a=V 2W(a);a.4H();8.4I()}.1A(8));o.1M(8.9.4x);n.1i(o)}8.34();v(8.9.25){v(8.9.1m=="1F"){8.9.R.17=$(8.9.R.17);8.9.R.13=$(8.9.R.13);8.9.R.I=$(8.9.R.I);8.9.R.17.1w("3E",J(){v(8.9.R.17.9[8.9.R.17.1j].1d!=""){v($Q(8.9.X))C a=V 15(8.9.X.1c());E C a=V 15(8.I.1c());a.setYear(8.9.R.17.9[8.9.R.17.1j].1d);v(!8.9.26)8.2e(a);8.2c(a);8.2B()}}.1A(8));8.9.R.13.1w("3E",J(){v(8.9.R.13.9[8.9.R.13.1j].1d!=""){v($Q(8.9.X))C a=V 15(8.9.X.1c());E C a=V 15(8.I.1c());C b=a.18();a.1G(1);a.setMonth(8.9.R.13.9[8.9.R.13.1j].1d.1H()-1);v(a.1C()>b)a.1G(b);E a.1G(a.1C());v(!8.9.26)8.2e(a);v(!$Q(8.2l[a.14()+"-"+a.16()]))8.2c(a);8.2C(8.9.R)}}.1A(8));8.9.R.I.1w("3E",J(){v($Q(8.9.X))C a=V 15(8.9.X.1c());E C a=V 15(8.I.1c());a.1G(8.9.R.I.9[8.9.R.I.1j].1d);8.2e(a)}.1A(8));8.4J()}E v(8.9.1m=="2P"){8.9.R=$(8.9.R);8.9.R.1w("focus",J(){8.3B()}.1A(8));8.9.R.1w("keydown",J(a){C a=V 2W(a);v((a.4K.1r==1)||(a.4K=="space"))a.4C()})}}v($Q(8.9.X)){v((2n.ie6)&&(8.9.1m=="1F")){(J(){8.2e(8.9.X);8.2c(8.9.X)}).4L(100,8)}E{8.2e(8.9.X);8.2c(8.9.X)}}_1.3G(8)},4J:J(){v(8.9.1m!="1F")K;8.9.R.17.3H();C a=V 1f("1Y");8.9.R.17.1i(a);C b=0;1g(b=8.9.1x.16();b<=8.9.1J.16();b++){a=V 1f("1Y",{\'1d\':b});a.3I(b);v(($Q(8.9.X))&&(8.9.X.16()==b))a.1K="1K";8.9.R.17.1i(a)}8.2B()},2B:J(){v(8.9.1m!="1F")K;C a=0;v(8.9.1x.16()==8.I.16())a=8.9.1x.14();C b=11;v(8.9.1J.16()==8.I.16())b=8.9.1J.14();8.9.R.13.3H();1v=V 1f("1Y");8.9.R.13.1i(1v);1g(i=a;i<=b;i++){1v=V 1f("1Y",{\'1d\':(i+1)});1v.3I(8.I.1a.1q[8.9.2Q][i]);v(($Q(8.9.X))&&(8.9.X.14()==i))1v.1K="1K";8.9.R.13.1i(1v)}8.2C()},2C:J(){v(8.9.1m!="1F")K;v((8.9.R.17.9[8.9.R.17.1j].1d!="")&&(8.9.R.13.9[8.9.R.13.1j].1d!=""))C a=V 15(8.9.R.17.9[8.9.R.17.1j].1d,8.9.R.13.9[8.9.R.13.1j].1d-1,1);E v($Q(8.9.X))C a=8.9.X;E C a=8.I;C b=a.1C();8.9.R.I.3H();1v=V 1f("1Y");8.9.R.I.1i(1v);C c;1g(i=1;i<=b;i++){1v=V 1f("1Y",{\'1d\':i});1v.3I(i);v(!8.2m(V 15(a.16(),a.14(),i))){1v.disabled=1p;1v.2o({\'4B\':\'#cccccc\'})}E v(($Q(8.9.X))&&(8.9.X.18()==i)&&(8.9.X.14()==8.9.R.13.9[8.9.R.13.1j].1d-1)&&(8.9.X.16()==8.9.R.17.9[8.9.R.17.1j].1d))1v.1K="1K";8.9.R.I.1i(1v)}},populateCalender:J(){K 8.34()},34:J(){C d=J(a,b){C c=0;8.2l=[];8.2A.1M("");1g(c=0;c<8.9.1S;c++){8.2A.4M+=8.3J(a,b);8.2l[a+"-"+b]=1p;a++;v(a>11){a=0;b++}}v(8.9.1S>1){8.2Y.setOpacity(0)}8.3K();v($Q(8.1z)){8.1z.2o({\'2p\':8.1n.1X(\'2p\'),\'1Q\':8.1n.1X(\'1Q\')})}8.2f("3q")};v(8.9.1S>1){v(8.1n.1X(\'1L\')>0){8.2Y.2o({\'1L\':0.5,\'1Q\':8.1n.1X(\'1Q\'),\'2p\':8.1n.1X(\'2p\')})}d.4L(1,8,[8.I.14(),8.I.16()])}E{8.2l=[];8.2A.4M=8.3J(8.I.14(),8.I.16());8.2l[8.I.14()+"-"+8.I.16()]=1p;8.3K();v($Q(8.1z)){8.1z.2p=8.1n.1X(\'2p\');8.1z.1Q=8.1n.1X(\'1Q\')}8.2f("3q")}},3J:J(a,b){C c=V Array();c[c.1r]=\'<2y 1E="\'+8.9.1b+\'2z" id="\'+8.9.1T+\'13-\'+(a+1)+\'-\'+b+\'-2y"> \'+8.I.1a.1q[8.9.2Q][a]+" "+b+\'\';C d=0;C f=0;1g(d=0;d<7;d++){f=(d+8.9.3m)%7;c[c.1r]=\'\'+8.I.1a.1k[8.9.4r][f]+\'\'}c[c.1r]=\'\';C g=V 15(b,a,1);g.1G(g.18()-(g.1D()-8.9.3m));v((g.18()<=7)&&(g.18()!=1)){g.1G(g.18()-7)}C d,j,l,n,m,k,o;C q=7;1g(d=1;d\';1g(j=1;j<=7;j++){l="";m="";k="";v(g.14()!=a){k=(g.14()+1)+\'-\'+g.18()+\'-\'+g.16();c[c.1r]=\'\'+8.9.3r(g)+\'\'}E{o=8.2m(g,1p);v(o[1]=="37"){l=8.9.1b+"37";m=8.9.3r(g)}E v(o[1]=="2x"){l=8.9.1b+"2x";m=8.9.4o(g)}E v(o[1]=="3L"){l=8.9.1b+"3L";m=8.9.4p(g)}E v(o[1]=="3M"){l=8.9.1b+"3M";m=8.9.4q(g)}E{m=8.9.4n(g)}v(o[0]){v(8.3N(g)){l+=" "+8.9.1b+"1K-1O";m=8.9.3s(g)}}k=(g.14()+1)+\'-\'+g.18()+\'-\'+g.16();8.1y[k]=[];c[c.1r]=\'\'+m+\'\';v(o[0])8.1y[k][\'1W\']=1p;v($Q(8.9.2R[k])){v(!$Q(8.1y[k][\'2g\']))8.1y[k][\'2g\']=[];1g(e in 8.9.2R[k]){8.1y[k][\'2g\'][e]=8.9.2R[k][e]}}v($Q(8.9.1U[k])){v(!$Q(8.1y[k][\'1U\']))8.1y[k][\'1U\']=[];8.1y[k][\'1U\'].3G(8.9.1U[k])}}g.1G(g.18()+1)}c[c.1r]=\'\';v((g.14()>a)&&(8.9.1S==1))q=6}c[c.1r]=\'\';K c.join("")},3K:J(){C d,f;1g(p in 8.1y){f=8.1y[p];d=$(8.9.1T+\'I-\'+p);C g=p;v($Q(f[\'1W\'])){d.1w("1W",J(a,b){C c=V 15().1Z(b);v(8.3N(c))8.38(c);E 8.2e(c)}.1A(8,[d,g]));d.2h("2D","3O");1g(e in 8.9.3u)d.1w(e,8.9.3u[e].1A(8,d))}v($Q(f[\'2g\'])){1g(ep in f[\'2g\']){v($1R(f[\'2g\'][ep])=="J"){d.1w(ep,f[\'2g\'][ep].1A(8,[d,g]))}}}v($Q(f[\'1U\'])){f[\'1U\'].3P(J(a){a.attempt([d,g],8)},8)}}8.1y=[]},4N:J(a){K 8.9.2x.3Q(a)},4O:J(a){v((8.9.2S)&&(2n.3R))K 19;K 8.9.4f.3Q(a)},4P:J(a){v((8.9.2S)&&(2n.3R))K 19;C a=8.1s(a);v(!$Q(a))K 19;C b=0;C c=8.9.3o.1r;1g(b=0;b8.9.1J.1c()))},2m:J(a,b){C a=8.1s(a);v(!$Q(a))K 19;v((!$Q(b))&&(!8.9.25))K 19;C c=[1p,a];v(8.2b(a))c=[19,\'37\'];E v(8.4O(a.1D()))c=[8.9.4g,\'3L\'];E v(8.4N(a.1D()))c=[8.9.4h,\'2x\'];E v(8.4P(a))c=[8.9.4e,\'3M\'];v((!8.9.25)&&(c[0]))c=[19,\'noSelection\'];v(!c[0])v(8.4Q(a))c=[1p,a];v($Q(b))K c;E K c[0];K[19,a]},4Q:J(a){v((8.9.2S)&&(2n.3R))K 19;C b=0;C c=8.9.3p.1r;C d;1g(b=0;b0)&&(8.1V.1r>=8.9.3n))K 19;v(8.2m(a)){v(8.9.1m=="1F"){C b=1p;C c=1p;v($Q(8.9.X)){v(a.16()==8.9.X.16())b=19;E v(a.14()==8.9.X.14())c=19}}v(!8.9.26)8.38(8.9.X);8.9.X=8.I=a;8.1V.3G(a.1c());C d=8.9.1T+\'I-\'+(a.14()+1)+\'-\'+a.18()+\'-\'+a.16();v($Q($(d))){$(d).3x(8.9.1b+"3v-3w");$(d).4y(8.9.1b+"1K-1O");$(d).1M(8.9.3s(8.9.X))}v(8.9.1m=="1F"){v(b)8.2B();E v(c)8.2C();8.3T()}E v(8.9.1m=="2P")8.4R();8.2f("4i");K 1p}E{v(8.9.1m=="1F"){8.9.R.I.1j=0;K 19}}8.33()},38:J(a){v(!8.9.25)K 19;C a=8.1s(a);v(!$Q(a))K 19;8.1V.remove(a.1c());C b=8.9.1T+\'I-\'+(a.14()+1)+\'-\'+a.18()+\'-\'+a.16();v($Q($(b)))$(b).3x(8.9.1b+"1K-1O");v(($Q(8.9.X))&&(8.9.26)){v(8.9.X.1c()==a.1c()){v($Q(8.1V.4S())){8.9.X=V 15(8.1V.4S())}E 8.9.X=1P}}E 8.9.X=1P;v(8.9.1m=="1F"){C c=1p;C d=1p;v($Q(8.9.X)){v(a.16()==8.9.X.16())c=19;E v(a.14()==8.9.X.14())d=19}v(c)8.2B();E v(d)8.2C();8.3T()}E v(8.9.1m=="2P")8.9.R.1d=8.9.R.1d.1h(a.3i(8.9.3l,8.9.1a),"");8.2f("4j",a)},4I:J(){v(!8.9.25)K 19;C b=8.1V.copy();b.3P(J(a){8.38(V 15(a))},8);8.2f("4m")},3T:J(){v(8.9.1m!="1F")K;v(!$Q(8.9.X)){8.9.R.I.1j=0;8.9.R.13.1j=0;8.9.R.17.1j=0;K}8.9.R.I.1j=8.9.X.18();C a;C b=0;C c=$(8.9.R.13).4T("1Y");a=c.1r;1g(b=0;b35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('q 1q={13:{\'V\':{\'1r\':[\'S\',\'M\',\'T\',\'W\',\'T\',\'F\',\'S\'],\'1c\':[\'20\',\'25\',\'26\',\'27\',\'2a\',\'2b\',\'2c\'],\'1k\':[\'2d\',\'2e\',\'2f\',\'2g\',\'2h\',\'2i\',\'2j\'],\'1a\':[\'2k\',\'2l\',\'2m\',\'2n\',\'2o\',\'2p\',\'2q\']},\'17\':{\'1c\':[\'2r\',\'2s\',\'2t\',\'2u\',\'1F\',\'2v\',\'2w\',\'2x\',\'2y\',\'2z\',\'2A\',\'2B\'],\'1a\':[\'2C\',\'2D\',\'2E\',\'2F\',\'1F\',\'2G\',\'2H\',\'2I\',\'2J\',\'2K\',\'2L\',\'2M\']},\'X\':{\'1h\':[\'2N\',\'2O\'],\'1i\':[\'2P\',\'2Q\']}},1f:C(){q a=11 J(9.1d(),9.14(),28);q i=28;1z(i=28;i<=32;i++){a.2R(i);8(a.14()!=9.14())x(i-1)}},1G:C(){q a=11 J(9.1d(),1,29);x(a.14()==1)},2S:C(a){q b=J.2T(a.K(/[-|\\\\]/g,"/"));8(2U(b)){a=a.1H();a=a.K(/(\\s)*([\\+|-])(\\s)*/g,"$2");q y=9.1d();q m=9.14();q d=9.18();a=a.K("2V","1j-1").K("2W","1j+1").K("1l 15","15-1").K("1I 15","15+1").K("1l 16","16-1").K("1I 16","16+1");8(a.E("1j+")>=0)d=d+a.K("1j+","").1g();k 8(a.E("1j-")>=0)d=d-a.K("1j-","").1g();k 8(a.E("15+")>=0){m=m+a.K("15+","").1g();q c=11 J(y,m,1).1f();8(d>c)d=c}k 8(a.E("15-")>=0){m=9.14()-a.K("15-","").1g();q c=11 J(y,m,1).1f();8(d>c)d=c}k 8(a.E("16+")>=0){y=y+a.K("16+","").1g();q c=11 J(y,m,1).1f();8(d>c)d=c}k 8(a.E("16-")>=0){y=9.1d()-a.K("16-","").1g();q c=11 J(y,m,1).1f();8(d>c)d=c}q e=11 J(y,m,d,9.1b(),9.1m(),9.1n(),9.1A())}k{q e=11 J(b)}x e},2X:C(a){q b={};q p;1z(p 2Y a)b[p]=a[p];8(!$I(b.v))b.v=9.18();8(!$I(b.15))b.15=9.14();8(!$I(b.16))b.16=9.1d();8(!$I(b.1s))b.1s=9.1b();8(!$I(b.1t))b.1t=9.1m();8(!$I(b.1u))b.1u=9.1n();8(!$I(b.1v))b.1v=9.1A();8($1J(b.v)!="1K"){q d=11 J(b.16,b.15,b.v,b.1s,b.1t,b.1u,b.1v);x d}b.v=b.v.1H();q e=11 J(b.16,b.15,1);q f;8(b.v.E("2Z")!=-1)f=0;k 8(b.v.E("30")!=-1)f=1;k 8(b.v.E("33")!=-1)f=2;k 8(b.v.E("34")!=-1)f=3;k 8(b.v.E("35")!=-1)f=4;k 8(b.v.E("36")!=-1)f=5;k 8(b.v.E("37")!=-1)f=6;8(e.1e()>f)q g=(7-e.1e())+f+1;k 8(e.1e()-10)&&(f<0))f="-0"+Q.3h(f);k 8((f<10)&&(f>0))f="0"+f;k f=f.1D();8(f>0)c+="+";8(g<10)g="0"+g;k g=g.1D();8(d=="P")q j=":";k q j="";c+=f+j+g}k 8(d=="Z")c+=9.1w();k 8(d=="c"){a=a.1p(0,i-0)+"Y-m-3i:i:3j"+a.1p(0,i);i--}k 8(d=="r"){a=a.1p(0,i-0)+"D, d M Y H:i:s O"+a.1p(0,i);i--}k 8(d=="U")c+=Q.R(9.1x(11 J(3k,0,1))/1y);k c+=d}x c},3l:C(){x Q.R(9.1C()/7)},1C:C(){x Q.R(9.1Q()/24)},1Q:C(){x Q.R(9.1R()/19)},1R:C(){x Q.R(9.1S()/19)},1S:C(){x Q.R(9.1T()/1y)},1T:C(){x 9.1x(11 J(9.1d(),0,1))},3m:C(a){x Q.R(9.1U(a)/7)},1U:C(a){x Q.R(9.1V(a)/24)},1V:C(a){x Q.R(9.1W(a)/19)},1W:C(a){x Q.R(9.1X(a)/19)},1X:C(a){x Q.R(9.1Y(a)/1y)},1Y:C(a){x 9.1x(a)},1x:C(a){x 9.1E()-a.1E()},1P:C(){q a=(9.1b()*3n)+(9.1m()*19)+9.1n()+((9.1w()+19)*19);q b=Q.R(a/1Z.4);x("@"+b)},3o:C(a){8($1J(a)=="1K")a=a.K("@","").1g();q b=Q.R(a*1Z.4)-((9.1w()+19)*19);q c=11 J(9.1d(),9.14(),9.18());c.3p(c.1E()+(b*1y));x c}};3q{$3r(J);J.3s(1q)}3t(e){3u.3v([J],1q)}3w 1q;',62,219,'||||||||if|this|||||||||||else||||||var|||||date||return|||||function||indexOf||||defined|Date|replace||||||Math|floor||||days||am_pm||||new||language|getMonth|month|year|months|getDate|60|long|getHours|short|getFullYear|getDay|daysInMonth|toInt|lowerCase|upperCase|today|mid|last|getMinutes|getSeconds|day|substr|_ng_date_object|char|hour|minute|second|millisecond|getTimezoneOffset|timeDifference|1000|for|getMilliseconds|1st|getDayInYear|toString|getTime|May|isLeapYear|toLowerCase|next|type|string|2nd|3rd|4th|5th|toSwatchInternetTime|getHourInYear|getMinuteInYear|getSecondInYear|getMillisecondInYear|getDaySince|getHourSince|getMinuteSince|getSecondSince|getMillisecondSince|86|Su|||||Mo|Tu|We|||Th|Fr|Sa|Sun|Mon|Tue|Wed|Thu|Fri|Sat|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Jan|Feb|Mar|Apr|Jun|Jul|Aug|Sep|Oct|Nov|Dec|January|February|March|April|June|July|August|September|October|November|December|am|pm|AM|PM|setDate|fromString|parse|isNaN|yesterday|tomorrow|fromObject|in|sunday|monday|||tuesday|wednesday|thursday|friday|saturday|while|print|length|charAt|st|nd|rd|th|00|abs|dTH|sP|1970|getWeekInYear|getWeekSince|3600|fromSwatchInternetTime|setTime|try|native|extend|catch|Native|implement|delete'.split('|'),0,{})) \ No newline at end of file diff --git a/js/tableActions.min.js b/js/tableActions.min.js new file mode 100644 index 0000000..534221f --- /dev/null +++ b/js/tableActions.min.js @@ -0,0 +1 @@ +eval(function(p,a,c,k,e,r){e=function(c){return(c35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('j 4={6:{},l:A,10:A,1a:A,1D:A,1E:11,1U:B(){j 1V=N.1W("h"),1q,E,O,C,1b,R,P,1c,z,1r,s,T,15,1F,1G,1s,I,1d,2p,2q,1X=0,12="12",1e="1e";/*@U/*@8(@13)12="1s";1e="1G";/*@V@*/v(j k=0,h;h=1V[k];k++){1q=h.f.o(/1Y-([\\S-]+)/)==-1?"":h.f.16(/1Y-([\\S]+)/)[1];E=h.f.o(/1Z-([\\S-]+)/)==-1?"":h.f.16(/1Z-([\\S]+)/)[1];O=h.f.o(/20-([\\S-]+)/)==-1?"":h.f.16(/20-([\\S]+)/)[1];C=h.f.o(/21-([\\S-]+)/)==-1?"":h.f.16(/21-([\\S]+)/)[1];1b=h.f.o(/2r/)!=-1;R=h.f.o(/22-([\\S-]+)/)==-1?"":h.f.16(/22-([\\S]+)/)[1];R=R.W("-",".");P=h.f.o(/23-([\\S-]+)/)==-1?"":h.f.16(/23-([\\S]+)/)[1];8(!(1q||O||E||C||P))1t;8(!h.9)h.9="2s-"+1X++;4.6[h.9]={"F":[],"1b":1b,"C":C,"R":R,"1f":[0,0],"O":O,"E":E};/*@U@8(@2t>=5.7)8(N.2u=="2v")4.6[h.9].17=M;@y 4.6[h.9].17=M;@V@*//*@U/*@8(@13)8(4.6[h.9].17){4.6[h.9].J=A;4.6[h.9].K=A;4.6[h.9].P=P};/*@V@*/z=[];1c=h.1W(\'a\');v(j i=0;i<1c.q;i++){1r=[];s=1c[i].2w;2x{8(s.24&&s.24.2y().o(/2z|2A/)!=-1){1r[1r.q]=s};s=s.2B}1u(s);8(i%2==0&&1q)1c[i].f=1c[i].f+" "+1q;z[z.q]=1r};/*@U/*@8(@13)8(4.6[h.9].17){8(!P&&!C&&!E&&!O)1t}y{8(!E&&!C)1t};@y@*/8(!E&&!C)1t;/*@V@*/8(z.q>0){8(E){T=z[0].q;v(j c=0;c1){T=T+(z[0][c].14(12)-1)}};15=1v 1K(z.q);v(j c=z.q;c--;){15[c]=1v 1K(T)};1d=1v 1K(T);v(j c=T;c--;){1d[c]=[]};v(j c=0;c<15.q;c++){1F=0;v(j i=0;i1)?I.14(12):1;1G=(I.14(1e)&&I.14(1e)>1)?I.14(1e):1;I.f=I.f+" 1L-"+i+"-"+(i+1M(1s));v(j t=0;((t<1s)&&((i+t)=m[2]){r[r.q]=i}};4.6[b.9].1f=[m[1],m[2]];n=[m[1],m[2]]}y{/*@U/*@8(@13)8(4.6[b.9].17){8(4.6[b.9].J){4.6[b.9].J.f=4.6[b.9].J.f.W(4.6[b.9].O,"");4.6[b.9].J=A};8(4.6[b.9].K){4.6[b.9].K.f=4.6[b.9].K.f.W(4.6[b.9].P,"");4.6[b.9].K=A}};/*@V@*/8(!4.6[b.9].E){L};v(j i=1B[0];i<=1B[1];i++){r[r.q]=i};4.6[b.9].1f=[0,0]};8(r.q){v(j i=0;i=4.6[b.9].1w.q)1t;1J=4.6[b.9].1w[r[i]];v(j 1o=0,s;s=1J[1o];1o++){s.f=s.f.W(4.6[b.9].E,"")}}};8(n.q){v(i=n[0];i1){rowLength=rowLength+(rowArr[0][c].getAttribute(fdTableSort.colspan)-1)}}workArr=new Array(rowArr.length);for(c=rowArr.length;c--;){workArr[c]=new Array(rowLength)}for(c=0;c1)?cel.getAttribute(fdTableSort.colspan):1;rowspan=(cel.getAttribute(fdTableSort.rowspan)>1)?cel.getAttribute(fdTableSort.rowspan):1;for(var t=0;((t","")+"\u201d";aclone.onclick=aclone.onkeydown=workArr[c][i].onclick=fdTableSort.initWrapper;workArr[c][i].appendChild(aclone);if(showArrow){workArr[c][i].appendChild(span.cloneNode(false))}workArr[c][i].className=workArr[c][i].className.replace(/fd-identical|fd-not-identical/,"");fdTableSort.disableSelection(workArr[c][i]);aclone=null}}}fdTableSort.tmpCache[tbl.id]={cols:rowLength,headers:workArr};workArr=null;multi=0;if("active" in columnNumSortObj){fdTableSort.tableId=tbl.id;fdTableSort.prepareTableData(document.getElementById(fdTableSort.tableId));delete columnNumSortObj.active;for(col in columnNumSortObj){obj=columnNumSortObj[col];if(!("thNode" in obj)){continue}fdTableSort.multi=true;len=obj.reverse?2:1;for(ii=0;ii0&&identical[col]&&identVal[col]!=txt){identical[col]=false}identVal[col]=txt;data[rowCnt][col]=txt}data[rowCnt][numberOfCols]=tr;rowCnt++}var colStyle=table.className.search(/colstyle-([\S]+)/)!=-1?table.className.match(/colstyle-([\S]+)/)[1]:false;var rowStyle=table.className.search(/rowstyle-([\S]+)/)!=-1?table.className.match(/rowstyle-([\S]+)/)[1]:false;var iCBack=table.className.search(/sortinitiatedcallback-([\S-]+)/)==-1?"sortInitiatedCallback":table.className.match(/sortinitiatedcallback-([\S]+)/)[1];var cCBack=table.className.search(/sortcompletecallback-([\S-]+)/)==-1?"sortCompleteCallback":table.className.match(/sortcompletecallback-([\S]+)/)[1];iCBack=iCBack.replace("-",".");cCBack=cCBack.replace("-",".");fdTableSort.tableCache[table.id]={hook:start,initiatedCallback:iCBack,completeCallback:cCBack,thList:[],colOrder:{},data:data,identical:identical,colStyle:colStyle,rowStyle:rowStyle,noArrow:table.className.search(/no-arrow/)!=-1};sortableColumnNumbers=data=tr=td=th=trs=identical=identVal=null},onUnload:function(){for(tbl in fdTableSort.tableCache){fdTableSort.removeTableCache(tbl)}for(tbl in fdTableSort.tmpCache){fdTableSort.removeTmpCache(tbl)}fdTableSort.removeEvent(window,"load",fdTableSort.initEvt);fdTableSort.removeEvent(window,"unload",fdTableSort.onUnload);fdTableSort.tmpCache=fdTableSort.tableCache=null},addThNode:function(){var dataObj=fdTableSort.tableCache[fdTableSort.tableId];var pos=fdTableSort.thNode.className.match(/fd-column-([0-9]+)/)[1];var alt=false;if(!fdTableSort.multi){if(dataObj.colStyle){var len=dataObj.thList.length;for(var i=0;i1){classToAdd=fdTableSort.thNode.className.search(/forwardSort/)!=-1?"reverseSort":"forwardSort";fdTableSort.removeClass(fdTableSort.thNode,"(forwardSort|reverseSort)");fdTableSort.addClass(fdTableSort.thNode,classToAdd);dataObj.pos=-1}else{if(alt){dataObj.pos=fdTableSort.thNode}}},initSort:function(noCallback,ident){var thNode=fdTableSort.thNode;var tableElem=document.getElementById(fdTableSort.tableId);if(!(fdTableSort.tableId in fdTableSort.tableCache)){fdTableSort.prepareTableData(document.getElementById(fdTableSort.tableId))}fdTableSort.addThNode();if(!noCallback){fdTableSort.doCallback(true)}fdTableSort.pos=thNode.className.match(/fd-column-([0-9]+)/)[1];var dataObj=fdTableSort.tableCache[tableElem.id];var lastPos=dataObj.pos&&dataObj.pos.className?dataObj.pos.className.match(/fd-column-([0-9]+)/)[1]:-1;var len1=dataObj.data.length;var len2=dataObj.data.length>0?dataObj.data[0].length-1:0;var identical=dataObj.identical[fdTableSort.pos];var classToAdd="forwardSort";if(dataObj.thList.length>1){var js="var sortWrapper = function(a,b) {\n";var l=dataObj.thList.length;var cnt=0;var e,d,th,p,f;for(var i=0;i"}else{if(i.nodeType==3){txt+=i.nodeValue}else{if(i.nodeType==1){txt+=fdTableSort.getInnerText(i)}}}}return txt},dateFormat:function(dateIn,favourDMY){var dateTest=[{regExp:/^(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])([- \/.])((\d\d)?\d\d)$/,d:3,m:1,y:5},{regExp:/^(0?[1-9]|[12][0-9]|3[01])([- \/.])(0?[1-9]|1[012])([- \/.])((\d\d)?\d\d)$/,d:1,m:3,y:5},{regExp:/^(\d\d\d\d)([- \/.])(0?[1-9]|1[012])([- \/.])(0?[1-9]|[12][0-9]|3[01])$/,d:5,m:3,y:1}];var start,cnt=0,numFormats=dateTest.length;while(cnt + * Maintained at http://code.google.com/p/php-excel-reader/ + * + * Format parsing and MUCH more contributed by: + * Matt Roxburgh < http://www.roxburgh.me.uk > + * + * DOCUMENTATION + * ============= + * http://code.google.com/p/php-excel-reader/wiki/Documentation + * + * CHANGE LOG + * ========== + * http://code.google.com/p/php-excel-reader/wiki/ChangeHistory + * + * DISCUSSION/SUPPORT + * ================== + * http://groups.google.com/group/php-excel-reader-discuss/topics + * + * -------------------------------------------------------------------------- + * + * Originally developed by Vadim Tkachenko under the name PHPExcelReader. + * (http://sourceforge.net/projects/phpexcelreader) + * Based on the Java version by Andy Khan (http://www.andykhan.com). Now + * maintained by David Sanders. Reads only Biff 7 and Biff 8 formats. + * + * PHP versions 4 and 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category Spreadsheet + * @package Spreadsheet_Excel_Reader + * @author Vadim Tkachenko + * @license http://www.php.net/license/3_0.txt PHP License 3.0 + * @version CVS: $Id$ + * @link http://pear.php.net/package/Spreadsheet_Excel_Reader + * @see OLE, Spreadsheet_Excel_Writer + * -------------------------------------------------------------------------- + */ + +define('NUM_BIG_BLOCK_DEPOT_BLOCKS_POS', 0x2c); +define('SMALL_BLOCK_DEPOT_BLOCK_POS', 0x3c); +define('ROOT_START_BLOCK_POS', 0x30); +define('BIG_BLOCK_SIZE', 0x200); +define('SMALL_BLOCK_SIZE', 0x40); +define('EXTENSION_BLOCK_POS', 0x44); +define('NUM_EXTENSION_BLOCK_POS', 0x48); +define('PROPERTY_STORAGE_BLOCK_SIZE', 0x80); +define('BIG_BLOCK_DEPOT_BLOCKS_POS', 0x4c); +define('SMALL_BLOCK_THRESHOLD', 0x1000); +// property storage offsets +define('SIZE_OF_NAME_POS', 0x40); +define('TYPE_POS', 0x42); +define('START_BLOCK_POS', 0x74); +define('SIZE_POS', 0x78); +define('IDENTIFIER_OLE', pack("CCCCCCCC",0xd0,0xcf,0x11,0xe0,0xa1,0xb1,0x1a,0xe1)); + + +function GetInt4d($data, $pos) { + $value = ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | (ord($data[$pos+3]) << 24); + if ($value>=4294967294) { + $value=-2; + } + return $value; +} + +// http://uk.php.net/manual/en/function.getdate.php +function gmgetdate($ts = null){ + $k = array('seconds','minutes','hours','mday','wday','mon','year','yday','weekday','month',0); + return(array_comb($k,split(":",gmdate('s:i:G:j:w:n:Y:z:l:F:U',is_null($ts)?time():$ts)))); + } + +// Added for PHP4 compatibility +function array_comb($array1, $array2) { + $out = array(); + foreach ($array1 as $key => $value) { + $out[$value] = $array2[$key]; + } + return $out; +} + +function v($data,$pos) { + return ord($data[$pos]) | ord($data[$pos+1])<<8; +} + +class OLERead { + var $data = ''; + function OLERead(){ } + + function read($sFileName){ + // check if file exist and is readable (Darko Miljanovic) + if(!is_readable($sFileName)) { + $this->error = 1; + return false; + } + $this->data = @file_get_contents($sFileName); + if (!$this->data) { + $this->error = 1; + return false; + } + if (substr($this->data, 0, 8) != IDENTIFIER_OLE) { + $this->error = 1; + return false; + } + $this->numBigBlockDepotBlocks = GetInt4d($this->data, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); + $this->sbdStartBlock = GetInt4d($this->data, SMALL_BLOCK_DEPOT_BLOCK_POS); + $this->rootStartBlock = GetInt4d($this->data, ROOT_START_BLOCK_POS); + $this->extensionBlock = GetInt4d($this->data, EXTENSION_BLOCK_POS); + $this->numExtensionBlocks = GetInt4d($this->data, NUM_EXTENSION_BLOCK_POS); + + $bigBlockDepotBlocks = array(); + $pos = BIG_BLOCK_DEPOT_BLOCKS_POS; + $bbdBlocks = $this->numBigBlockDepotBlocks; + if ($this->numExtensionBlocks != 0) { + $bbdBlocks = (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4; + } + + for ($i = 0; $i < $bbdBlocks; $i++) { + $bigBlockDepotBlocks[$i] = GetInt4d($this->data, $pos); + $pos += 4; + } + + + for ($j = 0; $j < $this->numExtensionBlocks; $j++) { + $pos = ($this->extensionBlock + 1) * BIG_BLOCK_SIZE; + $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, BIG_BLOCK_SIZE / 4 - 1); + + for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; $i++) { + $bigBlockDepotBlocks[$i] = GetInt4d($this->data, $pos); + $pos += 4; + } + + $bbdBlocks += $blocksToRead; + if ($bbdBlocks < $this->numBigBlockDepotBlocks) { + $this->extensionBlock = GetInt4d($this->data, $pos); + } + } + + // readBigBlockDepot + $pos = 0; + $index = 0; + $this->bigBlockChain = array(); + + for ($i = 0; $i < $this->numBigBlockDepotBlocks; $i++) { + $pos = ($bigBlockDepotBlocks[$i] + 1) * BIG_BLOCK_SIZE; + //echo "pos = $pos"; + for ($j = 0 ; $j < BIG_BLOCK_SIZE / 4; $j++) { + $this->bigBlockChain[$index] = GetInt4d($this->data, $pos); + $pos += 4 ; + $index++; + } + } + + // readSmallBlockDepot(); + $pos = 0; + $index = 0; + $sbdBlock = $this->sbdStartBlock; + $this->smallBlockChain = array(); + + while ($sbdBlock != -2) { + $pos = ($sbdBlock + 1) * BIG_BLOCK_SIZE; + for ($j = 0; $j < BIG_BLOCK_SIZE / 4; $j++) { + $this->smallBlockChain[$index] = GetInt4d($this->data, $pos); + $pos += 4; + $index++; + } + $sbdBlock = $this->bigBlockChain[$sbdBlock]; + } + + + // readData(rootStartBlock) + $block = $this->rootStartBlock; + $pos = 0; + $this->entry = $this->__readData($block); + $this->__readPropertySets(); + } + + function __readData($bl) { + $block = $bl; + $pos = 0; + $data = ''; + while ($block != -2) { + $pos = ($block + 1) * BIG_BLOCK_SIZE; + $data = $data.substr($this->data, $pos, BIG_BLOCK_SIZE); + $block = $this->bigBlockChain[$block]; + } + return $data; + } + + function __readPropertySets(){ + $offset = 0; + while ($offset < strlen($this->entry)) { + $d = substr($this->entry, $offset, PROPERTY_STORAGE_BLOCK_SIZE); + $nameSize = ord($d[SIZE_OF_NAME_POS]) | (ord($d[SIZE_OF_NAME_POS+1]) << 8); + $type = ord($d[TYPE_POS]); + $startBlock = GetInt4d($d, START_BLOCK_POS); + $size = GetInt4d($d, SIZE_POS); + $name = ''; + for ($i = 0; $i < $nameSize ; $i++) { + $name .= $d[$i]; + } + $name = str_replace("\x00", "", $name); + $this->props[] = array ( + 'name' => $name, + 'type' => $type, + 'startBlock' => $startBlock, + 'size' => $size); + if ((strtolower($name) == "workbook") || ( strtolower($name) == "book")) { + $this->wrkbook = count($this->props) - 1; + } + if ($name == "Root Entry") { + $this->rootentry = count($this->props) - 1; + } + $offset += PROPERTY_STORAGE_BLOCK_SIZE; + } + + } + + + function getWorkBook(){ + if ($this->props[$this->wrkbook]['size'] < SMALL_BLOCK_THRESHOLD){ + $rootdata = $this->__readData($this->props[$this->rootentry]['startBlock']); + $streamData = ''; + $block = $this->props[$this->wrkbook]['startBlock']; + $pos = 0; + while ($block != -2) { + $pos = $block * SMALL_BLOCK_SIZE; + $streamData .= substr($rootdata, $pos, SMALL_BLOCK_SIZE); + $block = $this->smallBlockChain[$block]; + } + return $streamData; + }else{ + $numBlocks = $this->props[$this->wrkbook]['size'] / BIG_BLOCK_SIZE; + if ($this->props[$this->wrkbook]['size'] % BIG_BLOCK_SIZE != 0) { + $numBlocks++; + } + + if ($numBlocks == 0) return ''; + $streamData = ''; + $block = $this->props[$this->wrkbook]['startBlock']; + $pos = 0; + while ($block != -2) { + $pos = ($block + 1) * BIG_BLOCK_SIZE; + $streamData .= substr($this->data, $pos, BIG_BLOCK_SIZE); + $block = $this->bigBlockChain[$block]; + } + return $streamData; + } + } + +} + +define('SPREADSHEET_EXCEL_READER_BIFF8', 0x600); +define('SPREADSHEET_EXCEL_READER_BIFF7', 0x500); +define('SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS', 0x5); +define('SPREADSHEET_EXCEL_READER_WORKSHEET', 0x10); +define('SPREADSHEET_EXCEL_READER_TYPE_BOF', 0x809); +define('SPREADSHEET_EXCEL_READER_TYPE_EOF', 0x0a); +define('SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET', 0x85); +define('SPREADSHEET_EXCEL_READER_TYPE_DIMENSION', 0x200); +define('SPREADSHEET_EXCEL_READER_TYPE_ROW', 0x208); +define('SPREADSHEET_EXCEL_READER_TYPE_DBCELL', 0xd7); +define('SPREADSHEET_EXCEL_READER_TYPE_FILEPASS', 0x2f); +define('SPREADSHEET_EXCEL_READER_TYPE_NOTE', 0x1c); +define('SPREADSHEET_EXCEL_READER_TYPE_TXO', 0x1b6); +define('SPREADSHEET_EXCEL_READER_TYPE_RK', 0x7e); +define('SPREADSHEET_EXCEL_READER_TYPE_RK2', 0x27e); +define('SPREADSHEET_EXCEL_READER_TYPE_MULRK', 0xbd); +define('SPREADSHEET_EXCEL_READER_TYPE_MULBLANK', 0xbe); +define('SPREADSHEET_EXCEL_READER_TYPE_INDEX', 0x20b); +define('SPREADSHEET_EXCEL_READER_TYPE_SST', 0xfc); +define('SPREADSHEET_EXCEL_READER_TYPE_EXTSST', 0xff); +define('SPREADSHEET_EXCEL_READER_TYPE_CONTINUE', 0x3c); +define('SPREADSHEET_EXCEL_READER_TYPE_LABEL', 0x204); +define('SPREADSHEET_EXCEL_READER_TYPE_LABELSST', 0xfd); +define('SPREADSHEET_EXCEL_READER_TYPE_NUMBER', 0x203); +define('SPREADSHEET_EXCEL_READER_TYPE_NAME', 0x18); +define('SPREADSHEET_EXCEL_READER_TYPE_ARRAY', 0x221); +define('SPREADSHEET_EXCEL_READER_TYPE_STRING', 0x207); +define('SPREADSHEET_EXCEL_READER_TYPE_FORMULA', 0x406); +define('SPREADSHEET_EXCEL_READER_TYPE_FORMULA2', 0x6); +define('SPREADSHEET_EXCEL_READER_TYPE_FORMAT', 0x41e); +define('SPREADSHEET_EXCEL_READER_TYPE_XF', 0xe0); +define('SPREADSHEET_EXCEL_READER_TYPE_BOOLERR', 0x205); +define('SPREADSHEET_EXCEL_READER_TYPE_FONT', 0x0031); +define('SPREADSHEET_EXCEL_READER_TYPE_PALETTE', 0x0092); +define('SPREADSHEET_EXCEL_READER_TYPE_UNKNOWN', 0xffff); +define('SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR', 0x22); +define('SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS', 0xE5); +define('SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS' , 25569); +define('SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904', 24107); +define('SPREADSHEET_EXCEL_READER_MSINADAY', 86400); +define('SPREADSHEET_EXCEL_READER_TYPE_HYPER', 0x01b8); +define('SPREADSHEET_EXCEL_READER_TYPE_COLINFO', 0x7d); +define('SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH', 0x55); +define('SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH', 0x99); +define('SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT', "%s"); + + +/* +* Main Class +*/ +class Spreadsheet_Excel_Reader { + + // MK: Added to make data retrieval easier + var $colnames = array(); + var $colindexes = array(); + var $standardColWidth = 0; + var $defaultColWidth = 0; + + function myHex($d) { + if ($d < 16) return "0" . dechex($d); + return dechex($d); + } + + function dumpHexData($data, $pos, $length) { + $info = ""; + for ($i = 0; $i <= $length; $i++) { + $info .= ($i==0?"":" ") . $this->myHex(ord($data[$pos + $i])) . (ord($data[$pos + $i])>31? "[" . $data[$pos + $i] . "]":''); + } + return $info; + } + + function getCol($col) { + if (is_string($col)) { + $col = strtolower($col); + if (array_key_exists($col,$this->colnames)) { + $col = $this->colnames[$col]; + } + } + return $col; + } + + // PUBLIC API FUNCTIONS + // -------------------- + + function val($row,$col,$sheet=0) { + $col = $this->getCol($col); + if (array_key_exists($row,$this->sheets[$sheet]['cells']) && array_key_exists($col,$this->sheets[$sheet]['cells'][$row])) { + return $this->sheets[$sheet]['cells'][$row][$col]; + } + return ""; + } + function value($row,$col,$sheet=0) { + return $this->val($row,$col,$sheet); + } + function info($row,$col,$type='',$sheet=0) { + $col = $this->getCol($col); + if (array_key_exists('cellsInfo',$this->sheets[$sheet]) + && array_key_exists($row,$this->sheets[$sheet]['cellsInfo']) + && array_key_exists($col,$this->sheets[$sheet]['cellsInfo'][$row]) + && array_key_exists($type,$this->sheets[$sheet]['cellsInfo'][$row][$col])) { + return $this->sheets[$sheet]['cellsInfo'][$row][$col][$type]; + } + return ""; + } + function type($row,$col,$sheet=0) { + return $this->info($row,$col,'type',$sheet); + } + function raw($row,$col,$sheet=0) { + return $this->info($row,$col,'raw',$sheet); + } + function rowspan($row,$col,$sheet=0) { + $val = $this->info($row,$col,'rowspan',$sheet); + if ($val=="") { return 1; } + return $val; + } + function colspan($row,$col,$sheet=0) { + $val = $this->info($row,$col,'colspan',$sheet); + if ($val=="") { return 1; } + return $val; + } + function hyperlink($row,$col,$sheet=0) { + $link = $this->sheets[$sheet]['cellsInfo'][$row][$col]['hyperlink']; + if ($link) { + return $link['link']; + } + return ''; + } + function rowcount($sheet=0) { + return $this->sheets[$sheet]['numRows']; + } + function colcount($sheet=0) { + return $this->sheets[$sheet]['numCols']; + } + function colwidth($col,$sheet=0) { + // Col width is actually the width of the number 0. So we have to estimate and come close + return $this->colInfo[$sheet][$col]['width']/9142*200; + } + function colhidden($col,$sheet=0) { + return !!$this->colInfo[$sheet][$col]['hidden']; + } + function rowheight($row,$sheet=0) { + return $this->rowInfo[$sheet][$row]['height']; + } + function rowhidden($row,$sheet=0) { + return !!$this->rowInfo[$sheet][$row]['hidden']; + } + + // GET THE CSS FOR FORMATTING + // ========================== + function style($row,$col,$sheet=0,$properties='') { + $css = ""; + $font=$this->font($row,$col,$sheet); + if ($font!="") { + $css .= "font-family:$font;"; + } + $align=$this->align($row,$col,$sheet); + if ($align!="") { + $css .= "text-align:$align;"; + } + $height=$this->height($row,$col,$sheet); + if ($height!="") { + $css .= "font-size:$height"."px;"; + } + $bgcolor=$this->bgColor($row,$col,$sheet); + if ($bgcolor!="") { + $bgcolor = $this->colors[$bgcolor]; + $css .= "background-color:$bgcolor;"; + } + $color=$this->color($row,$col,$sheet); + if ($color!="") { + $css .= "color:$color;"; + } + $bold=$this->bold($row,$col,$sheet); + if ($bold) { + $css .= "font-weight:bold;"; + } + $italic=$this->italic($row,$col,$sheet); + if ($italic) { + $css .= "font-style:italic;"; + } + $underline=$this->underline($row,$col,$sheet); + if ($underline) { + $css .= "text-decoration:underline;"; + } + // Borders + $bLeft = $this->borderLeft($row,$col,$sheet); + $bRight = $this->borderRight($row,$col,$sheet); + $bTop = $this->borderTop($row,$col,$sheet); + $bBottom = $this->borderBottom($row,$col,$sheet); + $bLeftCol = $this->borderLeftColor($row,$col,$sheet); + $bRightCol = $this->borderRightColor($row,$col,$sheet); + $bTopCol = $this->borderTopColor($row,$col,$sheet); + $bBottomCol = $this->borderBottomColor($row,$col,$sheet); + // Try to output the minimal required style + if ($bLeft!="" && $bLeft==$bRight && $bRight==$bTop && $bTop==$bBottom) { + $css .= "border:" . $this->lineStylesCss[$bLeft] .";"; + } + else { + if ($bLeft!="") { $css .= "border-left:" . $this->lineStylesCss[$bLeft] .";"; } + if ($bRight!="") { $css .= "border-right:" . $this->lineStylesCss[$bRight] .";"; } + if ($bTop!="") { $css .= "border-top:" . $this->lineStylesCss[$bTop] .";"; } + if ($bBottom!="") { $css .= "border-bottom:" . $this->lineStylesCss[$bBottom] .";"; } + } + // Only output border colors if there is an actual border specified + if ($bLeft!="" && $bLeftCol!="") { $css .= "border-left-color:" . $bLeftCol .";"; } + if ($bRight!="" && $bRightCol!="") { $css .= "border-right-color:" . $bRightCol .";"; } + if ($bTop!="" && $bTopCol!="") { $css .= "border-top-color:" . $bTopCol . ";"; } + if ($bBottom!="" && $bBottomCol!="") { $css .= "border-bottom-color:" . $bBottomCol .";"; } + + return $css; + } + + // FORMAT PROPERTIES + // ================= + function format($row,$col,$sheet=0) { + return $this->info($row,$col,'format',$sheet); + } + function formatIndex($row,$col,$sheet=0) { + return $this->info($row,$col,'formatIndex',$sheet); + } + function formatColor($row,$col,$sheet=0) { + return $this->info($row,$col,'formatColor',$sheet); + } + + // CELL (XF) PROPERTIES + // ==================== + function xfRecord($row,$col,$sheet=0) { + $xfIndex = $this->info($row,$col,'xfIndex',$sheet); + if ($xfIndex!="") { + return $this->xfRecords[$xfIndex]; + } + return null; + } + function xfProperty($row,$col,$sheet,$prop) { + $xfRecord = $this->xfRecord($row,$col,$sheet); + if ($xfRecord!=null) { + return $xfRecord[$prop]; + } + return ""; + } + function align($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'align'); + } + function bgColor($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'bgColor'); + } + function borderLeft($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'borderLeft'); + } + function borderRight($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'borderRight'); + } + function borderTop($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'borderTop'); + } + function borderBottom($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'borderBottom'); + } + function borderLeftColor($row,$col,$sheet=0) { + return $this->colors[$this->xfProperty($row,$col,$sheet,'borderLeftColor')]; + } + function borderRightColor($row,$col,$sheet=0) { + return $this->colors[$this->xfProperty($row,$col,$sheet,'borderRightColor')]; + } + function borderTopColor($row,$col,$sheet=0) { + return $this->colors[$this->xfProperty($row,$col,$sheet,'borderTopColor')]; + } + function borderBottomColor($row,$col,$sheet=0) { + return $this->colors[$this->xfProperty($row,$col,$sheet,'borderBottomColor')]; + } + + // FONT PROPERTIES + // =============== + function fontRecord($row,$col,$sheet=0) { + $xfRecord = $this->xfRecord($row,$col,$sheet); + if ($xfRecord!=null) { + $font = $xfRecord['fontIndex']; + if ($font!=null) { + return $this->fontRecords[$font]; + } + } + return null; + } + function fontProperty($row,$col,$sheet=0,$prop) { + $font = $this->fontRecord($row,$col,$sheet); + if ($font!=null) { + return $font[$prop]; + } + return false; + } + function fontIndex($row,$col,$sheet=0) { + return $this->xfProperty($row,$col,$sheet,'fontIndex'); + } + function color($row,$col,$sheet=0) { + $formatColor = $this->formatColor($row,$col,$sheet); + if ($formatColor!="") { + return $formatColor; + } + $ci = $this->fontProperty($row,$col,$sheet,'color'); + return $this->rawColor($ci); + } + function rawColor($ci) { + if (($ci <> 0x7FFF) && ($ci <> '')) { + return $this->colors[$ci]; + } + return ""; + } + function bold($row,$col,$sheet=0) { + return $this->fontProperty($row,$col,$sheet,'bold'); + } + function italic($row,$col,$sheet=0) { + return $this->fontProperty($row,$col,$sheet,'italic'); + } + function underline($row,$col,$sheet=0) { + return $this->fontProperty($row,$col,$sheet,'under'); + } + function height($row,$col,$sheet=0) { + return $this->fontProperty($row,$col,$sheet,'height'); + } + function font($row,$col,$sheet=0) { + return $this->fontProperty($row,$col,$sheet,'font'); + } + + // DUMP AN HTML TABLE OF THE ENTIRE XLS DATA + // ========================================= + function dump($row_numbers=false,$col_letters=false,$sheet=0,$table_class='excel') { + $out = ""; + if ($col_letters) { + $out .= "\n\t"; + if ($row_numbers) { + $out .= "\n\t\t"; + } + for($i=1;$i<=$this->colcount($sheet);$i++) { + $style = "width:" . ($this->colwidth($i,$sheet)*1) . "px;"; + if ($this->colhidden($i,$sheet)) { + $style .= "display:none;"; + } + $out .= "\n\t\t"; + } + $out .= "\n"; + } + + $out .= "\n"; + for($row=1;$row<=$this->rowcount($sheet);$row++) { + $rowheight = $this->rowheight($row,$sheet); + $style = "height:" . ($rowheight*(4/3)) . "px;"; + if ($this->rowhidden($row,$sheet)) { + $style .= "display:none;"; + } + $out .= "\n\t"; + if ($row_numbers) { + $out .= "\n\t\t"; + } + for($col=1;$col<=$this->colcount($sheet);$col++) { + // Account for Rowspans/Colspans + $rowspan = $this->rowspan($row,$col,$sheet); + $colspan = $this->colspan($row,$col,$sheet); + for($i=0;$i<$rowspan;$i++) { + for($j=0;$j<$colspan;$j++) { + if ($i>0 || $j>0) { + $this->sheets[$sheet]['cellsInfo'][$row+$i][$col+$j]['dontprint']=1; + } + } + } + if(!$this->sheets[$sheet]['cellsInfo'][$row][$col]['dontprint']) { + $style = $this->style($row,$col,$sheet); + if ($this->colhidden($col,$sheet)) { + $style .= "display:none;"; + } + $out .= "\n\t\t"; + } + } + $out .= "\n"; + } + $out .= "
 " . strtoupper($this->colindexes[$i]) . "
$row 1?" colspan=$colspan":"") . ($rowspan > 1?" rowspan=$rowspan":"") . ">"; + $val = $this->val($row,$col,$sheet); + if ($val=='') { $val=" "; } + else { + $val = htmlentities($val); + $link = $this->hyperlink($row,$col,$sheet); + if ($link!='') { + $val = "$val"; + } + } + $out .= "".nl2br($val).""; + $out .= "
"; + return $out; + } + + // -------------- + // END PUBLIC API + + + var $boundsheets = array(); + var $formatRecords = array(); + var $fontRecords = array(); + var $xfRecords = array(); + var $colInfo = array(); + var $rowInfo = array(); + + var $sst = array(); + var $sheets = array(); + + var $data; + var $_ole; + var $_defaultEncoding = "UTF-8"; + var $_defaultFormat = SPREADSHEET_EXCEL_READER_DEF_NUM_FORMAT; + var $_columnsFormat = array(); + var $_rowoffset = 1; + var $_coloffset = 1; + + /** + * List of default date formats used by Excel + */ + var $dateFormats = array ( + 0xe => "m/d/Y", + 0xf => "M-d-Y", + 0x10 => "d-M", + 0x11 => "M-Y", + 0x12 => "h:i a", + 0x13 => "h:i:s a", + 0x14 => "H:i", + 0x15 => "H:i:s", + 0x16 => "d/m/Y H:i", + 0x2d => "i:s", + 0x2e => "H:i:s", + 0x2f => "i:s.S" + ); + + /** + * Default number formats used by Excel + */ + var $numberFormats = array( + 0x1 => "0", + 0x2 => "0.00", + 0x3 => "#,##0", + 0x4 => "#,##0.00", + 0x5 => "\$#,##0;(\$#,##0)", + 0x6 => "\$#,##0;[Red](\$#,##0)", + 0x7 => "\$#,##0.00;(\$#,##0.00)", + 0x8 => "\$#,##0.00;[Red](\$#,##0.00)", + 0x9 => "0%", + 0xa => "0.00%", + 0xb => "0.00E+00", + 0x25 => "#,##0;(#,##0)", + 0x26 => "#,##0;[Red](#,##0)", + 0x27 => "#,##0.00;(#,##0.00)", + 0x28 => "#,##0.00;[Red](#,##0.00)", + 0x29 => "#,##0;(#,##0)", // Not exactly + 0x2a => "\$#,##0;(\$#,##0)", // Not exactly + 0x2b => "#,##0.00;(#,##0.00)", // Not exactly + 0x2c => "\$#,##0.00;(\$#,##0.00)", // Not exactly + 0x30 => "##0.0E+0" + ); + + var $colors = Array( + 0x00 => "#000000", + 0x01 => "#FFFFFF", + 0x02 => "#FF0000", + 0x03 => "#00FF00", + 0x04 => "#0000FF", + 0x05 => "#FFFF00", + 0x06 => "#FF00FF", + 0x07 => "#00FFFF", + 0x08 => "#000000", + 0x09 => "#FFFFFF", + 0x0A => "#FF0000", + 0x0B => "#00FF00", + 0x0C => "#0000FF", + 0x0D => "#FFFF00", + 0x0E => "#FF00FF", + 0x0F => "#00FFFF", + 0x10 => "#800000", + 0x11 => "#008000", + 0x12 => "#000080", + 0x13 => "#808000", + 0x14 => "#800080", + 0x15 => "#008080", + 0x16 => "#C0C0C0", + 0x17 => "#808080", + 0x18 => "#9999FF", + 0x19 => "#993366", + 0x1A => "#FFFFCC", + 0x1B => "#CCFFFF", + 0x1C => "#660066", + 0x1D => "#FF8080", + 0x1E => "#0066CC", + 0x1F => "#CCCCFF", + 0x20 => "#000080", + 0x21 => "#FF00FF", + 0x22 => "#FFFF00", + 0x23 => "#00FFFF", + 0x24 => "#800080", + 0x25 => "#800000", + 0x26 => "#008080", + 0x27 => "#0000FF", + 0x28 => "#00CCFF", + 0x29 => "#CCFFFF", + 0x2A => "#CCFFCC", + 0x2B => "#FFFF99", + 0x2C => "#99CCFF", + 0x2D => "#FF99CC", + 0x2E => "#CC99FF", + 0x2F => "#FFCC99", + 0x30 => "#3366FF", + 0x31 => "#33CCCC", + 0x32 => "#99CC00", + 0x33 => "#FFCC00", + 0x34 => "#FF9900", + 0x35 => "#FF6600", + 0x36 => "#666699", + 0x37 => "#969696", + 0x38 => "#003366", + 0x39 => "#339966", + 0x3A => "#003300", + 0x3B => "#333300", + 0x3C => "#993300", + 0x3D => "#993366", + 0x3E => "#333399", + 0x3F => "#333333", + 0x40 => "#000000", + 0x41 => "#FFFFFF", + + 0x43 => "#000000", + 0x4D => "#000000", + 0x4E => "#FFFFFF", + 0x4F => "#000000", + 0x50 => "#FFFFFF", + 0x51 => "#000000", + + 0x7FFF => "#000000" + ); + + var $lineStyles = array( + 0x00 => "", + 0x01 => "Thin", + 0x02 => "Medium", + 0x03 => "Dashed", + 0x04 => "Dotted", + 0x05 => "Thick", + 0x06 => "Double", + 0x07 => "Hair", + 0x08 => "Medium dashed", + 0x09 => "Thin dash-dotted", + 0x0A => "Medium dash-dotted", + 0x0B => "Thin dash-dot-dotted", + 0x0C => "Medium dash-dot-dotted", + 0x0D => "Slanted medium dash-dotted" + ); + + var $lineStylesCss = array( + "Thin" => "1px solid", + "Medium" => "2px solid", + "Dashed" => "1px dashed", + "Dotted" => "1px dotted", + "Thick" => "3px solid", + "Double" => "double", + "Hair" => "1px solid", + "Medium dashed" => "2px dashed", + "Thin dash-dotted" => "1px dashed", + "Medium dash-dotted" => "2px dashed", + "Thin dash-dot-dotted" => "1px dashed", + "Medium dash-dot-dotted" => "2px dashed", + "Slanted medium dash-dotte" => "2px dashed" + ); + + function read16bitstring($data, $start) { + $len = 0; + while (ord($data[$start + $len]) + ord($data[$start + $len + 1]) > 0) $len++; + return substr($data, $start, $len); + } + + // ADDED by Matt Kruse for better formatting + function _format_value($format,$num,$f) { + // 49==TEXT format + // http://code.google.com/p/php-excel-reader/issues/detail?id=7 + if ( (!$f && $format=="%s") || ($f==49) || ($format=="GENERAL") ) { + return array('string'=>$num, 'formatColor'=>null); + } + + // Custom pattern can be POSITIVE;NEGATIVE;ZERO + // The "text" option as 4th parameter is not handled + $parts = split(";",$format); + $pattern = $parts[0]; + // Negative pattern + if (count($parts)>2 && $num==0) { + $pattern = $parts[2]; + } + // Zero pattern + if (count($parts)>1 && $num<0) { + $pattern = $parts[1]; + $num = abs($num); + } + + $color = ""; + $matches = array(); + $color_regex = "/^\[(BLACK|BLUE|CYAN|GREEN|MAGENTA|RED|WHITE|YELLOW)\]/i"; + if (preg_match($color_regex,$pattern,$matches)) { + $color = strtolower($matches[1]); + $pattern = preg_replace($color_regex,"",$pattern); + } + + // In Excel formats, "_" is used to add spacing, which we can't do in HTML + $pattern = preg_replace("/_./","",$pattern); + + // Some non-number characters are escaped with \, which we don't need + $pattern = preg_replace("/\\\/","",$pattern); + + // Some non-number strings are quoted, so we'll get rid of the quotes + $pattern = preg_replace("/\"/","",$pattern); + + // TEMPORARY - Convert # to 0 + $pattern = preg_replace("/\#/","0",$pattern); + + // Find out if we need comma formatting + $has_commas = preg_match("/,/",$pattern); + if ($has_commas) { + $pattern = preg_replace("/,/","",$pattern); + } + + // Handle Percentages + if (preg_match("/\d(\%)([^\%]|$)/",$pattern,$matches)) { + $num = $num * 100; + $pattern = preg_replace("/(\d)(\%)([^\%]|$)/","$1%$3",$pattern); + } + + // Handle the number itself + $number_regex = "/(\d+)(\.?)(\d*)/"; + if (preg_match($number_regex,$pattern,$matches)) { + $left = $matches[1]; + $dec = $matches[2]; + $right = $matches[3]; + if ($has_commas) { + $formatted = number_format($num,strlen($right)); + } + else { + $sprintf_pattern = "%1.".strlen($right)."f"; + $formatted = sprintf($sprintf_pattern, $num); + } + $pattern = preg_replace($number_regex, $formatted, $pattern); + } + + return array( + 'string'=>$pattern, + 'formatColor'=>$color + ); + } + + /** + * Constructor + * + * Some basic initialisation + */ + function Spreadsheet_Excel_Reader($file='',$store_extended_info=true,$outputEncoding='') { + $this->_ole =& new OLERead(); + $this->setUTFEncoder('iconv'); + if ($outputEncoding != '') { + $this->setOutputEncoding($outputEncoding); + } + for ($i=1; $i<245; $i++) { + $name = strtolower(( (($i-1)/26>=1)?chr(($i-1)/26+64):'') . chr(($i-1)%26+65)); + $this->colnames[$name] = $i; + $this->colindexes[$i] = $name; + } + $this->store_extended_info = $store_extended_info; + if ($file!="") { + $this->read($file); + } + } + + /** + * Set the encoding method + */ + function setOutputEncoding($encoding) { + $this->_defaultEncoding = $encoding; + } + + /** + * $encoder = 'iconv' or 'mb' + * set iconv if you would like use 'iconv' for encode UTF-16LE to your encoding + * set mb if you would like use 'mb_convert_encoding' for encode UTF-16LE to your encoding + */ + function setUTFEncoder($encoder = 'iconv') { + $this->_encoderFunction = ''; + if ($encoder == 'iconv') { + $this->_encoderFunction = function_exists('iconv') ? 'iconv' : ''; + } elseif ($encoder == 'mb') { + $this->_encoderFunction = function_exists('mb_convert_encoding') ? 'mb_convert_encoding' : ''; + } + } + + function setRowColOffset($iOffset) { + $this->_rowoffset = $iOffset; + $this->_coloffset = $iOffset; + } + + /** + * Set the default number format + */ + function setDefaultFormat($sFormat) { + $this->_defaultFormat = $sFormat; + } + + /** + * Force a column to use a certain format + */ + function setColumnFormat($column, $sFormat) { + $this->_columnsFormat[$column] = $sFormat; + } + + /** + * Read the spreadsheet file using OLE, then parse + */ + function read($sFileName) { + $res = $this->_ole->read($sFileName); + + // oops, something goes wrong (Darko Miljanovic) + if($res === false) { + // check error code + if($this->_ole->error == 1) { + // bad file + die('The filename ' . $sFileName . ' is not readable'); + } + // check other error codes here (eg bad fileformat, etc...) + } + $this->data = $this->_ole->getWorkBook(); + $this->_parse(); + } + + /** + * Parse a workbook + * + * @access private + * @return bool + */ + function _parse() { + $pos = 0; + $data = $this->data; + + $code = v($data,$pos); + $length = v($data,$pos+2); + $version = v($data,$pos+4); + $substreamType = v($data,$pos+6); + + $this->version = $version; + + if (($version != SPREADSHEET_EXCEL_READER_BIFF8) && + ($version != SPREADSHEET_EXCEL_READER_BIFF7)) { + return false; + } + + if ($substreamType != SPREADSHEET_EXCEL_READER_WORKBOOKGLOBALS){ + return false; + } + + $pos += $length + 4; + + $code = v($data,$pos); + $length = v($data,$pos+2); + + while ($code != SPREADSHEET_EXCEL_READER_TYPE_EOF) { + switch ($code) { + case SPREADSHEET_EXCEL_READER_TYPE_SST: + $spos = $pos + 4; + $limitpos = $spos + $length; + $uniqueStrings = $this->_GetInt4d($data, $spos+4); + $spos += 8; + for ($i = 0; $i < $uniqueStrings; $i++) { + // Read in the number of characters + if ($spos == $limitpos) { + $opcode = v($data,$spos); + $conlength = v($data,$spos+2); + if ($opcode != 0x3c) { + return -1; + } + $spos += 4; + $limitpos = $spos + $conlength; + } + $numChars = ord($data[$spos]) | (ord($data[$spos+1]) << 8); + $spos += 2; + $optionFlags = ord($data[$spos]); + $spos++; + $asciiEncoding = (($optionFlags & 0x01) == 0) ; + $extendedString = ( ($optionFlags & 0x04) != 0); + + // See if string contains formatting information + $richString = ( ($optionFlags & 0x08) != 0); + + if ($richString) { + // Read in the crun + $formattingRuns = v($data,$spos); + $spos += 2; + } + + if ($extendedString) { + // Read in cchExtRst + $extendedRunLength = $this->_GetInt4d($data, $spos); + $spos += 4; + } + + $len = ($asciiEncoding)? $numChars : $numChars*2; + if ($spos + $len < $limitpos) { + $retstr = substr($data, $spos, $len); + $spos += $len; + } + else{ + // found countinue + $retstr = substr($data, $spos, $limitpos - $spos); + $bytesRead = $limitpos - $spos; + $charsLeft = $numChars - (($asciiEncoding) ? $bytesRead : ($bytesRead / 2)); + $spos = $limitpos; + + while ($charsLeft > 0){ + $opcode = v($data,$spos); + $conlength = v($data,$spos+2); + if ($opcode != 0x3c) { + return -1; + } + $spos += 4; + $limitpos = $spos + $conlength; + $option = ord($data[$spos]); + $spos += 1; + if ($asciiEncoding && ($option == 0)) { + $len = min($charsLeft, $limitpos - $spos); // min($charsLeft, $conlength); + $retstr .= substr($data, $spos, $len); + $charsLeft -= $len; + $asciiEncoding = true; + } + elseif (!$asciiEncoding && ($option != 0)) { + $len = min($charsLeft * 2, $limitpos - $spos); // min($charsLeft, $conlength); + $retstr .= substr($data, $spos, $len); + $charsLeft -= $len/2; + $asciiEncoding = false; + } + elseif (!$asciiEncoding && ($option == 0)) { + // Bummer - the string starts off as Unicode, but after the + // continuation it is in straightforward ASCII encoding + $len = min($charsLeft, $limitpos - $spos); // min($charsLeft, $conlength); + for ($j = 0; $j < $len; $j++) { + $retstr .= $data[$spos + $j].chr(0); + } + $charsLeft -= $len; + $asciiEncoding = false; + } + else{ + $newstr = ''; + for ($j = 0; $j < strlen($retstr); $j++) { + $newstr = $retstr[$j].chr(0); + } + $retstr = $newstr; + $len = min($charsLeft * 2, $limitpos - $spos); // min($charsLeft, $conlength); + $retstr .= substr($data, $spos, $len); + $charsLeft -= $len/2; + $asciiEncoding = false; + } + $spos += $len; + } + } + // $retstr = ($asciiEncoding) ? $retstr : $this->_encodeUTF16($retstr); + $retstr = ($asciiEncoding) ? iconv('CP1252', $this->_defaultEncoding, $retstr) + : $this->_encodeUTF16($retstr); + + if ($richString){ + $spos += 4 * $formattingRuns; + } + + // For extended strings, skip over the extended string data + if ($extendedString) { + $spos += $extendedRunLength; + } + $this->sst[]=$retstr; + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_FILEPASS: + return false; + break; + case SPREADSHEET_EXCEL_READER_TYPE_NAME: + break; + case SPREADSHEET_EXCEL_READER_TYPE_FORMAT: + $indexCode = v($data,$pos+4); + if ($version == SPREADSHEET_EXCEL_READER_BIFF8) { + $numchars = v($data,$pos+6); + if (ord($data[$pos+8]) == 0){ + $formatString = substr($data, $pos+9, $numchars); + } else { + $formatString = substr($data, $pos+9, $numchars*2); + } + } else { + $numchars = ord($data[$pos+6]); + $formatString = substr($data, $pos+7, $numchars*2); + } + $this->formatRecords[$indexCode] = $formatString; + break; + case SPREADSHEET_EXCEL_READER_TYPE_FONT: + $height = v($data,$pos+4); + $option = v($data,$pos+6); + $color = v($data,$pos+8); + $weight = v($data,$pos+10); + $under = ord($data[$pos+14]); + $font = ""; + // Font name + $numchars = ord($data[$pos+18]); + if ((ord($data[$pos+19]) & 1) == 0){ + $font = substr($data, $pos+20, $numchars); + } else { + $font = substr($data, $pos+20, $numchars*2); + $font = $this->_encodeUTF16($font); + } + $this->fontRecords[] = array( + 'height' => $height / 20, + 'italic' => !!($option & 2), + 'color' => $color, + 'under' => !($under==0), + 'bold' => ($weight==700), + 'font' => $font, + 'raw' => $this->dumpHexData($data, $pos+3, $length) + ); + break; + + case SPREADSHEET_EXCEL_READER_TYPE_PALETTE: + $colors = ord($data[$pos+4]) | ord($data[$pos+5]) << 8; + for ($coli = 0; $coli < $colors; $coli++) { + $colOff = $pos + 2 + ($coli * 4); + $colr = ord($data[$colOff]); + $colg = ord($data[$colOff+1]); + $colb = ord($data[$colOff+2]); + $this->colors[0x07 + $coli] = '#' . $this->myhex($colr) . $this->myhex($colg) . $this->myhex($colb); + } + break; + + case SPREADSHEET_EXCEL_READER_TYPE_XF: + $fontIndexCode = (ord($data[$pos+4]) | ord($data[$pos+5]) << 8) - 1; + $fontIndexCode = max(0,$fontIndexCode); + $indexCode = ord($data[$pos+6]) | ord($data[$pos+7]) << 8; + $alignbit = ord($data[$pos+10]) & 3; + $bgi = (ord($data[$pos+22]) | ord($data[$pos+23]) << 8) & 0x3FFF; + $bgcolor = ($bgi & 0x7F); +// $bgcolor = ($bgi & 0x3f80) >> 7; + $align = ""; + if ($alignbit==3) { $align="right"; } + if ($alignbit==2) { $align="center"; } + + $fillPattern = (ord($data[$pos+21]) & 0xFC) >> 2; + if ($fillPattern == 0) { + $bgcolor = ""; + } + + $xf = array(); + $xf['formatIndex'] = $indexCode; + $xf['align'] = $align; + $xf['fontIndex'] = $fontIndexCode; + $xf['bgColor'] = $bgcolor; + $xf['fillPattern'] = $fillPattern; + + $border = ord($data[$pos+14]) | (ord($data[$pos+15]) << 8) | (ord($data[$pos+16]) << 16) | (ord($data[$pos+17]) << 24); + $xf['borderLeft'] = $this->lineStyles[($border & 0xF)]; + $xf['borderRight'] = $this->lineStyles[($border & 0xF0) >> 4]; + $xf['borderTop'] = $this->lineStyles[($border & 0xF00) >> 8]; + $xf['borderBottom'] = $this->lineStyles[($border & 0xF000) >> 12]; + + $xf['borderLeftColor'] = ($border & 0x7F0000) >> 16; + $xf['borderRightColor'] = ($border & 0x3F800000) >> 23; + $border = (ord($data[$pos+18]) | ord($data[$pos+19]) << 8); + + $xf['borderTopColor'] = ($border & 0x7F); + $xf['borderBottomColor'] = ($border & 0x3F80) >> 7; + + if (array_key_exists($indexCode, $this->dateFormats)) { + $xf['type'] = 'date'; + $xf['format'] = $this->dateFormats[$indexCode]; + if ($align=='') { $xf['align'] = 'right'; } + }elseif (array_key_exists($indexCode, $this->numberFormats)) { + $xf['type'] = 'number'; + $xf['format'] = $this->numberFormats[$indexCode]; + if ($align=='') { $xf['align'] = 'right'; } + }else{ + $isdate = FALSE; + $formatstr = ''; + if ($indexCode > 0){ + if (isset($this->formatRecords[$indexCode])) + $formatstr = $this->formatRecords[$indexCode]; + if ($formatstr!="") { + $tmp = preg_replace("/\;.*/","",$formatstr); + $tmp = preg_replace("/^\[[^\]]*\]/","",$tmp); + if (preg_match("/[^hmsday\/\-:\s\\\,AMP]/i", $tmp) == 0) { // found day and time format + $isdate = TRUE; + $formatstr = $tmp; + $formatstr = str_replace(array('AM/PM','mmmm','mmm'), array('a','F','M'), $formatstr); + // m/mm are used for both minutes and months - oh SNAP! + // This mess tries to fix for that. + // 'm' == minutes only if following h/hh or preceding s/ss + $formatstr = preg_replace("/(h:?)mm?/","$1i", $formatstr); + $formatstr = preg_replace("/mm?(:?s)/","i$1", $formatstr); + // A single 'm' = n in PHP + $formatstr = preg_replace("/(^|[^m])m([^m]|$)/", '$1n$2', $formatstr); + $formatstr = preg_replace("/(^|[^m])m([^m]|$)/", '$1n$2', $formatstr); + // else it's months + $formatstr = str_replace('mm', 'm', $formatstr); + // Convert single 'd' to 'j' + $formatstr = preg_replace("/(^|[^d])d([^d]|$)/", '$1j$2', $formatstr); + $formatstr = str_replace(array('dddd','ddd','dd','yyyy','yy','hh','h'), array('l','D','d','Y','y','H','g'), $formatstr); + $formatstr = preg_replace("/ss?/", 's', $formatstr); + } + } + } + if ($isdate){ + $xf['type'] = 'date'; + $xf['format'] = $formatstr; + if ($align=='') { $xf['align'] = 'right'; } + }else{ + // If the format string has a 0 or # in it, we'll assume it's a number + if (preg_match("/[0#]/", $formatstr)) { + $xf['type'] = 'number'; + if ($align=='') { $xf['align']='right'; } + } + else { + $xf['type'] = 'other'; + } + $xf['format'] = $formatstr; + $xf['code'] = $indexCode; + } + } + $this->xfRecords[] = $xf; + break; + case SPREADSHEET_EXCEL_READER_TYPE_NINETEENFOUR: + $this->nineteenFour = (ord($data[$pos+4]) == 1); + break; + case SPREADSHEET_EXCEL_READER_TYPE_BOUNDSHEET: + $rec_offset = $this->_GetInt4d($data, $pos+4); + $rec_typeFlag = ord($data[$pos+8]); + $rec_visibilityFlag = ord($data[$pos+9]); + $rec_length = ord($data[$pos+10]); + + if ($version == SPREADSHEET_EXCEL_READER_BIFF8){ + $chartype = ord($data[$pos+11]); + if ($chartype == 0){ + $rec_name = substr($data, $pos+12, $rec_length); + } else { + $rec_name = $this->_encodeUTF16(substr($data, $pos+12, $rec_length*2)); + } + }elseif ($version == SPREADSHEET_EXCEL_READER_BIFF7){ + $rec_name = substr($data, $pos+11, $rec_length); + } + $this->boundsheets[] = array('name'=>$rec_name,'offset'=>$rec_offset); + break; + + } + + $pos += $length + 4; + $code = ord($data[$pos]) | ord($data[$pos+1])<<8; + $length = ord($data[$pos+2]) | ord($data[$pos+3])<<8; + } + + foreach ($this->boundsheets as $key=>$val){ + $this->sn = $key; + $this->_parsesheet($val['offset']); + } + return true; + } + + /** + * Parse a worksheet + */ + function _parsesheet($spos) { + $cont = true; + $data = $this->data; + // read BOF + $code = ord($data[$spos]) | ord($data[$spos+1])<<8; + $length = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + + $version = ord($data[$spos + 4]) | ord($data[$spos + 5])<<8; + $substreamType = ord($data[$spos + 6]) | ord($data[$spos + 7])<<8; + + if (($version != SPREADSHEET_EXCEL_READER_BIFF8) && ($version != SPREADSHEET_EXCEL_READER_BIFF7)) { + return -1; + } + + if ($substreamType != SPREADSHEET_EXCEL_READER_WORKSHEET){ + return -2; + } + $spos += $length + 4; + while($cont) { + $lowcode = ord($data[$spos]); + if ($lowcode == SPREADSHEET_EXCEL_READER_TYPE_EOF) break; + $code = $lowcode | ord($data[$spos+1])<<8; + $length = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $spos += 4; + $this->sheets[$this->sn]['maxrow'] = $this->_rowoffset - 1; + $this->sheets[$this->sn]['maxcol'] = $this->_coloffset - 1; + unset($this->rectype); + switch ($code) { + case SPREADSHEET_EXCEL_READER_TYPE_DIMENSION: + if (!isset($this->numRows)) { + if (($length == 10) || ($version == SPREADSHEET_EXCEL_READER_BIFF7)){ + $this->sheets[$this->sn]['numRows'] = ord($data[$spos+2]) | ord($data[$spos+3]) << 8; + $this->sheets[$this->sn]['numCols'] = ord($data[$spos+6]) | ord($data[$spos+7]) << 8; + } else { + $this->sheets[$this->sn]['numRows'] = ord($data[$spos+4]) | ord($data[$spos+5]) << 8; + $this->sheets[$this->sn]['numCols'] = ord($data[$spos+10]) | ord($data[$spos+11]) << 8; + } + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_MERGEDCELLS: + $cellRanges = ord($data[$spos]) | ord($data[$spos+1])<<8; + for ($i = 0; $i < $cellRanges; $i++) { + $fr = ord($data[$spos + 8*$i + 2]) | ord($data[$spos + 8*$i + 3])<<8; + $lr = ord($data[$spos + 8*$i + 4]) | ord($data[$spos + 8*$i + 5])<<8; + $fc = ord($data[$spos + 8*$i + 6]) | ord($data[$spos + 8*$i + 7])<<8; + $lc = ord($data[$spos + 8*$i + 8]) | ord($data[$spos + 8*$i + 9])<<8; + if ($lr - $fr > 0) { + $this->sheets[$this->sn]['cellsInfo'][$fr+1][$fc+1]['rowspan'] = $lr - $fr + 1; + } + if ($lc - $fc > 0) { + $this->sheets[$this->sn]['cellsInfo'][$fr+1][$fc+1]['colspan'] = $lc - $fc + 1; + } + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_RK: + case SPREADSHEET_EXCEL_READER_TYPE_RK2: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $rknum = $this->_GetInt4d($data, $spos + 6); + $numValue = $this->_GetIEEE754($rknum); + $info = $this->_getCellDetails($spos,$numValue,$column); + $this->addcell($row, $column, $info['string'],$info); + break; + case SPREADSHEET_EXCEL_READER_TYPE_LABELSST: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $xfindex = ord($data[$spos+4]) | ord($data[$spos+5])<<8; + $index = $this->_GetInt4d($data, $spos + 6); + $this->addcell($row, $column, $this->sst[$index], array('xfIndex'=>$xfindex) ); + break; + case SPREADSHEET_EXCEL_READER_TYPE_MULRK: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $colFirst = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $colLast = ord($data[$spos + $length - 2]) | ord($data[$spos + $length - 1])<<8; + $columns = $colLast - $colFirst + 1; + $tmppos = $spos+4; + for ($i = 0; $i < $columns; $i++) { + $numValue = $this->_GetIEEE754($this->_GetInt4d($data, $tmppos + 2)); + $info = $this->_getCellDetails($tmppos-4,$numValue,$colFirst + $i + 1); + $tmppos += 6; + $this->addcell($row, $colFirst + $i, $info['string'], $info); + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_NUMBER: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $tmp = unpack("ddouble", substr($data, $spos + 6, 8)); // It machine machine dependent + if ($this->isDate($spos)) { + $numValue = $tmp['double']; + } + else { + $numValue = $this->createNumber($spos); + } + $info = $this->_getCellDetails($spos,$numValue,$column); + $this->addcell($row, $column, $info['string'], $info); + break; + + case SPREADSHEET_EXCEL_READER_TYPE_FORMULA: + case SPREADSHEET_EXCEL_READER_TYPE_FORMULA2: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + if ((ord($data[$spos+6])==0) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) { + //String formula. Result follows in a STRING record + // This row/col are stored to be referenced in that record + // http://code.google.com/p/php-excel-reader/issues/detail?id=4 + $previousRow = $row; + $previousCol = $column; + } elseif ((ord($data[$spos+6])==1) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) { + //Boolean formula. Result is in +2; 0=false,1=true + // http://code.google.com/p/php-excel-reader/issues/detail?id=4 + if (ord($this->data[$spos+8])==1) { + $this->addcell($row, $column, "TRUE"); + } else { + $this->addcell($row, $column, "FALSE"); + } + } elseif ((ord($data[$spos+6])==2) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) { + //Error formula. Error code is in +2; + } elseif ((ord($data[$spos+6])==3) && (ord($data[$spos+12])==255) && (ord($data[$spos+13])==255)) { + //Formula result is a null string. + $this->addcell($row, $column, ''); + } else { + // result is a number, so first 14 bytes are just like a _NUMBER record + $tmp = unpack("ddouble", substr($data, $spos + 6, 8)); // It machine machine dependent + if ($this->isDate($spos)) { + $numValue = $tmp['double']; + } + else { + $numValue = $this->createNumber($spos); + } + $info = $this->_getCellDetails($spos,$numValue,$column); + $this->addcell($row, $column, $info['string'], $info); + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_BOOLERR: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $string = ord($data[$spos+6]); + $this->addcell($row, $column, $string); + break; + case SPREADSHEET_EXCEL_READER_TYPE_STRING: + // http://code.google.com/p/php-excel-reader/issues/detail?id=4 + if ($version == SPREADSHEET_EXCEL_READER_BIFF8){ + // Unicode 16 string, like an SST record + $xpos = $spos; + $numChars =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8); + $xpos += 2; + $optionFlags =ord($data[$xpos]); + $xpos++; + $asciiEncoding = (($optionFlags &0x01) == 0) ; + $extendedString = (($optionFlags & 0x04) != 0); + // See if string contains formatting information + $richString = (($optionFlags & 0x08) != 0); + if ($richString) { + // Read in the crun + $formattingRuns =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8); + $xpos += 2; + } + if ($extendedString) { + // Read in cchExtRst + $extendedRunLength =$this->_GetInt4d($this->data, $xpos); + $xpos += 4; + } + $len = ($asciiEncoding)?$numChars : $numChars*2; + $retstr =substr($data, $xpos, $len); + $xpos += $len; + $retstr = ($asciiEncoding)? $retstr : $this->_encodeUTF16($retstr); + } + elseif ($version == SPREADSHEET_EXCEL_READER_BIFF7){ + // Simple byte string + $xpos = $spos; + $numChars =ord($data[$xpos]) | (ord($data[$xpos+1]) << 8); + $xpos += 2; + $retstr =substr($data, $xpos, $numChars); + } + $this->addcell($previousRow, $previousCol, $retstr); + break; + case SPREADSHEET_EXCEL_READER_TYPE_ROW: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $rowInfo = ord($data[$spos + 6]) | ((ord($data[$spos+7]) << 8) & 0x7FFF); + if (($rowInfo & 0x8000) > 0) { + $rowHeight = -1; + } else { + $rowHeight = $rowInfo & 0x7FFF; + } + $rowHidden = (ord($data[$spos + 12]) & 0x20) >> 5; + $this->rowInfo[$this->sn][$row+1] = Array('height' => $rowHeight / 20, 'hidden'=>$rowHidden ); + break; + case SPREADSHEET_EXCEL_READER_TYPE_DBCELL: + break; + case SPREADSHEET_EXCEL_READER_TYPE_MULBLANK: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $cols = ($length / 2) - 3; + for ($c = 0; $c < $cols; $c++) { + $xfindex = ord($data[$spos + 4 + ($c * 2)]) | ord($data[$spos + 5 + ($c * 2)])<<8; + $this->addcell($row, $column + $c, "", array('xfIndex'=>$xfindex)); + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_LABEL: + $row = ord($data[$spos]) | ord($data[$spos+1])<<8; + $column = ord($data[$spos+2]) | ord($data[$spos+3])<<8; + $this->addcell($row, $column, substr($data, $spos + 8, ord($data[$spos + 6]) | ord($data[$spos + 7])<<8)); + break; + case SPREADSHEET_EXCEL_READER_TYPE_EOF: + $cont = false; + break; + case SPREADSHEET_EXCEL_READER_TYPE_HYPER: + // Only handle hyperlinks to a URL + $row = ord($this->data[$spos]) | ord($this->data[$spos+1])<<8; + $row2 = ord($this->data[$spos+2]) | ord($this->data[$spos+3])<<8; + $column = ord($this->data[$spos+4]) | ord($this->data[$spos+5])<<8; + $column2 = ord($this->data[$spos+6]) | ord($this->data[$spos+7])<<8; + $linkdata = Array(); + $flags = ord($this->data[$spos + 28]); + $udesc = ""; + $ulink = ""; + $uloc = 32; + $linkdata['flags'] = $flags; + if (($flags & 1) > 0 ) { // is a type we understand + // is there a description ? + if (($flags & 0x14) == 0x14 ) { // has a description + $uloc += 4; + $descLen = ord($this->data[$spos + 32]) | ord($this->data[$spos + 33]) << 8; + $udesc = substr($this->data, $spos + $uloc, $descLen * 2); + $uloc += 2 * $descLen; + } + $ulink = $this->read16bitstring($this->data, $spos + $uloc + 20); + if ($udesc == "") { + $udesc = $ulink; + } + } + $linkdata['desc'] = $udesc; + $linkdata['link'] = $this->_encodeUTF16($ulink); + for ($r=$row; $r<=$row2; $r++) { + for ($c=$column; $c<=$column2; $c++) { + $this->sheets[$this->sn]['cellsInfo'][$r+1][$c+1]['hyperlink'] = $linkdata; + } + } + break; + case SPREADSHEET_EXCEL_READER_TYPE_DEFCOLWIDTH: + $this->defaultColWidth = ord($data[$spos+4]) | ord($data[$spos+5]) << 8; + break; + case SPREADSHEET_EXCEL_READER_TYPE_STANDARDWIDTH: + $this->standardColWidth = ord($data[$spos+4]) | ord($data[$spos+5]) << 8; + break; + case SPREADSHEET_EXCEL_READER_TYPE_COLINFO: + $colfrom = ord($data[$spos+0]) | ord($data[$spos+1]) << 8; + $colto = ord($data[$spos+2]) | ord($data[$spos+3]) << 8; + $cw = ord($data[$spos+4]) | ord($data[$spos+5]) << 8; + $cxf = ord($data[$spos+6]) | ord($data[$spos+7]) << 8; + $co = ord($data[$spos+8]); + for ($coli = $colfrom; $coli <= $colto; $coli++) { + $this->colInfo[$this->sn][$coli+1] = Array('width' => $cw, 'xf' => $cxf, 'hidden' => ($co & 0x01), 'collapsed' => ($co & 0x1000) >> 12); + } + break; + + default: + break; + } + $spos += $length; + } + + if (!isset($this->sheets[$this->sn]['numRows'])) + $this->sheets[$this->sn]['numRows'] = $this->sheets[$this->sn]['maxrow']; + if (!isset($this->sheets[$this->sn]['numCols'])) + $this->sheets[$this->sn]['numCols'] = $this->sheets[$this->sn]['maxcol']; + } + + function isDate($spos) { + $xfindex = ord($this->data[$spos+4]) | ord($this->data[$spos+5]) << 8; + return ($this->xfRecords[$xfindex]['type'] == 'date'); + } + + // Get the details for a particular cell + function _getCellDetails($spos,$numValue,$column) { + $xfindex = ord($this->data[$spos+4]) | ord($this->data[$spos+5]) << 8; + $xfrecord = $this->xfRecords[$xfindex]; + $type = $xfrecord['type']; + + $format = $xfrecord['format']; + $formatIndex = $xfrecord['formatIndex']; + $fontIndex = $xfrecord['fontIndex']; + $formatColor = ""; + $rectype = ''; + $string = ''; + $raw = ''; + + if (isset($this->_columnsFormat[$column + 1])){ + $format = $this->_columnsFormat[$column + 1]; + } + + if ($type == 'date') { + // See http://groups.google.com/group/php-excel-reader-discuss/browse_frm/thread/9c3f9790d12d8e10/f2045c2369ac79de + $rectype = 'date'; + // Convert numeric value into a date + $utcDays = floor($numValue - ($this->nineteenFour ? SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS1904 : SPREADSHEET_EXCEL_READER_UTCOFFSETDAYS)); + $utcValue = ($utcDays) * SPREADSHEET_EXCEL_READER_MSINADAY; + $dateinfo = gmgetdate($utcValue); + + $raw = $numValue; + $fractionalDay = $numValue - floor($numValue) + .0000001; // The .0000001 is to fix for php/excel fractional diffs + + $totalseconds = floor(SPREADSHEET_EXCEL_READER_MSINADAY * $fractionalDay); + $secs = $totalseconds % 60; + $totalseconds -= $secs; + $hours = floor($totalseconds / (60 * 60)); + $mins = floor($totalseconds / 60) % 60; + $string = date ($format, mktime($hours, $mins, $secs, $dateinfo["mon"], $dateinfo["mday"], $dateinfo["year"])); + } else if ($type == 'number') { + $rectype = 'number'; + $formatted = $this->_format_value($format, $numValue, $formatIndex); + $string = $formatted['string']; + $formatColor = $formatted['formatColor']; + $raw = $numValue; + } else{ + if ($format=="") { + $format = $this->_defaultFormat; + } + $rectype = 'unknown'; + $formatted = $this->_format_value($format, $numValue, $formatIndex); + $string = $formatted['string']; + $formatColor = $formatted['formatColor']; + $raw = $numValue; + } + + return array( + 'string'=>$string, + 'raw'=>$raw, + 'rectype'=>$rectype, + 'format'=>$format, + 'formatIndex'=>$formatIndex, + 'fontIndex'=>$fontIndex, + 'formatColor'=>$formatColor, + 'xfIndex'=>$xfindex + ); + + } + + + function createNumber($spos) { + $rknumhigh = $this->_GetInt4d($this->data, $spos + 10); + $rknumlow = $this->_GetInt4d($this->data, $spos + 6); + $sign = ($rknumhigh & 0x80000000) >> 31; + $exp = ($rknumhigh & 0x7ff00000) >> 20; + $mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); + $mantissalow1 = ($rknumlow & 0x80000000) >> 31; + $mantissalow2 = ($rknumlow & 0x7fffffff); + $value = $mantissa / pow( 2 , (20- ($exp - 1023))); + if ($mantissalow1 != 0) $value += 1 / pow (2 , (21 - ($exp - 1023))); + $value += $mantissalow2 / pow (2 , (52 - ($exp - 1023))); + if ($sign) {$value = -1 * $value;} + return $value; + } + + function addcell($row, $col, $string, $info=null) { + $this->sheets[$this->sn]['maxrow'] = max($this->sheets[$this->sn]['maxrow'], $row + $this->_rowoffset); + $this->sheets[$this->sn]['maxcol'] = max($this->sheets[$this->sn]['maxcol'], $col + $this->_coloffset); + $this->sheets[$this->sn]['cells'][$row + $this->_rowoffset][$col + $this->_coloffset] = $string; + if ($this->store_extended_info && $info) { + foreach ($info as $key=>$val) { + $this->sheets[$this->sn]['cellsInfo'][$row + $this->_rowoffset][$col + $this->_coloffset][$key] = $val; + } + } + } + + + function _GetIEEE754($rknum) { + if (($rknum & 0x02) != 0) { + $value = $rknum >> 2; + } else { + //mmp + // I got my info on IEEE754 encoding from + // http://research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html + // The RK format calls for using only the most significant 30 bits of the + // 64 bit floating point value. The other 34 bits are assumed to be 0 + // So, we use the upper 30 bits of $rknum as follows... + $sign = ($rknum & 0x80000000) >> 31; + $exp = ($rknum & 0x7ff00000) >> 20; + $mantissa = (0x100000 | ($rknum & 0x000ffffc)); + $value = $mantissa / pow( 2 , (20- ($exp - 1023))); + if ($sign) { + $value = -1 * $value; + } + //end of changes by mmp + } + if (($rknum & 0x01) != 0) { + $value /= 100; + } + return $value; + } + + function _encodeUTF16($string) { + $result = $string; + if ($this->_defaultEncoding){ + switch ($this->_encoderFunction){ + case 'iconv' : $result = iconv('UTF-16LE', $this->_defaultEncoding, $string); + break; + case 'mb_convert_encoding' : $result = mb_convert_encoding($string, $this->_defaultEncoding, 'UTF-16LE' ); + break; + } + } + return $result; + } + + function _GetInt4d($data, $pos) { + $value = ord($data[$pos]) | (ord($data[$pos+1]) << 8) | (ord($data[$pos+2]) << 16) | (ord($data[$pos+3]) << 24); + if ($value>=4294967294) { + $value=-2; + } + return $value; + } + +} + +?> diff --git a/lib/gettext/AUTHORS b/lib/gettext/AUTHORS new file mode 100644 index 0000000..da6ade7 --- /dev/null +++ b/lib/gettext/AUTHORS @@ -0,0 +1,3 @@ +Danilo Segan +Nico Kaiser (contributed most changes between 1.0.2 and 1.0.3, bugfix for 1.0.5) +Steven Armstrong (gettext.inc, leading to 1.0.6) diff --git a/lib/gettext/COPYING b/lib/gettext/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/lib/gettext/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + 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 +this service 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. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 General Public License as published by + the Free Software Foundation; either version 2 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/lib/gettext/README b/lib/gettext/README new file mode 100644 index 0000000..bca4f91 --- /dev/null +++ b/lib/gettext/README @@ -0,0 +1,161 @@ +PHP-gettext 1.0 (https://launchpad.net/php-gettext) + +Copyright 2003, 2006, 2009 -- Danilo "angry with PHP[1]" Segan +Licensed under GPLv2 (or any later version, see COPYING) + +[1] PHP is actually cyrillic, and translates roughly to + "works-doesn't-work" (UTF-8: Ради-Не-Ради) + + +Introduction + + How many times did you look for a good translation tool, and + found out that gettext is best for the job? Many times. + + How many times did you try to use gettext in PHP, but failed + miserably, because either your hosting provider didn't support + it, or the server didn't have adequate locale? Many times. + + Well, this is a solution to your needs. It allows using gettext + tools for managing translations, yet it doesn't require gettext + library at all. It parses generated MO files directly, and thus + might be a bit slower than the (maybe provided) gettext library. + + PHP-gettext is a simple reader for GNU gettext MO files. Those + are binary containers for translations, produced by GNU msgfmt. + +Why? + + I got used to having gettext work even without gettext + library. It's there in my favourite language Python, so I was + surprised that I couldn't find it in PHP. I even Googled for it, + but to no avail. + + So, I said, what the heck, I'm going to write it for this + disguisting language of PHP, because I'm often constrained to it. + +Features + + o Support for simple translations + Just define a simple alias for translate() function (suggested + use of _() or gettext(); see provided example). + + o Support for ngettext calls (plural forms, see a note under bugs) + You may also use plural forms. Translations in MO files need to + provide this, and they must also provide "plural-forms" header. + Please see 'info gettext' for more details. + + o Support for reading straight files, or strings (!!!) + Since I can imagine many different backends for reading in the MO + file data, I used imaginary abstract class StreamReader to do all + the input (check streams.php). For your convenience, I've already + provided two classes for reading files: FileReader and + StringReader (CachedFileReader is a combination of the two: it + loads entire file contents into a string, and then works on that). + See example below for usage. You can for instance use StringReader + when you read in data from a database, or you can create your own + derivative of StreamReader for anything you like. + + +Bugs + + Report them on https://bugs.launchpad.net/php-gettext + +Usage + + Put files streams.php and gettext.php somewhere you can load them + from, and require 'em in where you want to use them. + + Then, create one 'stream reader' (a class that provides functions + like read(), seekto(), currentpos() and length()) which will + provide data for the 'gettext_reader', with eg. + $streamer = new FileStream('data.mo'); + + Then, use that as a parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer); + + If you want to disable pre-loading of entire message catalog in + memory (if, for example, you have a multi-thousand message catalog + which you'll use only occasionally), use "false" for second + parameter to gettext_reader constructor: + $wohoo = new gettext_reader($streamer, false); + + From now on, you have all the benefits of gettext data at your + disposal, so may run: + print $wohoo->translate("This is a test"); + print $wohoo->ngettext("%d bird", "%d birds", $birds); + + You might need to pass parameter "-k" to xgettext to make it + extract all the strings. In above example, try with + xgettext -ktranslate -kngettext:1,2 file.php + what should create messages.po which contains two messages for + translation. + + I suggest creating simple aliases for these functions (see + example/pigs.php for how do I do it, which means it's probably a + bad way). + + +Usage with gettext.inc (standard gettext interfaces emulation) + + Check example in examples/pig_dropin.php, basically you include + gettext.inc and use all the standard gettext interfaces as + documented on: + + http://www.php.net/gettext + + The only catch is that you can check return value of setlocale() + to see if your locale is system supported or not. + + +Example + + See in examples/ subdirectory. There are a couple of files. + pigs.php is an example, serbian.po is a translation to Serbian + language, and serbian.mo is generated with + msgfmt -o serbian.mo serbian.po + There is also simple "update" script that can be used to generate + POT file and to update the translation using msgmerge. + +TODO: + + o Improve speed to be even more comparable to the native gettext + implementation. + + o Try to use hash tables in MO files: with pre-loading, would it + be useful at all? + +Never-asked-questions: + + o Why did you mark this as version 1.0 when this is the first code + release? + + Well, it's quite simple. I consider that the first released thing + should be labeled "version 1" (first, right?). Zero is there to + indicate that there's zero improvement and/or change compared to + "version 1". + + I plan to use version numbers 1.0.* for small bugfixes, and to + release 1.1 as "first stable release of version 1". + + This may trick someone that this is actually useful software, but + as with any other free software, I take NO RESPONSIBILITY for + creating such a masterpiece that will smoke crack, trash your + hard disk, and make lasers in your CD device dance to the tune of + Mozart's 40th Symphony (there is one like that, right?). + + o Can I...? + + Yes, you can. This is free software (as in freedom, free speech), + and you might do whatever you wish with it, provided you do not + limit freedom of others (GPL). + + I'm considering licensing this under LGPL, but I *do* want + *every* PHP-gettext user to contribute and respect ideas of free + software, so don't count on it happening anytime soon. + + I'm sorry that I'm taking away your freedom of taking others' + freedom away, but I believe that's neglible as compared to what + freedoms you could take away. ;-) + + Uhm, whatever. diff --git a/lib/gettext/gettext.inc b/lib/gettext/gettext.inc new file mode 100644 index 0000000..00b9666 --- /dev/null +++ b/lib/gettext/gettext.inc @@ -0,0 +1,536 @@ + + Copyright (c) 2009 Danilo Segan + + Drop in replacement for native gettext. + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ +/* +LC_CTYPE 0 +LC_NUMERIC 1 +LC_TIME 2 +LC_COLLATE 3 +LC_MONETARY 4 +LC_MESSAGES 5 +LC_ALL 6 +*/ + +// LC_MESSAGES is not available if php-gettext is not loaded +// while the other constants are already available from session extension. +if (!defined('LC_MESSAGES')) { + define('LC_MESSAGES', 5); +} + +require('streams.php'); +require('gettext.php'); + + +// Variables + +global $text_domains, $default_domain, $LC_CATEGORIES, $EMULATEGETTEXT, $CURRENTLOCALE; +$text_domains = array(); +$default_domain = 'messages'; +$LC_CATEGORIES = array('LC_CTYPE', 'LC_NUMERIC', 'LC_TIME', 'LC_COLLATE', 'LC_MONETARY', 'LC_MESSAGES', 'LC_ALL'); +$EMULATEGETTEXT = 0; +$CURRENTLOCALE = ''; + +/* Class to hold a single domain included in $text_domains. */ +class domain { + var $l10n; + var $path; + var $codeset; +} + +// Utility functions + +/** + * Return a list of locales to try for any POSIX-style locale specification. + */ +function get_list_of_locales($locale) { + /* Figure out all possible locale names and start with the most + * specific ones. I.e. for sr_CS.UTF-8@latin, look through all of + * sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr. + */ + $locale_names = array(); + $lang = NULL; + $country = NULL; + $charset = NULL; + $modifier = NULL; + if ($locale) { + if (preg_match("/^(?P[a-z]{2,3})" // language code + ."(?:_(?P[A-Z]{2}))?" // country code + ."(?:\.(?P[-A-Za-z0-9_]+))?" // charset + ."(?:@(?P[-A-Za-z0-9_]+))?$/", // @ modifier + $locale, $matches)) { + + if (isset($matches["lang"])) $lang = $matches["lang"]; + if (isset($matches["country"])) $country = $matches["country"]; + if (isset($matches["charset"])) $charset = $matches["charset"]; + if (isset($matches["modifier"])) $modifier = $matches["modifier"]; + + if ($modifier) { + if ($country) { + if ($charset) + array_push($locale_names, "${lang}_$country.$charset@$modifier"); + array_push($locale_names, "${lang}_$country@$modifier"); + } elseif ($charset) + array_push($locale_names, "${lang}.$charset@$modifier"); + array_push($locale_names, "$lang@$modifier"); + } + if ($country) { + if ($charset) + array_push($locale_names, "${lang}_$country.$charset"); + array_push($locale_names, "${lang}_$country"); + } elseif ($charset) + array_push($locale_names, "${lang}.$charset"); + array_push($locale_names, $lang); + } + + // If the locale name doesn't match POSIX style, just include it as-is. + if (!in_array($locale, $locale_names)) + array_push($locale_names, $locale); + } + return $locale_names; +} + +/** + * Utility function to get a StreamReader for the given text domain. + */ +function _get_reader($domain=null, $category=5, $enable_cache=true) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + if (!isset($text_domains[$domain]->l10n)) { + // get the current locale + $locale = _setlocale(LC_MESSAGES, 0); + $bound_path = isset($text_domains[$domain]->path) ? + $text_domains[$domain]->path : './'; + $subpath = $LC_CATEGORIES[$category] ."/$domain.mo"; + + $locale_names = get_list_of_locales($locale); + $input = null; + foreach ($locale_names as $locale) { + $full_path = $bound_path . $locale . "/" . $subpath; + if (file_exists($full_path)) { + $input = new FileReader($full_path); + break; + } + } + + if (!array_key_exists($domain, $text_domains)) { + // Initialize an empty domain object. + $text_domains[$domain] = new domain(); + } + $text_domains[$domain]->l10n = new gettext_reader($input, + $enable_cache); + } + return $text_domains[$domain]->l10n; +} + +/** + * Returns whether we are using our emulated gettext API or PHP built-in one. + */ +function locale_emulation() { + global $EMULATEGETTEXT; + return $EMULATEGETTEXT; +} + +/** + * Checks if the current locale is supported on this system. + */ +function _check_locale_and_function($function=false) { + global $EMULATEGETTEXT; + if ($function and !function_exists($function)) + return false; + return !$EMULATEGETTEXT; +} + +/** + * Get the codeset for the given domain. + */ +function _get_codeset($domain=null) { + global $text_domains, $default_domain, $LC_CATEGORIES; + if (!isset($domain)) $domain = $default_domain; + return (isset($text_domains[$domain]->codeset))? $text_domains[$domain]->codeset : ini_get('mbstring.internal_encoding'); +} + +/** + * Convert the given string to the encoding set by bind_textdomain_codeset. + */ +function _encode($text) { + $source_encoding = mb_detect_encoding($text); + $target_encoding = _get_codeset(); + if ($source_encoding != $target_encoding) { + return mb_convert_encoding($text, $target_encoding, $source_encoding); + } + else { + return $text; + } +} + + +// Custom implementation of the standard gettext related functions + +/** + * Returns passed in $locale, or environment variable $LANG if $locale == ''. + */ +function _get_default_locale($locale) { + if ($locale == '') // emulate variable support + return getenv('LANG'); + else + return $locale; +} + +/** + * Sets a requested locale, if needed emulates it. + */ +function _setlocale($category, $locale) { + global $CURRENTLOCALE, $EMULATEGETTEXT; + if ($locale === 0) { // use === to differentiate between string "0" + if ($CURRENTLOCALE != '') + return $CURRENTLOCALE; + else + // obey LANG variable, maybe extend to support all of LC_* vars + // even if we tried to read locale without setting it first + return _setlocale($category, $CURRENTLOCALE); + } else { + if (function_exists('setlocale')) { + $ret = setlocale($category, $locale); + if (($locale == '' and !$ret) or // failed setting it by env + ($locale != '' and $ret != $locale)) { // failed setting it + // Failed setting it according to environment. + $CURRENTLOCALE = _get_default_locale($locale); + $EMULATEGETTEXT = 1; + } else { + $CURRENTLOCALE = $ret; + $EMULATEGETTEXT = 0; + } + } else { + // No function setlocale(), emulate it all. + $CURRENTLOCALE = _get_default_locale($locale); + $EMULATEGETTEXT = 1; + } + // Allow locale to be changed on the go for one translation domain. + global $text_domains, $default_domain; + if (array_key_exists($default_domain, $text_domains)) { + unset($text_domains[$default_domain]->l10n); + } + return $CURRENTLOCALE; + } +} + +/** + * Sets the path for a domain. + */ +function _bindtextdomain($domain, $path) { + global $text_domains; + // ensure $path ends with a slash ('/' should work for both, but lets still play nice) + if (substr(php_uname(), 0, 7) == "Windows") { + if ($path[strlen($path)-1] != '\\' and $path[strlen($path)-1] != '/') + $path .= '\\'; + } else { + if ($path[strlen($path)-1] != '/') + $path .= '/'; + } + if (!array_key_exists($domain, $text_domains)) { + // Initialize an empty domain object. + $text_domains[$domain] = new domain(); + } + $text_domains[$domain]->path = $path; +} + +/** + * Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. + */ +function _bind_textdomain_codeset($domain, $codeset) { + global $text_domains; + $text_domains[$domain]->codeset = $codeset; +} + +/** + * Sets the default domain. + */ +function _textdomain($domain) { + global $default_domain; + $default_domain = $domain; +} + +/** + * Lookup a message in the current domain. + */ +function _gettext($msgid) { + $l10n = _get_reader(); + return _encode($l10n->translate($msgid)); +} + +/** + * Alias for gettext. + */ +function __($msgid) { + return _gettext($msgid); +} + +/** + * Plural version of gettext. + */ +function _ngettext($singular, $plural, $number) { + $l10n = _get_reader(); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Override the current domain. + */ +function _dgettext($domain, $msgid) { + $l10n = _get_reader($domain); + return _encode($l10n->translate($msgid)); +} + +/** + * Plural version of dgettext. + */ +function _dngettext($domain, $singular, $plural, $number) { + $l10n = _get_reader($domain); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Overrides the domain and category for a single lookup. + */ +function _dcgettext($domain, $msgid, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->translate($msgid)); +} +/** + * Plural version of dcgettext. + */ +function _dcngettext($domain, $singular, $plural, $number, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->ngettext($singular, $plural, $number)); +} + +/** + * Context version of gettext. + */ +function _pgettext($context, $msgid) { + $l10n = _get_reader(); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Override the current domain in a context gettext call. + */ +function _dpgettext($domain, $context, $msgid) { + $l10n = _get_reader($domain); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Overrides the domain and category for a single context-based lookup. + */ +function _dcpgettext($domain, $context, $msgid, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->pgettext($context, $msgid)); +} + +/** + * Context version of ngettext. + */ +function _npgettext($context, $singular, $plural) { + $l10n = _get_reader(); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + +/** + * Override the current domain in a context ngettext call. + */ +function _dnpgettext($domain, $context, $singular, $plural) { + $l10n = _get_reader($domain); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + +/** + * Overrides the domain and category for a plural context-based lookup. + */ +function _dcnpgettext($domain, $context, $singular, $plural, $category) { + $l10n = _get_reader($domain, $category); + return _encode($l10n->npgettext($context, $singular, $plural)); +} + + + +// Wrappers to use if the standard gettext functions are available, +// but the current locale is not supported by the system. +// Use the standard impl if the current locale is supported, use the +// custom impl otherwise. + +function T_setlocale($category, $locale) { + return _setlocale($category, $locale); +} + +function T_bindtextdomain($domain, $path) { + if (_check_locale_and_function()) return bindtextdomain($domain, $path); + else return _bindtextdomain($domain, $path); +} +function T_bind_textdomain_codeset($domain, $codeset) { + // bind_textdomain_codeset is available only in PHP 4.2.0+ + if (_check_locale_and_function('bind_textdomain_codeset')) + return bind_textdomain_codeset($domain, $codeset); + else return _bind_textdomain_codeset($domain, $codeset); +} +function T_textdomain($domain) { + if (_check_locale_and_function()) return textdomain($domain); + else return _textdomain($domain); +} +function T_gettext($msgid) { + if (_check_locale_and_function()) return gettext($msgid); + else return _gettext($msgid); +} +function T_($msgid) { + if (_check_locale_and_function()) return _($msgid); + return __($msgid); +} +function T_ngettext($singular, $plural, $number) { + if (_check_locale_and_function()) + return ngettext($singular, $plural, $number); + else return _ngettext($singular, $plural, $number); +} +function T_dgettext($domain, $msgid) { + if (_check_locale_and_function()) return dgettext($domain, $msgid); + else return _dgettext($domain, $msgid); +} +function T_dngettext($domain, $singular, $plural, $number) { + if (_check_locale_and_function()) + return dngettext($domain, $singular, $plural, $number); + else return _dngettext($domain, $singular, $plural, $number); +} +function T_dcgettext($domain, $msgid, $category) { + if (_check_locale_and_function()) + return dcgettext($domain, $msgid, $category); + else return _dcgettext($domain, $msgid, $category); +} +function T_dcngettext($domain, $singular, $plural, $number, $category) { + if (_check_locale_and_function()) + return dcngettext($domain, $singular, $plural, $number, $category); + else return _dcngettext($domain, $singular, $plural, $number, $category); +} + +function T_pgettext($context, $msgid) { + if (_check_locale_and_function('pgettext')) + return pgettext($context, $msgid); + else + return _pgettext($context, $msgid); +} + +function T_dpgettext($domain, $context, $msgid) { + if (_check_locale_and_function('dpgettext')) + return dpgettext($domain, $context, $msgid); + else + return _dpgettext($domain, $context, $msgid); +} + +function T_dcpgettext($domain, $context, $msgid, $category) { + if (_check_locale_and_function('dcpgettext')) + return dcpgettext($domain, $context, $msgid, $category); + else + return _dcpgettext($domain, $context, $msgid, $category); +} + +function T_npgettext($context, $singular, $plural, $number) { + if (_check_locale_and_function('npgettext')) + return npgettext($context, $singular, $plural, $number); + else + return _npgettext($context, $singular, $plural, $number); +} + +function T_dnpgettext($domain, $context, $singular, $plural, $number) { + if (_check_locale_and_function('dnpgettext')) + return dnpgettext($domain, $context, $singular, $plural, $number); + else + return _dnpgettext($domain, $context, $singular, $plural, $number); +} + +function T_dcnpgettext($domain, $context, $singular, $plural, + $number, $category) { + if (_check_locale_and_function('dcnpgettext')) + return dcnpgettext($domain, $context, $singular, + $plural, $number, $category); + else + return _dcnpgettext($domain, $context, $singular, + $plural, $number, $category); +} + + + +// Wrappers used as a drop in replacement for the standard gettext functions + +if (!function_exists('gettext')) { + function bindtextdomain($domain, $path) { + return _bindtextdomain($domain, $path); + } + function bind_textdomain_codeset($domain, $codeset) { + return _bind_textdomain_codeset($domain, $codeset); + } + function textdomain($domain) { + return _textdomain($domain); + } + function gettext($msgid) { + return _gettext($msgid); + } + function _($msgid) { + return __($msgid); + } + function ngettext($singular, $plural, $number) { + return _ngettext($singular, $plural, $number); + } + function dgettext($domain, $msgid) { + return _dgettext($domain, $msgid); + } + function dngettext($domain, $singular, $plural, $number) { + return _dngettext($domain, $singular, $plural, $number); + } + function dcgettext($domain, $msgid, $category) { + return _dcgettext($domain, $msgid, $category); + } + function dcngettext($domain, $singular, $plural, $number, $category) { + return _dcngettext($domain, $singular, $plural, $number, $category); + } + function pgettext($context, $msgid) { + return _pgettext($context, $msgid); + } + function npgettext($context, $singular, $plural, $number) { + return _npgettext($context, $singular, $plural, $number); + } + function dpgettext($domain, $context, $msgid) { + return _dpgettext($domain, $context, $msgid); + } + function dnpgettext($domain, $context, $singular, $plural, $number) { + return _dnpgettext($domain, $context, $singular, $plural, $number); + } + function dcpgettext($domain, $context, $msgid, $category) { + return _dcpgettext($domain, $context, $msgid, $category); + } + function dcnpgettext($domain, $context, $singular, $plural, + $number, $category) { + return _dcnpgettext($domain, $context, $singular, $plural, + $number, $category); + } +} + +?> diff --git a/lib/gettext/gettext.php b/lib/gettext/gettext.php new file mode 100644 index 0000000..5064047 --- /dev/null +++ b/lib/gettext/gettext.php @@ -0,0 +1,432 @@ +. + Copyright (c) 2005 Nico Kaiser + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +/** + * Provides a simple gettext replacement that works independently from + * the system's gettext abilities. + * It can read MO files and use them for translating strings. + * The files are passed to gettext_reader as a Stream (see streams.php) + * + * This version has the ability to cache all strings and translations to + * speed up the string lookup. + * While the cache is enabled by default, it can be switched off with the + * second parameter in the constructor (e.g. whenusing very large MO files + * that you don't want to keep in memory) + */ +class gettext_reader { + //public: + var $error = 0; // public variable that holds error code (0 if no error) + + //private: + var $BYTEORDER = 0; // 0: low endian, 1: big endian + var $STREAM = NULL; + var $short_circuit = false; + var $enable_cache = false; + var $originals = NULL; // offset of original table + var $translations = NULL; // offset of translation table + var $pluralheader = NULL; // cache header field for plural forms + var $total = 0; // total string count + var $table_originals = NULL; // table for original strings (offsets) + var $table_translations = NULL; // table for translated strings (offsets) + var $cache_translations = NULL; // original -> translation mapping + + + /* Methods */ + + + /** + * Reads a 32bit Integer from the Stream + * + * @access private + * @return Integer from the Stream + */ + function readint() { + if ($this->BYTEORDER == 0) { + // low endian + $input=unpack('V', $this->STREAM->read(4)); + return array_shift($input); + } else { + // big endian + $input=unpack('N', $this->STREAM->read(4)); + return array_shift($input); + } + } + + function read($bytes) { + return $this->STREAM->read($bytes); + } + + /** + * Reads an array of Integers from the Stream + * + * @param int count How many elements should be read + * @return Array of Integers + */ + function readintarray($count) { + if ($this->BYTEORDER == 0) { + // low endian + return unpack('V'.$count, $this->STREAM->read(4 * $count)); + } else { + // big endian + return unpack('N'.$count, $this->STREAM->read(4 * $count)); + } + } + + /** + * Constructor + * + * @param object Reader the StreamReader object + * @param boolean enable_cache Enable or disable caching of strings (default on) + */ + function gettext_reader($Reader, $enable_cache = true) { + // If there isn't a StreamReader, turn on short circuit mode. + if (! $Reader || isset($Reader->error) ) { + $this->short_circuit = true; + return; + } + + // Caching can be turned off + $this->enable_cache = $enable_cache; + + $MAGIC1 = "\x95\x04\x12\xde"; + $MAGIC2 = "\xde\x12\x04\x95"; + + $this->STREAM = $Reader; + $magic = $this->read(4); + if ($magic == $MAGIC1) { + $this->BYTEORDER = 1; + } elseif ($magic == $MAGIC2) { + $this->BYTEORDER = 0; + } else { + $this->error = 1; // not MO file + return false; + } + + // FIXME: Do we care about revision? We should. + $revision = $this->readint(); + + $this->total = $this->readint(); + $this->originals = $this->readint(); + $this->translations = $this->readint(); + } + + /** + * Loads the translation tables from the MO file into the cache + * If caching is enabled, also loads all strings into a cache + * to speed up translation lookups + * + * @access private + */ + function load_tables() { + if (is_array($this->cache_translations) && + is_array($this->table_originals) && + is_array($this->table_translations)) + return; + + /* get original and translations tables */ + if (!is_array($this->table_originals)) { + $this->STREAM->seekto($this->originals); + $this->table_originals = $this->readintarray($this->total * 2); + } + if (!is_array($this->table_translations)) { + $this->STREAM->seekto($this->translations); + $this->table_translations = $this->readintarray($this->total * 2); + } + + if ($this->enable_cache) { + $this->cache_translations = array (); + /* read all strings in the cache */ + for ($i = 0; $i < $this->total; $i++) { + $this->STREAM->seekto($this->table_originals[$i * 2 + 2]); + $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]); + $this->STREAM->seekto($this->table_translations[$i * 2 + 2]); + $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]); + $this->cache_translations[$original] = $translation; + } + } + } + + /** + * Returns a string from the "originals" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_original_string($num) { + $length = $this->table_originals[$num * 2 + 1]; + $offset = $this->table_originals[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Returns a string from the "translations" table + * + * @access private + * @param int num Offset number of original string + * @return string Requested string if found, otherwise '' + */ + function get_translation_string($num) { + $length = $this->table_translations[$num * 2 + 1]; + $offset = $this->table_translations[$num * 2 + 2]; + if (! $length) + return ''; + $this->STREAM->seekto($offset); + $data = $this->STREAM->read($length); + return (string)$data; + } + + /** + * Binary search for string + * + * @access private + * @param string string + * @param int start (internally used in recursive function) + * @param int end (internally used in recursive function) + * @return int string number (offset in originals table) + */ + function find_string($string, $start = -1, $end = -1) { + if (($start == -1) or ($end == -1)) { + // find_string is called with only one parameter, set start end end + $start = 0; + $end = $this->total; + } + if (abs($start - $end) <= 1) { + // We're done, now we either found the string, or it doesn't exist + $txt = $this->get_original_string($start); + if ($string == $txt) + return $start; + else + return -1; + } else if ($start > $end) { + // start > end -> turn around and start over + return $this->find_string($string, $end, $start); + } else { + // Divide table in two parts + $half = (int)(($start + $end) / 2); + $cmp = strcmp($string, $this->get_original_string($half)); + if ($cmp == 0) + // string is exactly in the middle => return it + return $half; + else if ($cmp < 0) + // The string is in the upper half + return $this->find_string($string, $start, $half); + else + // The string is in the lower half + return $this->find_string($string, $half, $end); + } + } + + /** + * Translates a string + * + * @access public + * @param string string to be translated + * @return string translated string (or original, if not found) + */ + function translate($string) { + if ($this->short_circuit) + return $string; + $this->load_tables(); + + if ($this->enable_cache) { + // Caching enabled, get translated string from cache + if (array_key_exists($string, $this->cache_translations)) + return $this->cache_translations[$string]; + else + return $string; + } else { + // Caching not enabled, try to find string + $num = $this->find_string($string); + if ($num == -1) + return $string; + else + return $this->get_translation_string($num); + } + } + + /** + * Sanitize plural form expression for use in PHP eval call. + * + * @access private + * @return string sanitized plural form expression + */ + function sanitize_plural_expression($expr) { + // Get rid of disallowed characters. + $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr); + + // Add parenthesis for tertiary '?' operator. + $expr .= ';'; + $res = ''; + $p = 0; + for ($i = 0; $i < strlen($expr); $i++) { + $ch = $expr[$i]; + switch ($ch) { + case '?': + $res .= ' ? ('; + $p++; + break; + case ':': + $res .= ') : ('; + break; + case ';': + $res .= str_repeat( ')', $p) . ';'; + $p = 0; + break; + default: + $res .= $ch; + } + } + return $res; + } + + /** + * Parse full PO header and extract only plural forms line. + * + * @access private + * @return string verbatim plural form header field + */ + function extract_plural_forms_header_from_po_header($header) { + if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) + $expr = $regs[2]; + else + $expr = "nplurals=2; plural=n == 1 ? 0 : 1;"; + return $expr; + } + + /** + * Get possible plural forms from MO header + * + * @access private + * @return string plural form header + */ + function get_plural_forms() { + // lets assume message number 0 is header + // this is true, right? + $this->load_tables(); + + // cache header field for plural forms + if (! is_string($this->pluralheader)) { + if ($this->enable_cache) { + $header = $this->cache_translations[""]; + } else { + $header = $this->get_translation_string(0); + } + $expr = $this->extract_plural_forms_header_from_po_header($header); + $this->pluralheader = $this->sanitize_plural_expression($expr); + } + return $this->pluralheader; + } + + /** + * Detects which plural form to take + * + * @access private + * @param n count + * @return int array index of the right plural form + */ + function select_string($n) { + $string = $this->get_plural_forms(); + $string = str_replace('nplurals',"\$total",$string); + $string = str_replace("n",$n,$string); + $string = str_replace('plural',"\$plural",$string); + + $total = 0; + $plural = 0; + + eval("$string"); + if ($plural >= $total) $plural = $total - 1; + return $plural; + } + + /** + * Plural version of gettext + * + * @access public + * @param string single + * @param string plural + * @param string number + * @return translated plural form + */ + function ngettext($single, $plural, $number) { + if ($this->short_circuit) { + if ($number != 1) + return $plural; + else + return $single; + } + + // find out the appropriate form + $select = $this->select_string($number); + + // this should contains all strings separated by NULLs + $key = $single . chr(0) . $plural; + + + if ($this->enable_cache) { + if (! array_key_exists($key, $this->cache_translations)) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->cache_translations[$key]; + $list = explode(chr(0), $result); + return $list[$select]; + } + } else { + $num = $this->find_string($key); + if ($num == -1) { + return ($number != 1) ? $plural : $single; + } else { + $result = $this->get_translation_string($num); + $list = explode(chr(0), $result); + return $list[$select]; + } + } + } + + function pgettext($context, $msgid) { + $key = $context . chr(4) . $msgid; + $ret = $this->translate($key); + if (strpos($ret, "\004") !== FALSE) { + return $msgid; + } else { + return $ret; + } + } + + function npgettext($context, $singular, $plural, $number) { + $key = $context . chr(4) . $singular; + $ret = $this->ngettext($key, $plural, $number); + if (strpos($ret, "\004") !== FALSE) { + return $singular; + } else { + return $ret; + } + + } +} + +?> diff --git a/lib/gettext/streams.php b/lib/gettext/streams.php new file mode 100644 index 0000000..3cdc158 --- /dev/null +++ b/lib/gettext/streams.php @@ -0,0 +1,167 @@ +. + + This file is part of PHP-gettext. + + PHP-gettext is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + PHP-gettext 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with PHP-gettext; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + + + // Simple class to wrap file streams, string streams, etc. + // seek is essential, and it should be byte stream +class StreamReader { + // should return a string [FIXME: perhaps return array of bytes?] + function read($bytes) { + return false; + } + + // should return new position + function seekto($position) { + return false; + } + + // returns current position + function currentpos() { + return false; + } + + // returns length of entire stream (limit for seekto()s) + function length() { + return false; + } +}; + +class StringReader { + var $_pos; + var $_str; + + function StringReader($str='') { + $this->_str = $str; + $this->_pos = 0; + } + + function read($bytes) { + $data = substr($this->_str, $this->_pos, $bytes); + $this->_pos += $bytes; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + + return $data; + } + + function seekto($pos) { + $this->_pos = $pos; + if (strlen($this->_str)<$this->_pos) + $this->_pos = strlen($this->_str); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return strlen($this->_str); + } + +}; + + +class FileReader { + var $_pos; + var $_fd; + var $_length; + + function FileReader($filename) { + if (file_exists($filename)) { + + $this->_length=filesize($filename); + $this->_pos = 0; + $this->_fd = fopen($filename,'rb'); + if (!$this->_fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + } else { + $this->error = 2; // File doesn't exist + return false; + } + } + + function read($bytes) { + if ($bytes) { + fseek($this->_fd, $this->_pos); + + // PHP 5.1.1 does not read more than 8192 bytes in one fread() + // the discussions at PHP Bugs suggest it's the intended behaviour + $data = ''; + while ($bytes > 0) { + $chunk = fread($this->_fd, $bytes); + $data .= $chunk; + $bytes -= strlen($chunk); + } + $this->_pos = ftell($this->_fd); + + return $data; + } else return ''; + } + + function seekto($pos) { + fseek($this->_fd, $pos); + $this->_pos = ftell($this->_fd); + return $this->_pos; + } + + function currentpos() { + return $this->_pos; + } + + function length() { + return $this->_length; + } + + function close() { + fclose($this->_fd); + } + +}; + +// Preloads entire file in memory first, then creates a StringReader +// over it (it assumes knowledge of StringReader internals) +class CachedFileReader extends StringReader { + function CachedFileReader($filename) { + if (file_exists($filename)) { + + $length=filesize($filename); + $fd = fopen($filename,'rb'); + + if (!$fd) { + $this->error = 3; // Cannot read file, probably permissions + return false; + } + $this->_str = fread($fd, $length); + fclose($fd); + + } else { + $this->error = 2; // File doesn't exist + return false; + } + } +}; + + +?> diff --git a/lib/identicon.php b/lib/identicon.php new file mode 100644 index 0000000..b7a113e --- /dev/null +++ b/lib/identicon.php @@ -0,0 +1,446 @@ + + +And that's all there is to it! + +If you're not satisfied with the millions of image variations that the +program generates, additional variation in final image can be done by +image rotation. Simply uncomment the following line in the program: +// $identicon=imagerotate($identicon,$angle,$bg); + +* DONATIONS + +If you find this program useful, please pass it along to your friends +or donate any amount you feel will motivate the contributor/s to make +the program better. Send your donations by following this link: + +https://sourceforge.net/donate/index.php?group_id=271757#blurb + +*/ + +/* generate sprite for corners and sides */ +function getsprite($shape,$R,$G,$B,$rotation) { + global $spriteZ; + $sprite=imagecreatetruecolor($spriteZ,$spriteZ); + imageantialias($sprite,TRUE); + $fg=imagecolorallocate($sprite,$R,$G,$B); + $bg=imagecolorallocate($sprite,255,255,255); + imagefilledrectangle($sprite,0,0,$spriteZ,$spriteZ,$bg); + switch($shape) { + case 0: // triangle + $shape=array( + 0.5,1, + 1,0, + 1,1 + ); + break; + case 1: // parallelogram + $shape=array( + 0.5,0, + 1,0, + 0.5,1, + 0,1 + ); + break; + case 2: // mouse ears + $shape=array( + 0.5,0, + 1,0, + 1,1, + 0.5,1, + 1,0.5 + ); + break; + case 3: // ribbon + $shape=array( + 0,0.5, + 0.5,0, + 1,0.5, + 0.5,1, + 0.5,0.5 + ); + break; + case 4: // sails + $shape=array( + 0,0.5, + 1,0, + 1,1, + 0,1, + 1,0.5 + ); + break; + case 5: // fins + $shape=array( + 1,0, + 1,1, + 0.5,1, + 1,0.5, + 0.5,0.5 + ); + break; + case 6: // beak + $shape=array( + 0,0, + 1,0, + 1,0.5, + 0,0, + 0.5,1, + 0,1 + ); + break; + case 7: // chevron + $shape=array( + 0,0, + 0.5,0, + 1,0.5, + 0.5,1, + 0,1, + 0.5,0.5 + ); + break; + case 8: // fish + $shape=array( + 0.5,0, + 0.5,0.5, + 1,0.5, + 1,1, + 0.5,1, + 0.5,0.5, + 0,0.5 + ); + break; + case 9: // kite + $shape=array( + 0,0, + 1,0, + 0.5,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ); + break; + case 10: // trough + $shape=array( + 0,0.5, + 0.5,1, + 1,0.5, + 0.5,0, + 1,0, + 1,1, + 0,1 + ); + break; + case 11: // rays + $shape=array( + 0.5,0, + 1,0, + 1,1, + 0.5,1, + 1,0.75, + 0.5,0.5, + 1,0.25 + ); + break; + case 12: // double rhombus + $shape=array( + 0,0.5, + 0.5,0, + 0.5,0.5, + 1,0, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ); + break; + case 13: // crown + $shape=array( + 0,0, + 1,0, + 1,1, + 0,1, + 1,0.5, + 0.5,0.25, + 0.5,0.75, + 0,0.5, + 0.5,0.25 + ); + break; + case 14: // radioactive + $shape=array( + 0,0.5, + 0.5,0.5, + 0.5,0, + 1,0, + 0.5,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ); + break; + default: // tiles + $shape=array( + 0,0, + 1,0, + 0.5,0.5, + 0.5,0, + 0,0.5, + 1,0.5, + 0.5,1, + 0.5,0.5, + 0,1 + ); + break; + } + /* apply ratios */ + for ($i=0;$i0 && (abs($fR-$bR)>127 || abs($fG-$bG)>127 || abs($fB-$bB)>127)) + $bg=imagecolorallocate($sprite,$bR,$bG,$bB); + else + $bg=imagecolorallocate($sprite,255,255,255); + imagefilledrectangle($sprite,0,0,$spriteZ,$spriteZ,$bg); + switch($shape) { + case 0: // empty + $shape=array(); + break; + case 1: // fill + $shape=array( + 0,0, + 1,0, + 1,1, + 0,1 + ); + break; + case 2: // diamond + $shape=array( + 0.5,0, + 1,0.5, + 0.5,1, + 0,0.5 + ); + break; + case 3: // reverse diamond + $shape=array( + 0,0, + 1,0, + 1,1, + 0,1, + 0,0.5, + 0.5,1, + 1,0.5, + 0.5,0, + 0,0.5 + ); + break; + case 4: // cross + $shape=array( + 0.25,0, + 0.75,0, + 0.5,0.5, + 1,0.25, + 1,0.75, + 0.5,0.5, + 0.75,1, + 0.25,1, + 0.5,0.5, + 0,0.75, + 0,0.25, + 0.5,0.5 + ); + break; + case 5: // morning star + $shape=array( + 0,0, + 0.5,0.25, + 1,0, + 0.75,0.5, + 1,1, + 0.5,0.75, + 0,1, + 0.25,0.5 + ); + break; + case 6: // small square + $shape=array( + 0.33,0.33, + 0.67,0.33, + 0.67,0.67, + 0.33,0.67 + ); + break; + case 7: // checkerboard + $shape=array( + 0,0, + 0.33,0, + 0.33,0.33, + 0.66,0.33, + 0.67,0, + 1,0, + 1,0.33, + 0.67,0.33, + 0.67,0.67, + 1,0.67, + 1,1, + 0.67,1, + 0.67,0.67, + 0.33,0.67, + 0.33,1, + 0,1, + 0,0.67, + 0.33,0.67, + 0.33,0.33, + 0,0.33 + ); + break; + } + /* apply ratios */ + for ($i=0;$i0) + imagefilledpolygon($sprite,$shape,count($shape)/2,$fg); + return $sprite; +} + +/* parse hash string */ + +$csh=hexdec(substr($_GET["hash"],0,1)); // corner sprite shape +$ssh=hexdec(substr($_GET["hash"],1,1)); // side sprite shape +$xsh=hexdec(substr($_GET["hash"],2,1))&7; // center sprite shape + +$cro=hexdec(substr($_GET["hash"],3,1))&3; // corner sprite rotation +$sro=hexdec(substr($_GET["hash"],4,1))&3; // side sprite rotation +$xbg=hexdec(substr($_GET["hash"],5,1))%2; // center sprite background + +/* corner sprite foreground color */ +$cfr=hexdec(substr($_GET["hash"],6,2)); +$cfg=hexdec(substr($_GET["hash"],8,2)); +$cfb=hexdec(substr($_GET["hash"],10,2)); + +/* side sprite foreground color */ +$sfr=hexdec(substr($_GET["hash"],12,2)); +$sfg=hexdec(substr($_GET["hash"],14,2)); +$sfb=hexdec(substr($_GET["hash"],16,2)); + +/* final angle of rotation */ +$angle=hexdec(substr($_GET["hash"],18,2)); + +/* size of each sprite */ +$spriteZ=128; + +/* start with blank 3x3 identicon */ +$identicon=imagecreatetruecolor($spriteZ*3,$spriteZ*3); +imageantialias($identicon,TRUE); + +/* assign white as background */ +$bg=imagecolorallocate($identicon,255,255,255); +imagefilledrectangle($identicon,0,0,$spriteZ,$spriteZ,$bg); + +/* generate corner sprites */ +$corner=getsprite($csh,$cfr,$cfg,$cfb,$cro); +imagecopy($identicon,$corner,0,0,0,0,$spriteZ,$spriteZ); +$corner=imagerotate($corner,90,$bg); +imagecopy($identicon,$corner,0,$spriteZ*2,0,0,$spriteZ,$spriteZ); +$corner=imagerotate($corner,90,$bg); +imagecopy($identicon,$corner,$spriteZ*2,$spriteZ*2,0,0,$spriteZ,$spriteZ); +$corner=imagerotate($corner,90,$bg); +imagecopy($identicon,$corner,$spriteZ*2,0,0,0,$spriteZ,$spriteZ); + +/* generate side sprites */ +$side=getsprite($ssh,$sfr,$sfg,$sfb,$sro); +imagecopy($identicon,$side,$spriteZ,0,0,0,$spriteZ,$spriteZ); +$side=imagerotate($side,90,$bg); +imagecopy($identicon,$side,0,$spriteZ,0,0,$spriteZ,$spriteZ); +$side=imagerotate($side,90,$bg); +imagecopy($identicon,$side,$spriteZ,$spriteZ*2,0,0,$spriteZ,$spriteZ); +$side=imagerotate($side,90,$bg); +imagecopy($identicon,$side,$spriteZ*2,$spriteZ,0,0,$spriteZ,$spriteZ); + +/* generate center sprite */ +$center=getcenter($xsh,$cfr,$cfg,$cfb,$sfr,$sfg,$sfb,$xbg); +imagecopy($identicon,$center,$spriteZ,$spriteZ,0,0,$spriteZ,$spriteZ); + +// $identicon=imagerotate($identicon,$angle,$bg); + +/* make white transparent */ +imagecolortransparent($identicon,$bg); + +/* create blank image according to specified dimensions */ +$resized=imagecreatetruecolor($_GET["size"],$_GET["size"]); +imageantialias($resized,TRUE); + +/* assign white as background */ +$bg=imagecolorallocate($resized,255,255,255); +imagefilledrectangle($resized,0,0,$_GET["size"],$_GET["size"],$bg); + +/* resize identicon according to specification */ +imagecopyresampled($resized,$identicon,0,0,(imagesx($identicon)-$spriteZ*3)/2,(imagesx($identicon)-$spriteZ*3)/2,$_GET["size"],$_GET["size"],$spriteZ*3,$spriteZ*3); + +/* make white transparent */ +imagecolortransparent($resized,$bg); + +/* and finally, send to standard output */ +header("Content-Type: image/png"); +imagepng($resized); + +?> diff --git a/lib/parsecsv.lib.php b/lib/parsecsv.lib.php new file mode 100644 index 0000000..4f1ba89 --- /dev/null +++ b/lib/parsecsv.lib.php @@ -0,0 +1,771 @@ +data); + ---------------- + # tab delimited, and encoding conversion + $csv = new parseCSV(); + $csv->encoding('UTF-16', 'UTF-8'); + $csv->delimiter = "\t"; + $csv->parse('data.tsv'); + print_r($csv->data); + ---------------- + # auto-detect delimiter character + $csv = new parseCSV(); + $csv->auto('data.csv'); + print_r($csv->data); + ---------------- + # modify data in a csv file + $csv = new parseCSV(); + $csv->sort_by = 'id'; + $csv->parse('data.csv'); + # "4" is the value of the "id" column of the CSV row + $csv->data[4] = array('firstname' => 'John', 'lastname' => 'Doe', 'email' => 'john@doe.com'); + $csv->save(); + ---------------- + # add row/entry to end of CSV file + # - only recommended when you know the extact sctructure of the file + $csv = new parseCSV(); + $csv->save('data.csv', array(array('1986', 'Home', 'Nowhere', '')), true); + ---------------- + # convert 2D array to csv data and send headers + # to browser to treat output as a file and download it + $csv = new parseCSV(); + $csv->output (true, 'movies.csv', $array); + ---------------- + + +*/ + + + /** + * Configuration + * - set these options with $object->var_name = 'value'; + */ + + # use first line/entry as field names + var $heading = true; + + # override field names + var $fields = array(); + + # sort entries by this field + var $sort_by = null; + var $sort_reverse = false; + + # sort behavior passed to ksort/krsort functions + # regular = SORT_REGULAR + # numeric = SORT_NUMERIC + # string = SORT_STRING + var $sort_type = null; + + # delimiter (comma) and enclosure (double quote) + var $delimiter = ','; + var $enclosure = '"'; + + # basic SQL-like conditions for row matching + var $conditions = null; + + # number of rows to ignore from beginning of data + var $offset = null; + + # limits the number of returned rows to specified amount + var $limit = null; + + # number of rows to analyze when attempting to auto-detect delimiter + var $auto_depth = 15; + + # characters to ignore when attempting to auto-detect delimiter + var $auto_non_chars = "a-zA-Z0-9\n\r"; + + # preferred delimiter characters, only used when all filtering method + # returns multiple possible delimiters (happens very rarely) + var $auto_preferred = ",;\t.:|"; + + # character encoding options + var $convert_encoding = false; + var $input_encoding = 'ISO-8859-1'; + var $output_encoding = 'ISO-8859-1'; + + # used by unparse(), save(), and output() functions + var $linefeed = "\r\n"; + + # only used by output() function + var $output_delimiter = ','; + var $output_filename = 'data.csv'; + + # keep raw file data in memory after successful parsing (useful for debugging) + var $keep_file_data = false; + + /** + * Internal variables + */ + + # current file + var $file; + + # loaded file contents + var $file_data; + + # error while parsing input data + # 0 = No errors found. Everything should be fine :) + # 1 = Hopefully correctable syntax error was found. + # 2 = Enclosure character (double quote by default) + # was found in non-enclosed field. This means + # the file is either corrupt, or does not + # standard CSV formatting. Please validate + # the parsed data yourself. + var $error = 0; + + # detailed error info + var $error_info = array(); + + # array of field values in data parsed + var $titles = array(); + + # two dimentional array of CSV data + var $data = array(); + + + /** + * Constructor + * @param input CSV file or string + * @return nothing + */ + function parseCSV ($input = null, $offset = null, $limit = null, $conditions = null) { + if ( $offset !== null ) $this->offset = $offset; + if ( $limit !== null ) $this->limit = $limit; + if ( count($conditions) > 0 ) $this->conditions = $conditions; + if ( !empty($input) ) $this->parse($input); + } + + + // ============================================== + // ----- [ Main Functions ] --------------------- + // ============================================== + + /** + * Parse CSV file or string + * @param input CSV file or string + * @return nothing + */ + function parse ($input = null, $offset = null, $limit = null, $conditions = null) { + if ( $input === null ) $input = $this->file; + if ( !empty($input) ) { + if ( $offset !== null ) $this->offset = $offset; + if ( $limit !== null ) $this->limit = $limit; + if ( count($conditions) > 0 ) $this->conditions = $conditions; + if ( is_readable($input) ) { + $this->data = $this->parse_file($input); + } else { + $this->file_data = &$input; + $this->data = $this->parse_string(); + } + if ( $this->data === false ) return false; + } + return true; + } + + /** + * Save changes, or new file and/or data + * @param file file to save to + * @param data 2D array with data + * @param append append current data to end of target CSV if exists + * @param fields field names + * @return true or false + */ + function save ($file = null, $data = array(), $append = false, $fields = array()) { + if ( empty($file) ) $file = &$this->file; + $mode = ( $append ) ? 'at' : 'wt' ; + $is_php = ( preg_match('/\.php$/i', $file) ) ? true : false ; + return $this->_wfile($file, $this->unparse($data, $fields, $append, $is_php), $mode); + } + + /** + * Generate CSV based string for output + * @param filename if specified, headers and data will be output directly to browser as a downloable file + * @param data 2D array with data + * @param fields field names + * @param delimiter delimiter used to separate data + * @return CSV data using delimiter of choice, or default + */ + function output ($filename = null, $data = array(), $fields = array(), $delimiter = null) { + if ( empty($filename) ) $filename = $this->output_filename; + if ( $delimiter === null ) $delimiter = $this->output_delimiter; + $data = $this->unparse($data, $fields, null, null, $delimiter); + if ( $filename !== null ) { + header('Content-type: application/csv'); + header('Content-Disposition: attachment; filename="'.$filename.'"'); + echo $data; + } + return $data; + } + + /** + * Convert character encoding + * @param input input character encoding, uses default if left blank + * @param output output character encoding, uses default if left blank + * @return nothing + */ + function encoding ($input = null, $output = null) { + $this->convert_encoding = true; + if ( $input !== null ) $this->input_encoding = $input; + if ( $output !== null ) $this->output_encoding = $output; + } + + /** + * Auto-Detect Delimiter: Find delimiter by analyzing a specific number of + * rows to determine most probable delimiter character + * @param file local CSV file + * @param parse true/false parse file directly + * @param search_depth number of rows to analyze + * @param preferred preferred delimiter characters + * @param enclosure enclosure character, default is double quote ("). + * @return delimiter character + */ + function auto ($file = null, $parse = true, $search_depth = null, $preferred = null, $enclosure = null) { + + if ( $file === null ) $file = $this->file; + if ( empty($search_depth) ) $search_depth = $this->auto_depth; + if ( $enclosure === null ) $enclosure = $this->enclosure; + + if ( $preferred === null ) $preferred = $this->auto_preferred; + + if ( empty($this->file_data) ) { + if ( $this->_check_data($file) ) { + $data = &$this->file_data; + } else return false; + } else { + $data = &$this->file_data; + } + + $chars = array(); + $strlen = strlen($data); + $enclosed = false; + $n = 1; + $to_end = true; + + // walk specific depth finding posssible delimiter characters + for ( $i=0; $i < $strlen; $i++ ) { + $ch = $data{$i}; + $nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ; + $pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ; + + // open and closing quotes + if ( $ch == $enclosure ) { + if ( !$enclosed || $nch != $enclosure ) { + $enclosed = ( $enclosed ) ? false : true ; + } elseif ( $enclosed ) { + $i++; + } + + // end of row + } elseif ( ($ch == "\n" && $pch != "\r" || $ch == "\r") && !$enclosed ) { + if ( $n >= $search_depth ) { + $strlen = 0; + $to_end = false; + } else { + $n++; + } + + // count character + } elseif (!$enclosed) { + if ( !preg_match('/['.preg_quote($this->auto_non_chars, '/').']/i', $ch) ) { + if ( !isset($chars[$ch][$n]) ) { + $chars[$ch][$n] = 1; + } else { + $chars[$ch][$n]++; + } + } + } + } + + // filtering + $depth = ( $to_end ) ? $n-1 : $n ; + $filtered = array(); + foreach( $chars as $char => $value ) { + if ( $match = $this->_check_count($char, $value, $depth, $preferred) ) { + $filtered[$match] = $char; + } + } + + // capture most probable delimiter + ksort($filtered); + $this->delimiter = reset($filtered); + + // parse data + if ( $parse ) $this->data = $this->parse_string(); + + return $this->delimiter; + + } + + + // ============================================== + // ----- [ Core Functions ] --------------------- + // ============================================== + + /** + * Read file to string and call parse_string() + * @param file local CSV file + * @return 2D array with CSV data, or false on failure + */ + function parse_file ($file = null) { + if ( $file === null ) $file = $this->file; + if ( empty($this->file_data) ) $this->load_data($file); + return ( !empty($this->file_data) ) ? $this->parse_string() : false ; + } + + /** + * Parse CSV strings to arrays + * @param data CSV string + * @return 2D array with CSV data, or false on failure + */ + function parse_string ($data = null) { + if ( empty($data) ) { + if ( $this->_check_data() ) { + $data = &$this->file_data; + } else return false; + } + + $white_spaces = str_replace($this->delimiter, '', " \t\x0B\0"); + + $rows = array(); + $row = array(); + $row_count = 0; + $current = ''; + $head = ( !empty($this->fields) ) ? $this->fields : array() ; + $col = 0; + $enclosed = false; + $was_enclosed = false; + $strlen = strlen($data); + + // walk through each character + for ( $i=0; $i < $strlen; $i++ ) { + $ch = $data{$i}; + $nch = ( isset($data{$i+1}) ) ? $data{$i+1} : false ; + $pch = ( isset($data{$i-1}) ) ? $data{$i-1} : false ; + + // open/close quotes, and inline quotes + if ( $ch == $this->enclosure ) { + if ( !$enclosed ) { + if ( ltrim($current, $white_spaces) == '' ) { + $enclosed = true; + $was_enclosed = true; + } else { + $this->error = 2; + $error_row = count($rows) + 1; + $error_col = $col + 1; + if ( !isset($this->error_info[$error_row.'-'.$error_col]) ) { + $this->error_info[$error_row.'-'.$error_col] = array( + 'type' => 2, + 'info' => 'Syntax error found on row '.$error_row.'. Non-enclosed fields can not contain double-quotes.', + 'row' => $error_row, + 'field' => $error_col, + 'field_name' => (!empty($head[$col])) ? $head[$col] : null, + ); + } + $current .= $ch; + } + } elseif ($nch == $this->enclosure) { + $current .= $ch; + $i++; + } elseif ( $nch != $this->delimiter && $nch != "\r" && $nch != "\n" ) { + for ( $x=($i+1); isset($data{$x}) && ltrim($data{$x}, $white_spaces) == ''; $x++ ) {} + if ( $data{$x} == $this->delimiter ) { + $enclosed = false; + $i = $x; + } else { + if ( $this->error < 1 ) { + $this->error = 1; + } + $error_row = count($rows) + 1; + $error_col = $col + 1; + if ( !isset($this->error_info[$error_row.'-'.$error_col]) ) { + $this->error_info[$error_row.'-'.$error_col] = array( + 'type' => 1, + 'info' => + 'Syntax error found on row '.(count($rows) + 1).'. '. + 'A single double-quote was found within an enclosed string. '. + 'Enclosed double-quotes must be escaped with a second double-quote.', + 'row' => count($rows) + 1, + 'field' => $col + 1, + 'field_name' => (!empty($head[$col])) ? $head[$col] : null, + ); + } + $current .= $ch; + $enclosed = false; + } + } else { + $enclosed = false; + } + + // end of field/row + } elseif ( ($ch == $this->delimiter || $ch == "\n" || $ch == "\r") && !$enclosed ) { + $key = ( !empty($head[$col]) ) ? $head[$col] : $col ; + $row[$key] = ( $was_enclosed ) ? $current : trim($current) ; + $current = ''; + $was_enclosed = false; + $col++; + + // end of row + if ( $ch == "\n" || $ch == "\r" ) { + if ( $this->_validate_offset($row_count) && $this->_validate_row_conditions($row, $this->conditions) ) { + if ( $this->heading && empty($head) ) { + $head = $row; + } elseif ( empty($this->fields) || (!empty($this->fields) && (($this->heading && $row_count > 0) || !$this->heading)) ) { + if ( !empty($this->sort_by) && !empty($row[$this->sort_by]) ) { + if ( isset($rows[$row[$this->sort_by]]) ) { + $rows[$row[$this->sort_by].'_0'] = &$rows[$row[$this->sort_by]]; + unset($rows[$row[$this->sort_by]]); + for ( $sn=1; isset($rows[$row[$this->sort_by].'_'.$sn]); $sn++ ) {} + $rows[$row[$this->sort_by].'_'.$sn] = $row; + } else $rows[$row[$this->sort_by]] = $row; + } else $rows[] = $row; + } + } + $row = array(); + $col = 0; + $row_count++; + if ( $this->sort_by === null && $this->limit !== null && count($rows) == $this->limit ) { + $i = $strlen; + } + if ( $ch == "\r" && $nch == "\n" ) $i++; + } + + // append character to current field + } else { + $current .= $ch; + } + } + $this->titles = $head; + if ( !empty($this->sort_by) ) { + $sort_type = SORT_REGULAR; + if ( $this->sort_type == 'numeric' ) { + $sort_type = SORT_NUMERIC; + } elseif ( $this->sort_type == 'string' ) { + $sort_type = SORT_STRING; + } + ( $this->sort_reverse ) ? krsort($rows, $sort_type) : ksort($rows, $sort_type) ; + if ( $this->offset !== null || $this->limit !== null ) { + $rows = array_slice($rows, ($this->offset === null ? 0 : $this->offset) , $this->limit, true); + } + } + if ( !$this->keep_file_data ) { + $this->file_data = null; + } + return $rows; + } + + /** + * Create CSV data from array + * @param data 2D array with data + * @param fields field names + * @param append if true, field names will not be output + * @param is_php if a php die() call should be put on the first + * line of the file, this is later ignored when read. + * @param delimiter field delimiter to use + * @return CSV data (text string) + */ + function unparse ( $data = array(), $fields = array(), $append = false , $is_php = false, $delimiter = null) { + if ( !is_array($data) || empty($data) ) $data = &$this->data; + if ( !is_array($fields) || empty($fields) ) $fields = &$this->titles; + if ( $delimiter === null ) $delimiter = $this->delimiter; + + $string = ( $is_php ) ? "".$this->linefeed : '' ; + $entry = array(); + + // create heading + if ( $this->heading && !$append && !empty($fields) ) { + foreach( $fields as $key => $value ) { + $entry[] = $this->_enclose_value($value); + } + $string .= implode($delimiter, $entry).$this->linefeed; + $entry = array(); + } + + // create data + foreach( $data as $key => $row ) { + foreach( $row as $field => $value ) { + $entry[] = $this->_enclose_value($value); + } + $string .= implode($delimiter, $entry).$this->linefeed; + $entry = array(); + } + + return $string; + } + + /** + * Load local file or string + * @param input local CSV file + * @return true or false + */ + function load_data ($input = null) { + $data = null; + $file = null; + if ( $input === null ) { + $file = $this->file; + } elseif ( file_exists($input) ) { + $file = $input; + } else { + $data = $input; + } + if ( !empty($data) || $data = $this->_rfile($file) ) { + if ( $this->file != $file ) $this->file = $file; + if ( preg_match('/\.php$/i', $file) && preg_match('/<\?.*?\?>(.*)/ims', $data, $strip) ) { + $data = ltrim($strip[1]); + } + if ( $this->convert_encoding ) $data = iconv($this->input_encoding, $this->output_encoding, $data); + if ( substr($data, -1) != "\n" ) $data .= "\n"; + $this->file_data = &$data; + return true; + } + return false; + } + + + // ============================================== + // ----- [ Internal Functions ] ----------------- + // ============================================== + + /** + * Validate a row against specified conditions + * @param row array with values from a row + * @param conditions specified conditions that the row must match + * @return true of false + */ + function _validate_row_conditions ($row = array(), $conditions = null) { + if ( !empty($row) ) { + if ( !empty($conditions) ) { + $conditions = (strpos($conditions, ' OR ') !== false) ? explode(' OR ', $conditions) : array($conditions) ; + $or = ''; + foreach( $conditions as $key => $value ) { + if ( strpos($value, ' AND ') !== false ) { + $value = explode(' AND ', $value); + $and = ''; + foreach( $value as $k => $v ) { + $and .= $this->_validate_row_condition($row, $v); + } + $or .= (strpos($and, '0') !== false) ? '0' : '1' ; + } else { + $or .= $this->_validate_row_condition($row, $value); + } + } + return (strpos($or, '1') !== false) ? true : false ; + } + return true; + } + return false; + } + + /** + * Validate a row against a single condition + * @param row array with values from a row + * @param condition specified condition that the row must match + * @return true of false + */ + function _validate_row_condition ($row, $condition) { + $operators = array( + '=', 'equals', 'is', + '!=', 'is not', + '<', 'is less than', + '>', 'is greater than', + '<=', 'is less than or equals', + '>=', 'is greater than or equals', + 'contains', + 'does not contain', + ); + $operators_regex = array(); + foreach( $operators as $value ) { + $operators_regex[] = preg_quote($value, '/'); + } + $operators_regex = implode('|', $operators_regex); + if ( preg_match('/^(.+) ('.$operators_regex.') (.+)$/i', trim($condition), $capture) ) { + $field = $capture[1]; + $op = $capture[2]; + $value = $capture[3]; + if ( preg_match('/^([\'\"]{1})(.*)([\'\"]{1})$/i', $value, $capture) ) { + if ( $capture[1] == $capture[3] ) { + $value = $capture[2]; + $value = str_replace("\\n", "\n", $value); + $value = str_replace("\\r", "\r", $value); + $value = str_replace("\\t", "\t", $value); + $value = stripslashes($value); + } + } + if ( array_key_exists($field, $row) ) { + if ( ($op == '=' || $op == 'equals' || $op == 'is') && $row[$field] == $value ) { + return '1'; + } elseif ( ($op == '!=' || $op == 'is not') && $row[$field] != $value ) { + return '1'; + } elseif ( ($op == '<' || $op == 'is less than' ) && $row[$field] < $value ) { + return '1'; + } elseif ( ($op == '>' || $op == 'is greater than') && $row[$field] > $value ) { + return '1'; + } elseif ( ($op == '<=' || $op == 'is less than or equals' ) && $row[$field] <= $value ) { + return '1'; + } elseif ( ($op == '>=' || $op == 'is greater than or equals') && $row[$field] >= $value ) { + return '1'; + } elseif ( $op == 'contains' && preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) { + return '1'; + } elseif ( $op == 'does not contain' && !preg_match('/'.preg_quote($value, '/').'/i', $row[$field]) ) { + return '1'; + } else { + return '0'; + } + } + } + return '1'; + } + + /** + * Validates if the row is within the offset or not if sorting is disabled + * @param current_row the current row number being processed + * @return true of false + */ + function _validate_offset ($current_row) { + if ( $this->sort_by === null && $this->offset !== null && $current_row < $this->offset ) return false; + return true; + } + + /** + * Enclose values if needed + * - only used by unparse() + * @param value string to process + * @return Processed value + */ + function _enclose_value ($value = null) { + if ( $value !== null && $value != '' ) { + $delimiter = preg_quote($this->delimiter, '/'); + $enclosure = preg_quote($this->enclosure, '/'); + if ( preg_match("/".$delimiter."|".$enclosure."|\n|\r/i", $value) || ($value{0} == ' ' || substr($value, -1) == ' ') ) { + $value = str_replace($this->enclosure, $this->enclosure.$this->enclosure, $value); + $value = $this->enclosure.$value.$this->enclosure; + } + } + return $value; + } + + /** + * Check file data + * @param file local filename + * @return true or false + */ + function _check_data ($file = null) { + if ( empty($this->file_data) ) { + if ( $file === null ) $file = $this->file; + return $this->load_data($file); + } + return true; + } + + + /** + * Check if passed info might be delimiter + * - only used by find_delimiter() + * @return special string used for delimiter selection, or false + */ + function _check_count ($char, $array, $depth, $preferred) { + if ( $depth == count($array) ) { + $first = null; + $equal = null; + $almost = false; + foreach( $array as $key => $value ) { + if ( $first == null ) { + $first = $value; + } elseif ( $value == $first && $equal !== false) { + $equal = true; + } elseif ( $value == $first+1 && $equal !== false ) { + $equal = true; + $almost = true; + } else { + $equal = false; + } + } + if ( $equal ) { + $match = ( $almost ) ? 2 : 1 ; + $pref = strpos($preferred, $char); + $pref = ( $pref !== false ) ? str_pad($pref, 3, '0', STR_PAD_LEFT) : '999' ; + return $pref.$match.'.'.(99999 - str_pad($first, 5, '0', STR_PAD_LEFT)); + } else return false; + } + } + + /** + * Read local file + * @param file local filename + * @return Data from file, or false on failure + */ + function _rfile ($file = null) { + if ( is_readable($file) ) { + if ( !($fh = fopen($file, 'r')) ) return false; + $data = fread($fh, filesize($file)); + fclose($fh); + return $data; + } + return false; + } + + /** + * Write to local file + * @param file local filename + * @param string data to write to file + * @param mode fopen() mode + * @param lock flock() mode + * @return true or false + */ + function _wfile ($file, $string = '', $mode = 'wb', $lock = 2) { + if ( $fp = fopen($file, $mode) ) { + flock($fp, $lock); + $re = fwrite($fp, $string); + $re2 = fclose($fp); + if ( $re != false && $re2 != false ) return true; + } + return false; + } + +} + +?> \ No newline at end of file diff --git a/map.php b/map.php new file mode 100644 index 0000000..a213bbb --- /dev/null +++ b/map.php @@ -0,0 +1,171 @@ + +<?php echo ucfmsg("ADDRESS_BOOK").($group_name != "" ? " ($group_name)":""); ?> + $key) { + if(str_replace($domain,"",$_SERVER['SERVER_NAME']).$domain == $_SERVER['SERVER_NAME']) { + $google_maps_key = $key; + } + } + } + } + + $delay = 200000; // usecs before each fetching + $base_url = "http://maps.google.ch/maps/geo?output=csv&key=".$google_maps_key; + $first_fetch = true; + + $cache_write = true; + $has_620 = false; + $single_address = false; + + $addresses = Addresses::withSearchString($searchstring, $alphabet); + $result = $addresses->getResults(); + $coords = array(); + + // foreach($addresses as $address) { + while($myrow = mysqli_fetch_array($result)) { + + $coord['addr'] = trim(str_replace("\n", ", ", trim($myrow['address'])),","); + $coord['html'] = "".$myrow['firstname'].(isset($myrow['middlename']) ? " ".$myrow['middlename'] : "")." ".$myrow['lastname']."
"; + $coord['html'] .= ($myrow['company'] != "" ? "".$myrow['company']."
" : ""); + $coord['html'] .= str_replace("\n","",str_replace("\r","",nl2br($myrow['address']))); + $coord['id'] = $myrow['id']; + $coord['long'] = $myrow['addr_long']; + $coord['lati'] = $myrow['addr_lat']; + $coord['status'] = $myrow['addr_status']; + + // + // Geo-code if long/lat is not yet defined + // + if(!($coord['status'] == 200 || $coord['status'] == 602 )) { + + $request_url = $base_url . "&q=" . urlencode($coord['addr']); + if($first_fetch) usleep($delay); + $first_fetch = false; + $csv = file_get_contents($request_url) or die("url not loading"); + + $csvSplit = explode(",", $csv); + + // http://code.google.com/intl/de-DE/apis/maps/documentation/javascript/v2/reference.html#GGeoStatusCode + $coord['status'] = $csvSplit[0]; + if($coord['status'] == 200) { + $coord['lati'] = $csvSplit[2]; + $coord['long'] = $csvSplit[3]; + + $sql = "UPDATE $table + SET addr_long = '".$coord['long']."' + , addr_lat = '".$coord['lati']."' + , addr_status = '".$coord['status']."' + WHERE id = '".$myrow['id']."' + AND domain_id = '$domain_id' + AND deprecated is null;"; + $upd_result = mysqli_query($db,$sql); + } else { + $sql = "UPDATE $table + SET addr_status = '".$coord['status']."' + WHERE id = '".$myrow['id']."' + AND domain_id = '$domain_id' + AND deprecated is null;"; + $upd_result = mysqli_query($db,$sql); + } + } + $coords[] = $coord; + } + if($single_address) { + $coords = array(); + $coords[] = $single_coord; + } + + // + // Concat multiple entries on one place: + // * Sort places + // * Concat content + // + $longs = array(); + $latis = array(); + foreach ($coords as $key => $coord) { + $longs[$key] = $coord['long']; + $latis[$key] = $coord['lati']; + + $coords[$key]['bubble'] = $coord['html']."
"; + $coords[$key]['bubble'] .= "...".msg('MORE').""; + } + array_multisort($longs, SORT_ASC, $latis, SORT_ASC, $coords); + // print_r($coords); + + ?> + + + + + +

+
+ + \ No newline at end of file diff --git a/notes.htm b/notes.htm new file mode 100644 index 0000000..a7c5e28 --- /dev/null +++ b/notes.htm @@ -0,0 +1,95 @@ + + + + + + + PHP Address Book v8.x + + + + + +

Donations:

+

The software is free, anyone who enjoys it a lot, is welcome to send me some mental support over PayPal. I will use it as wisely as possible.

+ + Donate to this Project + + +

PHP-addressbook Version 8.x

+requirements | installation | licence | disclaimer + +

Download:

+

For the most recent version of "PHP-addressbook" see: https://sourceforge.net/project/showfiles.php?group_id=157964.

+ +

Requirements:

+

You will need a webserver which has installed PHP 4/5, and mySQL version 3.2 upwards.

+ +

Installation:

+

1. Unzip the files and place them on your webserver (or locally, as you will have to make some changes and then upload) in a directory called addressbook or whatever.

+

2. Open config.php and change the variables to your server specification.

+

3. You need to create the table addressbook in your mySQL database. (You can alter the table name, but you need to change the entry in config.php, and the table name in addressbook.sql.) Either via telnet, or using a program such as mySQLfront, or phpMyAdmin enter the information stored in addressbook.sql. You can create a separate database for the addressbook, it`s up to you.

+

That`s it!

+ +

User Features

+- Address and birthday management.
+- Address grouping and mailing.
+- Phonebook printing.
+- Birthday reminder list.
+- vCard an csv export capabilities.
+- Gmail, Yahoo and Hotmail integrations.
+- Many translations.
+- Google map integration.
+- User management in database.
+- iPhone + Android connection support (beta, on deman). + +

Technical Features

+- Full UTF-8 support.
+- Browser language detection.
+- Fast AJAX-Search.
+- Page compression for bandwidth saving. + +

Intro:

+

This is an address book program for people who have their own web space. At present it is does not have a multiple user function. I wrote it because I wanted a place where I could store all my addresses so I can access them from multiple locations, and its a handy backup if you lose your address book, and there weren`t any freeware programs that suited my needs. IT IS NOT SUITABLE FOR COMMERCIAL USE. If you want to use this program on an intranet, please email rob@widgetmonkey.com . I am working on additions, such as multiple log in. I might also expand it to become a calendar / organiser. We`ll see.

+

Optional extras:

+

You might want to password protect the addressbook - not everyone wants to have their details freely available on the web! It is HIGHLY recommended that you do. Password proctection is best done with .htaccess and .htpassword files. There are tutorials out there to help.

+

You can move the config.php file to another directory if you want to make it safer - just remember to alter the include( ) statement in dbconnect.php.

+

Any questions: Submit your feedback at sourceforge.net +or send me an e-mail chatalao{a}users.sourceforge.net

+ +

Licence:

+

This addressbook is free under the AGPL license.

+

For details about the "famfamfam" icons, please look here: famfamfam.

+For details about the "Table Sort Script", please look here: Table Sort Script. + +

Disclaimer:

+

Any use or modifications of this program is at the user`s risk. Any distribution or malicious use of address details, email addresses or telephone numbers is not the responsibility of the author. There may be several unknown securities-leaks, strong protection of the website and data is no part of this package. This is under the sole reposibility of the user.

+ +

Donations:

+

The software is free, anyone who enjoys it a lot, is welcome to send me some mental support over PayPal. I will use it as wisely as possible.

+ + Donate to this Project + + +
+
+
+ If you like the script, please rate it! + +
+ + +
+
+ +

Just enjoy "PHP address book"!

+
+
Last Update: 16. June 2010 - chatelao
+ + diff --git a/photo.php b/photo.php new file mode 100644 index 0000000..d183958 --- /dev/null +++ b/photo.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/preferences.php b/preferences.php new file mode 100644 index 0000000..af6eb1b --- /dev/null +++ b/preferences.php @@ -0,0 +1,39 @@ + +Preferences | <?php echo ucfmsg("ADDRESS_BOOK").($group_name != "" ? " ($group_name)":""); ?> + + +

+
+ + + + " /> +
+
+
+ + + + + + +
+ + diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..5b9850e --- /dev/null +++ b/readme.txt @@ -0,0 +1,40 @@ +PHP Address Book +================ + +Read "_USER_GUIDE.pdf" for further informations about: +* Installations +* Upgrades +* User setup +* Smart Phone integration +* Export / Import +* Language support +* ... and much more + +ENJOY !! + + +(c)AGPL by chatelao 2007-2012 + +Silk icon set 1.3 + +_________________________________________ +Mark James +http://www.famfamfam.com/lab/icons/silk/ +http://www.famfamfam.com/lab/icons/flags/ +_________________________________________ + +This work is licensed under a +Creative Commons Attribution 2.5 License. +[ http://creativecommons.org/licenses/by/2.5/ ] + +This means you may use it for any purpose, +and make any changes you like. +All I ask is that you include a link back +to this page in your credits. + +Are you using this icon set? Send me an email +(including a link or picture if available) to +mjames@gmail.com + +Any other questions about this icon set please +contact mjames@gmail.com \ No newline at end of file diff --git a/register/admin_index.php b/register/admin_index.php new file mode 100644 index 0000000..d51e3db --- /dev/null +++ b/register/admin_index.php @@ -0,0 +1,109 @@ + + + + + + +AMS Agent Index + + + + + + + +
+ Admin Index
+
+
+
+
+

+ + | Traffic Report

+
+ + + + +
+ +   + +
+

+ + "; + + +$query = "SELECT * FROM users WHERE (`id` LIKE \"%$var%\" OR `username` LIKE \"%$var%\" OR `password` LIKE \"%$var%\" OR `email` LIKE \"%$var%\" OR `lastname`LIKE \"%$var%\" OR `firstname`LIKE \"%$var%\") ORDER BY `id` desc"; + +$numresults=mysqli_query($db, $query); +$numrows=mysqli_num_rows($numresults); + +// get results +$result = mysqli_query($query) or die("Couldn't execute query"); + +// now you can display the results returned +while ($row= mysqli_fetch_array($result)) { + +$id= $row["id"]; +$username= $row["username"]; +$password= $row["password"]; +$lastname= $row["lastname"]; +$firstname= $row["firstname"]; +$phone= $row["phone"]; +$email= $row["email"]; +$permissions = $row["permissions"]; +$email_sub = substr($email, 0, 50); + + + $row_color = ($row_count % 2) ? $color1 : $color2; +//DISPLAY DATA HERE_____________ + +echo " + + + + "; + $row_count++; +} + + +echo""; + + + ?> +

+

 

+ +

+ + +
+ + + + + + + + +
$username$lastname, $firstname$phone$email_sub$permissionsedit
+
+
+
+
+ + diff --git a/register/auth_check_header.php b/register/auth_check_header.php new file mode 100644 index 0000000..21cdf6d --- /dev/null +++ b/register/auth_check_header.php @@ -0,0 +1,56 @@ +'$threshold'"; + +} + +$result=mysqli_query($db,$sql); + +// mysqli_num_row is counting table rows + +$count=mysqli_num_rows($result); + +// If result matches $myusername and $mypassword, table row must be 1 row + +if($count==0){ + +{ + +header("location:login.php"); + +} + +} + +$query = "SELECT * FROM users WHERE `username`='$username_from_cookie'"; + +$numresults=mysqli_query($db, $query); +$numrows=mysqli_num_rows($numresults); + +// get results +$result = mysqli_query($query) or die("Couldn't execute query"); + +// now you can display the results returned +while ($row= mysqli_fetch_array($result)) { + +$permissions= $row["permissions"]; + +} + +//end Chris Carr Auth Check Header + +$username = $username_from_cookie; + +?> \ No newline at end of file diff --git a/register/checklogin.php b/register/checklogin.php new file mode 100644 index 0000000..3eabf55 --- /dev/null +++ b/register/checklogin.php @@ -0,0 +1,64 @@ +"; + +//$sql="SELECT * FROM agents WHERE username='$urlun' and password='$urlpw'"; +$sql="SELECT * FROM users WHERE username='$urlun' and password='$cleanpw'"; + +$result=mysqli_query($db,$sql); + +// mysqli_num_row is counting table rows + +$count=mysqli_num_rows($result); + +// If result matches $myusername and $mypassword, table row must be 1 row + +//echo"Count:$count
"; + +if($count==1){ + +// Register $myusername and redirect to file designated success file + +$cookie_name ="$cookiename"; + +$cookie_value ="$urlun"; + +//set to 24 hours + +$cookie_expire ="86400"; + +setcookie($cookie_name,$cookie_value,time() + (86400),"/", $cookie_domain); + +header("location:$successful_login_url"); + +}else{ + +header("location:$failed_login"); + +} + + +?> + + + + + diff --git a/register/delete_this.php b/register/delete_this.php new file mode 100644 index 0000000..4561319 --- /dev/null +++ b/register/delete_this.php @@ -0,0 +1,69 @@ + + + + +Untitled Document + + + + + + + + + + + + + + + + + + + + + + +

readyclip.com logo + +
+ + diff --git a/register/delete_user.php b/register/delete_user.php new file mode 100644 index 0000000..2eaeaf0 --- /dev/null +++ b/register/delete_user.php @@ -0,0 +1,65 @@ + + + + + + +AMS Agent Index + + + + + + + +
+ Delete User
+
+
+
+
+

+

+ Successfully deleted the entry." ); +} +else +{ +die( "Error: Could not delete entry: " . mysqli_error() ); +} + + + + ?> +

+ + + + +
+ + +
+
+
+
+ + diff --git a/register/delete_user_confirm.php b/register/delete_user_confirm.php new file mode 100644 index 0000000..df0137f --- /dev/null +++ b/register/delete_user_confirm.php @@ -0,0 +1,51 @@ + + + + + + +AMS Agent Index + + + + + + + +
+ Delete User
+
+
+
+
+

+

+
Are you sure you want to delete user ID: $id?

No

Yes
"; + + + ?> +

+ + + + +
+ + +
+
+
+
+ + diff --git a/register/edit_user.php b/register/edit_user.php new file mode 100644 index 0000000..b1200e8 --- /dev/null +++ b/register/edit_user.php @@ -0,0 +1,150 @@ + + + + + + +AMS Agent Index + + + + + + + +
+ Edit User
+
+
+
+
+

+
+ + + + +
+

  + "; + + +$query = "SELECT * FROM users WHERE `id`='$id'"; + +$numresults=mysqli_query($query); +$numrows=mysqli_num_rows($numresults); + +// get results +$result = mysqli_query($query) or die("Couldn't execute query"); + +// now you can display the results returned +while ($row= mysqli_fetch_array($result)) { + +$id= $row["id"]; +$username= $row["username"]; +$password= $row["password"]; +$lastname= $row["lastname"]; +$firstname= $row["firstname"]; +$phone= $row["phone"]; +$notes= $row["notes"]; +$email= $row["email"]; +$permissions= $row["permissions"]; +$email_sub = substr($email, 0, 50); + +} + + +echo""; + + + ?> +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID +
Username +
Password +
Lastname + +
Firstname + +
Phone + +
Email + +
Permissions + +
Notes

+ +

+

+ +

+

 

+

Delete User

+
+

+ +

 

+

+ +

+

 

+

 

+
+

+
+
+
+
+ + diff --git a/register/edit_user_save.php b/register/edit_user_save.php new file mode 100644 index 0000000..616bc96 --- /dev/null +++ b/register/edit_user_save.php @@ -0,0 +1,80 @@ + + + + + + +AMS Agent Index + + + + + + + +
+ Edit User
+
+
+
+
+

Admin Index | Traffic Report| Logout

+ + + + + +
Your changes have been made sucessfully." ); +} +else +{ +die( "Trouble saving information to the database: " . mysqli_error() ); +} + + + + + + ?> +
+ + +
+
+
+
+ + diff --git a/register/email_password.php b/register/email_password.php new file mode 100644 index 0000000..9dcc9a8 --- /dev/null +++ b/register/email_password.php @@ -0,0 +1,54 @@ + + + + +Untitled Document + + + + + + + +
+ +
+ +
+ +
+
+ Reset Password Via Email

+
+
+
+
+
+
Email Address Associated with this account
+
+ + +
+
+

+ +

+

I remember now. Back to Login

+

+

 

+

 

+

+ +
+
+
+ +
+


+

+
+
+
+
+ + diff --git a/register/email_password_sender.php b/register/email_password_sender.php new file mode 100644 index 0000000..9bd66a1 --- /dev/null +++ b/register/email_password_sender.php @@ -0,0 +1,72 @@ +Sorry but we don't have that email in our system. Please try again. Thank you!"); + +}else{ + + //comes from config which pulls from /inc + $email_body = $forgot_password_email; +} + +$from = $from_email; +$reply_to = $reply_to_email; +$return_path = $return_path_email; + +$to = $email; + +$subject = $forgot_password_email_subject; + +//***attaches view tracker to link tracked code*** - CCC +$mailbody= "$email_body"; + +//____________________________Begin Multipart Mail Sender +//add From: header +$headers = "From:$from\nReply-to:$reply_to\nReturn-path:$return_path\nJobID:$date\n"; + +//specify MIME version 1.0 +$headers .= "MIME-Version: 1.0\n"; + +//unique boundary +$boundary = uniqid("HTMLDEMO8656856"); + +//tell e-mail client this e-mail contains//alternate versions +$headers.="X-Priority: 3\n"; +$headers.="Content-Type: multipart/alternative; boundary=\"".$boundary."\"\n"; +$headers.="Content-Transfer-Encoding: 7bit\n"; + +//message to people with clients who don't +//understand MIME +$headers .= "This is a MIME encoded message.\n\n"; + +//plain text version of message +$headers .= "--$boundary\n" . + "Content-Type: text/plain; charset=ISO-8859-1\r\n" . + "Content-Transfer-Encoding: base64\n\n"; +$headers .= chunk_split(base64_encode("$mailbody")); + +//HTML version of message +$headers .= "--$boundary\n" . + "Content-Type: text/html; charset=ISO-8859-1\n" . + "Content-Transfer-Encoding: base64\n\n"; +$headers .= chunk_split(base64_encode("$mailbody")); + +//send message + +If (mail("$to", "$subject", "", $headers)) +{ +echo"An account verification link has been sent to $email. This link will allow you to reset your password

Emails may take up to 10 minutes to arrive. Check your spam folder also and whitelist this site if you find our message there. Thanks! +Back to login
"; +} +?> diff --git a/register/email_sent.php b/register/email_sent.php new file mode 100644 index 0000000..a8fea5d --- /dev/null +++ b/register/email_sent.php @@ -0,0 +1,25 @@ +"; + +// If result matches $myusername and $mypassword, table row must be 1 row + +if($count==0){ + +{ + +echo"Sorry but we don't have that email in our system. Please try again. Thank you!" + +} + +?> diff --git a/register/header.html b/register/header.html new file mode 100644 index 0000000..4d04b2f --- /dev/null +++ b/register/header.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + Addressbuch + +
+ +
+ + +
\ No newline at end of file diff --git a/register/include_menu.php b/register/include_menu.php new file mode 100644 index 0000000..d6d1b4d --- /dev/null +++ b/register/include_menu.php @@ -0,0 +1 @@ +Admin Index | Logout diff --git a/register/index.html b/register/index.html new file mode 100644 index 0000000..052f5d6 --- /dev/null +++ b/register/index.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/register/linktick.php b/register/linktick.php new file mode 100644 index 0000000..917dfcb --- /dev/null +++ b/register/linktick.php @@ -0,0 +1,41 @@ + $max))) + return FALSE; + return $string; +} + + + $site = $_REQUEST['site']; + $site = sanitize_paranoid_string($site); + + //Time & Date + $date = date ('m/d/y g:i a'); + + //IP Address + $ip = $_SERVER['REMOTE_ADDR']; + + $type = "link"; + + $query = "INSERT INTO `traffic` (`date`,`ip`,`link`,`notes`,`site`,`type`) + VALUES ( '$date','$ip','$link','$notes','$site','$type')"; + + // save the info to the database + $results = mysqli_query( $query ); + +$url = str_replace("316AMPERSAND316","&","$link"); + +//echo "URL: $url"; + +header ("Location: $url"); + + +?> diff --git a/register/login.php b/register/login.php new file mode 100644 index 0000000..35fbd8f --- /dev/null +++ b/register/login.php @@ -0,0 +1,63 @@ + + + + +Untitled Document + + + + + + + +
+

+
+ +
+ +
+
+ BasicLogin

+
+
+
+
+
+
Username
+
+ + +
+
+
+
Password
+
+ + +
+
+
+ +

+ +

+

Create a new account

+

Forgot Password / Change Password

+

 

+

 

+

+ +
+
+
+ +
+


+

+
+
+
+
+ + diff --git a/register/login_config.php b/register/login_config.php new file mode 100644 index 0000000..ac8137e --- /dev/null +++ b/register/login_config.php @@ -0,0 +1,109 @@ +
Password:$password

"; + +if($password_hint !== '') { + +//**OPTION: YOU CAN EDIT THE COPY IN THESE EMAILS IF YOU WANT. THIS ONE IS FOR PEOPLE WHO HAVE A PASSWORD HINT + +$restore_link = "$domain//reset_password.php?email=$email&password=$password"; + +$forgot_password_email = "This is the password manager at $sitename. We do not store passwords...only encrypted data.

Here is a password hint that you provided when you set up your account: $password_hint

If that helps, then you can try again without resetting your password

+ +
If you still can't remember then please Click Here to reset your password. Clicking this link will assign an encrypted, temporary password for added security. The process can easily be repeated if you experience difficutlties.

Best Regards - The Database
"; +} +else +{ + +//**OPTION: THIS ONE IS FOR PEOPLE WHO DON'T HAVE A PASSWORD HINT + +$forgot_password_email = "This is the password manager at $sitename. We do not store passwords...only encrypted data.
+
Please Click Here to reset your password. Clicking this link will assign an encrypted, temporary password for added security. The process can easily be repeated if you experience difficutlties.

Best Regards - The Database
"; +} + +}} + +//**OPTION: THAT'S ALL FOR NOW. MORE OPTIONS MAY BE ADDED LATER. WE'LL LET YOU KNOW. OTHERWISE, FEEL FREE TO ADD YOUR OWN! +?> \ No newline at end of file diff --git a/register/login_failed.php b/register/login_failed.php new file mode 100644 index 0000000..efd0e1f --- /dev/null +++ b/register/login_failed.php @@ -0,0 +1,23 @@ + + + + +Untitled Document + + + + + + + +
+


+
+ Wrong Username / Password combination.
+
+ Please try again. Thanks!

+


+

+
+ + diff --git a/register/logout.php b/register/logout.php new file mode 100644 index 0000000..a012290 --- /dev/null +++ b/register/logout.php @@ -0,0 +1,19 @@ + + + +


+ You are now logged out
+
+ Return to Login

diff --git a/register/master_inc.php b/register/master_inc.php new file mode 100644 index 0000000..9cba333 --- /dev/null +++ b/register/master_inc.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/register/readme.php b/register/readme.php new file mode 100644 index 0000000..3e05a12 --- /dev/null +++ b/register/readme.php @@ -0,0 +1,59 @@ + + + + +Basic Login Readme + + + +
+

Basic Login is a basic PHP login script that can be downloaded used and modified under the general user public license provisions.  This is not a PHP login script for a government institution or fortune 500 company as it uses standard encryption and cookie based user identification that a determined hacker might exploit.  It is, however, perfect for a standard website that would benefit from a basic PHP to MySQL login script that is easy to deploy and uses standard methods of restricting access and granting permissions.

+

After writing countless PHP login scripts using various combinations of standard components tailored to the needs of the sites for which they were designed, I got wise and distilled the elements that I used most frequently into a basic application that can be easily uploaded and deployed on any project I was working on.  While there are no grand inventions happening here, Basic Login should do what you need it to do right out of the gate.  Features, functions and components are as follows:

+

PHP to MySQL structure
+ Password encryption
+ Email forgotten password hint
+ Email password change/ recreation
+ New user account creation with error handling
+ User permission level control
+ User login routing by permission on login
+ Central configuration file
+ Administrative user management report
+ First user gets admin permissions

+

Using Basic Login is easy:

+
    +
  1. Download Basic Login files
  2. +
  3. Unzip Basic Login files
  4. +
  5. Upload Basic Login files to a directory of your choice
  6. +
  7. Create users table in MySQL:
    + Just run this query or cut and paste the following code into the SQL window of phpmyadmin or an equivalent MySQL control panel:
  8. +
+

CREATE TABLE `users` (
+  `id` int(11) NOT NULL auto_increment,
+  `username` varchar(50) NOT NULL default '',
+  `password` varchar(150) NOT NULL default '',
+  `password_hint` varchar(255) NOT NULL default '',
+  `lastname` varchar(50) NOT NULL default '',
+  `firstname` varchar(50) NOT NULL default '',
+  `email` varchar(100) NOT NULL default '',
+  `phone` varchar(50) NOT NULL default '',
+  `address1` varchar(100) NOT NULL default '',
+  `address2` varchar(100) NOT NULL default '',
+  `city` varchar(80) NOT NULL default '',
+  `state` varchar(20) NOT NULL default '',
+  `zip` varchar(20) NOT NULL default '',
+  `country` varchar(50) NOT NULL default '',
+  `url` varchar(125) NOT NULL default '',
+  `permissions` varchar(20) NOT NULL default '1',
+  PRIMARY KEY  (`id`)
+)        

+
    +
  1. Open login_config.php and set the configuration values as desired
  2. +
  3. Add your first account.  It will be configured as the administrator by default. 
  4. +
  5. Put pages behind the security framework < ? include "auth_check_header"; ?> at the very top of any pages that are for members only. Make sure they are in the same directory as the Basic Login files.
  6. +
  7. Happy coding!
  8. +
+

 

+

 

+
+ + diff --git a/register/reset_password.php b/register/reset_password.php new file mode 100644 index 0000000..ee04482 --- /dev/null +++ b/register/reset_password.php @@ -0,0 +1,123 @@ +An encrypted temporary password has been assigned. Choose a new password in the form below:
" ); +} +else +{ +die( "Trouble saving information to the database:

" . mysqli_error() ); +} + + +?> +
+

Password Reset

+
+ + + + + + + +
Username + + + +
+ + + + + + + +
New Password + +
+ + + + + + + + + + + +
Confirm New Password + +
New Password Hint + +
+

+ +

+
+

+ +

+

 

+
diff --git a/register/reset_password_save.php b/register/reset_password_save.php new file mode 100644 index 0000000..539b85f --- /dev/null +++ b/register/reset_password_save.php @@ -0,0 +1,40 @@ +email = $email | username = $username
"; + +$query = "UPDATE `users` SET `password`='$cleanpw', `password_hint`='$password_hint' WHERE `email`='$email'"; + +// save the info to the database +$results = mysqli_query( $query ); + +// print out the results +if( $results ) + +{ echo( "Your changes have been made sucessfully.

Back to Login
" ); +} +else +{ +die( "Trouble saving information to the database: " . mysqli_error() ); +} + +} +else +{ +echo"Your new passwords do not match. Please try again"; +} + +?> diff --git a/register/router.php b/register/router.php new file mode 100644 index 0000000..102210d --- /dev/null +++ b/register/router.php @@ -0,0 +1,61 @@ + \ No newline at end of file diff --git a/register/sample_page.php b/register/sample_page.php new file mode 100644 index 0000000..cbd3dbb --- /dev/null +++ b/register/sample_page.php @@ -0,0 +1,33 @@ + + + + + + +AMS Agent Index + + + + + + + +
+

 

+

Sample Page +

+

You are logged in and can see content for which you are authorized

+

Log out

+
+
+
+
+
+ + diff --git a/register/traffic.php b/register/traffic.php new file mode 100644 index 0000000..528a4bc --- /dev/null +++ b/register/traffic.php @@ -0,0 +1,202 @@ + + + + + + + + + + + +Basic Login - Download a free basic PHP Login Script + + + + + + + +
+

BasicLogin.com +
top bar
+
+ + + +
+ +
+ + + + + +
+ +
+
+ +   + +
+

Sort By: ID | IP | Date | Site | Type

+ + + + +
+ "; + +If($criteria_url==''){$criteria="id";}else{$criteria=$criteria_url;} + +//echo "Criteria:$criteria
"; + + +$var = $_REQUEST['var']; + +$sql = "select * from traffic WHERE `id` LIKE \"%$var%\" OR `site` LIKE \"%$var%\" OR `type` LIKE \"%$var%\" OR `date` LIKE \"%$var%\" OR `ip` LIKE \"%$var%\" OR `link` LIKE \"%$var%\" OR `page` LIKE \"%$var%\"order by `$criteria` desc"; + + + $numresults=mysqli_query($sql); + $numrows=mysqli_num_rows($numresults); + + echo"Results: $numrows
"; + + if (empty($s)) { + $s=0; + } + +// get results + $query .= " limit $s,$limit"; + +// get results + $result = mysqli_query($query) or die("Couldn't execute query"); + + +// now you can display the results returned + while ($row= mysqli_fetch_array($result)) { + + $id= $row["id"]; + $ip= $row["ip"]; + $date = $row["date"]; + $site = $row["site"]; + $type= $row["type"]; + $link_raw= $row["link"]; + $page= $row["page"]; + $link=nicetrim($link_raw); + + + +echo "$id | $ip | $date | $site | $type | $page $link

"; + +} + +$currPage = (($s/$limit) + 1); + +//break before paging + echo "
"; + + // next we need to do the links to other results + if ($s>=1) { // bypass PREV link if s is 0 + $prevs=($s-$limit); + print " << + Prev 100  "; + } + +// calculate number of pages needing links + $pages=intval($numrows/$limit); + +// $pages now contains int of pages needed unless there is a remainder from division + + if ($numrows%$limit) { + // has remainder so add one page + $pages++; + } + +// check to see if last page + if (!((($s+$limit)/$limit)==$pages) && $pages!=1) { + + // not last page so give NEXT link + $news=$s+$limit; + + echo " Next 100 >>"; + } + +$a = $s + ($limit) ; + if ($a > $numrows) { $a = $numrows ; } + $b = $s + 1 ; + +?> +
+

  +

+
+
+

 

+
bottom bar
+ +

+

 

+


+
+

+
+ + + diff --git a/register/user_add.form.php b/register/user_add.form.php new file mode 100644 index 0000000..8ae82a9 --- /dev/null +++ b/register/user_add.form.php @@ -0,0 +1,9 @@ +
+
+
+
+ +
+ diff --git a/register/user_add.php b/register/user_add.php new file mode 100644 index 0000000..9965209 --- /dev/null +++ b/register/user_add.php @@ -0,0 +1,17 @@ + + + + + diff --git a/register/user_add_errors.php b/register/user_add_errors.php new file mode 100644 index 0000000..780730e --- /dev/null +++ b/register/user_add_errors.php @@ -0,0 +1,56 @@ + + + +"; + echo "That username is too short. Please make it more than 4 characters.

"; + echo "
"; + } + + if($username_already_in_use==104) { + echo ""; + echo "That username is already in use. Please try again or log in to your existing account.

"; //
"; + echo ""; + } + + if($email_already_in_use==104) { + echo ""; + echo "That email is already in use. That probably means you have an existing account. Log in or reset your password

"; + echo "
"; + } + + if($pw_insecure==104){ + echo ""; + echo "Your Password is not formatted correctly. Please choose a password that is between 4 and 20 characters and has at least 1 uppercase letter, one lower case letter and one number I.E. Hello23.

"; + echo "
"; + } + + if($bad_email==104){ + echo ""; + echo "Your email does not appear to be valid"; + echo "

"; + } + + + ?>
+ +

+ +

+

 

+
diff --git a/register/user_add_save.php b/register/user_add_save.php new file mode 100644 index 0000000..44a2d7c --- /dev/null +++ b/register/user_add_save.php @@ -0,0 +1,117 @@ += 4){ + //email unique? + $sql = "SELECT * FROM ".$usertable." WHERE username='$username'"; + $result = mysqli_query($db, $sql); + $count = mysqli_num_rows($result); + if($count>0){ + $username_already_in_use = 104; + } +}else{ + $username_too_short = 104; +} + +//email format check +$email_raw = $_REQUEST['email']; +if(preg_match('/^[_a-z0-9-]+(\.[_a-z0-9-]+)*@([a-z0-9-]{2,3})+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$/i', $email_raw)) +{ + $email = $email_raw; +}else{ + $bad_email = 104; +} + +if($require_email_unique) { + $sql="SELECT * FROM ".$usertable." WHERE email='$email'"; + $result=mysqli_query($db, $sql); + $count=mysqli_num_rows($result); + if($count>0){ + $email_already_in_use=104; + } +} + +//Secure Password Format Checks +$password = $_POST['password']; +if($require_secure_password) { + $pw_clean = strip_tags(substr($password,0,32)); + if (preg_match("/[A-Z]+[a-z]+[0-9]/", $pw_clean, $matches)) { + }else{ + $pw_insecure = 104; + } +} else { + $pw_clean = $password; +} + +if($username_already_in_use==104 OR $email_already_in_use==104 OR $pw_insecure==104 OR $bad_email==104 OR $username_too_short==104){ +header( + "location:user_add_errors.php?pw_insecure=$pw_insecure&email_already_in_use=$email_already_in_use&username_already_in_use=$username_already_in_use&bad_email=$bad_email&username_too_short=$username_too_short" +."&email=$email&password=$password"); +die(); +} +//End Error Checks_________________________________________________________ + +//Encrypt Password +$encrypted_pw = md5($pw_clean); +$sql = "INSERT INTO ".$usertable." +(domain_id, username, md5_pass, lastname, firstname, email, phone, password_hint) +select max(domain_id)+1 domain_id + , '".mysqli_real_escape_string($username)."' username + , '".mysqli_real_escape_string($encrypted_pw)."' md5_pass + , '".mysqli_real_escape_string($lastname)."' lastname + , '".mysqli_real_escape_string($firstname)."' firstname + , '".mysqli_real_escape_string($email)."' email + , '".mysqli_real_escape_string($phone)."' phone + , '".mysqli_real_escape_string($password_hint)."' password_hint from ".$usertable.";"; + +// save the info to the database +$results = mysqli_query( $sql ); +// print out the results +if( $results ) { + // + // Automatic login after registration + // + $ip_date = $_SERVER['REMOTE_ADDR']."_".date('Y-m'); + $uin = md5($username.$encrypted_pw.$ip_date); + setcookie("uin", $uin, 0, "/"); +?> + +Your changes have been made sucessfully. +

Back to login"; +*/ +} +else +{ +die( "Trouble saving information to the database: " . mysqli_error() ); +} +//email unique? +$sql="SELECT * FROM ".$usertable.""; +$result=mysqli_query($db, $sql); +$count=mysqli_num_rows($result); +if($count==1){ + +$sql = "UPDATE `users` SET `permissions`='5' WHERE `email`='$email'"; +// save the info to the database +$results = mysqli_query( $sql ); +// print out the results +if( $results ) +{ echo( "

Since this is the first user in the database we have configured the account with administrative privileges. Subsequent changes to permission levels can be made in the database. Thank you.
" ); +} +else +{ +die( "Trouble saving information to the database: " . mysqli_error() ); +} + +} +?> \ No newline at end of file diff --git a/register/user_add_sso.php b/register/user_add_sso.php new file mode 100644 index 0000000..a94d7b3 --- /dev/null +++ b/register/user_add_sso.php @@ -0,0 +1,55 @@ +authenticate( $username ); + + // grab the user profile + $user_profile = $adapter->getUserProfile(); + + // a) Does user with "xxx" = identifier exist? + // -> Yes, then login as user + + // b) Does email of user exist? + // -> No, then create new user + + // c) Does email of user exist? + // -> Yes, ask for regular login. Preset email = login + + $provider_uid = $user_profile->identifier; + $email = $user_profile->email; + $first_name = $user_profile->firstName; + $last_name = $user_profile->lastName; + $display_name = $user_profile->displayName; + $website_url = $user_profile->webSiteURL; + $profile_url = $user_profile->profileURL; + $password = rand( ) ; # for the password we generate something random + + echo $provider_uid."
"; + echo $email; + } + catch( Exception $e ){ + // Display the recived error + switch( $e->getCode() ){ + case 0 : $error = "Unspecified error."; break; + case 1 : $error = "Hybriauth configuration error."; break; + case 2 : $error = "Provider not properly configured."; break; + case 3 : $error = "Unknown or disabled provider."; break; + case 4 : $error = "Missing provider application credentials."; break; + case 5 : $error = "Authentification failed. The user has canceled the authentication or the provider refused the connection."; break; + case 6 : $error = "User profile request failed. Most likely the user is not connected to the provider and he should to authenticate again."; + $adapter->logout(); + break; + case 7 : $error = "User not connected to the provider."; + $adapter->logout(); + break; + } + } +?> \ No newline at end of file diff --git a/signin/README.txt b/signin/README.txt new file mode 100644 index 0000000..bbaa10a --- /dev/null +++ b/signin/README.txt @@ -0,0 +1,49 @@ +Copyright (c) 2009-2012 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + +Thank you for downloading UserCake, the simple user management package. + +//--Installation. + +1. Before proceeding please open up models/db-settings.php + +2. Create a database on your server / web hosting package. + +3. Fill out the connection details in db-settings.php + +4. UserCake supports MySQLi and requires MySQL server version 4.1.3 or newer. + +5. To use the installer visit http://yourdomain.com/install/ in your browser. UserCake will attempt to build the database for you. After completion + delete the install folder. + +- That's it you're good to go! In only five steps you have a fully functional user management system. + For further documentation visit http://usercake.com + +//--Credits + +UserCake created by: Adam Davis +UserCake V2.0 designed by: Jonathan Cassels + +--------------------------------------------------------------- + +Vers: 2.01 +http://usercake.com +http://usercake.com/LICENCE.txt diff --git a/signin/account.php b/signin/account.php new file mode 100644 index 0000000..ec86e6d --- /dev/null +++ b/signin/account.php @@ -0,0 +1,31 @@ + +
+
+
+

UserCake

+

Account

+
"; + +include("left-nav.php"); + +echo " +
+
+Hey, $loggedInUser->displayname. This is an example secure page designed to demonstrate some of the basic features of UserCake. Just so you know, your title at the moment is $loggedInUser->title, and that can be changed in the admin panel. You registered this account on " . date("M d, Y", $loggedInUser->signupTimeStamp()) . ". +
+
+
+ +"; + +?> diff --git a/signin/activate-account.php b/signin/activate-account.php new file mode 100644 index 0000000..80522b8 --- /dev/null +++ b/signin/activate-account.php @@ -0,0 +1,66 @@ + +
+
+
+

UserCake

+

Activate Account

+ +
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+
+
+ +"; + +?> diff --git a/signin/admin_configuration.php b/signin/admin_configuration.php new file mode 100644 index 0000000..7c42098 --- /dev/null +++ b/signin/admin_configuration.php @@ -0,0 +1,240 @@ + 72 OR $newResend_activation_threshold < 0) + { + $errors[] = lang("CONFIG_ACTIVATION_RESEND_RANGE",array(0,72)); + } + else if (count($errors) == 0) { + $cfgId[] = 5; + $cfgValue[5] = $newResend_activation_threshold; + $resend_activation_threshold = $newResend_activation_threshold; + } + } + + //Validate new language selection + if ($newSettings[6] != $language) { + $newLanguage = $newSettings[6]; + if(minMaxRange(1,150,$language)) + { + $errors[] = lang("CONFIG_LANGUAGE_CHAR_LIMIT",array(1,150)); + } + elseif (!file_exists($newLanguage)) { + $errors[] = lang("CONFIG_LANGUAGE_INVALID",array($newLanguage)); + } + else if (count($errors) == 0) { + $cfgId[] = 6; + $cfgValue[6] = $newLanguage; + $language = $newLanguage; + } + } + + //Validate new template selection + if ($newSettings[7] != $template) { + $newTemplate = $newSettings[7]; + if(minMaxRange(1,150,$template)) + { + $errors[] = lang("CONFIG_TEMPLATE_CHAR_LIMIT",array(1,150)); + } + elseif (!file_exists($newTemplate)) { + $errors[] = lang("CONFIG_TEMPLATE_INVALID",array($newTemplate)); + } + else if (count($errors) == 0) { + $cfgId[] = 7; + $cfgValue[7] = $newTemplate; + $template = $newTemplate; + } + } + + //Update configuration table with new settings + if (count($errors) == 0 AND count($cfgId) > 0) { + if (updateConfig($cfgId, $cfgValue)) { + $successes[] = lang("CONFIG_UPDATE_SUCCESSFUL"); + } + else { + $errors[] = lang("SQL_ERROR"); + } + } +} + +$languages = getLanguageFiles(); //Retrieve list of language files +$templates = getTemplateFiles(); //Retrieve list of template files +$permissionData = fetchAllPermissions(); //Retrieve list of all permission levels +require_once("models/header.php"); + +echo " + +
+
+
+

UserCake

+

Admin Configuration

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+
+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ + +

+

+ +"; +} +else { + echo " + + + "; +} + +echo "

+

+ + +

+ +
+
+
+
+
+ +"; + +?> diff --git a/signin/admin_page.php b/signin/admin_page.php new file mode 100644 index 0000000..388d6b6 --- /dev/null +++ b/signin/admin_page.php @@ -0,0 +1,159 @@ + +
+
+
+

UserCake

+

Admin Page

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+ + + + +
+

Page Information

+
+

+ +".$pageDetails['id']." +

+

+ +".$pageDetails['page']." +

+

+"; + +//Display private checkbox +if ($pageDetails['private'] == 1){ + echo ""; +} +else { + echo ""; +} + +echo " +

+
+

Page Access

+
+

+Remove Access:"; + +//Display list of permission levels with access +foreach ($permissionData as $v1) { + if(isset($pagePermissions[$v1['id']])){ + echo "
".$v1['name']; + } +} + +echo" +

Add Access:"; + +//Display list of permission levels without access +foreach ($permissionData as $v1) { + if(!isset($pagePermissions[$v1['id']])){ + echo "
".$v1['name']; + } +} + +echo" +

+
+
+

+ + +

+
+
+
+
+ +"; + +?> diff --git a/signin/admin_pages.php b/signin/admin_pages.php new file mode 100644 index 0000000..d47a2f2 --- /dev/null +++ b/signin/admin_pages.php @@ -0,0 +1,95 @@ + 0) { + createPages($creations) ; +} + +if (count($dbpages) > 0){ + //Check if DB contains pages that don't exist + foreach ($dbpages as $page){ + if(!isset($pages[$page['page']])){ + $deletions[] = $page['id']; + } + } +} + +//Delete pages from DB if not found +if (count($deletions) > 0) { + deletePages($deletions); +} + +//Update DB pages +$dbpages = fetchAllPages(); + +require_once("models/header.php"); + +echo " + +
+
+
+

UserCake

+

Admin Pages

+
"; + +include("left-nav.php"); + +echo " +
+
+ +"; + +//Display list of pages +foreach ($dbpages as $page){ + echo " + + + + + "; +} + +echo " +
IdPageAccess
+ ".$page['id']." + + ".$page['page']." + "; + + //Show public/private setting of page + if($page['private'] == 0){ + echo "Public"; + } + else { + echo "Private"; + } + + echo " +
+
+
+
+ +"; + +?> diff --git a/signin/admin_permission.php b/signin/admin_permission.php new file mode 100644 index 0000000..bf1a6fd --- /dev/null +++ b/signin/admin_permission.php @@ -0,0 +1,219 @@ + +
+
+
+

UserCake

+

Admin Permissions

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+ + + + +
+

Permission Information

+
+

+ +".$permissionDetails['id']." +

+

+ + +

+ + +

+
+

Permission Membership

+
+

+Remove Members:"; + +//List users with permission level +foreach ($userData as $v1) { + if(isset($permissionUsers[$v1['id']])){ + echo "
".$v1['display_name']; + } +} + +echo" +

Add Members:"; + +//List users without permission level +foreach ($userData as $v1) { + if(!isset($permissionUsers[$v1['id']])){ + echo "
".$v1['display_name']; + } +} + +echo" +

+
+
+

Permission Access

+
+

+Public Access:"; + +//List public pages +foreach ($pageData as $v1) { + if($v1['private'] != 1){ + echo "
".$v1['page']; + } +} + +echo" +

+

+Remove Access:"; + +//List pages accessible to permission level +foreach ($pageData as $v1) { + if(isset($pagePermissions[$v1['id']]) AND $v1['private'] == 1){ + echo "
".$v1['page']; + } +} + +echo" +

Add Access:"; + +//List pages inaccessible to permission level +foreach ($pageData as $v1) { + if(!isset($pagePermissions[$v1['id']]) AND $v1['private'] == 1){ + echo "
".$v1['page']; + } +} + +echo" +

+
+
+

+ + +

+
+
+
+
+ +"; + +?> diff --git a/signin/admin_permissions.php b/signin/admin_permissions.php new file mode 100644 index 0000000..5533d9b --- /dev/null +++ b/signin/admin_permissions.php @@ -0,0 +1,96 @@ + +
+
+
+

UserCake

+

Admin Permissions

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+ + + +"; + +//List each permission level +foreach ($permissionData as $v1) { + echo " + + + + "; +} + +echo " +
DeletePermission Name
".$v1['name']."
+

+ + +

+ +
+
+
+
+ +"; + +?> diff --git a/signin/admin_user.php b/signin/admin_user.php new file mode 100644 index 0000000..cf0da6f --- /dev/null +++ b/signin/admin_user.php @@ -0,0 +1,264 @@ + +
+
+
+

UserCake

+

Admin User

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+ + + +
+

User Information

+
+

+ +".$userdetails['id']." +

+

+ +".$userdetails['user_name']." +

+

+ + +

+

+ + +

+

+"; + +//Display activation link, if account inactive +if ($userdetails['active'] == '1'){ + echo "Yes"; +} +else{ + echo "No +

+

+ + + "; +} + +echo " +

+

+ + +

+

+ +".date("j M, Y", $userdetails['sign_up_stamp'])." +

+

+"; + +//Last sign in, interpretation +if ($userdetails['last_sign_in_stamp'] == '0'){ + echo "Never"; +} +else { + echo date("j M, Y", $userdetails['last_sign_in_stamp']); +} + +echo " +

+

+ + +

+

+ + +

+
+
+

Permission Membership

+
+

Remove Permission:"; + +//List of permission levels user is apart of +foreach ($permissionData as $v1) { + if(isset($userPermission[$v1['id']])){ + echo "
".$v1['name']; + } +} + +//List of permission levels user is not apart of +echo "

Add Permission:"; +foreach ($permissionData as $v1) { + if(!isset($userPermission[$v1['id']])){ + echo "
".$v1['name']; + } +} + +echo" +

+
+
+
+
+
+
+ +"; + +?> diff --git a/signin/admin_users.php b/signin/admin_users.php new file mode 100644 index 0000000..5cd81e4 --- /dev/null +++ b/signin/admin_users.php @@ -0,0 +1,81 @@ + +
+
+
+

UserCake

+

Admin Users

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+ + + +"; + +//Cycle through users +foreach ($userData as $v1) { + echo " + + + + + + + "; +} + +echo " +
DeleteUsernameDisplay NameTitleLast Sign In
".$v1['user_name']."".$v1['display_name']."".$v1['title']." + "; + + //Interprety last login + if ($v1['last_sign_in_stamp'] == '0'){ + echo "Never"; + } + else { + echo date("j M, Y", $v1['last_sign_in_stamp']); + } + echo " +
+ +
+
+
+
+ +"; + +?> diff --git a/signin/forgot-password.php b/signin/forgot-password.php new file mode 100644 index 0000000..6281d36 --- /dev/null +++ b/signin/forgot-password.php @@ -0,0 +1,202 @@ + array("#GENERATED-PASS#","#USERNAME#"), + "subjectStrs" => array($rand_pass,$userdetails["display_name"]) + ); + + if(!$mail->newTemplateMsg("your-lost-password.txt",$hooks)) + { + $errors[] = lang("MAIL_TEMPLATE_BUILD_ERROR"); + } + else + { + if(!$mail->sendMail($userdetails["email"],"Your new password")) + { + $errors[] = lang("MAIL_ERROR"); + } + else + { + if(!updatePasswordFromToken($secure_pass,$token)) + { + $errors[] = lang("SQL_ERROR"); + } + else + { + flagLostPasswordRequest($userdetails["user_name"],0); + $successes[] = lang("FORGOTPASS_NEW_PASS_EMAIL"); + } + } + } + } +} + +//User has denied this request +if(!empty($_GET["deny"])) +{ + $token = trim($_GET["deny"]); + + if($token == "" || !validateActivationToken($token,TRUE)) + { + $errors[] = lang("FORGOTPASS_INVALID_TOKEN"); + } + else + { + + $userdetails = fetchUserDetails(NULL,$token); + + flagLostPasswordRequest($userdetails["user_name"],0); + + $successes[] = lang("FORGOTPASS_REQUEST_CANNED"); + } +} + +//Forms posted +if(!empty($_POST)) +{ + $email = $_POST["email"]; + $username = sanitize($_POST["username"]); + + //Perform some validation + //Feel free to edit / change as required + + if(trim($email) == "") + { + $errors[] = lang("ACCOUNT_SPECIFY_EMAIL"); + } + //Check to ensure email is in the correct format / in the db + else if(!isValidEmail($email) || !emailExists($email)) + { + $errors[] = lang("ACCOUNT_INVALID_EMAIL"); + } + + if(trim($username) == "") + { + $errors[] = lang("ACCOUNT_SPECIFY_USERNAME"); + } + else if(!usernameExists($username)) + { + $errors[] = lang("ACCOUNT_INVALID_USERNAME"); + } + + if(count($errors) == 0) + { + + //Check that the username / email are associated to the same account + if(!emailUsernameLinked($email,$username)) + { + $errors[] = lang("ACCOUNT_USER_OR_EMAIL_INVALID"); + } + else + { + //Check if the user has any outstanding lost password requests + $userdetails = fetchUserDetails($username); + if($userdetails["lost_password_request"] == 1) + { + $errors[] = lang("FORGOTPASS_REQUEST_EXISTS"); + } + else + { + //Email the user asking to confirm this change password request + //We can use the template builder here + + //We use the activation token again for the url key it gets regenerated everytime it's used. + + $mail = new userCakeMail(); + $confirm_url = lang("CONFIRM")."\n".$websiteUrl."forgot-password.php?confirm=".$userdetails["activation_token"]; + $deny_url = lang("DENY")."\n".$websiteUrl."forgot-password.php?deny=".$userdetails["activation_token"]; + + //Setup our custom hooks + $hooks = array( + "searchStrs" => array("#CONFIRM-URL#","#DENY-URL#","#USERNAME#"), + "subjectStrs" => array($confirm_url,$deny_url,$userdetails["user_name"]) + ); + + if(!$mail->newTemplateMsg("lost-password-request.txt",$hooks)) + { + $errors[] = lang("MAIL_TEMPLATE_BUILD_ERROR"); + } + else + { + if(!$mail->sendMail($userdetails["email"],"Lost password request")) + { + $errors[] = lang("MAIL_ERROR"); + } + else + { + //Update the DB to show this account has an outstanding request + flagLostPasswordRequest($username,1); + + $successes[] = lang("FORGOTPASS_REQUEST_SUCCESS"); + } + } + } + } + } +} + +require_once("models/header.php"); +echo " + +
+
+
+

UserCake

+

Forgot Password

+
"; + +include("left-nav.php"); + +echo " +
+
"; + +echo resultBlock($errors,$successes); + +echo " +
+
+

+ + +

+

+ + +

+

+ + +

+
+
+
+
+
+ +"; + +?> diff --git a/signin/index.php b/signin/index.php new file mode 100644 index 0000000..4a1e2b0 --- /dev/null +++ b/signin/index.php @@ -0,0 +1,46 @@ + +
+
+
+

UserCake

+

2.00

+
"; +include("left-nav.php"); + +echo " +
+
+

Thank you for downloading UserCake. 100% Free and Opensource.

+

Copyright (c) 2009-2012

+

Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software.

+

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.

+
+
+
+ +"; + +?> diff --git a/signin/left-nav.php b/signin/left-nav.php new file mode 100644 index 0000000..774a766 --- /dev/null +++ b/signin/left-nav.php @@ -0,0 +1,43 @@ + +
  • Account Home
  • +
  • User Settings
  • +
  • Logout
  • + "; + + //Links for permission level 2 (default admin) + if ($loggedInUser->checkPermission(array(2))){ + echo " + "; + } +} +//Links for users not logged in +else { + echo " + "; +} + +?> diff --git a/signin/login.php b/signin/login.php new file mode 100644 index 0000000..ad96480 --- /dev/null +++ b/signin/login.php @@ -0,0 +1,124 @@ +email = $userdetails["email"]; + $loggedInUser->user_id = $userdetails["id"]; + $loggedInUser->hash_pw = $userdetails["password"]; + $loggedInUser->title = $userdetails["title"]; + $loggedInUser->displayname = $userdetails["display_name"]; + $loggedInUser->username = $userdetails["user_name"]; + + //Update last sign in + $loggedInUser->updateLastSignIn(); + $_SESSION["userCakeUser"] = $loggedInUser; + + //Redirect to user account page + header("Location: account.php"); + die(); + } + } + } + } +} + +require_once("models/header.php"); + +echo " + +
    +
    +
    +

    UserCake

    +

    Login

    +
    "; + +include("left-nav.php"); + +echo " +
    +
    "; + +echo resultBlock($errors,$successes); + +echo " +
    +
    +

    + + +

    +

    + + +

    +

    + + +

    +
    +
    +
    +
    +
    + +"; + +?> diff --git a/signin/logout.php b/signin/logout.php new file mode 100644 index 0000000..7a5434e --- /dev/null +++ b/signin/logout.php @@ -0,0 +1,34 @@ +userLogOut(); +} + +if(!empty($websiteUrl)) +{ + $add_http = ""; + + if(strpos($websiteUrl,"http://") === false) + { + $add_http = "http://"; + } + + header("Location: ".$add_http.$websiteUrl); + die(); +} +else +{ + header("Location: http://".$_SERVER['HTTP_HOST']); + die(); +} + +?> + diff --git a/signin/models/captcha.php b/signin/models/captcha.php new file mode 100644 index 0000000..0058c14 --- /dev/null +++ b/signin/models/captcha.php @@ -0,0 +1,28 @@ + diff --git a/signin/models/class.mail.php b/signin/models/class.mail.php new file mode 100644 index 0000000..c2d5faf --- /dev/null +++ b/signin/models/class.mail.php @@ -0,0 +1,55 @@ +contents = file_get_contents($mail_templates_dir.$template); + + //Check to see we can access the file / it has some contents + if(!$this->contents || empty($this->contents)) + { + return false; + } + else + { + //Replace default hooks + $this->contents = replaceDefaultHook($this->contents); + + //Replace defined / custom hooks + $this->contents = str_replace($additionalHooks["searchStrs"],$additionalHooks["subjectStrs"],$this->contents); + + return true; + } + } + + public function sendMail($email,$subject,$msg = NULL) + { + global $websiteName,$emailAddress; + + $header = "MIME-Version: 1.0\r\n"; + $header .= "Content-type: text/plain; charset=iso-8859-1\r\n"; + $header .= "From: ". $websiteName . " <" . $emailAddress . ">\r\n"; + + //Check to see if we sending a template email. + if($msg == NULL) + $msg = $this->contents; + + $message = $msg; + + $message = wordwrap($message, 70); + + return mail($email,$subject,$message,$header); + } +} + +?> \ No newline at end of file diff --git a/signin/models/class.newuser.php b/signin/models/class.newuser.php new file mode 100644 index 0000000..18d3288 --- /dev/null +++ b/signin/models/class.newuser.php @@ -0,0 +1,162 @@ +displayname = $display; + + //Sanitize + $this->clean_email = sanitize($email); + $this->clean_password = trim($pass); + $this->username = sanitize($user); + + if(usernameExists($this->username)) + { + $this->username_taken = true; + } + else if(displayNameExists($this->displayname)) + { + $this->displayname_taken = true; + } + else if(emailExists($this->clean_email)) + { + $this->email_taken = true; + } + else + { + //No problems have been found. + $this->status = true; + } + } + + public function userCakeAddUser() + { + global $mysqli,$emailActivation,$websiteUrl,$db_table_prefix; + + //Prevent this function being called if there were construction errors + if($this->status) + { + //Construct a secure hash for the plain text password + $secure_pass = generateHash($this->clean_password); + + //Construct a unique activation token + $this->activation_token = generateActivationToken(); + + //Do we need to send out an activation email? + if($emailActivation == "true") + { + //User must activate their account first + $this->user_active = 0; + + $mail = new userCakeMail(); + + //Build the activation message + $activation_message = lang("ACCOUNT_ACTIVATION_MESSAGE",array($websiteUrl,$this->activation_token)); + + //Define more if you want to build larger structures + $hooks = array( + "searchStrs" => array("#ACTIVATION-MESSAGE","#ACTIVATION-KEY","#USERNAME#"), + "subjectStrs" => array($activation_message,$this->activation_token,$this->displayname) + ); + + /* Build the template - Optional, you can just use the sendMail function + Instead to pass a message. */ + + if(!$mail->newTemplateMsg("new-registration.txt",$hooks)) + { + $this->mail_failure = true; + } + else + { + //Send the mail. Specify users email here and subject. + //SendMail can have a third parementer for message if you do not wish to build a template. + + if(!$mail->sendMail($this->clean_email,"New User")) + { + $this->mail_failure = true; + } + } + $this->success = lang("ACCOUNT_REGISTRATION_COMPLETE_TYPE2"); + } + else + { + //Instant account activation + $this->user_active = 1; + $this->success = lang("ACCOUNT_REGISTRATION_COMPLETE_TYPE1"); + } + + + if(!$this->mail_failure) + { + //Insert the user into the database providing no errors have been found. + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."users ( + username, + display_name, + md5_pass, + email, + activation_token, + last_activation_request, + lost_password_request, + active, + title, + sign_up_stamp, + last_sign_in_stamp + ) + VALUES ( + ?, + ?, + ?, + ?, + ?, + '".time()."', + '0', + ?, + 'New Member', + '".time()."', + '0' + )"); + + $stmt->bind_param("sssssi", $this->username, $this->displayname, $secure_pass, $this->clean_email, $this->activation_token, $this->user_active); + $stmt->execute(); + $inserted_id = $mysqli->insert_id; + $stmt->close(); + + //Insert default permission into matches table + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."user_permission_matches ( + user_id, + permission_id + ) + VALUES ( + ?, + '1' + )"); + $stmt->bind_param("s", $inserted_id); + $stmt->execute(); + $stmt->close(); + } + } + } +} + +?> \ No newline at end of file diff --git a/signin/models/class.user.php b/signin/models/class.user.php new file mode 100644 index 0000000..47ce108 --- /dev/null +++ b/signin/models/class.user.php @@ -0,0 +1,114 @@ +prepare("UPDATE ".$db_table_prefix."users + SET + last_sign_in_stamp = ? + WHERE + user_id = ?"); + $stmt->bind_param("ii", $time, $this->user_id); + $stmt->execute(); + $stmt->close(); + } + + //Return the timestamp when the user registered + public function signupTimeStamp() + { + global $mysqli,$db_table_prefix; + + $stmt = $mysqli->prepare("SELECT sign_up_stamp + FROM ".$db_table_prefix."users + WHERE user_id = ?"); + $stmt->bind_param("i", $this->user_id); + $stmt->execute(); + $stmt->bind_result($timestamp); + $stmt->fetch(); + $stmt->close(); + return ($timestamp); + } + + //Update a users password + public function updatePassword($pass) + { + global $mysqli,$db_table_prefix; + $secure_pass = generateHash($pass); + $this->hash_pw = $secure_pass; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET + md5_pass = ? + WHERE + user_id = ?"); + $stmt->bind_param("si", $secure_pass, $this->user_id); + $stmt->execute(); + $stmt->close(); + } + + //Update a users email + public function updateEmail($email) + { + global $mysqli,$db_table_prefix; + $this->email = $email; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET + email = ? + WHERE + user_id = ?"); + $stmt->bind_param("si", $email, $this->user_id); + $stmt->execute(); + $stmt->close(); + } + + //Is a user has a permission + public function checkPermission($permission) + { + global $mysqli,$db_table_prefix; + + $stmt = $mysqli->prepare("SELECT id + FROM ".$db_table_prefix."user_permission_matches + WHERE user_id = ? + AND permission_id = ? + LIMIT 1 + "); + $access = 0; + foreach($permission as $check){ + if ($access == 0){ + $stmt->bind_param("ii", $this->user_id, $check); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0){ + $access = 1; + } + } + } + if ($access == 1) + { + return true; + } + else + { + return false; + } + $stmt->close(); + } + + //Logout + public function userLogOut() + { + destroySession("userCakeUser"); + } +} + +?> \ No newline at end of file diff --git a/signin/models/config.php b/signin/models/config.php new file mode 100644 index 0000000..e1d9a7f --- /dev/null +++ b/signin/models/config.php @@ -0,0 +1,58 @@ + diff --git a/signin/models/db-settings.php b/signin/models/db-settings.php new file mode 100644 index 0000000..e2b9bb3 --- /dev/null +++ b/signin/models/db-settings.php @@ -0,0 +1,44 @@ + \ No newline at end of file diff --git a/signin/models/funcs.js b/signin/models/funcs.js new file mode 100644 index 0000000..00608be --- /dev/null +++ b/signin/models/funcs.js @@ -0,0 +1,11 @@ +/* +UserCake Version: 2.0.1 +http://usercake.com +*/ +function showHide(div){ + if(document.getElementById(div).style.display = 'block'){ + document.getElementById(div).style.display = 'none'; + }else{ + document.getElementById(div).style.display = 'block'; + } +} diff --git a/signin/models/funcs.php b/signin/models/funcs.php new file mode 100644 index 0000000..b4b7bf5 --- /dev/null +++ b/signin/models/funcs.php @@ -0,0 +1,1176 @@ + $max) + return true; + else + return false; +} + +//Replaces hooks with specified text +function replaceDefaultHook($str) +{ + global $default_hooks,$default_replace; + return (str_replace($default_hooks,$default_replace,$str)); +} + +//Displays error and success messages +function resultBlock($errors,$successes){ + //Error block + if(count($errors) > 0) + { + echo "
    + [X] +
      "; + foreach($errors as $error) + { + echo "
    • ".$error."
    • "; + } + echo "
    "; + echo "
    "; + } + //Success block + if(count($successes) > 0) + { + echo "
    + [X] +
      "; + foreach($successes as $success) + { + echo "
    • ".$success."
    • "; + } + echo "
    "; + echo "
    "; + } +} + +//Completely sanitizes text +function sanitize($str) +{ + return strtolower(strip_tags(trim(($str)))); +} + +//Functions that interact mainly with .users table +//------------------------------------------------------------------------------ + +//Delete a defined array of users +function deleteUsers($users) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("DELETE FROM ".$db_table_prefix."users + WHERE user_id = ?"); + $stmt2 = $mysqli->prepare("DELETE FROM ".$db_table_prefix."user_permission_matches + WHERE user_id = ?"); + foreach($users as $id){ + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt2->bind_param("i", $id); + $stmt2->execute(); + $i++; + } + $stmt->close(); + $stmt2->close(); + return $i; +} + +//Check if a display name exists in the DB +function displayNameExists($displayname) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE + display_name = ? + LIMIT 1"); + $stmt->bind_param("s", $displayname); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Check if an email exists in the DB +function emailExists($email) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE + email = ? + LIMIT 1"); + $stmt->bind_param("s", $email); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Check if a user name and email belong to the same user +function emailUsernameLinked($email,$username) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE username = ? + AND + email = ? + LIMIT 1 + "); + $stmt->bind_param("ss", $username, $email); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Retrieve information for all users +function fetchAllUsers() +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + user_id id, + username user_name, + display_name, + md5_pass password, + email, + activation_token, + last_activation_request, + lost_password_request, + active, + title, + sign_up_stamp, + last_sign_in_stamp + FROM ".$db_table_prefix."users"); + $stmt->execute(); + $stmt->bind_result($id, $user, $display, $password, $email, $token, $activationRequest, $passwordRequest, $active, $title, $signUp, $signIn); + + while ($stmt->fetch()){ + $row[] = array('id' => $id, 'user_name' => $user, 'display_name' => $display, 'password' => $password, 'email' => $email, 'activation_token' => $token, 'last_activation_request' => $activationRequest, 'lost_password_request' => $passwordRequest, 'active' => $active, 'title' => $title, 'sign_up_stamp' => $signUp, 'last_sign_in_stamp' => $signIn); + } + $stmt->close(); + return ($row); +} + +//Retrieve complete user information by username, token or ID +function fetchUserDetails($username=NULL,$token=NULL, $id=NULL) +{ + global $mysqli,$db_table_prefix; + if($username!=NULL) + { + $stmt = $mysqli->prepare("SELECT + user_id id, + username user_name, + display_name, + md5_pass password, + email, + activation_token, + last_activation_request, + lost_password_request, + active, + title, + sign_up_stamp, + last_sign_in_stamp + FROM ".$db_table_prefix."users + WHERE + username = ? + LIMIT 1"); + $stmt->bind_param("s", $username); + } + elseif($id!=NULL) + { + $stmt = $mysqli->prepare("SELECT + user_id id, + username user_name, + display_name, + md5_pass password, + email, + activation_token, + last_activation_request, + lost_password_request, + active, + title, + sign_up_stamp, + last_sign_in_stamp + FROM ".$db_table_prefix."users + WHERE + user_id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + } + else + { + $stmt = $mysqli->prepare("SELECT + user_id id, + username user_name, + display_name, + md5_pass password, + email, + activation_token, + last_activation_request, + lost_password_request, + active, + title, + sign_up_stamp, + last_sign_in_stamp + FROM ".$db_table_prefix."users + WHERE + activation_token = ? + LIMIT 1"); + $stmt->bind_param("s", $token); + } + $stmt->execute(); + $stmt->bind_result($id, $user, $display, $password, $email, $token, $activationRequest, $passwordRequest, $active, $title, $signUp, $signIn); + while ($stmt->fetch()){ + $row = array('id' => $id, 'user_name' => $user, 'display_name' => $display, 'password' => $password, 'email' => $email, 'activation_token' => $token, 'last_activation_request' => $activationRequest, 'lost_password_request' => $passwordRequest, 'active' => $active, 'title' => $title, 'sign_up_stamp' => $signUp, 'last_sign_in_stamp' => $signIn); + } + $stmt->close(); + return ($row); +} + +//Toggle if lost password request flag on or off +function flagLostPasswordRequest($username,$value) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET lost_password_request = ? + WHERE + username = ? + LIMIT 1 + "); + $stmt->bind_param("ss", $value, $username); + return $stmt->execute(); + $stmt->close(); +} + +//Check if a user is logged in +function isUserLoggedIn() +{ + global $loggedInUser,$mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + user_id id, + md5_pass password + FROM ".$db_table_prefix."users + WHERE + user_id = ? + AND + md5_pass = ? + AND + active = 1 + LIMIT 1"); + $stmt->bind_param("is", $loggedInUser->user_id, $loggedInUser->hash_pw); + $stmt->execute(); + $stmt->store_result(); + if($loggedInUser == NULL) + { + return false; + } + else + { + if ($stmt->num_rows > 0) + { + return true; + } + else + { + destroySession("userCakeUser"); + return false; + } + } + $stmt->close(); +} + +//Change a user from inactive to active +function setUserActive($token) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET active = 1 + WHERE + activation_token = ? + LIMIT 1"); + $stmt->bind_param("s", $token); + return $stmt->execute(); + $stmt->close(); +} + +//Change a user's display name +function updateDisplayName($id, $display) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET display_name = ? + WHERE + user_id = ? + LIMIT 1"); + $stmt->bind_param("si", $display, $id); + return $stmt->execute(); + $stmt->close(); +} + +//Update a user's email +function updateEmail($id, $email) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET + email = ? + WHERE + user_id = ?"); + $stmt->bind_param("si", $email, $id); + return $stmt->execute(); + $stmt->close(); +} + +//Input new activation token, and update the time of the most recent activation request +function updateLastActivationRequest($new_activation_token,$username,$email) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET activation_token = ?, + last_activation_request = ? + WHERE email = ? + AND + username = ?"); + $stmt->bind_param("ssss", $new_activation_token, time(), $email, $username); + return $stmt->execute(); + $stmt->close(); +} + +//Generate a random password, and new token +function updatePasswordFromToken($pass,$token) +{ + global $mysqli,$db_table_prefix; + $new_activation_token = generateActivationToken(); + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET md5_pass = ?, + activation_token = ? + WHERE + activation_token = ?"); + $stmt->bind_param("sss", $pass, $new_activation_token, $token); + return $stmt->execute(); + $stmt->close(); +} + +//Update a user's title +function updateTitle($id, $title) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."users + SET + title = ? + WHERE + user_id = ?"); + $stmt->bind_param("si", $title, $id); + return $stmt->execute(); + $stmt->close(); +} + +//Check if a user ID exists in the DB +function userIdExists($id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE + user_id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Checks if a username exists in the DB +function usernameExists($username) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE + username = ? + LIMIT 1"); + $stmt->bind_param("s", $username); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Check if activation token exists in DB +function validateActivationToken($token,$lostpass=NULL) +{ + global $mysqli,$db_table_prefix; + if($lostpass == NULL) + { + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE active = 0 + AND + activation_token = ? + LIMIT 1"); + } + else + { + $stmt = $mysqli->prepare("SELECT active + FROM ".$db_table_prefix."users + WHERE active = 1 + AND + activation_token = ? + AND + lost_password_request = 1 + LIMIT 1"); + } + $stmt->bind_param("s", $token); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Functions that interact mainly with .permissions table +//------------------------------------------------------------------------------ + +//Create a permission level in DB +function createPermission($permission) { + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."permissions ( + name + ) + VALUES ( + ? + )"); + $stmt->bind_param("s", $permission); + return $stmt->execute(); + $stmt->close(); +} + +//Delete a permission level from the DB +function deletePermission($permission) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("DELETE FROM ".$db_table_prefix."permissions + WHERE id = ?"); + $stmt2 = $mysqli->prepare("DELETE FROM ".$db_table_prefix."user_permission_matches + WHERE permission_id = ?"); + $stmt3 = $mysqli->prepare("DELETE FROM ".$db_table_prefix."permission_page_matches + WHERE permission_id = ?"); + foreach($permission as $id){ + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt2->bind_param("i", $id); + $stmt2->execute(); + $stmt3->bind_param("i", $id); + $stmt3->execute(); + $i++; + } + $stmt->close(); + $stmt2->close(); + $stmt3->close(); + return $i; +} + +//Retrieve information for all permission levels +function fetchAllPermissions() +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + name + FROM ".$db_table_prefix."permissions"); + $stmt->execute(); + $stmt->bind_result($id, $name); + while ($stmt->fetch()){ + $row[] = array('id' => $id, 'name' => $name); + } + $stmt->close(); + return ($row); +} + +//Retrieve information for a single permission level +function fetchPermissionDetails($id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + name + FROM ".$db_table_prefix."permissions + WHERE + id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->bind_result($id, $name); + while ($stmt->fetch()){ + $row = array('id' => $id, 'name' => $name); + } + $stmt->close(); + return ($row); +} + +//Check if a permission level ID exists in the DB +function permissionIdExists($id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT id + FROM ".$db_table_prefix."permissions + WHERE + id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->store_result(); + + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Check if a permission level name exists in the DB +function permissionNameExists($permission) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT id + FROM ".$db_table_prefix."permissions + WHERE + name = ? + LIMIT 1"); + $stmt->bind_param("s", $permission); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Change a permission level's name +function updatePermissionName($id, $name) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."permissions + SET name = ? + WHERE + id = ? + LIMIT 1"); + $stmt->bind_param("si", $name, $id); + return $stmt->execute(); + $stmt->close(); +} + +//Functions that interact mainly with .user_permission_matches table +//------------------------------------------------------------------------------ + +//Match permission level(s) with user(s) +function addPermission($permission, $user) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."user_permission_matches ( + permission_id, + user_id + ) + VALUES ( + ?, + ? + )"); + if (is_array($permission)){ + foreach($permission as $id){ + $stmt->bind_param("ii", $id, $user); + $stmt->execute(); + $i++; + } + } + elseif (is_array($user)){ + foreach($user as $id){ + $stmt->bind_param("ii", $permission, $id); + $stmt->execute(); + $i++; + } + } + else { + $stmt->bind_param("ii", $permission, $user); + $stmt->execute(); + $i++; + } + $stmt->close(); + return $i; +} + +//Retrieve information for all user/permission level matches +function fetchAllMatches() +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + user_id, + permission_id + FROM ".$db_table_prefix."user_permission_matches"); + $stmt->execute(); + $stmt->bind_result($id, $user, $permission); + while ($stmt->fetch()){ + $row[] = array('id' => $id, 'user_id' => $user, 'permission_id' => $permission); + } + $stmt->close(); + return ($row); +} + +//Retrieve list of permission levels a user has +function fetchUserPermissions($user_id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + permission_id + FROM ".$db_table_prefix."user_permission_matches + WHERE user_id = ? + "); + $stmt->bind_param("i", $user_id); + $stmt->execute(); + $stmt->bind_result($id, $permission); + while ($stmt->fetch()){ + $row[$permission] = array('id' => $id, 'permission_id' => $permission); + } + $stmt->close(); + if (isset($row)){ + return ($row); + } +} + +//Retrieve list of users who have a permission level +function fetchPermissionUsers($permission_id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT id, user_id + FROM ".$db_table_prefix."user_permission_matches + WHERE permission_id = ? + "); + $stmt->bind_param("i", $permission_id); + $stmt->execute(); + $stmt->bind_result($id, $user); + while ($stmt->fetch()){ + $row[$user] = array('id' => $id, 'user_id' => $user); + } + $stmt->close(); + if (isset($row)){ + return ($row); + } +} + +//Unmatch permission level(s) from user(s) +function removePermission($permission, $user) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("DELETE FROM ".$db_table_prefix."user_permission_matches + WHERE permission_id = ? + AND user_id =?"); + if (is_array($permission)){ + foreach($permission as $id){ + $stmt->bind_param("ii", $id, $user); + $stmt->execute(); + $i++; + } + } + elseif (is_array($user)){ + foreach($user as $id){ + $stmt->bind_param("ii", $permission, $id); + $stmt->execute(); + $i++; + } + } + else { + $stmt->bind_param("ii", $permission, $user); + $stmt->execute(); + $i++; + } + $stmt->close(); + return $i; +} + +//Functions that interact mainly with .configuration table +//------------------------------------------------------------------------------ + +//Update configuration table +function updateConfig($id, $value) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."configuration + SET + value = ? + WHERE + id = ?"); + foreach ($id as $cfg){ + $stmt->bind_param("si", $value[$cfg], $cfg); + return $stmt->execute(); + } + $stmt->close(); +} + +//Functions that interact mainly with .pages table +//------------------------------------------------------------------------------ + +//Add a page to the DB +function createPages($pages) { + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."pages ( + page + ) + VALUES ( + ? + )"); + foreach($pages as $page){ + $stmt->bind_param("s", $page); + $stmt->execute(); + } + $stmt->close(); +} + +//Delete a page from the DB +function deletePages($pages) { + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("DELETE FROM ".$db_table_prefix."pages + WHERE id = ?"); + $stmt2 = $mysqli->prepare("DELETE FROM ".$db_table_prefix."permission_page_matches + WHERE page_id = ?"); + foreach($pages as $id){ + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt2->bind_param("i", $id); + $stmt2->execute(); + } + $stmt->close(); + $stmt2->close(); +} + +//Fetch information on all pages +function fetchAllPages() +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + page, + private + FROM ".$db_table_prefix."pages"); + $stmt->execute(); + $stmt->bind_result($id, $page, $private); + while ($stmt->fetch()){ + $row[$page] = array('id' => $id, 'page' => $page, 'private' => $private); + } + $stmt->close(); + if (isset($row)){ + return ($row); + } +} + +//Fetch information for a specific page +function fetchPageDetails($id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + page, + private + FROM ".$db_table_prefix."pages + WHERE + id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->bind_result($id, $page, $private); + while ($stmt->fetch()){ + $row = array('id' => $id, 'page' => $page, 'private' => $private); + } + $stmt->close(); + return ($row); +} + +//Check if a page ID exists +function pageIdExists($id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT private + FROM ".$db_table_prefix."pages + WHERE + id = ? + LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->store_result(); + if ($stmt->num_rows > 0) + { + return true; + } + else + { + return false; + } + $stmt->close(); +} + +//Toggle private/public setting of a page +function updatePrivate($id, $private) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("UPDATE ".$db_table_prefix."pages + SET + private = ? + WHERE + id = ?"); + $stmt->bind_param("ii", $private, $id); + return $stmt->execute(); + $stmt->close(); +} + +//Functions that interact mainly with .permission_page_matches table +//------------------------------------------------------------------------------ + +//Match permission level(s) with page(s) +function addPage($page, $permission) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("INSERT INTO ".$db_table_prefix."permission_page_matches ( + permission_id, + page_id + ) + VALUES ( + ?, + ? + )"); + if (is_array($permission)){ + foreach($permission as $id){ + $stmt->bind_param("ii", $id, $page); + $stmt->execute(); + $i++; + } + } + elseif (is_array($page)){ + foreach($page as $id){ + $stmt->bind_param("ii", $permission, $id); + $stmt->execute(); + $i++; + } + } + else { + $stmt->bind_param("ii", $permission, $page); + $stmt->execute(); + $i++; + } + $stmt->close(); + return $i; +} + +//Retrieve list of permission levels that can access a page +function fetchPagePermissions($page_id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + permission_id + FROM ".$db_table_prefix."permission_page_matches + WHERE page_id = ? + "); + $stmt->bind_param("i", $page_id); + $stmt->execute(); + $stmt->bind_result($id, $permission); + while ($stmt->fetch()){ + $row[$permission] = array('id' => $id, 'permission_id' => $permission); + } + $stmt->close(); + if (isset($row)){ + return ($row); + } +} + +//Retrieve list of pages that a permission level can access +function fetchPermissionPages($permission_id) +{ + global $mysqli,$db_table_prefix; + $stmt = $mysqli->prepare("SELECT + id, + page_id + FROM ".$db_table_prefix."permission_page_matches + WHERE permission_id = ? + "); + $stmt->bind_param("i", $permission_id); + $stmt->execute(); + $stmt->bind_result($id, $page); + while ($stmt->fetch()){ + $row[$page] = array('id' => $id, 'permission_id' => $page); + } + $stmt->close(); + if (isset($row)){ + return ($row); + } +} + +//Unmatched permission and page +function removePage($page, $permission) { + global $mysqli,$db_table_prefix; + $i = 0; + $stmt = $mysqli->prepare("DELETE FROM ".$db_table_prefix."permission_page_matches + WHERE page_id = ? + AND permission_id =?"); + if (is_array($page)){ + foreach($page as $id){ + $stmt->bind_param("ii", $id, $permission); + $stmt->execute(); + $i++; + } + } + elseif (is_array($permission)){ + foreach($permission as $id){ + $stmt->bind_param("ii", $page, $id); + $stmt->execute(); + $i++; + } + } + else { + $stmt->bind_param("ii", $permission, $user); + $stmt->execute(); + $i++; + } + $stmt->close(); + return $i; +} + +//Check if a user has access to a page +function securePage($uri){ + //Separate document name from uri + $tokens = explode('/', $uri); + $page = $tokens[sizeof($tokens)-1]; + global $mysqli,$db_table_prefix,$loggedInUser; + //retrieve page details + $stmt = $mysqli->prepare("SELECT + id, + page, + private + FROM ".$db_table_prefix."pages + WHERE + page = ? + LIMIT 1"); + $stmt->bind_param("s", $page); + $stmt->execute(); + $stmt->bind_result($id, $page, $private); + while ($stmt->fetch()){ + $pageDetails = array('id' => $id, 'page' => $page, 'private' => $private); + } + $stmt->close(); + //If page does not exist in DB, allow access + if (empty($pageDetails)){ + return true; + } + //If page is public, allow access + elseif ($pageDetails['private'] == 0) { + return true; + } + //If user is not logged in, deny access + elseif(!isUserLoggedIn()) + { + header("Location: login.php"); + return false; + } + else { + //Retrieve list of permission levels with access to page + $stmt = $mysqli->prepare("SELECT + permission_id + FROM ".$db_table_prefix."permission_page_matches + WHERE page_id = ? + "); + $stmt->bind_param("i", $pageDetails['id']); + $stmt->execute(); + $stmt->bind_result($permission); + while ($stmt->fetch()){ + $pagePermissions[] = $permission; + } + $stmt->close(); + //Check if user's permission levels allow access to page + if ($loggedInUser->checkPermission($pagePermissions)){ + return true; + } + else { + header("Location: account.php"); + return false; + } + } +} + +?> diff --git a/signin/models/header.php b/signin/models/header.php new file mode 100644 index 0000000..cf3d80e --- /dev/null +++ b/signin/models/header.php @@ -0,0 +1,17 @@ + + + + +".$websiteName." + + +"; + +?> diff --git a/signin/models/languages/en.php b/signin/models/languages/en.php new file mode 100644 index 0000000..7c591da --- /dev/null +++ b/signin/models/languages/en.php @@ -0,0 +1,118 @@ + "Please enter your username", + "ACCOUNT_SPECIFY_PASSWORD" => "Please enter your password", + "ACCOUNT_SPECIFY_EMAIL" => "Please enter your email address", + "ACCOUNT_INVALID_EMAIL" => "Invalid email address", + "ACCOUNT_USER_OR_EMAIL_INVALID" => "Username or email address is invalid", + "ACCOUNT_USER_OR_PASS_INVALID" => "Username or password is invalid", + "ACCOUNT_ALREADY_ACTIVE" => "Your account is already activated", + "ACCOUNT_INACTIVE" => "Your account is in-active. Check your emails / spam folder for account activation instructions", + "ACCOUNT_USER_CHAR_LIMIT" => "Your username must be between %m1% and %m2% characters in length", + "ACCOUNT_DISPLAY_CHAR_LIMIT" => "Your display name must be between %m1% and %m2% characters in length", + "ACCOUNT_PASS_CHAR_LIMIT" => "Your password must be between %m1% and %m2% characters in length", + "ACCOUNT_TITLE_CHAR_LIMIT" => "Titles must be between %m1% and %m2% characters in length", + "ACCOUNT_PASS_MISMATCH" => "Your password and confirmation password must match", + "ACCOUNT_DISPLAY_INVALID_CHARACTERS" => "Display name can only include alpha-numeric characters", + "ACCOUNT_USERNAME_IN_USE" => "Username %m1% is already in use", + "ACCOUNT_DISPLAYNAME_IN_USE" => "Display name %m1% is already in use", + "ACCOUNT_EMAIL_IN_USE" => "Email %m1% is already in use", + "ACCOUNT_LINK_ALREADY_SENT" => "An activation email has already been sent to this email address in the last %m1% hour(s)", + "ACCOUNT_NEW_ACTIVATION_SENT" => "We have emailed you a new activation link, please check your email", + "ACCOUNT_SPECIFY_NEW_PASSWORD" => "Please enter your new password", + "ACCOUNT_SPECIFY_CONFIRM_PASSWORD" => "Please confirm your new password", + "ACCOUNT_NEW_PASSWORD_LENGTH" => "New password must be between %m1% and %m2% characters in length", + "ACCOUNT_PASSWORD_INVALID" => "Current password doesn't match the one we have on record", + "ACCOUNT_DETAILS_UPDATED" => "Account details updated", + "ACCOUNT_ACTIVATION_MESSAGE" => "You will need to activate your account before you can login. Please follow the link below to activate your account. \n\n + %m1%activate-account.php?token=%m2%", + "ACCOUNT_ACTIVATION_COMPLETE" => "You have successfully activated your account. You can now login here.", + "ACCOUNT_REGISTRATION_COMPLETE_TYPE1" => "You have successfully registered. You can now login here.", + "ACCOUNT_REGISTRATION_COMPLETE_TYPE2" => "You have successfully registered. You will soon receive an activation email. + You must activate your account before logging in.", + "ACCOUNT_PASSWORD_NOTHING_TO_UPDATE" => "You cannot update with the same password", + "ACCOUNT_PASSWORD_UPDATED" => "Account password updated", + "ACCOUNT_EMAIL_UPDATED" => "Account email updated", + "ACCOUNT_TOKEN_NOT_FOUND" => "Token does not exist / Account is already activated", + "ACCOUNT_USER_INVALID_CHARACTERS" => "Username can only include alpha-numeric characters", + "ACCOUNT_DELETIONS_SUCCESSFUL" => "You have successfully deleted %m1% users", + "ACCOUNT_MANUALLY_ACTIVATED" => "%m1%'s account has been manually activated", + "ACCOUNT_DISPLAYNAME_UPDATED" => "Displayname changed to %m1%", + "ACCOUNT_TITLE_UPDATED" => "%m1%'s title changed to %m2%", + "ACCOUNT_PERMISSION_ADDED" => "Added access to %m1% permission levels", + "ACCOUNT_PERMISSION_REMOVED" => "Removed access from %m1% permission levels", + )); + +//Configuration +$lang = array_merge($lang,array( + "CONFIG_NAME_CHAR_LIMIT" => "Site name must be between %m1% and %m2% characters in length", + "CONFIG_URL_CHAR_LIMIT" => "Site name must be between %m1% and %m2% characters in length", + "CONFIG_EMAIL_CHAR_LIMIT" => "Site name must be between %m1% and %m2% characters in length", + "CONFIG_ACTIVATION_TRUE_FALSE" => "Email activation must be either `true` or `false`", + "CONFIG_ACTIVATION_RESEND_RANGE" => "Activation Threshold must be between %m1% and %m2% hours", + "CONFIG_LANGUAGE_CHAR_LIMIT" => "Language path must be between %m1% and %m2% characters in length", + "CONFIG_LANGUAGE_INVALID" => "There is no file for the language key `%m1%`", + "CONFIG_TEMPLATE_CHAR_LIMIT" => "Template path must be between %m1% and %m2% characters in length", + "CONFIG_TEMPLATE_INVALID" => "There is no file for the template key `%m1%`", + "CONFIG_EMAIL_INVALID" => "The email you have entered is not valid", + "CONFIG_INVALID_URL_END" => "Please include the ending / in your site's URL", + "CONFIG_UPDATE_SUCCESSFUL" => "Your site's configuration has been updated. You may need to load a new page for all the settings to take effect", + )); + +//Forgot Password +$lang = array_merge($lang,array( + "FORGOTPASS_INVALID_TOKEN" => "Your activation token is not valid", + "FORGOTPASS_NEW_PASS_EMAIL" => "We have emailed you a new password", + "FORGOTPASS_REQUEST_CANNED" => "Lost password request cancelled", + "FORGOTPASS_REQUEST_EXISTS" => "There is already a outstanding lost password request on this account", + "FORGOTPASS_REQUEST_SUCCESS" => "We have emailed you instructions on how to regain access to your account", + )); + +//Mail +$lang = array_merge($lang,array( + "MAIL_ERROR" => "Fatal error attempting mail, contact your server administrator", + "MAIL_TEMPLATE_BUILD_ERROR" => "Error building email template", + "MAIL_TEMPLATE_DIRECTORY_ERROR" => "Unable to open mail-templates directory. Perhaps try setting the mail directory to %m1%", + "MAIL_TEMPLATE_FILE_EMPTY" => "Template file is empty... nothing to send", + )); + +//Miscellaneous +$lang = array_merge($lang,array( + "CAPTCHA_FAIL" => "Failed security question", + "CONFIRM" => "Confirm", + "DENY" => "Deny", + "SUCCESS" => "Success", + "ERROR" => "Error", + "NOTHING_TO_UPDATE" => "Nothing to update", + "SQL_ERROR" => "Fatal SQL error", + "FEATURE_DISABLED" => "This feature is currently disabled", + "PAGE_PRIVATE_TOGGLED" => "This page is now %m1%", + "PAGE_ACCESS_REMOVED" => "Page access removed for %m1% permission level(s)", + "PAGE_ACCESS_ADDED" => "Page access added for %m1% permission level(s)", + )); + +//Permissions +$lang = array_merge($lang,array( + "PERMISSION_CHAR_LIMIT" => "Permission names must be between %m1% and %m2% characters in length", + "PERMISSION_NAME_IN_USE" => "Permission name %m1% is already in use", + "PERMISSION_DELETIONS_SUCCESSFUL" => "Successfully deleted %m1% permission level(s)", + "PERMISSION_CREATION_SUCCESSFUL" => "Successfully created the permission level `%m1%`", + "PERMISSION_NAME_UPDATE" => "Permission level name changed to `%m1%`", + "PERMISSION_REMOVE_PAGES" => "Successfully removed access to %m1% page(s)", + "PERMISSION_ADD_PAGES" => "Successfully added access to %m1% page(s)", + "PERMISSION_REMOVE_USERS" => "Successfully removed %m1% user(s)", + "PERMISSION_ADD_USERS" => "Successfully added %m1% user(s)", + )); +?> \ No newline at end of file diff --git a/signin/models/mail-templates/lost-password-request.txt b/signin/models/mail-templates/lost-password-request.txt new file mode 100644 index 0000000..ef0819f --- /dev/null +++ b/signin/models/mail-templates/lost-password-request.txt @@ -0,0 +1,9 @@ +Hello #USERNAME# + +A lost password request has been submitted for your account on #DATE#. + +To confirm / deny this request click one of the below links + +#CONFIRM-URL# + +#DENY-URL# diff --git a/signin/models/mail-templates/new-registration.txt b/signin/models/mail-templates/new-registration.txt new file mode 100644 index 0000000..e1841fb --- /dev/null +++ b/signin/models/mail-templates/new-registration.txt @@ -0,0 +1,6 @@ +Hello #USERNAME# + +Thank you for joining our website #WEBSITENAME# +#ACTIVATION-MESSAGE + +-Regards \ No newline at end of file diff --git a/signin/models/mail-templates/resend-activation.txt b/signin/models/mail-templates/resend-activation.txt new file mode 100644 index 0000000..a0dab1c --- /dev/null +++ b/signin/models/mail-templates/resend-activation.txt @@ -0,0 +1,9 @@ +Hello #USERNAME# + +We have received a new activation request for your account. Please follow the link below to activate. + +If you did not request this e-mail, please disregard this message. + +#ACTIVATION-URL + +-Regards \ No newline at end of file diff --git a/signin/models/mail-templates/your-lost-password.txt b/signin/models/mail-templates/your-lost-password.txt new file mode 100644 index 0000000..38f505c --- /dev/null +++ b/signin/models/mail-templates/your-lost-password.txt @@ -0,0 +1,9 @@ +Hello #USERNAME# + +We have set up a temporary password for your account at #WEBSITENAME#. + +Please login at #WEBSITEURL#login.php as soon as possible and change this password to something you will remember. + +Your Password: #GENERATED-PASS# + +-Regards diff --git a/signin/models/site-templates/default.css b/signin/models/site-templates/default.css new file mode 100644 index 0000000..71a6da0 --- /dev/null +++ b/signin/models/site-templates/default.css @@ -0,0 +1,112 @@ +html, body { + margin: 0px; + background: #fff; + font-family:Verdana, Arial, Helvetica, sans-serif; + font-size:0.95em; + color:#4d4948; +} + +h1 { + margin: 0; + text-align: center; + font-size: 150%; + padding: 0px; +} + +h2 { + margin: 0; + text-align: center; + font-size: 120%; + padding: 0px; +} + +h3 { + margin: 0; + font-size: 105%; + padding: 0px; +} + +a { + color:#4d4948; +} + +#top { + margin: 0 auto 0 auto; + background:url('images/top-bg.jpg') repeat-x; + width:100%; + height:115px; +} + +#logo { + margin: 0 auto 0 auto; + background:url('images/latest-build.gif'); + width: 155px; + height: 124px; +} + +#content { + margin: 0 auto 0 auto; + width: 95%; +} + +#content #left-nav { + width:15%; + float:left; + font-size:95%; +} + +#content #left-nav ul { + padding:0 0 50px 0; + margin:0; +} + +#content #left-nav ul li { + padding:0; + margin:0; + list-style:none; +} + +#content #left-nav ul li a { + text-decoration:none; +} + +#content #left-nav ul li a:hover { + color:#ff0505; + text-decoration:underline; +} + +#content #main { + float:left; + width:85%; + font-size:90%; +} + +#content #main #regbox { + padding: 0 0 0 0; +} + +#content #main #regbox label { + width:100px; + float:left; +} + +table.admin td { + vertical-align: top; +} + +#error { + display:block; + margin:5px; + color:#4d4948; + background-color:#fffebe; + border: 1px solid #cbcbcb; + font-size:90%; +} + +#success { + margin:5px; + color:#4d4948; + background-color:#bce9b5; + border: 1px solid #7ace6c; + font-size:90%; +} diff --git a/signin/models/site-templates/images/latest-build.gif b/signin/models/site-templates/images/latest-build.gif new file mode 100644 index 0000000..616c805 Binary files /dev/null and b/signin/models/site-templates/images/latest-build.gif differ diff --git a/signin/models/site-templates/images/top-bg.jpg b/signin/models/site-templates/images/top-bg.jpg new file mode 100644 index 0000000..1adf673 Binary files /dev/null and b/signin/models/site-templates/images/top-bg.jpg differ diff --git a/signin/register.php b/signin/register.php new file mode 100644 index 0000000..fc4fcbd --- /dev/null +++ b/signin/register.php @@ -0,0 +1,148 @@ +status) + { + if($user->username_taken) $errors[] = lang("ACCOUNT_USERNAME_IN_USE",array($username)); + if($user->displayname_taken) $errors[] = lang("ACCOUNT_DISPLAYNAME_IN_USE",array($displayname)); + if($user->email_taken) $errors[] = lang("ACCOUNT_EMAIL_IN_USE",array($email)); + } + else + { + //Attempt to add the user to the database, carry out finishing tasks like emailing the user (if required) + if(!$user->userCakeAddUser()) + { + if($user->mail_failure) $errors[] = lang("MAIL_ERROR"); + if($user->sql_failure) $errors[] = lang("SQL_ERROR"); + } + } + } + if(count($errors) == 0) { + $successes[] = $user->success; + $username = ""; + $displayname = ""; + $password = ""; + $email = ""; + } +} + +require_once("models/header.php"); +echo " + +
    +
    +
    +

    UserCake

    +

    Register

    + +
    "; +include("left-nav.php"); +echo " +
    + +
    "; + +echo resultBlock($errors,$successes); + +echo " +
    +
    + +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    + + +

    +
    + +
    +
    +
    + +"; +?> diff --git a/signin/resend-activation.php b/signin/resend-activation.php new file mode 100644 index 0000000..1eed6f1 --- /dev/null +++ b/signin/resend-activation.php @@ -0,0 +1,165 @@ + array("#ACTIVATION-URL","#USERNAME#"), + "subjectStrs" => array($activation_url,$userdetails["display_name"]) + ); + + if(!$mail->newTemplateMsg("resend-activation.txt",$hooks)) + { + $errors[] = lang("MAIL_TEMPLATE_BUILD_ERROR"); + } + else + { + if(!$mail->sendMail($userdetails["email"],"Activate your ".$websiteName." Account")) + { + $errors[] = lang("MAIL_ERROR"); + } + else + { + //Success, user details have been updated in the db now mail this information out. + $successes[] = lang("ACCOUNT_NEW_ACTIVATION_SENT"); + } + } + } + } + } + } + } +} + +//Prevent the user visiting the logged in page if he/she is already logged in +if(isUserLoggedIn()) { header("Location: account.php"); die(); } + +require_once("models/header.php"); + +echo " + +
    +
    +
    +

    UserCake

    +

    Resend Activation

    +
    "; + +include("left-nav.php"); + +echo " +
    +
    "; + +echo resultBlock($errors,$successes); + +echo "
    "; + +//Show disabled if email activation not required +if(!$emailActivation) +{ + echo lang("FEATURE_DISABLED"); +} +else +{ + echo "
    +

    + + +

    +

    + + +

    +

    + + +

    +
    "; +} + +echo " +
    +
    +
    +
    + +"; + +?> diff --git a/signin/user_settings.php b/signin/user_settings.php new file mode 100644 index 0000000..759041b --- /dev/null +++ b/signin/user_settings.php @@ -0,0 +1,151 @@ +hash_pw); + + if (trim($password) == ""){ + $errors[] = lang("ACCOUNT_SPECIFY_PASSWORD"); + } + else if($entered_pass != $loggedInUser->hash_pw) + { + //No match + $errors[] = lang("ACCOUNT_PASSWORD_INVALID"); + } + if($email != $loggedInUser->email) + { + if(trim($email) == "") + { + $errors[] = lang("ACCOUNT_SPECIFY_EMAIL"); + } + else if(!isValidEmail($email)) + { + $errors[] = lang("ACCOUNT_INVALID_EMAIL"); + } + else if(emailExists($email)) + { + $errors[] = lang("ACCOUNT_EMAIL_IN_USE", array($email)); + } + + //End data validation + if(count($errors) == 0) + { + $loggedInUser->updateEmail($email); + $successes[] = lang("ACCOUNT_EMAIL_UPDATED"); + } + } + + if ($password_new != "" OR $password_confirm != "") + { + if(trim($password_new) == "") + { + $errors[] = lang("ACCOUNT_SPECIFY_NEW_PASSWORD"); + } + else if(trim($password_confirm) == "") + { + $errors[] = lang("ACCOUNT_SPECIFY_CONFIRM_PASSWORD"); + } + else if(minMaxRange($pass_min_len,$pass_max_len,$password_new)) + { + $errors[] = lang("ACCOUNT_NEW_PASSWORD_LENGTH",array($pass_min_len, $pass_max_len)); + } + else if($password_new != $password_confirm) + { + $errors[] = lang("ACCOUNT_PASS_MISMATCH"); + } + + //End data validation + if(count($errors) == 0) + { + //Also prevent updating if someone attempts to update with the same password + $entered_pass_new = generateHash($password_new,$loggedInUser->hash_pw); + + if($entered_pass_new == $loggedInUser->hash_pw) + { + //Don't update, this fool is trying to update with the same password ¬¬ + $errors[] = lang("ACCOUNT_PASSWORD_NOTHING_TO_UPDATE"); + } + else + { + //This function will create the new hash and update the hash_pw property. + $loggedInUser->updatePassword($password_new); + $successes[] = lang("ACCOUNT_PASSWORD_UPDATED"); + } + } + } + if(count($errors) == 0 AND count($successes) == 0){ + $errors[] = lang("NOTHING_TO_UPDATE"); + } +} + +require_once("models/header.php"); +echo " + +
    +
    +
    +

    UserCake

    +

    User Settings

    +
    "; +include("left-nav.php"); + +echo " +
    +
    "; + +echo resultBlock($errors,$successes); + +echo " +
    +
    +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    +

    + + +

    +
    +
    +
    +
    +
    + +"; + +?> diff --git a/skins/header-blue.jpg b/skins/header-blue.jpg new file mode 100644 index 0000000..bf28b1a Binary files /dev/null and b/skins/header-blue.jpg differ diff --git a/skins/header-blue_144.jpg b/skins/header-blue_144.jpg new file mode 100644 index 0000000..cf909ed Binary files /dev/null and b/skins/header-blue_144.jpg differ diff --git a/skins/header-blue_288.jpg b/skins/header-blue_288.jpg new file mode 100644 index 0000000..2fb3cb4 Binary files /dev/null and b/skins/header-blue_288.jpg differ diff --git a/skins/header-brown.jpg b/skins/header-brown.jpg new file mode 100644 index 0000000..d741973 Binary files /dev/null and b/skins/header-brown.jpg differ diff --git a/skins/header-brown_144.jpg b/skins/header-brown_144.jpg new file mode 100644 index 0000000..989de04 Binary files /dev/null and b/skins/header-brown_144.jpg differ diff --git a/skins/header-brown_288.jpg b/skins/header-brown_288.jpg new file mode 100644 index 0000000..b8fcdab Binary files /dev/null and b/skins/header-brown_288.jpg differ diff --git a/skins/header-green.jpg b/skins/header-green.jpg new file mode 100644 index 0000000..9cba979 Binary files /dev/null and b/skins/header-green.jpg differ diff --git a/skins/header-green_144.jpg b/skins/header-green_144.jpg new file mode 100644 index 0000000..c10bf9a Binary files /dev/null and b/skins/header-green_144.jpg differ diff --git a/skins/header-green_288.jpg b/skins/header-green_288.jpg new file mode 100644 index 0000000..f49d0b9 Binary files /dev/null and b/skins/header-green_288.jpg differ diff --git a/skins/header-grey.jpg b/skins/header-grey.jpg new file mode 100644 index 0000000..23bf3cb Binary files /dev/null and b/skins/header-grey.jpg differ diff --git a/skins/header-grey_144.jpg b/skins/header-grey_144.jpg new file mode 100644 index 0000000..4732d07 Binary files /dev/null and b/skins/header-grey_144.jpg differ diff --git a/skins/header-grey_288.jpg b/skins/header-grey_288.jpg new file mode 100644 index 0000000..b74e31a Binary files /dev/null and b/skins/header-grey_288.jpg differ diff --git a/skins/header-pink.jpg b/skins/header-pink.jpg new file mode 100644 index 0000000..a970265 Binary files /dev/null and b/skins/header-pink.jpg differ diff --git a/skins/header-pink_144.jpg b/skins/header-pink_144.jpg new file mode 100644 index 0000000..8769d11 Binary files /dev/null and b/skins/header-pink_144.jpg differ diff --git a/skins/header-pink_288.jpg b/skins/header-pink_288.jpg new file mode 100644 index 0000000..c73e28c Binary files /dev/null and b/skins/header-pink_288.jpg differ diff --git a/skins/header-purple.jpg b/skins/header-purple.jpg new file mode 100644 index 0000000..ee7364c Binary files /dev/null and b/skins/header-purple.jpg differ diff --git a/skins/header-purple_144.jpg b/skins/header-purple_144.jpg new file mode 100644 index 0000000..8e88f82 Binary files /dev/null and b/skins/header-purple_144.jpg differ diff --git a/skins/header-purple_288.jpg b/skins/header-purple_288.jpg new file mode 100644 index 0000000..6b30b40 Binary files /dev/null and b/skins/header-purple_288.jpg differ diff --git a/skins/header-red.jpg b/skins/header-red.jpg new file mode 100644 index 0000000..d19fa5b Binary files /dev/null and b/skins/header-red.jpg differ diff --git a/skins/header-red_144.jpg b/skins/header-red_144.jpg new file mode 100644 index 0000000..dbbc377 Binary files /dev/null and b/skins/header-red_144.jpg differ diff --git a/skins/header-red_288.jpg b/skins/header-red_288.jpg new file mode 100644 index 0000000..962a5a3 Binary files /dev/null and b/skins/header-red_288.jpg differ diff --git a/skins/header-turquoise.jpg b/skins/header-turquoise.jpg new file mode 100644 index 0000000..49352d1 Binary files /dev/null and b/skins/header-turquoise.jpg differ diff --git a/skins/header-turquoise_144.jpg b/skins/header-turquoise_144.jpg new file mode 100644 index 0000000..c3f3534 Binary files /dev/null and b/skins/header-turquoise_144.jpg differ diff --git a/skins/header-turquoise_288.jpg b/skins/header-turquoise_288.jpg new file mode 100644 index 0000000..99f8231 Binary files /dev/null and b/skins/header-turquoise_288.jpg differ diff --git a/skins/header-yellow.jpg b/skins/header-yellow.jpg new file mode 100644 index 0000000..50cf291 Binary files /dev/null and b/skins/header-yellow.jpg differ diff --git a/skins/header-yellow_144.jpg b/skins/header-yellow_144.jpg new file mode 100644 index 0000000..39e1cb9 Binary files /dev/null and b/skins/header-yellow_144.jpg differ diff --git a/skins/header-yellow_288.jpg b/skins/header-yellow_288.jpg new file mode 100644 index 0000000..cc5d09f Binary files /dev/null and b/skins/header-yellow_288.jpg differ diff --git a/style.css b/style.css new file mode 100644 index 0000000..79a9e9f --- /dev/null +++ b/style.css @@ -0,0 +1,65 @@ +table#maintable th a {color:#fff;} +body,#footer,ul {margin:0;padding:0;} +body,p,td,h1,h2,a,a:hover {font-family:Verdana,Helvetica,sans-serif;font-size:12px} +h1 {font-size:18px} +h2 {font-size:14px} +a {color:#036} +a:hover {color:#06C;text-decoration:none} +img {border:0;} +textarea {font-family:Arial,Helvetica,sans-serif;font-size:10pt} + +#container {margin:0 auto;width:780px;border:0} +#top{color:#fff;margin:5px 0 0;height:20px;text-align:right;} +#header {height:80px;} +#header h1 {display:none;} + +#nav {margin:0 0 20px;height:25px;width:770px;float:left;border:0;display:inline;} +#nav ul li a {color:#fff;padding:0 4px;} +#nav ul li img {display:none;} + +#content {margin:20px 0 0;width:780px;} +#footer {margin:45px 0 0;padding:20px 0;clear:both;} + +ul {list-style:none;} +ul li {display:inline;} +#footer ul li {display:block;} + +label {margin-right:0.5em;width:10em;float:left;text-align:left;display:block;} + +#search-az {text-align:center;padding:2px;} +#a-z a {font-size:75%;} + +.odd {background:#e5e5e5;} +.even {background:#f3f3f3;} + +#right,.right {float:right;} +#left,.left {float:left;} +.clear {clear:both;} + +.msgbox {padding:16px;border:1px solid #ccc;background:#fff4b4;width:60%;font-weight:700;} +.msgbox i {font-weight:400;} + +table {width:100%;border:1px solid #ccc;border-collapse:collapse;} +table tr td {border:1px solid #ccc;padding:2px 1px} +table img,.center {text-align:center;} +table th {text-align:left;font-size:14px;padding:8px 4px;} + +table#birthdays {border:0;} +.tablespace td {border:0;} + +/* View.php */ +table#view,table#view td {border:1px solid #000;border-collapse:collapse;} +table#view td {padding:5px;} + +/* Edit.php */ +#content input[type=text],#content textarea {width:220px; margin-bottom:3px;} +#content input[type=text] {height:1,1em} +// #content textarea {height:8em} +input.byear{width:4em !important;} + +/* Source Forge */ +#download {margin:0;width:180px;background:#63A624;color:#fff;border:1px solid #000;text-align:center;} +#download a,#top a {color:#fff;} + +/* Login */ +#content input[name=user],input[name=pass] {width:150px; margin-bottom:3px;} \ No newline at end of file diff --git a/title.gif b/title.gif new file mode 100644 index 0000000..1a50df7 Binary files /dev/null and b/title.gif differ diff --git a/title.png b/title.png new file mode 100644 index 0000000..cfe9c3c Binary files /dev/null and b/title.png differ diff --git a/title_x2.png b/title_x2.png new file mode 100644 index 0000000..48cbf8a Binary files /dev/null and b/title_x2.png differ diff --git a/title_x4.png b/title_x4.png new file mode 100644 index 0000000..6c7bd47 Binary files /dev/null and b/title_x4.png differ diff --git a/translations/LOCALES/ab/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ab/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..b765d09 Binary files /dev/null and b/translations/LOCALES/ab/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ar/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ar/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..ffdd82d Binary files /dev/null and b/translations/LOCALES/ar/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/be/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/be/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..cac7244 Binary files /dev/null and b/translations/LOCALES/be/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/bg/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/bg/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..836cf82 Binary files /dev/null and b/translations/LOCALES/bg/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ca/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ca/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..5d24ebe Binary files /dev/null and b/translations/LOCALES/ca/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/cs/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/cs/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..9623ac2 Binary files /dev/null and b/translations/LOCALES/cs/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/da/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/da/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..8bfc1a1 Binary files /dev/null and b/translations/LOCALES/da/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/de/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/de/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..a8aae76 Binary files /dev/null and b/translations/LOCALES/de/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/el/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/el/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..8e5f266 Binary files /dev/null and b/translations/LOCALES/el/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/en/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/en/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..6857c10 Binary files /dev/null and b/translations/LOCALES/en/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/es/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/es/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..f1adbfc Binary files /dev/null and b/translations/LOCALES/es/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/fa/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/fa/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..527f0c2 Binary files /dev/null and b/translations/LOCALES/fa/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/fi/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/fi/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..96b423a Binary files /dev/null and b/translations/LOCALES/fi/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/fr/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/fr/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..76a62af Binary files /dev/null and b/translations/LOCALES/fr/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/he/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/he/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..dcf436b Binary files /dev/null and b/translations/LOCALES/he/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/hi/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/hi/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..a09cf7c Binary files /dev/null and b/translations/LOCALES/hi/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/hu/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/hu/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..f975539 Binary files /dev/null and b/translations/LOCALES/hu/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/it/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/it/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..5126e26 Binary files /dev/null and b/translations/LOCALES/it/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ja/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ja/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..5246b04 Binary files /dev/null and b/translations/LOCALES/ja/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ko/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ko/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..48398f7 Binary files /dev/null and b/translations/LOCALES/ko/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/nl/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/nl/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..a5dea28 Binary files /dev/null and b/translations/LOCALES/nl/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/no/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/no/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..0c4b651 Binary files /dev/null and b/translations/LOCALES/no/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/pl/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/pl/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..583bbcc Binary files /dev/null and b/translations/LOCALES/pl/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/pt/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/pt/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..94d6963 Binary files /dev/null and b/translations/LOCALES/pt/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ro/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ro/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..0848cb3 Binary files /dev/null and b/translations/LOCALES/ro/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/ru/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/ru/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..2519e86 Binary files /dev/null and b/translations/LOCALES/ru/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/sk/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/sk/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..54bbd64 Binary files /dev/null and b/translations/LOCALES/sk/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/sl/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/sl/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..6f3f1ee Binary files /dev/null and b/translations/LOCALES/sl/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/sr/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/sr/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..013bfd8 Binary files /dev/null and b/translations/LOCALES/sr/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/sv/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/sv/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..467d674 Binary files /dev/null and b/translations/LOCALES/sv/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/templates/php-addressbook.pot b/translations/LOCALES/templates/php-addressbook.pot new file mode 100644 index 0000000..ebaab51 --- /dev/null +++ b/translations/LOCALES/templates/php-addressbook.pot @@ -0,0 +1,476 @@ +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:47+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "" + +msgid "auto" +msgstr "" + +msgid "USER" +msgstr "" + +msgid "PASSWORD" +msgstr "" + +msgid "LOGIN" +msgstr "" + +msgid "LOGOUT" +msgstr "" + +msgid "JANUARY" +msgstr "" + +msgid "FEBRUARY" +msgstr "" + +msgid "MARCH" +msgstr "" + +msgid "APRIL" +msgstr "" + +msgid "MAY" +msgstr "" + +msgid "JUNE" +msgstr "" + +msgid "JULY" +msgstr "" + +msgid "AUGUST" +msgstr "" + +msgid "SEPTEMBER" +msgstr "" + +msgid "OCTOBER" +msgstr "" + +msgid "NOVEMBER" +msgstr "" + +msgid "DECEMBER" +msgstr "" + +msgid "ADDRESS_BOOK" +msgstr "" + +msgid "FOR" +msgstr "" + +msgid "SEARCH" +msgstr "" + +msgid "HOME" +msgstr "" + +msgid "NEXT_BIRTHDAYS" +msgstr "" + +msgid "ADD_NEW" +msgstr "" + +msgid "PRINT_ALL" +msgstr "" + +msgid "PRINT_PHONES" +msgstr "" + +msgid "EXPORT_CSV" +msgstr "" + +msgid "EXPORT" +msgstr "" + +msgid "IMPORT" +msgstr "" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "" + +msgid "GROUPS" +msgstr "" + +msgid "MANAGE_GROUPS" +msgstr "" + +msgid "NEW_GROUP" +msgstr "" + +msgid "DELETE_GROUPS" +msgstr "" + +msgid "EDIT_GROUP" +msgstr "" + +msgid "GROUP_NAME" +msgstr "" + +msgid "GROUP_HEADER" +msgstr "" + +msgid "GROUP_FOOTER" +msgstr "" + +msgid "GROUP_PARENT" +msgstr "" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "" + +msgid "NUMBER_OF_RESULTS" +msgstr "" + +msgid "ALL" +msgstr "" + +msgid "NONE" +msgstr "" + +msgid "SELECT_ALL" +msgstr "" + +msgid "REMOVE_FROM" +msgstr "" + +msgid "MAIL_CLIENT" +msgstr "" + +msgid "SEND_EMAIL" +msgstr "" + +msgid "ADD_TO" +msgstr "" + +msgid "DETAILS" +msgstr "" + +msgid "EDIT" +msgstr "" + +msgid "MODIFY" +msgstr "" + +msgid "PRINT" +msgstr "" + +msgid "EDIT_ADD_ENTRY" +msgstr "" + +msgid "GUESSED_HOMEPAGE" +msgstr "" + +msgid "PREFERENCES" +msgstr "" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "" + +msgid "PHONE_HOME" +msgstr "" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "" + +msgid "HOMEPAGE" +msgstr "" + +msgid "ZIP" +msgstr "" + +msgid "CITY" +msgstr "" + +msgid "E_MAIL_HOME" +msgstr "" + +msgid "E_MAIL_OFFICE" +msgstr "" + +msgid "2ND_ADDRESS" +msgstr "" + +msgid "2ND_PHONE" +msgstr "" + +msgid "NOTES" +msgstr "" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "" + +msgid "MODIFIED" +msgstr "" + +msgid "UPDATE" +msgstr "" + +msgid "DELETE" +msgstr "" + +msgid "INVALID" +msgstr "" + +msgid "ENTER" +msgstr "" + +msgid "MEMBER_OF" +msgstr "" + +msgid "SECONDARY" +msgstr "" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "" + +msgid "el" +msgstr "" + +msgid "en" +msgstr "" + +msgid "es" +msgstr "" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "" + +msgid "fr" +msgstr "" + +msgid "he" +msgstr "" + +msgid "hi" +msgstr "" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "" + +msgid "ja" +msgstr "" + +msgid "ko" +msgstr "" + +msgid "nl" +msgstr "" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "" + +msgid "pt" +msgstr "" + +msgid "rm" +msgstr "" + +msgid "ru" +msgstr "" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "" + +msgid "sr" +msgstr "" + +msgid "sv" +msgstr "" + +msgid "th" +msgstr "" + +msgid "tr" +msgstr "" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "" + +msgid "zh" +msgstr "" diff --git a/translations/LOCALES/th/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/th/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..e1076a2 Binary files /dev/null and b/translations/LOCALES/th/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/tr/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/tr/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..31d60d5 Binary files /dev/null and b/translations/LOCALES/tr/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/uk/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/uk/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..30d7447 Binary files /dev/null and b/translations/LOCALES/uk/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/vi/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/vi/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..7c87a9b Binary files /dev/null and b/translations/LOCALES/vi/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/LOCALES/zh/LC_MESSAGES/php-addressbook.mo b/translations/LOCALES/zh/LC_MESSAGES/php-addressbook.mo new file mode 100644 index 0000000..4cb167d Binary files /dev/null and b/translations/LOCALES/zh/LC_MESSAGES/php-addressbook.mo differ diff --git a/translations/php-addressbook-ab.po b/translations/php-addressbook-ab.po new file mode 100644 index 0000000..a43ccd3 --- /dev/null +++ b/translations/php-addressbook-ab.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-04 20:31+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "ภาษา" + +msgid "auto" +msgstr "การตั้งตัว" + +msgid "USER" +msgstr "ผู้ใช้งาน" + +msgid "PASSWORD" +msgstr "รหัสผ่าน" + +msgid "LOGIN" +msgstr "เข้าสู่ระบบ" + +msgid "LOGOUT" +msgstr "ออกจากระบบ" + +msgid "JANUARY" +msgstr "มกราคม" + +msgid "FEBRUARY" +msgstr "กุมภาพันธ์" + +msgid "MARCH" +msgstr "มีนาคม" + +msgid "APRIL" +msgstr "เมษายน" + +msgid "MAY" +msgstr "พฤษภาคม" + +msgid "JUNE" +msgstr "มิถุนายน" + +msgid "JULY" +msgstr "กรกฎาคม" + +msgid "AUGUST" +msgstr "สิงหาคม" + +msgid "SEPTEMBER" +msgstr "กันยายน" + +msgid "OCTOBER" +msgstr "ตุลาคม" + +msgid "NOVEMBER" +msgstr "พฤศจิกายน" + +msgid "DECEMBER" +msgstr "ธันวาคม" + +msgid "ADDRESS_BOOK" +msgstr "โทร สมุด" + +msgid "FOR" +msgstr "สำหรับ" + +msgid "SEARCH" +msgstr "สืบ" + +msgid "HOME" +msgstr "หน้าแรก" + +msgid "NEXT_BIRTHDAYS" +msgstr "วันเกิด" + +msgid "ADD_NEW" +msgstr "ใหม่ รายการ" + +msgid "PRINT_ALL" +msgstr "ตีพิมพ์" + +msgid "PRINT_PHONES" +msgstr "รายการ" + +msgid "EXPORT_CSV" +msgstr "ส่งออก csv" + +msgid "EXPORT" +msgstr "ส่งออก" + +msgid "IMPORT" +msgstr "การนำเข้า" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "กลุ่ม" + +msgid "GROUPS" +msgstr "ขันธ์" + +msgid "MANAGE_GROUPS" +msgstr "กลุ่ม" + +msgid "NEW_GROUP" +msgstr "กลุ่มใหม่" + +msgid "DELETE_GROUPS" +msgstr "กลุ่มลบ (s)" + +msgid "EDIT_GROUP" +msgstr "กลุ่มแก้ไข" + +msgid "GROUP_NAME" +msgstr "ชื่อกลุ่ม" + +msgid "GROUP_HEADER" +msgstr "Tên nhóm" + +msgid "GROUP_FOOTER" +msgstr "Nhóm đầu (Logo)" + +msgid "GROUP_PARENT" +msgstr "กลุ่มผู้ปกครอง" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "ค้นหาข้อความใดๆ" + +msgid "NUMBER_OF_RESULTS" +msgstr "ผลสอบ" + +msgid "ALL" +msgstr "ทั้งนั้น" + +msgid "NONE" +msgstr "หมดตูด" + +msgid "SELECT_ALL" +msgstr "ทั้งนั้น" + +msgid "REMOVE_FROM" +msgstr "ลบ" + +msgid "MAIL_CLIENT" +msgstr "từ bỏ" + +msgid "SEND_EMAIL" +msgstr "ส่งจดหมาย จดหมายอิเล็กทรอนิกส์" + +msgid "ADD_TO" +msgstr "ต่อเติม" + +msgid "DETAILS" +msgstr "ขยายความใน" + +msgid "EDIT" +msgstr "เกลา" + +msgid "MODIFY" +msgstr "ก้าวไกล" + +msgid "PRINT" +msgstr "พิมพ์หนังสือ" + +msgid "EDIT_ADD_ENTRY" +msgstr "ประพันธ์" + +msgid "GUESSED_HOMEPAGE" +msgstr "คาด หน้าแรก" + +msgid "PREFERENCES" +msgstr "การตั้งตัว" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "ตั้งชื่อ" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "ตั้งชื่อ" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "บริษัท" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "ที่อยู่" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "เบอร์โทร" + +msgid "PHONE_HOME" +msgstr "เอกชน" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "มือถือ" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "กิจการ" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "แฟกซ์" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "จดหมายอิเล็กทรอนิกส์" + +msgid "HOMEPAGE" +msgstr "หน้าแรก" + +msgid "ZIP" +msgstr "รหัสไปรษณีย์" + +msgid "CITY" +msgstr "บุรี" + +msgid "E_MAIL_HOME" +msgstr "จดหมายอิเล็กทรอนิกส์ เอกชน" + +msgid "E_MAIL_OFFICE" +msgstr "จดหมายอิเล็กทรอนิกส์ กิจการ" + +msgid "2ND_ADDRESS" +msgstr "ที่สอง ที่อยู่" + +msgid "2ND_PHONE" +msgstr "ที่สอง เบอร์" + +msgid "NOTES" +msgstr "บันทึกย่อ" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "วันเกิด" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "สร้างขึ้น" + +msgid "MODIFIED" +msgstr "แก้ไข" + +msgid "UPDATE" +msgstr "ก้าวไกล" + +msgid "DELETE" +msgstr "ขีดฆ่า" + +msgid "INVALID" +msgstr "โมฆะ" + +msgid "ENTER" +msgstr "เก็บ" + +msgid "MEMBER_OF" +msgstr "ภาคี" + +msgid "SECONDARY" +msgstr "ครึ่งหลัง" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "ภาษาอาหรับ" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "บัลแกเรีย" + +msgid "ca" +msgstr "Catalonian" + +msgid "cs" +msgstr "Tiếng Anh" + +msgid "da" +msgstr "ภาษาเดนมาร์ก" + +msgid "de" +msgstr "ภาษาเยอรมัน" + +msgid "el" +msgstr "กรีก" + +msgid "en" +msgstr "ภาษาอังกฤษ" + +msgid "es" +msgstr "สเปน" + +msgid "fa" +msgstr "Tiếng Farsi" + +msgid "fi" +msgstr "ฟินแลนด์" + +msgid "fr" +msgstr "ฝรั่งเศส" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "ภาษาฮินดี" + +msgid "hu" +msgstr "Hungary" + +msgid "it" +msgstr "ภาษาอิตาลี" + +msgid "ja" +msgstr "ภาษาญี่ปุ่น" + +msgid "ko" +msgstr "เกาหลี" + +msgid "nl" +msgstr "ดัตช์" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Tiếng Đức" + +msgid "pt" +msgstr "ภาษาโปรตุเกส" + +msgid "rm" +msgstr "เรโต - Romance" + +msgid "ru" +msgstr "คนรัสเซีย" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "ภาษาสโลเวเนีย" + +msgid "sr" +msgstr "เซอร์เบีย" + +msgid "sv" +msgstr "สวีเดน" + +msgid "th" +msgstr "ภาษาไทย" + +msgid "tr" +msgstr "ภาษาตุรกี" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "เวียตนาม" + +msgid "zh" +msgstr "ภาษาจีน" diff --git a/translations/php-addressbook-ar.po b/translations/php-addressbook-ar.po new file mode 100644 index 0000000..70cc3f4 --- /dev/null +++ b/translations/php-addressbook-ar.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:33+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "لغة" + +msgid "auto" +msgstr "مزودة بمضخة إعدادات (التلقائي)" + +msgid "USER" +msgstr "مستخدم" + +msgid "PASSWORD" +msgstr "كلمة السر" + +msgid "LOGIN" +msgstr "تسجيل الدخول" + +msgid "LOGOUT" +msgstr "خروج" + +msgid "JANUARY" +msgstr "يناير" + +msgid "FEBRUARY" +msgstr "فبراير" + +msgid "MARCH" +msgstr "مارس" + +msgid "APRIL" +msgstr "أبريل" + +msgid "MAY" +msgstr "مايو" + +msgid "JUNE" +msgstr "يونيو" + +msgid "JULY" +msgstr "يوليو" + +msgid "AUGUST" +msgstr "أغسطس" + +msgid "SEPTEMBER" +msgstr "سبتمبر" + +msgid "OCTOBER" +msgstr "أكتوبر" + +msgid "NOVEMBER" +msgstr "نوفمبر" + +msgid "DECEMBER" +msgstr "ديسمبر" + +msgid "ADDRESS_BOOK" +msgstr "book دفتر العناوين" + +msgid "FOR" +msgstr "لأجل" + +msgid "SEARCH" +msgstr "بحث" + +msgid "HOME" +msgstr "منزل" + +msgid "NEXT_BIRTHDAYS" +msgstr "أعياد الميلاد القادمة" + +msgid "ADD_NEW" +msgstr "إضافة جديدة" + +msgid "PRINT_ALL" +msgstr "طباعة جميع" + +msgid "PRINT_PHONES" +msgstr "طباعة هواتف" + +msgid "EXPORT_CSV" +msgstr "تصدير csv" + +msgid "EXPORT" +msgstr "تصدير" + +msgid "IMPORT" +msgstr "استيراد" + +msgid "MAP" +msgstr "خريطة" + +msgid "MORE" +msgstr "أكثر" + +msgid "GROUP" +msgstr "المجموعة" + +msgid "GROUPS" +msgstr "مجموعات" + +msgid "MANAGE_GROUPS" +msgstr "إدارة المجموعات" + +msgid "NEW_GROUP" +msgstr "مجموعة جديدة" + +msgid "DELETE_GROUPS" +msgstr "حذف مجموعة (ق)" + +msgid "EDIT_GROUP" +msgstr "تحرير مجموعة" + +msgid "GROUP_NAME" +msgstr "اسم المجموعة" + +msgid "GROUP_HEADER" +msgstr "مجموعة رأسية (الشعار)" + +msgid "GROUP_FOOTER" +msgstr "مجموعة الذيل (التعليق)" + +msgid "GROUP_PARENT" +msgstr "المجموعة الأم" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "البحث عن أي نص" + +msgid "NUMBER_OF_RESULTS" +msgstr "عدد من النتائج" + +msgid "ALL" +msgstr "الكل" + +msgid "NONE" +msgstr "بلا" + +msgid "SELECT_ALL" +msgstr "اختيار كل" + +msgid "REMOVE_FROM" +msgstr "إزالة من" + +msgid "MAIL_CLIENT" +msgstr "البريد العملاء" + +msgid "SEND_EMAIL" +msgstr "إرسال البريد الإلكتروني" + +msgid "ADD_TO" +msgstr "إضافة إلى" + +msgid "DETAILS" +msgstr "تفاصيل" + +msgid "EDIT" +msgstr "تحرير" + +msgid "MODIFY" +msgstr "تعديل" + +msgid "PRINT" +msgstr "طباعة" + +msgid "EDIT_ADD_ENTRY" +msgstr "تحرير / إضافة دفتر العناوين الدخول" + +msgid "GUESSED_HOMEPAGE" +msgstr "خمنت الصفحة الرئيسية" + +msgid "PREFERENCES" +msgstr "أفضليات" + +msgid "NAME_PREFIX" +msgstr "بادئة" + +msgid "FIRSTNAME" +msgstr "الإسم" + +msgid "MIDDLENAME" +msgstr "الاسم الأول الثاني / inital (ق)" + +msgid "LASTNAME" +msgstr "اللقب" + +msgid "NAME_SUFFIX" +msgstr "لاحقة" + +msgid "NICKNAME" +msgstr "كنية" + +msgid "COMPANY" +msgstr "شركة" + +msgid "DEPT" +msgstr "قسم" + +msgid "OCCUPATION" +msgstr "مهنة" + +msgid "TITLES" +msgstr "لقب" + +msgid "ADDRESS" +msgstr "عنوان" + +msgid "POB" +msgstr "عدد" + +msgid "APT" +msgstr "شقة" + +msgid "STREET" +msgstr "عنوان" + +msgid "STATE" +msgstr "دولة" + +msgid "COUNTRY" +msgstr "بلد" + +msgid "TELEPHONE" +msgstr "الهاتف" + +msgid "PHONE_HOME" +msgstr "منزل" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "متنقل" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "عمل" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "الفاكس" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "بيجر" + +msgid "EMAIL" +msgstr "البريد الإلكتروني" + +msgid "HOMEPAGE" +msgstr "الصفحة الرئيسية" + +msgid "ZIP" +msgstr "الرمز البريدي" + +msgid "CITY" +msgstr "مدينة" + +msgid "E_MAIL_HOME" +msgstr "البريد الإلكتروني الصفحة الرئيسية" + +msgid "E_MAIL_OFFICE" +msgstr "مكتب البريد الإلكتروني" + +msgid "2ND_ADDRESS" +msgstr "الثانية معالجة" + +msgid "2ND_PHONE" +msgstr "الهاتف الثاني" + +msgid "NOTES" +msgstr "تلاحظ" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "عيد ميلاد" + +msgid "ANNIVERSARY" +msgstr "الذكرى" + +msgid "CREATED" +msgstr "خلق" + +msgid "MODIFIED" +msgstr "تعديل" + +msgid "UPDATE" +msgstr "تحديث" + +msgid "DELETE" +msgstr "حذف" + +msgid "INVALID" +msgstr "باطل" + +msgid "ENTER" +msgstr "إدخال" + +msgid "MEMBER_OF" +msgstr "عضو" + +msgid "SECONDARY" +msgstr "الثانوي" + +msgid "CREATE_ACCOUNT" +msgstr "إنشاء حساب" + +msgid "FORGOT_PASSWORD" +msgstr "نسيت كلمة المرور" + +msgid "UPDATED" +msgstr "تحديث" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "لقب" + +msgid "MOBILE" +msgstr "متنقل" + +msgid "WORK" +msgstr "عمل" + +msgid "FIRST_LAST" +msgstr "الأول واسم العائلة" + +msgid "NEXT" +msgstr "أكثر" + +msgid "PHOTO" +msgstr "صور" + +msgid "ALL_PHONES" +msgstr "جميع إلزامية." + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "الدخول مع" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "العربية" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "البلغارية" + +msgid "ca" +msgstr "كاتالونيا" + +msgid "cs" +msgstr "التشيكية" + +msgid "da" +msgstr "الدانماركية" + +msgid "de" +msgstr "الألمانية" + +msgid "el" +msgstr "اليونانية" + +msgid "en" +msgstr "الانجليزية" + +msgid "es" +msgstr "الأسبانية" + +msgid "fa" +msgstr "الفارسية" + +msgid "fi" +msgstr "الفنلندية" + +msgid "fr" +msgstr "الفرنسية" + +msgid "he" +msgstr "العبرية" + +msgid "hi" +msgstr "الهندية" + +msgid "hu" +msgstr "الهنغارية" + +msgid "it" +msgstr "الايطالية" + +msgid "ja" +msgstr "اليابانية" + +msgid "ko" +msgstr "الكورية" + +msgid "nl" +msgstr "الهولندية" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "البولندية" + +msgid "pt" +msgstr "البرتغالية" + +msgid "rm" +msgstr "Rhaeto - الرومانسية" + +msgid "ru" +msgstr "الروسية" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "السلوفينية" + +msgid "sr" +msgstr "صربي" + +msgid "sv" +msgstr "السويدية" + +msgid "th" +msgstr "التايلاندية" + +msgid "tr" +msgstr "التركية" + +msgid "ua" +msgstr "الأوكراني" + +msgid "vi" +msgstr "الفيتنامية" + +msgid "zh" +msgstr "الصينية" diff --git a/translations/php-addressbook-be.po b/translations/php-addressbook-be.po new file mode 100644 index 0000000..5f85e6e --- /dev/null +++ b/translations/php-addressbook-be.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-04 20:36+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "език" + +msgid "auto" +msgstr "настройки на браузера (автоматични)" + +msgid "USER" +msgstr "Потребител" + +msgid "PASSWORD" +msgstr "Парола" + +msgid "LOGIN" +msgstr "Вход" + +msgid "LOGOUT" +msgstr "Изход" + +msgid "JANUARY" +msgstr "Януари" + +msgid "FEBRUARY" +msgstr "Февруари" + +msgid "MARCH" +msgstr "Март" + +msgid "APRIL" +msgstr "Април" + +msgid "MAY" +msgstr "Май" + +msgid "JUNE" +msgstr "Юни" + +msgid "JULY" +msgstr "Юли" + +msgid "AUGUST" +msgstr "Август" + +msgid "SEPTEMBER" +msgstr "Септември" + +msgid "OCTOBER" +msgstr "Октомври" + +msgid "NOVEMBER" +msgstr "Ноември" + +msgid "DECEMBER" +msgstr "Декември" + +msgid "ADDRESS_BOOK" +msgstr "адресна книга" + +msgid "FOR" +msgstr "за" + +msgid "SEARCH" +msgstr "търсене" + +msgid "HOME" +msgstr "начало" + +msgid "NEXT_BIRTHDAYS" +msgstr "следващ рожден ден" + +msgid "ADD_NEW" +msgstr "добави нов" + +msgid "PRINT_ALL" +msgstr "печат всички" + +msgid "PRINT_PHONES" +msgstr "печат телефони" + +msgid "EXPORT_CSV" +msgstr "експорт в csv" + +msgid "EXPORT" +msgstr "износ" + +msgid "IMPORT" +msgstr "внос" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "група" + +msgid "GROUPS" +msgstr "групи" + +msgid "MANAGE_GROUPS" +msgstr "управление на групи" + +msgid "NEW_GROUP" +msgstr "нова група" + +msgid "DELETE_GROUPS" +msgstr "изтриване на група(и)" + +msgid "EDIT_GROUP" +msgstr "редакция на група" + +msgid "GROUP_NAME" +msgstr "име на група" + +msgid "GROUP_HEADER" +msgstr "Лого на група" + +msgid "GROUP_FOOTER" +msgstr "Коментар на група" + +msgid "GROUP_PARENT" +msgstr "Родителите група" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "търси който и да е текст" + +msgid "NUMBER_OF_RESULTS" +msgstr "Брой резултати" + +msgid "ALL" +msgstr "всичко" + +msgid "NONE" +msgstr "нищо" + +msgid "SELECT_ALL" +msgstr "избери всички" + +msgid "REMOVE_FROM" +msgstr "премахни от" + +msgid "MAIL_CLIENT" +msgstr "клиент за поща" + +msgid "SEND_EMAIL" +msgstr "Изпрати Е-поща" + +msgid "ADD_TO" +msgstr "добави в" + +msgid "DETAILS" +msgstr "детайли" + +msgid "EDIT" +msgstr "редакция" + +msgid "MODIFY" +msgstr "промяна" + +msgid "PRINT" +msgstr "печат" + +msgid "EDIT_ADD_ENTRY" +msgstr "Рeдакция / добяване на нов контакт" + +msgid "GUESSED_HOMEPAGE" +msgstr "Предполагаема интернет страница" + +msgid "PREFERENCES" +msgstr "настройки" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "Име" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "Фамилия" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "дружество" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "адрес" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "телефон" + +msgid "PHONE_HOME" +msgstr "домашен" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "мобилен" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "служебен" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "факс" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "е-поща" + +msgid "HOMEPAGE" +msgstr "Начало" + +msgid "ZIP" +msgstr "пощенски код" + +msgid "CITY" +msgstr "град" + +msgid "E_MAIL_HOME" +msgstr "домашена е-поща" + +msgid "E_MAIL_OFFICE" +msgstr "служебна е-поща" + +msgid "2ND_ADDRESS" +msgstr "друг адрес" + +msgid "2ND_PHONE" +msgstr "друг телефон" + +msgid "NOTES" +msgstr "бележки" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "рожден ден" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "е създаден" + +msgid "MODIFIED" +msgstr "промяна" + +msgid "UPDATE" +msgstr "промени" + +msgid "DELETE" +msgstr "изтрии" + +msgid "INVALID" +msgstr "невалиден" + +msgid "ENTER" +msgstr "въведи" + +msgid "MEMBER_OF" +msgstr "член на" + +msgid "SECONDARY" +msgstr "Втори адрес" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Арабски" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Български" + +msgid "ca" +msgstr "Каталонски" + +msgid "cs" +msgstr "Чешки" + +msgid "da" +msgstr "Датски" + +msgid "de" +msgstr "Немски" + +msgid "el" +msgstr "Гръзки" + +msgid "en" +msgstr "Английски" + +msgid "es" +msgstr "Испански" + +msgid "fa" +msgstr "Персийски" + +msgid "fi" +msgstr "Фински" + +msgid "fr" +msgstr "Френски" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "Индиски" + +msgid "hu" +msgstr "Унгарски" + +msgid "it" +msgstr "Италиански" + +msgid "ja" +msgstr "Японски" + +msgid "ko" +msgstr "Корейски" + +msgid "nl" +msgstr "Холандски" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Полски" + +msgid "pt" +msgstr "Португалски" + +msgid "rm" +msgstr "Реторомански" + +msgid "ru" +msgstr "Руски" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Словенски" + +msgid "sr" +msgstr "Сръбски" + +msgid "sv" +msgstr "Шведски" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Турски" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Виетнамски" + +msgid "zh" +msgstr "Китайски" diff --git a/translations/php-addressbook-bg.po b/translations/php-addressbook-bg.po new file mode 100644 index 0000000..cec6b8a --- /dev/null +++ b/translations/php-addressbook-bg.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-15 12:38+0000\n" +"Last-Translator: Vladislav Nikolaev \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "език" + +msgid "auto" +msgstr "настройки на браузера (автоматични)" + +msgid "USER" +msgstr "Потребител" + +msgid "PASSWORD" +msgstr "Парола" + +msgid "LOGIN" +msgstr "Вход" + +msgid "LOGOUT" +msgstr "Изход" + +msgid "JANUARY" +msgstr "Януари" + +msgid "FEBRUARY" +msgstr "Февруари" + +msgid "MARCH" +msgstr "Март" + +msgid "APRIL" +msgstr "Април" + +msgid "MAY" +msgstr "Май" + +msgid "JUNE" +msgstr "Юни" + +msgid "JULY" +msgstr "Юли" + +msgid "AUGUST" +msgstr "Август" + +msgid "SEPTEMBER" +msgstr "Септември" + +msgid "OCTOBER" +msgstr "Октомври" + +msgid "NOVEMBER" +msgstr "Ноември" + +msgid "DECEMBER" +msgstr "Декември" + +msgid "ADDRESS_BOOK" +msgstr "адресна книга" + +msgid "FOR" +msgstr "за" + +msgid "SEARCH" +msgstr "търсене" + +msgid "HOME" +msgstr "начало" + +msgid "NEXT_BIRTHDAYS" +msgstr "следващ рожден ден" + +msgid "ADD_NEW" +msgstr "добави нов" + +msgid "PRINT_ALL" +msgstr "печат всички" + +msgid "PRINT_PHONES" +msgstr "печат телефони" + +msgid "EXPORT_CSV" +msgstr "експорт в csv" + +msgid "EXPORT" +msgstr "експорт" + +msgid "IMPORT" +msgstr "импорт" + +msgid "MAP" +msgstr "карта" + +msgid "MORE" +msgstr "още" + +msgid "GROUP" +msgstr "група" + +msgid "GROUPS" +msgstr "групи" + +msgid "MANAGE_GROUPS" +msgstr "управление на групи" + +msgid "NEW_GROUP" +msgstr "нова група" + +msgid "DELETE_GROUPS" +msgstr "изтриване на група(и)" + +msgid "EDIT_GROUP" +msgstr "редакция на група" + +msgid "GROUP_NAME" +msgstr "име на група" + +msgid "GROUP_HEADER" +msgstr "Лого на група" + +msgid "GROUP_FOOTER" +msgstr "Коментар на група" + +msgid "GROUP_PARENT" +msgstr "Предходна група" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "търси който и да е текст" + +msgid "NUMBER_OF_RESULTS" +msgstr "Брой резултати" + +msgid "ALL" +msgstr "всичко" + +msgid "NONE" +msgstr "нищо" + +msgid "SELECT_ALL" +msgstr "избери всички" + +msgid "REMOVE_FROM" +msgstr "премахни от" + +msgid "MAIL_CLIENT" +msgstr "клиент за поща" + +msgid "SEND_EMAIL" +msgstr "Изпрати Е-поща" + +msgid "ADD_TO" +msgstr "добави в" + +msgid "DETAILS" +msgstr "детайли" + +msgid "EDIT" +msgstr "редакция" + +msgid "MODIFY" +msgstr "промяна" + +msgid "PRINT" +msgstr "печат" + +msgid "EDIT_ADD_ENTRY" +msgstr "Рeдакция / добяване на нов контакт" + +msgid "GUESSED_HOMEPAGE" +msgstr "Предполагаема интернет страница" + +msgid "PREFERENCES" +msgstr "настройки" + +msgid "NAME_PREFIX" +msgstr "обръщение" + +msgid "FIRSTNAME" +msgstr "Име" + +msgid "MIDDLENAME" +msgstr "Презиме" + +msgid "LASTNAME" +msgstr "Фамилия" + +msgid "NAME_SUFFIX" +msgstr "суфикс име" + +msgid "NICKNAME" +msgstr "прякор" + +msgid "COMPANY" +msgstr "дружество" + +msgid "DEPT" +msgstr "отдел" + +msgid "OCCUPATION" +msgstr "длъжност" + +msgid "TITLES" +msgstr "титла" + +msgid "ADDRESS" +msgstr "адрес" + +msgid "POB" +msgstr "пощенска кутия (Post office box)" + +msgid "APT" +msgstr "апартамент (apartment)" + +msgid "STREET" +msgstr "улица" + +msgid "STATE" +msgstr "Щат" + +msgid "COUNTRY" +msgstr "държава" + +msgid "TELEPHONE" +msgstr "телефон" + +msgid "PHONE_HOME" +msgstr "домашен телефон" + +msgid "HOME_SHORT" +msgstr "кратък домашен телефон" + +msgid "PHONE_MOBILE" +msgstr "мобилен" + +msgid "MOBILE_SHORT" +msgstr "кратък мобилен номер" + +msgid "PHONE_WORK" +msgstr "служебен телефон" + +msgid "WORK_SHORT" +msgstr "кратък служебен телефон" + +msgid "FAX" +msgstr "факс" + +msgid "FAX_SHORT" +msgstr "кратък факс номер" + +msgid "PHONE2_SHORT" +msgstr "телефон 2" + +msgid "PAGER" +msgstr "пейджър" + +msgid "EMAIL" +msgstr "е-поща" + +msgid "HOMEPAGE" +msgstr "Начало" + +msgid "ZIP" +msgstr "пощенски код" + +msgid "CITY" +msgstr "град" + +msgid "E_MAIL_HOME" +msgstr "домашена е-поща" + +msgid "E_MAIL_OFFICE" +msgstr "служебна е-поща" + +msgid "2ND_ADDRESS" +msgstr "друг адрес" + +msgid "2ND_PHONE" +msgstr "друг телефон" + +msgid "NOTES" +msgstr "бележки" + +msgid "MISC" +msgstr "други" + +msgid "BIRTHDAY" +msgstr "рожден ден" + +msgid "ANNIVERSARY" +msgstr "Годишнина" + +msgid "CREATED" +msgstr "е създаден" + +msgid "MODIFIED" +msgstr "промяна" + +msgid "UPDATE" +msgstr "промени" + +msgid "DELETE" +msgstr "изтрий" + +msgid "INVALID" +msgstr "невалиден" + +msgid "ENTER" +msgstr "въведи" + +msgid "MEMBER_OF" +msgstr "член на" + +msgid "SECONDARY" +msgstr "Втори адрес" + +msgid "CREATE_ACCOUNT" +msgstr "създай регистрация" + +msgid "FORGOT_PASSWORD" +msgstr "забравена парола" + +msgid "UPDATED" +msgstr "обновен" + +msgid "TRANSLATOR" +msgstr "преводач" + +msgid "TITLE" +msgstr "ЗАГЛАВИЕ" + +msgid "MOBILE" +msgstr "мобилен" + +msgid "WORK" +msgstr "служебен" + +msgid "FIRST_LAST" +msgstr "име, фамилия" + +msgid "NEXT" +msgstr "следващ" + +msgid "PHOTO" +msgstr "снимка" + +msgid "ALL_PHONES" +msgstr "всички телефони" + +msgid "ALL_EMAILS" +msgstr "всички е-майли" + +msgid "SIGN_IN_WITH" +msgstr "влезте с" + +msgid "LAST_FIRST" +msgstr "фамилия, име" + +msgid "GRP_NAME" +msgstr "група" + +msgid "ab" +msgstr "Абкхазки" + +msgid "ar" +msgstr "Арабски" + +msgid "be" +msgstr "Белгийски" + +msgid "bg" +msgstr "Български" + +msgid "ca" +msgstr "Каталонски" + +msgid "cs" +msgstr "Чешки" + +msgid "da" +msgstr "Датски" + +msgid "de" +msgstr "Немски" + +msgid "el" +msgstr "Гръзки" + +msgid "en" +msgstr "Английски" + +msgid "es" +msgstr "Испански" + +msgid "fa" +msgstr "Персийски" + +msgid "fi" +msgstr "Финландски" + +msgid "fr" +msgstr "Френски" + +msgid "he" +msgstr "Еврейски" + +msgid "hi" +msgstr "Хинди" + +msgid "hu" +msgstr "Унгарски" + +msgid "it" +msgstr "Италиански" + +msgid "ja" +msgstr "Японски" + +msgid "ko" +msgstr "Корейски" + +msgid "nl" +msgstr "Холандски" + +msgid "no" +msgstr "Норвежки" + +msgid "pl" +msgstr "Полски" + +msgid "pt" +msgstr "Португалски" + +msgid "rm" +msgstr "Реторомански" + +msgid "ru" +msgstr "Руски" + +msgid "sk" +msgstr "Словашки" + +msgid "sl" +msgstr "Словенски" + +msgid "sr" +msgstr "Сръбски" + +msgid "sv" +msgstr "Шведски" + +msgid "th" +msgstr "Тайлански" + +msgid "tr" +msgstr "Турски" + +msgid "ua" +msgstr "Украйнски" + +msgid "vi" +msgstr "Виетнамски" + +msgid "zh" +msgstr "Китайски" diff --git a/translations/php-addressbook-ca.po b/translations/php-addressbook-ca.po new file mode 100644 index 0000000..a222e8e --- /dev/null +++ b/translations/php-addressbook-ca.po @@ -0,0 +1,480 @@ +# Catalan translation for php-addressbook +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the php-addressbook package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: php-addressbook\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:44+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Catalan \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "" + +msgid "auto" +msgstr "" + +msgid "USER" +msgstr "" + +msgid "PASSWORD" +msgstr "" + +msgid "LOGIN" +msgstr "" + +msgid "LOGOUT" +msgstr "" + +msgid "JANUARY" +msgstr "" + +msgid "FEBRUARY" +msgstr "" + +msgid "MARCH" +msgstr "" + +msgid "APRIL" +msgstr "" + +msgid "MAY" +msgstr "" + +msgid "JUNE" +msgstr "" + +msgid "JULY" +msgstr "" + +msgid "AUGUST" +msgstr "" + +msgid "SEPTEMBER" +msgstr "" + +msgid "OCTOBER" +msgstr "" + +msgid "NOVEMBER" +msgstr "" + +msgid "DECEMBER" +msgstr "" + +msgid "ADDRESS_BOOK" +msgstr "" + +msgid "FOR" +msgstr "" + +msgid "SEARCH" +msgstr "" + +msgid "HOME" +msgstr "" + +msgid "NEXT_BIRTHDAYS" +msgstr "" + +msgid "ADD_NEW" +msgstr "" + +msgid "PRINT_ALL" +msgstr "" + +msgid "PRINT_PHONES" +msgstr "" + +msgid "EXPORT_CSV" +msgstr "" + +msgid "EXPORT" +msgstr "" + +msgid "IMPORT" +msgstr "" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "" + +msgid "GROUPS" +msgstr "" + +msgid "MANAGE_GROUPS" +msgstr "" + +msgid "NEW_GROUP" +msgstr "" + +msgid "DELETE_GROUPS" +msgstr "" + +msgid "EDIT_GROUP" +msgstr "" + +msgid "GROUP_NAME" +msgstr "" + +msgid "GROUP_HEADER" +msgstr "" + +msgid "GROUP_FOOTER" +msgstr "" + +msgid "GROUP_PARENT" +msgstr "" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "" + +msgid "NUMBER_OF_RESULTS" +msgstr "" + +msgid "ALL" +msgstr "" + +msgid "NONE" +msgstr "" + +msgid "SELECT_ALL" +msgstr "" + +msgid "REMOVE_FROM" +msgstr "" + +msgid "MAIL_CLIENT" +msgstr "" + +msgid "SEND_EMAIL" +msgstr "" + +msgid "ADD_TO" +msgstr "" + +msgid "DETAILS" +msgstr "" + +msgid "EDIT" +msgstr "" + +msgid "MODIFY" +msgstr "" + +msgid "PRINT" +msgstr "" + +msgid "EDIT_ADD_ENTRY" +msgstr "" + +msgid "GUESSED_HOMEPAGE" +msgstr "" + +msgid "PREFERENCES" +msgstr "" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "" + +msgid "PHONE_HOME" +msgstr "" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "" + +msgid "HOMEPAGE" +msgstr "" + +msgid "ZIP" +msgstr "" + +msgid "CITY" +msgstr "" + +msgid "E_MAIL_HOME" +msgstr "" + +msgid "E_MAIL_OFFICE" +msgstr "" + +msgid "2ND_ADDRESS" +msgstr "" + +msgid "2ND_PHONE" +msgstr "" + +msgid "NOTES" +msgstr "" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "" + +msgid "MODIFIED" +msgstr "" + +msgid "UPDATE" +msgstr "" + +msgid "DELETE" +msgstr "" + +msgid "INVALID" +msgstr "" + +msgid "ENTER" +msgstr "" + +msgid "MEMBER_OF" +msgstr "" + +msgid "SECONDARY" +msgstr "" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "" + +msgid "el" +msgstr "" + +msgid "en" +msgstr "" + +msgid "es" +msgstr "" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "" + +msgid "fr" +msgstr "" + +msgid "he" +msgstr "" + +msgid "hi" +msgstr "" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "" + +msgid "ja" +msgstr "" + +msgid "ko" +msgstr "" + +msgid "nl" +msgstr "" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "" + +msgid "pt" +msgstr "" + +msgid "rm" +msgstr "" + +msgid "ru" +msgstr "" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "" + +msgid "sr" +msgstr "" + +msgid "sv" +msgstr "" + +msgid "th" +msgstr "" + +msgid "tr" +msgstr "" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "" + +msgid "zh" +msgstr "" diff --git a/translations/php-addressbook-cs.po b/translations/php-addressbook-cs.po new file mode 100644 index 0000000..380158e --- /dev/null +++ b/translations/php-addressbook-cs.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:48+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "jazyk" + +msgid "auto" +msgstr "nastavení prohlížeče(automaticky)" + +msgid "USER" +msgstr "" + +msgid "PASSWORD" +msgstr "" + +msgid "LOGIN" +msgstr "" + +msgid "LOGOUT" +msgstr "" + +msgid "JANUARY" +msgstr "Leden" + +msgid "FEBRUARY" +msgstr "Únor" + +msgid "MARCH" +msgstr "Březen" + +msgid "APRIL" +msgstr "Duben" + +msgid "MAY" +msgstr "Květen" + +msgid "JUNE" +msgstr "Červenec" + +msgid "JULY" +msgstr "Červen" + +msgid "AUGUST" +msgstr "Srpen" + +msgid "SEPTEMBER" +msgstr "Září" + +msgid "OCTOBER" +msgstr "Říjen" + +msgid "NOVEMBER" +msgstr "Listopad" + +msgid "DECEMBER" +msgstr "Prosinec" + +msgid "ADDRESS_BOOK" +msgstr "Adresář" + +msgid "FOR" +msgstr "pro" + +msgid "SEARCH" +msgstr "hledat" + +msgid "HOME" +msgstr "domů" + +msgid "NEXT_BIRTHDAYS" +msgstr "příští narozeniny" + +msgid "ADD_NEW" +msgstr "přidat nový" + +msgid "PRINT_ALL" +msgstr "tisknout vÅ¡echny" + +msgid "PRINT_PHONES" +msgstr "tisknout telefony" + +msgid "EXPORT_CSV" +msgstr "export do csv" + +msgid "EXPORT" +msgstr "" + +msgid "IMPORT" +msgstr "" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "" + +msgid "GROUPS" +msgstr "skupiny" + +msgid "MANAGE_GROUPS" +msgstr "spravovat skupiny" + +msgid "NEW_GROUP" +msgstr "nová skupina" + +msgid "DELETE_GROUPS" +msgstr "smazat skupinu" + +msgid "EDIT_GROUP" +msgstr "upravit skupinu" + +msgid "GROUP_NAME" +msgstr "jméno skupiny" + +msgid "GROUP_HEADER" +msgstr "Skupinová hlavička (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Skupinová patička (Komentář)" + +msgid "GROUP_PARENT" +msgstr "" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "hledat jakýkoliv text" + +msgid "NUMBER_OF_RESULTS" +msgstr "Počet výsledků" + +msgid "ALL" +msgstr "VÅ¡ichni" + +msgid "NONE" +msgstr "Žádný" + +msgid "SELECT_ALL" +msgstr "vybrat vÅ¡echny" + +msgid "REMOVE_FROM" +msgstr "odstranit z" + +msgid "MAIL_CLIENT" +msgstr "emailový program" + +msgid "SEND_EMAIL" +msgstr "Poslat e-mail" + +msgid "ADD_TO" +msgstr "přidat k" + +msgid "DETAILS" +msgstr "detaily" + +msgid "EDIT" +msgstr "upravit" + +msgid "MODIFY" +msgstr "změnit" + +msgid "PRINT" +msgstr "tisknout" + +msgid "EDIT_ADD_ENTRY" +msgstr "Upravit / přidat položku adresáře" + +msgid "GUESSED_HOMEPAGE" +msgstr "Možná domovská stránka" + +msgid "PREFERENCES" +msgstr "předvolby" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "jméno" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "příjmení" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "adresa" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "soukromá" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "práce" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "" + +msgid "ZIP" +msgstr "PSČ" + +msgid "CITY" +msgstr "město" + +msgid "E_MAIL_HOME" +msgstr "e-mail soukromý" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail pracovní" + +msgid "2ND_ADDRESS" +msgstr "druhá adresa" + +msgid "2ND_PHONE" +msgstr "druhý telefon" + +msgid "NOTES" +msgstr "" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "narozeniny" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "" + +msgid "MODIFIED" +msgstr "" + +msgid "UPDATE" +msgstr "upravit" + +msgid "DELETE" +msgstr "smazat" + +msgid "INVALID" +msgstr "neplatný" + +msgid "ENTER" +msgstr "vložit" + +msgid "MEMBER_OF" +msgstr "clen" + +msgid "SECONDARY" +msgstr "Další" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "Česky" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "Německy" + +msgid "el" +msgstr "" + +msgid "en" +msgstr "Anglický" + +msgid "es" +msgstr "" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "" + +msgid "fr" +msgstr "" + +msgid "he" +msgstr "" + +msgid "hi" +msgstr "" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "" + +msgid "ja" +msgstr "" + +msgid "ko" +msgstr "" + +msgid "nl" +msgstr "" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "czeski" + +msgid "pt" +msgstr "" + +msgid "rm" +msgstr "" + +msgid "ru" +msgstr "" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "" + +msgid "sr" +msgstr "" + +msgid "sv" +msgstr "" + +msgid "th" +msgstr "" + +msgid "tr" +msgstr "" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "" + +msgid "zh" +msgstr "" diff --git a/translations/php-addressbook-da.po b/translations/php-addressbook-da.po new file mode 100644 index 0000000..a4967fb --- /dev/null +++ b/translations/php-addressbook-da.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:48+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "sprog" + +msgid "auto" +msgstr "bowser indstillinger (automatisk)" + +msgid "USER" +msgstr "" + +msgid "PASSWORD" +msgstr "" + +msgid "LOGIN" +msgstr "" + +msgid "LOGOUT" +msgstr "" + +msgid "JANUARY" +msgstr "Januar" + +msgid "FEBRUARY" +msgstr "Februar" + +msgid "MARCH" +msgstr "Marts" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Mai" + +msgid "JUNE" +msgstr "Juni" + +msgid "JULY" +msgstr "Juli" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "adressebog" + +msgid "FOR" +msgstr "for" + +msgid "SEARCH" +msgstr "søgning" + +msgid "HOME" +msgstr "hjem" + +msgid "NEXT_BIRTHDAYS" +msgstr "næste fødselsdage" + +msgid "ADD_NEW" +msgstr "tilføje nye" + +msgid "PRINT_ALL" +msgstr "Udskriv alle" + +msgid "PRINT_PHONES" +msgstr "print-telefoner" + +msgid "EXPORT_CSV" +msgstr "eksportere csv" + +msgid "EXPORT" +msgstr "" + +msgid "IMPORT" +msgstr "" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "" + +msgid "GROUPS" +msgstr "grupper" + +msgid "MANAGE_GROUPS" +msgstr "forvalte grupper" + +msgid "NEW_GROUP" +msgstr "ny gruppe" + +msgid "DELETE_GROUPS" +msgstr "Slet gruppe (r)" + +msgid "EDIT_GROUP" +msgstr "Rediger gruppe" + +msgid "GROUP_NAME" +msgstr "gruppens navn" + +msgid "GROUP_HEADER" +msgstr "Gruppen header (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Gruppen footer (Kommentar)" + +msgid "GROUP_PARENT" +msgstr "" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "søge efter en hvilken som helst tekst" + +msgid "NUMBER_OF_RESULTS" +msgstr "Antal resultater" + +msgid "ALL" +msgstr "alle" + +msgid "NONE" +msgstr "ingen" + +msgid "SELECT_ALL" +msgstr "Vælg alle" + +msgid "REMOVE_FROM" +msgstr "fjerne fra" + +msgid "MAIL_CLIENT" +msgstr "e-mail-klient" + +msgid "SEND_EMAIL" +msgstr "Send e-mail" + +msgid "ADD_TO" +msgstr "Tilføj til" + +msgid "DETAILS" +msgstr "Oversigt" + +msgid "EDIT" +msgstr "redigere" + +msgid "MODIFY" +msgstr "modificere" + +msgid "PRINT" +msgstr "print" + +msgid "EDIT_ADD_ENTRY" +msgstr "Rediger / add-adresse dematerialiserede" + +msgid "GUESSED_HOMEPAGE" +msgstr "Gættet hjemmeside" + +msgid "PREFERENCES" +msgstr "præferencer" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "fornavn" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "efternavn" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "adresse" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "hjem" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "arbejde" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "by" + +msgid "E_MAIL_HOME" +msgstr "e-mail home" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail-kontor" + +msgid "2ND_ADDRESS" +msgstr "anden adresse" + +msgid "2ND_PHONE" +msgstr "anden telefon" + +msgid "NOTES" +msgstr "" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "fødselsdag" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "" + +msgid "MODIFIED" +msgstr "" + +msgid "UPDATE" +msgstr "opdatering" + +msgid "DELETE" +msgstr "slet" + +msgid "INVALID" +msgstr "ugyldig" + +msgid "ENTER" +msgstr "indtaste" + +msgid "MEMBER_OF" +msgstr "medlem af" + +msgid "SECONDARY" +msgstr "Sekundære" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabisk" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgarsk" + +msgid "ca" +msgstr "Katalonien" + +msgid "cs" +msgstr "Tjekkisk" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "Tysk" + +msgid "el" +msgstr "Græsk" + +msgid "en" +msgstr "Engelsk" + +msgid "es" +msgstr "Spansk" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "" + +msgid "fr" +msgstr "Fransk" + +msgid "he" +msgstr "Hebræisk" + +msgid "hi" +msgstr "Hindu" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "Italiensk" + +msgid "ja" +msgstr "Japansk" + +msgid "ko" +msgstr "Koreansk" + +msgid "nl" +msgstr "Hollandsk" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Polsk" + +msgid "pt" +msgstr "Portugisisk" + +msgid "rm" +msgstr "Rætoromansk" + +msgid "ru" +msgstr "Russisk" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "" + +msgid "sr" +msgstr "" + +msgid "sv" +msgstr "Svensk" + +msgid "th" +msgstr "Thailandsk" + +msgid "tr" +msgstr "Tyrkisk" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamesisk" + +msgid "zh" +msgstr "Kinesisk" diff --git a/translations/php-addressbook-de.po b/translations/php-addressbook-de.po new file mode 100644 index 0000000..a02109b --- /dev/null +++ b/translations/php-addressbook-de.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-01-23 00:47+0000\n" +"Last-Translator: Dennis Baudys \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "Sprache" + +msgid "auto" +msgstr "Browservorgabe (automatisch)" + +msgid "USER" +msgstr "Benutzer" + +msgid "PASSWORD" +msgstr "Passwort" + +msgid "LOGIN" +msgstr "Anmelden" + +msgid "LOGOUT" +msgstr "Abmelden" + +msgid "JANUARY" +msgstr "Januar" + +msgid "FEBRUARY" +msgstr "Februar" + +msgid "MARCH" +msgstr "März" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Mai" + +msgid "JUNE" +msgstr "Juni" + +msgid "JULY" +msgstr "Juli" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "Dezember" + +msgid "ADDRESS_BOOK" +msgstr "Addressbuch" + +msgid "FOR" +msgstr "für" + +msgid "SEARCH" +msgstr "suchen" + +msgid "HOME" +msgstr "Hauptseite" + +msgid "NEXT_BIRTHDAYS" +msgstr "Geburtstage" + +msgid "ADD_NEW" +msgstr "Neuer Eintrag" + +msgid "PRINT_ALL" +msgstr "Alle drucken" + +msgid "PRINT_PHONES" +msgstr "Telefonliste" + +msgid "EXPORT_CSV" +msgstr "CSV-Export" + +msgid "EXPORT" +msgstr "Export" + +msgid "IMPORT" +msgstr "Import" + +msgid "MAP" +msgstr "Karte" + +msgid "MORE" +msgstr "mehr" + +msgid "GROUP" +msgstr "Gruppe" + +msgid "GROUPS" +msgstr "Gruppen" + +msgid "MANAGE_GROUPS" +msgstr "Gruppen" + +msgid "NEW_GROUP" +msgstr "Gruppe hinzufügen" + +msgid "DELETE_GROUPS" +msgstr "Gruppe(n) löschen" + +msgid "EDIT_GROUP" +msgstr "Gruppe anpassen" + +msgid "GROUP_NAME" +msgstr "Gruppenname" + +msgid "GROUP_HEADER" +msgstr "Kopfzeile (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Fusszeile (Kommentar)" + +msgid "GROUP_PARENT" +msgstr "Übergerdnete Gruppe" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Freitextsuche" + +msgid "NUMBER_OF_RESULTS" +msgstr "Suchtreffer" + +msgid "ALL" +msgstr "Alle" + +msgid "NONE" +msgstr "Keine" + +msgid "SELECT_ALL" +msgstr "alles" + +msgid "REMOVE_FROM" +msgstr "entfernen aus" + +msgid "MAIL_CLIENT" +msgstr "Mailprogramm" + +msgid "SEND_EMAIL" +msgstr "E-Mail senden" + +msgid "ADD_TO" +msgstr "Hinzufügen zu" + +msgid "DETAILS" +msgstr "Details" + +msgid "EDIT" +msgstr "ändern" + +msgid "MODIFY" +msgstr "anpassen" + +msgid "PRINT" +msgstr "drucken" + +msgid "EDIT_ADD_ENTRY" +msgstr "Eintrag erstellen / ändern" + +msgid "GUESSED_HOMEPAGE" +msgstr "Mögliche Homepage" + +msgid "PREFERENCES" +msgstr "Einstellungen" + +msgid "NAME_PREFIX" +msgstr "Präfix" + +msgid "FIRSTNAME" +msgstr "Vorname" + +msgid "MIDDLENAME" +msgstr "zweiter Vorname / inital (s)" + +msgid "LASTNAME" +msgstr "Nachname" + +msgid "NAME_SUFFIX" +msgstr "Suffix" + +msgid "NICKNAME" +msgstr "Rufname" + +msgid "COMPANY" +msgstr "Firma" + +msgid "DEPT" +msgstr "Abt." + +msgid "OCCUPATION" +msgstr "Beruf" + +msgid "TITLES" +msgstr "Titel" + +msgid "ADDRESS" +msgstr "Adresse" + +msgid "POB" +msgstr "Nummer" + +msgid "APT" +msgstr "Wohnung" + +msgid "STREET" +msgstr "Adresse" + +msgid "STATE" +msgstr "Staat" + +msgid "COUNTRY" +msgstr "Land" + +msgid "TELEPHONE" +msgstr "Telefonnr." + +msgid "PHONE_HOME" +msgstr "Privat" + +msgid "HOME_SHORT" +msgstr "P" + +msgid "PHONE_MOBILE" +msgstr "Mobil" + +msgid "MOBILE_SHORT" +msgstr "M" + +msgid "PHONE_WORK" +msgstr "Geschäft" + +msgid "WORK_SHORT" +msgstr "G" + +msgid "FAX" +msgstr "Fax" + +msgid "FAX_SHORT" +msgstr "F" + +msgid "PHONE2_SHORT" +msgstr "T" + +msgid "PAGER" +msgstr "Pager" + +msgid "EMAIL" +msgstr "e-Mail" + +msgid "HOMEPAGE" +msgstr "Homepage" + +msgid "ZIP" +msgstr "PLZ" + +msgid "CITY" +msgstr "Stadt" + +msgid "E_MAIL_HOME" +msgstr "E-Mail Privat" + +msgid "E_MAIL_OFFICE" +msgstr "E-Mail Büro" + +msgid "2ND_ADDRESS" +msgstr "Zweitadresse" + +msgid "2ND_PHONE" +msgstr "Zweittelefon" + +msgid "NOTES" +msgstr "Notizen" + +msgid "MISC" +msgstr "Div" + +msgid "BIRTHDAY" +msgstr "Geburtstag" + +msgid "ANNIVERSARY" +msgstr "Jahrestag" + +msgid "CREATED" +msgstr "erstellt" + +msgid "MODIFIED" +msgstr "geändert" + +msgid "UPDATE" +msgstr "aktualisieren" + +msgid "DELETE" +msgstr "löschen" + +msgid "INVALID" +msgstr "ungültige" + +msgid "ENTER" +msgstr "speichern" + +msgid "MEMBER_OF" +msgstr "Mitglied" + +msgid "SECONDARY" +msgstr "Zweitadresse" + +msgid "CREATE_ACCOUNT" +msgstr "Konto erstellen" + +msgid "FORGOT_PASSWORD" +msgstr "Passwort vergessen" + +msgid "UPDATED" +msgstr "Aktualisiert" + +msgid "TRANSLATOR" +msgstr "ÜBERSETZER" + +msgid "TITLE" +msgstr "Titel" + +msgid "MOBILE" +msgstr "Mobil" + +msgid "WORK" +msgstr "Arbeit" + +msgid "FIRST_LAST" +msgstr "Vor- und Nachname" + +msgid "NEXT" +msgstr "weiter" + +msgid "PHOTO" +msgstr "Foto" + +msgid "ALL_PHONES" +msgstr "Alle Telefonnr." + +msgid "ALL_EMAILS" +msgstr "all e-mail" + +msgid "SIGN_IN_WITH" +msgstr "Einloggen mit" + +msgid "LAST_FIRST" +msgstr "Name" + +msgid "GRP_NAME" +msgstr "Gruppenname" + +msgid "ab" +msgstr "Abchasisch" + +msgid "ar" +msgstr "Arabisch" + +msgid "be" +msgstr "Weißrussisch" + +msgid "bg" +msgstr "Bulgarisch" + +msgid "ca" +msgstr "Katalanisch" + +msgid "cs" +msgstr "Tschechisch" + +msgid "da" +msgstr "Dänisch" + +msgid "de" +msgstr "Deutsch" + +msgid "el" +msgstr "Griechisch" + +msgid "en" +msgstr "Englisch" + +msgid "es" +msgstr "Spanisch" + +msgid "fa" +msgstr "Farsi" + +msgid "fi" +msgstr "Finnisch" + +msgid "fr" +msgstr "Französisch" + +msgid "he" +msgstr "Hebräisch" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Ungarisch" + +msgid "it" +msgstr "Italienisch" + +msgid "ja" +msgstr "Japanisch" + +msgid "ko" +msgstr "Koreanisch" + +msgid "nl" +msgstr "Holländisch" + +msgid "no" +msgstr "Norwegisch" + +msgid "pl" +msgstr "Polnisch" + +msgid "pt" +msgstr "Portugiesisch" + +msgid "rm" +msgstr "Rätoromanisch" + +msgid "ru" +msgstr "Russisch" + +msgid "sk" +msgstr "Slowakisch" + +msgid "sl" +msgstr "Slowenisch" + +msgid "sr" +msgstr "Serbisch" + +msgid "sv" +msgstr "Schwedisch" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Türkisch" + +msgid "ua" +msgstr "Ukrainisch" + +msgid "vi" +msgstr "Vietnamesisch" + +msgid "zh" +msgstr "Chinesisch" diff --git a/translations/php-addressbook-el.po b/translations/php-addressbook-el.po new file mode 100644 index 0000000..6c87acb --- /dev/null +++ b/translations/php-addressbook-el.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:48+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "γλώσσα" + +msgid "auto" +msgstr "bowser ρυθμίσεων (αυτόματο)" + +msgid "USER" +msgstr "Χρήστη" + +msgid "PASSWORD" +msgstr "Κωδικός πρόσβασης" + +msgid "LOGIN" +msgstr "Σύνδεση" + +msgid "LOGOUT" +msgstr "Αποσύνδεση" + +msgid "JANUARY" +msgstr "Ιανουάριος" + +msgid "FEBRUARY" +msgstr "Φεβρουάριος" + +msgid "MARCH" +msgstr "Μάρτης" + +msgid "APRIL" +msgstr "Απρίλιος" + +msgid "MAY" +msgstr "Μάιος" + +msgid "JUNE" +msgstr "Ιούνιος" + +msgid "JULY" +msgstr "Ιούλιος" + +msgid "AUGUST" +msgstr "Αύγουστος" + +msgid "SEPTEMBER" +msgstr "Σεπτέμβριος" + +msgid "OCTOBER" +msgstr "Οκτώβριος" + +msgid "NOVEMBER" +msgstr "Νοέμβριος" + +msgid "DECEMBER" +msgstr "Δεκέμβριος" + +msgid "ADDRESS_BOOK" +msgstr "Βιβλίο Διευθύνσεων" + +msgid "FOR" +msgstr "για" + +msgid "SEARCH" +msgstr "αναζήτηση" + +msgid "HOME" +msgstr "Αρχική" + +msgid "NEXT_BIRTHDAYS" +msgstr "Επόμενα γενέθλια" + +msgid "ADD_NEW" +msgstr "Προσθήκη νέου" + +msgid "PRINT_ALL" +msgstr "Εκτύπωση όλων" + +msgid "PRINT_PHONES" +msgstr "Εκτύπωση τηλεφώνων" + +msgid "EXPORT_CSV" +msgstr "Εξαγωγή csv" + +msgid "EXPORT" +msgstr "εξαγωγή" + +msgid "IMPORT" +msgstr "εισαγωγή" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "ομάδα" + +msgid "GROUPS" +msgstr "Ομάδες" + +msgid "MANAGE_GROUPS" +msgstr "Διαχείριση ομάδων" + +msgid "NEW_GROUP" +msgstr "Νέα ομάδα" + +msgid "DELETE_GROUPS" +msgstr "Διαγραφή ομάδας (s)" + +msgid "EDIT_GROUP" +msgstr "επεξεργασία ομάδας" + +msgid "GROUP_NAME" +msgstr "Όνομα ομάδας" + +msgid "GROUP_HEADER" +msgstr "Ομάδα header (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Ομάδα υποσέλιδο (Σχόλιο)" + +msgid "GROUP_PARENT" +msgstr "Μητρική της ομάδας" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Αναζήτηση για κάθε κείμενο" + +msgid "NUMBER_OF_RESULTS" +msgstr "Αριθμός αποτελεσμάτων" + +msgid "ALL" +msgstr "Όλα" + +msgid "NONE" +msgstr "Κανένα" + +msgid "SELECT_ALL" +msgstr "Επιλέξτε όλα" + +msgid "REMOVE_FROM" +msgstr "Αφαίρεση από" + +msgid "MAIL_CLIENT" +msgstr "mail client" + +msgid "SEND_EMAIL" +msgstr "Αποστολή e-mail" + +msgid "ADD_TO" +msgstr "Προσθήκη σε" + +msgid "DETAILS" +msgstr "λεπτομέρειες" + +msgid "EDIT" +msgstr "επεξεργαστείτε" + +msgid "MODIFY" +msgstr "τροποποιήσετε" + +msgid "PRINT" +msgstr "εκτύπωση" + +msgid "EDIT_ADD_ENTRY" +msgstr "Επεξεργασία / Προσθήκη εγγραφής" + +msgid "GUESSED_HOMEPAGE" +msgstr "Μάντεψε αρχική σελίδα" + +msgid "PREFERENCES" +msgstr "Ρυθμίσεις" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "όνομα" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "επώνυμο" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "εταιρεία" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "διεύθυνση" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "τηλέφωνο" + +msgid "PHONE_HOME" +msgstr "σπίτι" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "κινητό" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "εργασία" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "Φαξ" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Ιστοσελίδα" + +msgid "ZIP" +msgstr "ΤΚ" + +msgid "CITY" +msgstr "πόλη" + +msgid "E_MAIL_HOME" +msgstr "προσωπικό e-mail" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail γραφείου" + +msgid "2ND_ADDRESS" +msgstr "δεύτερη διεύθυνση" + +msgid "2ND_PHONE" +msgstr "δεύτερη τηλεφωνική" + +msgid "NOTES" +msgstr "Σημειώσεις" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "γενέθλια" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "Δημιουργία" + +msgid "MODIFIED" +msgstr "Τροποποιημένα" + +msgid "UPDATE" +msgstr "ενημέρωση" + +msgid "DELETE" +msgstr "διαγράφω" + +msgid "INVALID" +msgstr "άκυρο" + +msgid "ENTER" +msgstr "εισέλθει" + +msgid "MEMBER_OF" +msgstr "μέλος του" + +msgid "SECONDARY" +msgstr "Δευτεροβάθμια" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Αραβικά" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Βουλγαρικά" + +msgid "ca" +msgstr "Καταλονίας" + +msgid "cs" +msgstr "Τσέχικα" + +msgid "da" +msgstr "Δανική" + +msgid "de" +msgstr "Γερμανικά" + +msgid "el" +msgstr "Ελληνικά" + +msgid "en" +msgstr "Αγγλικά" + +msgid "es" +msgstr "Ισπανικά" + +msgid "fa" +msgstr "Περσικά" + +msgid "fi" +msgstr "Φινλανδική" + +msgid "fr" +msgstr "Γαλλικά" + +msgid "he" +msgstr "Εβραϊκά" + +msgid "hi" +msgstr "Ινδικά" + +msgid "hu" +msgstr "Ουγγρικός" + +msgid "it" +msgstr "Ιταλικά" + +msgid "ja" +msgstr "Ιαπωνικά" + +msgid "ko" +msgstr "Κορεάτικα" + +msgid "nl" +msgstr "Ολλανδικά" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Πολωνικά" + +msgid "pt" +msgstr "Πορτογαλικά" + +msgid "rm" +msgstr "Rhaeto-Romance" + +msgid "ru" +msgstr "Ρωσικά" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Σλοβενική" + +msgid "sr" +msgstr "Σέρβος" + +msgid "sv" +msgstr "Σουηδικά" + +msgid "th" +msgstr "Ταϊλανδικά" + +msgid "tr" +msgstr "Τουρκικά" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Βιετνάμ" + +msgid "zh" +msgstr "Κινεζικά" diff --git a/translations/php-addressbook-en.po b/translations/php-addressbook-en.po new file mode 100644 index 0000000..62e1193 --- /dev/null +++ b/translations/php-addressbook-en.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-06 19:58+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "language" + +msgid "auto" +msgstr "bowser settings (automatic)" + +msgid "USER" +msgstr "User" + +msgid "PASSWORD" +msgstr "Password" + +msgid "LOGIN" +msgstr "Login" + +msgid "LOGOUT" +msgstr "Logout" + +msgid "JANUARY" +msgstr "January" + +msgid "FEBRUARY" +msgstr "February" + +msgid "MARCH" +msgstr "March" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "May" + +msgid "JUNE" +msgstr "June" + +msgid "JULY" +msgstr "July" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "October" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "address book" + +msgid "FOR" +msgstr "for" + +msgid "SEARCH" +msgstr "search" + +msgid "HOME" +msgstr "home" + +msgid "NEXT_BIRTHDAYS" +msgstr "next birthdays" + +msgid "ADD_NEW" +msgstr "add new" + +msgid "PRINT_ALL" +msgstr "print all" + +msgid "PRINT_PHONES" +msgstr "print phones" + +msgid "EXPORT_CSV" +msgstr "export csv" + +msgid "EXPORT" +msgstr "export" + +msgid "IMPORT" +msgstr "import" + +msgid "MAP" +msgstr "map" + +msgid "MORE" +msgstr "more" + +msgid "GROUP" +msgstr "group" + +msgid "GROUPS" +msgstr "groups" + +msgid "MANAGE_GROUPS" +msgstr "groups" + +msgid "NEW_GROUP" +msgstr "new group" + +msgid "DELETE_GROUPS" +msgstr "delete group(s)" + +msgid "EDIT_GROUP" +msgstr "edit group" + +msgid "GROUP_NAME" +msgstr "group name" + +msgid "GROUP_HEADER" +msgstr "Group header (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Group footer (Comment)" + +msgid "GROUP_PARENT" +msgstr "Parent group" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "search for any text" + +msgid "NUMBER_OF_RESULTS" +msgstr "Number of results" + +msgid "ALL" +msgstr "all" + +msgid "NONE" +msgstr "none" + +msgid "SELECT_ALL" +msgstr "select all" + +msgid "REMOVE_FROM" +msgstr "remove from" + +msgid "MAIL_CLIENT" +msgstr "mail client" + +msgid "SEND_EMAIL" +msgstr "Send e-Mail" + +msgid "ADD_TO" +msgstr "add to" + +msgid "DETAILS" +msgstr "details" + +msgid "EDIT" +msgstr "edit" + +msgid "MODIFY" +msgstr "modify" + +msgid "PRINT" +msgstr "print" + +msgid "EDIT_ADD_ENTRY" +msgstr "Edit / add address book entry" + +msgid "GUESSED_HOMEPAGE" +msgstr "Guessed homepage" + +msgid "PREFERENCES" +msgstr "preferences" + +msgid "NAME_PREFIX" +msgstr "prefix" + +msgid "FIRSTNAME" +msgstr "first name" + +msgid "MIDDLENAME" +msgstr "middle name" + +msgid "LASTNAME" +msgstr "last name" + +msgid "NAME_SUFFIX" +msgstr "suffix" + +msgid "NICKNAME" +msgstr "nickname" + +msgid "COMPANY" +msgstr "company" + +msgid "DEPT" +msgstr "Dept." + +msgid "OCCUPATION" +msgstr "job title" + +msgid "TITLES" +msgstr "titles" + +msgid "ADDRESS" +msgstr "address" + +msgid "POB" +msgstr "box or number" + +msgid "APT" +msgstr "apartment or stop" + +msgid "STREET" +msgstr "street Address" + +msgid "STATE" +msgstr "state" + +msgid "COUNTRY" +msgstr "country" + +msgid "TELEPHONE" +msgstr "telephone" + +msgid "PHONE_HOME" +msgstr "home" + +msgid "HOME_SHORT" +msgstr "H" + +msgid "PHONE_MOBILE" +msgstr "mobile" + +msgid "MOBILE_SHORT" +msgstr "M" + +msgid "PHONE_WORK" +msgstr "work" + +msgid "WORK_SHORT" +msgstr "W" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "F" + +msgid "PHONE2_SHORT" +msgstr "P" + +msgid "PAGER" +msgstr "pager" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Homepage" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "city" + +msgid "E_MAIL_HOME" +msgstr "e-mail home" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail office" + +msgid "2ND_ADDRESS" +msgstr "second address" + +msgid "2ND_PHONE" +msgstr "second phone" + +msgid "NOTES" +msgstr "notes" + +msgid "MISC" +msgstr "Misc" + +msgid "BIRTHDAY" +msgstr "birthday" + +msgid "ANNIVERSARY" +msgstr "anniversary" + +msgid "CREATED" +msgstr "created" + +msgid "MODIFIED" +msgstr "modified" + +msgid "UPDATE" +msgstr "update" + +msgid "DELETE" +msgstr "delete" + +msgid "INVALID" +msgstr "invalid" + +msgid "ENTER" +msgstr "enter" + +msgid "MEMBER_OF" +msgstr "member of" + +msgid "SECONDARY" +msgstr "Secondary" + +msgid "CREATE_ACCOUNT" +msgstr "Create account" + +msgid "FORGOT_PASSWORD" +msgstr "Forgot password" + +msgid "UPDATED" +msgstr "updated" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "title" + +msgid "MOBILE" +msgstr "mobile" + +msgid "WORK" +msgstr "work" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "next" + +msgid "PHOTO" +msgstr "photo" + +msgid "ALL_PHONES" +msgstr "all phones" + +msgid "ALL_EMAILS" +msgstr "all e-mail" + +msgid "SIGN_IN_WITH" +msgstr "Sign in with" + +msgid "LAST_FIRST" +msgstr "name" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabic" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgarian" + +msgid "ca" +msgstr "Catalonian" + +msgid "cs" +msgstr "Czech" + +msgid "da" +msgstr "Danish" + +msgid "de" +msgstr "German" + +msgid "el" +msgstr "Greek" + +msgid "en" +msgstr "English" + +msgid "es" +msgstr "Spanish" + +msgid "fa" +msgstr "Farsi" + +msgid "fi" +msgstr "Finnish" + +msgid "fr" +msgstr "French" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Hungarian" + +msgid "it" +msgstr "Italian" + +msgid "ja" +msgstr "Japanese" + +msgid "ko" +msgstr "Korean" + +msgid "nl" +msgstr "Dutch" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Polish" + +msgid "pt" +msgstr "Portugese" + +msgid "rm" +msgstr "Rhaeto-Romance" + +msgid "ru" +msgstr "Russian" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenian" + +msgid "sr" +msgstr "Serbian" + +msgid "sv" +msgstr "Swedish" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Turkish" + +msgid "ua" +msgstr "Ukrainian" + +msgid "vi" +msgstr "Vietnamese" + +msgid "zh" +msgstr "Chinese" diff --git a/translations/php-addressbook-es.po b/translations/php-addressbook-es.po new file mode 100644 index 0000000..5efabf3 --- /dev/null +++ b/translations/php-addressbook-es.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-01-11 03:48+0000\n" +"Last-Translator: Ubuntu \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "idioma" + +msgid "auto" +msgstr "Ajustar navegador (automatico)" + +msgid "USER" +msgstr "Usuario" + +msgid "PASSWORD" +msgstr "Contraseña" + +msgid "LOGIN" +msgstr "Inicio de sesión" + +msgid "LOGOUT" +msgstr "Cerrar sesión" + +msgid "JANUARY" +msgstr "Enero" + +msgid "FEBRUARY" +msgstr "Febrero" + +msgid "MARCH" +msgstr "Marzo" + +msgid "APRIL" +msgstr "Abril" + +msgid "MAY" +msgstr "Mayo" + +msgid "JUNE" +msgstr "Junio" + +msgid "JULY" +msgstr "Julio" + +msgid "AUGUST" +msgstr "Agosto" + +msgid "SEPTEMBER" +msgstr "Septiembre" + +msgid "OCTOBER" +msgstr "Octubre" + +msgid "NOVEMBER" +msgstr "Noviembre" + +msgid "DECEMBER" +msgstr "Diciembre" + +msgid "ADDRESS_BOOK" +msgstr "libreta de direcciones" + +msgid "FOR" +msgstr "para" + +msgid "SEARCH" +msgstr "búsqueda" + +msgid "HOME" +msgstr "inicio" + +msgid "NEXT_BIRTHDAYS" +msgstr "cumpleaños" + +msgid "ADD_NEW" +msgstr "añadir" + +msgid "PRINT_ALL" +msgstr "imprimir" + +msgid "PRINT_PHONES" +msgstr "teléfonos" + +msgid "EXPORT_CSV" +msgstr "exportar en CSV" + +msgid "EXPORT" +msgstr "exportación" + +msgid "IMPORT" +msgstr "importación" + +msgid "MAP" +msgstr "MAPA" + +msgid "MORE" +msgstr "MAS" + +msgid "GROUP" +msgstr "grupo" + +msgid "GROUPS" +msgstr "grupos" + +msgid "MANAGE_GROUPS" +msgstr "grupos" + +msgid "NEW_GROUP" +msgstr "Añadir grupo" + +msgid "DELETE_GROUPS" +msgstr "Borrar grupo(s)" + +msgid "EDIT_GROUP" +msgstr "editar grupo" + +msgid "GROUP_NAME" +msgstr "nombre del grupo" + +msgid "GROUP_HEADER" +msgstr "Encabezado de grupo (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Pie de página de grupo (Comentarios)" + +msgid "GROUP_PARENT" +msgstr "grupo de Padres" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "búsqueda de cualquier texto" + +msgid "NUMBER_OF_RESULTS" +msgstr "Número de resultados" + +msgid "ALL" +msgstr "todo" + +msgid "NONE" +msgstr "ninguno" + +msgid "SELECT_ALL" +msgstr "seleccionar todos" + +msgid "REMOVE_FROM" +msgstr "eliminar de" + +msgid "MAIL_CLIENT" +msgstr "cliente de correo" + +msgid "SEND_EMAIL" +msgstr "Enviar correo electrónico" + +msgid "ADD_TO" +msgstr "Añadir a" + +msgid "DETAILS" +msgstr "detalles" + +msgid "EDIT" +msgstr "editar" + +msgid "MODIFY" +msgstr "modificar" + +msgid "PRINT" +msgstr "Imprimir" + +msgid "EDIT_ADD_ENTRY" +msgstr "Editar o añadir entrada en la libreta de direcciones" + +msgid "GUESSED_HOMEPAGE" +msgstr "Página principal adivinada" + +msgid "PREFERENCES" +msgstr "preferencias" + +msgid "NAME_PREFIX" +msgstr "PREFIJO" + +msgid "FIRSTNAME" +msgstr "Nombre" + +msgid "MIDDLENAME" +msgstr "SEGUNDO NOMBRE" + +msgid "LASTNAME" +msgstr "apellido" + +msgid "NAME_SUFFIX" +msgstr "SUFIJO" + +msgid "NICKNAME" +msgstr "SOBRENOMBRE" + +msgid "COMPANY" +msgstr "compañía" + +msgid "DEPT" +msgstr "DEPTO" + +msgid "OCCUPATION" +msgstr "OCUPACION" + +msgid "TITLES" +msgstr "TITULOS" + +msgid "ADDRESS" +msgstr "dirección" + +msgid "POB" +msgstr "POB" + +msgid "APT" +msgstr "APT" + +msgid "STREET" +msgstr "CALLE" + +msgid "STATE" +msgstr "ESTADO" + +msgid "COUNTRY" +msgstr "PAIS" + +msgid "TELEPHONE" +msgstr "teléfono" + +msgid "PHONE_HOME" +msgstr "casa" + +msgid "HOME_SHORT" +msgstr "CASA" + +msgid "PHONE_MOBILE" +msgstr "celular" + +msgid "MOBILE_SHORT" +msgstr "CELULAR" + +msgid "PHONE_WORK" +msgstr "trabajo" + +msgid "WORK_SHORT" +msgstr "TRABAJO" + +msgid "FAX" +msgstr "Fax" + +msgid "FAX_SHORT" +msgstr "FAX" + +msgid "PHONE2_SHORT" +msgstr "TEL 2" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "Correo electrónico" + +msgid "HOMEPAGE" +msgstr "Página Web" + +msgid "ZIP" +msgstr "Código Postal" + +msgid "CITY" +msgstr "ciudad" + +msgid "E_MAIL_HOME" +msgstr "e-mail personal" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail de oficina" + +msgid "2ND_ADDRESS" +msgstr "segunda dirección" + +msgid "2ND_PHONE" +msgstr "segundo teléfono" + +msgid "NOTES" +msgstr "notas" + +msgid "MISC" +msgstr "VARIOS" + +msgid "BIRTHDAY" +msgstr "cumpleaños" + +msgid "ANNIVERSARY" +msgstr "ANIVERSARIO" + +msgid "CREATED" +msgstr "creado" + +msgid "MODIFIED" +msgstr "modificado" + +msgid "UPDATE" +msgstr "actualizar" + +msgid "DELETE" +msgstr "borrar" + +msgid "INVALID" +msgstr "inválido" + +msgid "ENTER" +msgstr "guardar" + +msgid "MEMBER_OF" +msgstr "miembro de" + +msgid "SECONDARY" +msgstr "Contacto Secundario" + +msgid "CREATE_ACCOUNT" +msgstr "CREAR_CUENTA" + +msgid "FORGOT_PASSWORD" +msgstr "OLVIDAR_CONTRASENIA" + +msgid "UPDATED" +msgstr "Actualizado" + +msgid "TRANSLATOR" +msgstr "TRADUCTOR" + +msgid "TITLE" +msgstr "TITULO" + +msgid "MOBILE" +msgstr "CELULAR" + +msgid "WORK" +msgstr "TRABAJO" + +msgid "FIRST_LAST" +msgstr "NOMBRE" + +msgid "NEXT" +msgstr "SIGUIENTE" + +msgid "PHOTO" +msgstr "FOTO" + +msgid "ALL_PHONES" +msgstr "TELEFONOS" + +msgid "ALL_EMAILS" +msgstr "CORREOS" + +msgid "SIGN_IN_WITH" +msgstr "ENTRAR_CON" + +msgid "LAST_FIRST" +msgstr "ULTIMO_PRIMERO" + +msgid "GRP_NAME" +msgstr "NOMBRE_GRUPO" + +msgid "ab" +msgstr "ab" + +msgid "ar" +msgstr "Árabe" + +msgid "be" +msgstr "be" + +msgid "bg" +msgstr "Búlgaro" + +msgid "ca" +msgstr "Cataluña" + +msgid "cs" +msgstr "Checo" + +msgid "da" +msgstr "Danés" + +msgid "de" +msgstr "Alemán" + +msgid "el" +msgstr "Griego" + +msgid "en" +msgstr "Inglés" + +msgid "es" +msgstr "Español" + +msgid "fa" +msgstr "Persa" + +msgid "fi" +msgstr "Finlandés" + +msgid "fr" +msgstr "Francés" + +msgid "he" +msgstr "Hebreo" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Húngaro" + +msgid "it" +msgstr "Italiano" + +msgid "ja" +msgstr "Japonés" + +msgid "ko" +msgstr "Coreano" + +msgid "nl" +msgstr "Holandés" + +msgid "no" +msgstr "no" + +msgid "pl" +msgstr "Polaco" + +msgid "pt" +msgstr "Portugués" + +msgid "rm" +msgstr "Retorrománico" + +msgid "ru" +msgstr "Ruso" + +msgid "sk" +msgstr "sk" + +msgid "sl" +msgstr "Esloveno" + +msgid "sr" +msgstr "Serbio" + +msgid "sv" +msgstr "Sueco" + +msgid "th" +msgstr "Tailandés" + +msgid "tr" +msgstr "Turco" + +msgid "ua" +msgstr "ua" + +msgid "vi" +msgstr "Vietnamita" + +msgid "zh" +msgstr "Chino" diff --git a/translations/php-addressbook-fa.po b/translations/php-addressbook-fa.po new file mode 100644 index 0000000..3fbfab0 --- /dev/null +++ b/translations/php-addressbook-fa.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:32+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "زبان" + +msgid "auto" +msgstr "تنظیمات مرورگر (خودکار)" + +msgid "USER" +msgstr "کاربر" + +msgid "PASSWORD" +msgstr "رمز عبور" + +msgid "LOGIN" +msgstr "ورود به سیستم" + +msgid "LOGOUT" +msgstr "خروج" + +msgid "JANUARY" +msgstr "ژانویه" + +msgid "FEBRUARY" +msgstr "فوریه" + +msgid "MARCH" +msgstr "مارچ" + +msgid "APRIL" +msgstr "آپریل" + +msgid "MAY" +msgstr "می" + +msgid "JUNE" +msgstr "جون" + +msgid "JULY" +msgstr "جولای" + +msgid "AUGUST" +msgstr "آگوست" + +msgid "SEPTEMBER" +msgstr "سپتامبر" + +msgid "OCTOBER" +msgstr "اوکتبر" + +msgid "NOVEMBER" +msgstr "نوامبر" + +msgid "DECEMBER" +msgstr "دسامبر" + +msgid "ADDRESS_BOOK" +msgstr "دفتر تلفن" + +msgid "FOR" +msgstr "برای" + +msgid "SEARCH" +msgstr "جستجو" + +msgid "HOME" +msgstr "خانه" + +msgid "NEXT_BIRTHDAYS" +msgstr "روز تولد بعدی" + +msgid "ADD_NEW" +msgstr "مورد جدید" + +msgid "PRINT_ALL" +msgstr "چاپ همه مقادیر" + +msgid "PRINT_PHONES" +msgstr "فقط چاپ شماره‌ها" + +msgid "EXPORT_CSV" +msgstr "خروجی سی اس وی" + +msgid "EXPORT" +msgstr "صادرات" + +msgid "IMPORT" +msgstr "واردات" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "گروه" + +msgid "GROUPS" +msgstr "گروه‌ها" + +msgid "MANAGE_GROUPS" +msgstr "مدیریت گروه‌ها" + +msgid "NEW_GROUP" +msgstr "گروه جدید" + +msgid "DELETE_GROUPS" +msgstr "حذف گروه(ها)" + +msgid "EDIT_GROUP" +msgstr "ویرایش گروه" + +msgid "GROUP_NAME" +msgstr "اسم گروه" + +msgid "GROUP_HEADER" +msgstr "عنوان گروه" + +msgid "GROUP_FOOTER" +msgstr "مقادیر گروه (توضیحات)" + +msgid "GROUP_PARENT" +msgstr "گروه مرجع" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "جستجو برای هر مقدار" + +msgid "NUMBER_OF_RESULTS" +msgstr "تعداد موارد" + +msgid "ALL" +msgstr "همه" + +msgid "NONE" +msgstr "هیچکدام" + +msgid "SELECT_ALL" +msgstr "جستجوی همه موارد" + +msgid "REMOVE_FROM" +msgstr "حذف از" + +msgid "MAIL_CLIENT" +msgstr "برنامه E-mail" + +msgid "SEND_EMAIL" +msgstr "ارسال E-mail" + +msgid "ADD_TO" +msgstr "اضافه کردن به" + +msgid "DETAILS" +msgstr "جزئیات" + +msgid "EDIT" +msgstr "ویرایش" + +msgid "MODIFY" +msgstr "تغییر" + +msgid "PRINT" +msgstr "چاپ" + +msgid "EDIT_ADD_ENTRY" +msgstr "ویرایش / ایجاد شماره جدید" + +msgid "GUESSED_HOMEPAGE" +msgstr "خانه انتخابی" + +msgid "PREFERENCES" +msgstr "تنظیمات شخصی" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "نام" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "نام خانوادگی" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "شرکت" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "آدرس" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "شماره تلفن" + +msgid "PHONE_HOME" +msgstr "منزل" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "مبایل" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "شرکت" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "فکس" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "پست الکترونیک" + +msgid "HOMEPAGE" +msgstr "صفحه آغازه" + +msgid "ZIP" +msgstr "کد پستی" + +msgid "CITY" +msgstr "شهر" + +msgid "E_MAIL_HOME" +msgstr "پست الکترونیک خانه" + +msgid "E_MAIL_OFFICE" +msgstr "پست الکترونیک شرکت" + +msgid "2ND_ADDRESS" +msgstr "آدرس دوم" + +msgid "2ND_PHONE" +msgstr "شماره تلفن دوم" + +msgid "NOTES" +msgstr "یادداشت ها" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "تاریخ تولد" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "ساخته" + +msgid "MODIFIED" +msgstr "بار" + +msgid "UPDATE" +msgstr "بروز رسانی" + +msgid "DELETE" +msgstr "حذف" + +msgid "INVALID" +msgstr "اشتباه" + +msgid "ENTER" +msgstr "ورود" + +msgid "MEMBER_OF" +msgstr "عضو" + +msgid "SECONDARY" +msgstr "ثانوی" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "عربی" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "بلغاری" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "چک" + +msgid "da" +msgstr "دانمارکی" + +msgid "de" +msgstr "آلمانی" + +msgid "el" +msgstr "یونانی" + +msgid "en" +msgstr "انگلیسی" + +msgid "es" +msgstr "اسپانیایی" + +msgid "fa" +msgstr "فارسی" + +msgid "fi" +msgstr "فنلاندی" + +msgid "fr" +msgstr "فرانسوی" + +msgid "he" +msgstr "ابری" + +msgid "hi" +msgstr "هندی" + +msgid "hu" +msgstr "مجارستانی" + +msgid "it" +msgstr "ایتالیایی" + +msgid "ja" +msgstr "ژاپنی" + +msgid "ko" +msgstr "کره‌ای" + +msgid "nl" +msgstr "هلندی" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "هلندی" + +msgid "pt" +msgstr "پرتقالی" + +msgid "rm" +msgstr "رتو - رمانتیک" + +msgid "ru" +msgstr "روسی" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "اسلوونیایی" + +msgid "sr" +msgstr "صرب" + +msgid "sv" +msgstr "سؤدی" + +msgid "th" +msgstr "تای" + +msgid "tr" +msgstr "ترکی" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "ویتنامی" + +msgid "zh" +msgstr "چینی" diff --git a/translations/php-addressbook-fi.po b/translations/php-addressbook-fi.po new file mode 100644 index 0000000..81a7056 --- /dev/null +++ b/translations/php-addressbook-fi.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-01-16 06:46+0000\n" +"Last-Translator: JIIHOO \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "kieli" + +msgid "auto" +msgstr "Selain asetukset (automaattinen)" + +msgid "USER" +msgstr "Käyttäjä" + +msgid "PASSWORD" +msgstr "Salasana" + +msgid "LOGIN" +msgstr "Kirjaudu sisään" + +msgid "LOGOUT" +msgstr "Kirjaudu ulos" + +msgid "JANUARY" +msgstr "Tammikuu" + +msgid "FEBRUARY" +msgstr "Helmikuu" + +msgid "MARCH" +msgstr "Maaliskuu" + +msgid "APRIL" +msgstr "Huhtikuu" + +msgid "MAY" +msgstr "Toukokuu" + +msgid "JUNE" +msgstr "Kesäkuu" + +msgid "JULY" +msgstr "Heinäkuu" + +msgid "AUGUST" +msgstr "Elokuu" + +msgid "SEPTEMBER" +msgstr "Syyskuu" + +msgid "OCTOBER" +msgstr "Lokakuu" + +msgid "NOVEMBER" +msgstr "Marraskuu" + +msgid "DECEMBER" +msgstr "Joulukuu" + +msgid "ADDRESS_BOOK" +msgstr "osoitekirja" + +msgid "FOR" +msgstr "varten" + +msgid "SEARCH" +msgstr "haku" + +msgid "HOME" +msgstr "koti" + +msgid "NEXT_BIRTHDAYS" +msgstr "seuraavat syntymäpäivät" + +msgid "ADD_NEW" +msgstr "lisää uusi" + +msgid "PRINT_ALL" +msgstr "tulosta kaikki" + +msgid "PRINT_PHONES" +msgstr "tulosta puhelinnumerot" + +msgid "EXPORT_CSV" +msgstr "vie csv" + +msgid "EXPORT" +msgstr "vie" + +msgid "IMPORT" +msgstr "tuo" + +msgid "MAP" +msgstr "kartta" + +msgid "MORE" +msgstr "lisää" + +msgid "GROUP" +msgstr "ryhmä" + +msgid "GROUPS" +msgstr "ryhmät" + +msgid "MANAGE_GROUPS" +msgstr "Ryhmien hallinta" + +msgid "NEW_GROUP" +msgstr "uusi ryhmä" + +msgid "DELETE_GROUPS" +msgstr "poista ryhm(i)ä" + +msgid "EDIT_GROUP" +msgstr "muokkaa ryhmää" + +msgid "GROUP_NAME" +msgstr "ryhmän nimi" + +msgid "GROUP_HEADER" +msgstr "Ryhmän otsikko (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Ryhmän alatunniste (kommentti)" + +msgid "GROUP_PARENT" +msgstr "Parent ryhmä" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "etsi teksti" + +msgid "NUMBER_OF_RESULTS" +msgstr "Tulosten määrä" + +msgid "ALL" +msgstr "kaikki" + +msgid "NONE" +msgstr "ei mitään" + +msgid "SELECT_ALL" +msgstr "Valitse kaikki" + +msgid "REMOVE_FROM" +msgstr "poista" + +msgid "MAIL_CLIENT" +msgstr "sähköpostiohjelma" + +msgid "SEND_EMAIL" +msgstr "Lähetä e-Mail" + +msgid "ADD_TO" +msgstr "lisää" + +msgid "DETAILS" +msgstr "yksityiskohdat" + +msgid "EDIT" +msgstr "muokkaa" + +msgid "MODIFY" +msgstr "muuta" + +msgid "PRINT" +msgstr "tulosta" + +msgid "EDIT_ADD_ENTRY" +msgstr "muokkaa / lisää osoite" + +msgid "GUESSED_HOMEPAGE" +msgstr "Arvattu kotisivu" + +msgid "PREFERENCES" +msgstr "asetukset" + +msgid "NAME_PREFIX" +msgstr "etuliite" + +msgid "FIRSTNAME" +msgstr "etunimi" + +msgid "MIDDLENAME" +msgstr "toinen nimi" + +msgid "LASTNAME" +msgstr "sukunimi" + +msgid "NAME_SUFFIX" +msgstr "jälkiliite" + +msgid "NICKNAME" +msgstr "lempinimi" + +msgid "COMPANY" +msgstr "yhtiö" + +msgid "DEPT" +msgstr "osasto" + +msgid "OCCUPATION" +msgstr "titteli" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "osoite" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "osasto" + +msgid "STREET" +msgstr "katuosoite" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "maa" + +msgid "TELEPHONE" +msgstr "puhelin" + +msgid "PHONE_HOME" +msgstr "koti" + +msgid "HOME_SHORT" +msgstr "K" + +msgid "PHONE_MOBILE" +msgstr "mobiili" + +msgid "MOBILE_SHORT" +msgstr "M" + +msgid "PHONE_WORK" +msgstr "työ" + +msgid "WORK_SHORT" +msgstr "T" + +msgid "FAX" +msgstr "faksi" + +msgid "FAX_SHORT" +msgstr "F" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "hakulaite" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Kotisivu" + +msgid "ZIP" +msgstr "Postinumero" + +msgid "CITY" +msgstr "kaupunki" + +msgid "E_MAIL_HOME" +msgstr "e-mail koti" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail toimisto" + +msgid "2ND_ADDRESS" +msgstr "toinen osoite" + +msgid "2ND_PHONE" +msgstr "toinen puhelin" + +msgid "NOTES" +msgstr "merkinnät" + +msgid "MISC" +msgstr "yleinen" + +msgid "BIRTHDAY" +msgstr "syntymäpäivä" + +msgid "ANNIVERSARY" +msgstr "vuosipäivä" + +msgid "CREATED" +msgstr "luonut" + +msgid "MODIFIED" +msgstr "muutettu" + +msgid "UPDATE" +msgstr "päivitä" + +msgid "DELETE" +msgstr "poista" + +msgid "INVALID" +msgstr "virheellinen" + +msgid "ENTER" +msgstr "Tallenna" + +msgid "MEMBER_OF" +msgstr "jäsen" + +msgid "SECONDARY" +msgstr "Toissijainen" + +msgid "CREATE_ACCOUNT" +msgstr "Luo tili" + +msgid "FORGOT_PASSWORD" +msgstr "Salasana unohtunut" + +msgid "UPDATED" +msgstr "päivitetty" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "nimike" + +msgid "MOBILE" +msgstr "mobiili" + +msgid "WORK" +msgstr "työ" + +msgid "FIRST_LAST" +msgstr "nimi" + +msgid "NEXT" +msgstr "seuraava" + +msgid "PHOTO" +msgstr "kuva" + +msgid "ALL_PHONES" +msgstr "kaikki puhnrot" + +msgid "ALL_EMAILS" +msgstr "kaikki e-mailit" + +msgid "SIGN_IN_WITH" +msgstr "Kirjaudu käyttäen" + +msgid "LAST_FIRST" +msgstr "Nimi" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabia" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgaria" + +msgid "ca" +msgstr "Katalonian" + +msgid "cs" +msgstr "TÅ¡ekki" + +msgid "da" +msgstr "Tanska" + +msgid "de" +msgstr "Saksa" + +msgid "el" +msgstr "Kreikka" + +msgid "en" +msgstr "Englanti" + +msgid "es" +msgstr "Espanja" + +msgid "fa" +msgstr "Persia" + +msgid "fi" +msgstr "Suomi" + +msgid "fr" +msgstr "Ranska" + +msgid "he" +msgstr "Heprea" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Unkari" + +msgid "it" +msgstr "Italia" + +msgid "ja" +msgstr "Japani" + +msgid "ko" +msgstr "Korea" + +msgid "nl" +msgstr "Hollanti" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Puola" + +msgid "pt" +msgstr "Portugali" + +msgid "rm" +msgstr "Retoromaani" + +msgid "ru" +msgstr "Venäjä" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenian" + +msgid "sr" +msgstr "Serbialainen" + +msgid "sv" +msgstr "Ruotsi" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Turkki" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnam" + +msgid "zh" +msgstr "Kiina" diff --git a/translations/php-addressbook-fr.po b/translations/php-addressbook-fr.po new file mode 100644 index 0000000..0a1228f --- /dev/null +++ b/translations/php-addressbook-fr.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:32+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "Langue" + +msgid "auto" +msgstr "Paramètres du navigateur (automatique)" + +msgid "USER" +msgstr "Utilisateur" + +msgid "PASSWORD" +msgstr "Mot de passe" + +msgid "LOGIN" +msgstr "Connexion" + +msgid "LOGOUT" +msgstr "Déconnexion" + +msgid "JANUARY" +msgstr "Janvier" + +msgid "FEBRUARY" +msgstr "Février" + +msgid "MARCH" +msgstr "Mars" + +msgid "APRIL" +msgstr "Avril" + +msgid "MAY" +msgstr "Mai" + +msgid "JUNE" +msgstr "Juin" + +msgid "JULY" +msgstr "Juillet" + +msgid "AUGUST" +msgstr "Août" + +msgid "SEPTEMBER" +msgstr "Septembre" + +msgid "OCTOBER" +msgstr "Octobre" + +msgid "NOVEMBER" +msgstr "Novembre" + +msgid "DECEMBER" +msgstr "Décembre" + +msgid "ADDRESS_BOOK" +msgstr "Carnet d'adresses" + +msgid "FOR" +msgstr "Pour" + +msgid "SEARCH" +msgstr "Recherche" + +msgid "HOME" +msgstr "Accueil" + +msgid "NEXT_BIRTHDAYS" +msgstr "Anniversaires" + +msgid "ADD_NEW" +msgstr "Ajouter une adresse" + +msgid "PRINT_ALL" +msgstr "Imprimer fiches" + +msgid "PRINT_PHONES" +msgstr "Imprimer répertoire" + +msgid "EXPORT_CSV" +msgstr "Exporter en csv" + +msgid "EXPORT" +msgstr "exportation" + +msgid "IMPORT" +msgstr "importation" + +msgid "MAP" +msgstr "carte" + +msgid "MORE" +msgstr "plus" + +msgid "GROUP" +msgstr "Groupe" + +msgid "GROUPS" +msgstr "Groupes" + +msgid "MANAGE_GROUPS" +msgstr "Gérer des groupes" + +msgid "NEW_GROUP" +msgstr "Nouveau groupe" + +msgid "DELETE_GROUPS" +msgstr "Supprimer le(s) groupe(s)" + +msgid "EDIT_GROUP" +msgstr "Editer le groupe" + +msgid "GROUP_NAME" +msgstr "Nom du groupe" + +msgid "GROUP_HEADER" +msgstr "En-tête du groupe (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Pied de page du groupe (Commentaire)" + +msgid "GROUP_PARENT" +msgstr "groupe de parents" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Rechercher texte" + +msgid "NUMBER_OF_RESULTS" +msgstr "Nombre de résultats" + +msgid "ALL" +msgstr "Tous" + +msgid "NONE" +msgstr "Aucun" + +msgid "SELECT_ALL" +msgstr "Tout sélectionner" + +msgid "REMOVE_FROM" +msgstr "Retirer" + +msgid "MAIL_CLIENT" +msgstr "Client de messagerie" + +msgid "SEND_EMAIL" +msgstr "Envoyer un e-mail" + +msgid "ADD_TO" +msgstr "Ajouter à" + +msgid "DETAILS" +msgstr "Détails" + +msgid "EDIT" +msgstr "Editer" + +msgid "MODIFY" +msgstr "Modifier" + +msgid "PRINT" +msgstr "Imprimer" + +msgid "EDIT_ADD_ENTRY" +msgstr "Modifier / ajouter une entrée" + +msgid "GUESSED_HOMEPAGE" +msgstr "Deviner page d'accueil" + +msgid "PREFERENCES" +msgstr "Préférences" + +msgid "NAME_PREFIX" +msgstr "préfixe" + +msgid "FIRSTNAME" +msgstr "Prénom" + +msgid "MIDDLENAME" +msgstr "prénom / inital (s)" + +msgid "LASTNAME" +msgstr "Nom" + +msgid "NAME_SUFFIX" +msgstr "suffixe" + +msgid "NICKNAME" +msgstr "surnom" + +msgid "COMPANY" +msgstr "Entreprise" + +msgid "DEPT" +msgstr "Département" + +msgid "OCCUPATION" +msgstr "titre d'emploi" + +msgid "TITLES" +msgstr "titres" + +msgid "ADDRESS" +msgstr "adresse" + +msgid "POB" +msgstr "boîte ou plusieurs" + +msgid "APT" +msgstr "appartement ou d'arrêt" + +msgid "STREET" +msgstr "Adresse rue" + +msgid "STATE" +msgstr "état" + +msgid "COUNTRY" +msgstr "pays" + +msgid "TELEPHONE" +msgstr "Téléphone" + +msgid "PHONE_HOME" +msgstr "Domicile" + +msgid "HOME_SHORT" +msgstr "T" + +msgid "PHONE_MOBILE" +msgstr "Mobile" + +msgid "MOBILE_SHORT" +msgstr "M" + +msgid "PHONE_WORK" +msgstr "Professionel" + +msgid "WORK_SHORT" +msgstr "B" + +msgid "FAX" +msgstr "Fax" + +msgid "FAX_SHORT" +msgstr "F" + +msgid "PHONE2_SHORT" +msgstr "T" + +msgid "PAGER" +msgstr "pager" + +msgid "EMAIL" +msgstr "E-mail" + +msgid "HOMEPAGE" +msgstr "Page personnelle" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "Ville" + +msgid "E_MAIL_HOME" +msgstr "E-mail personnel" + +msgid "E_MAIL_OFFICE" +msgstr "E-mail professionel" + +msgid "2ND_ADDRESS" +msgstr "Seconde adresse" + +msgid "2ND_PHONE" +msgstr "Second téléphone" + +msgid "NOTES" +msgstr "Notes" + +msgid "MISC" +msgstr "Divers" + +msgid "BIRTHDAY" +msgstr "Anniversaire" + +msgid "ANNIVERSARY" +msgstr "anniversaire" + +msgid "CREATED" +msgstr "créé" + +msgid "MODIFIED" +msgstr "Mise à jour" + +msgid "UPDATE" +msgstr "Mettre à jour" + +msgid "DELETE" +msgstr "Supprimer" + +msgid "INVALID" +msgstr "Non valide" + +msgid "ENTER" +msgstr "Entrer" + +msgid "MEMBER_OF" +msgstr "Membre de" + +msgid "SECONDARY" +msgstr "Secondaire" + +msgid "CREATE_ACCOUNT" +msgstr "Créer un compte" + +msgid "FORGOT_PASSWORD" +msgstr "Mot de passe oublié" + +msgid "UPDATED" +msgstr "mis à jour" + +msgid "TRANSLATOR" +msgstr "traducteur" + +msgid "TITLE" +msgstr "titre" + +msgid "MOBILE" +msgstr "mobile" + +msgid "WORK" +msgstr "travail" + +msgid "FIRST_LAST" +msgstr "prénom et nom" + +msgid "NEXT" +msgstr "prochain" + +msgid "PHOTO" +msgstr "photo" + +msgid "ALL_PHONES" +msgstr "Tous téléphones" + +msgid "ALL_EMAILS" +msgstr "Toutes e-mail" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "nom et prénom" + +msgid "GRP_NAME" +msgstr "nom du group" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabe" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgare" + +msgid "ca" +msgstr "Catalogne" + +msgid "cs" +msgstr "Tchèque" + +msgid "da" +msgstr "Danois" + +msgid "de" +msgstr "Allemand" + +msgid "el" +msgstr "Grec" + +msgid "en" +msgstr "Anglais" + +msgid "es" +msgstr "Espagnol" + +msgid "fa" +msgstr "Persan" + +msgid "fi" +msgstr "Finnois" + +msgid "fr" +msgstr "Français" + +msgid "he" +msgstr "Hébreu" + +msgid "hi" +msgstr "Hindî" + +msgid "hu" +msgstr "Hongroise" + +msgid "it" +msgstr "Italien" + +msgid "ja" +msgstr "Japonais" + +msgid "ko" +msgstr "Coréen" + +msgid "nl" +msgstr "Néerlandais" + +msgid "no" +msgstr "norv覩en" + +msgid "pl" +msgstr "Polonais" + +msgid "pt" +msgstr "Portugais" + +msgid "rm" +msgstr "Rhèto-roman" + +msgid "ru" +msgstr "Russe" + +msgid "sk" +msgstr "slovaque" + +msgid "sl" +msgstr "Slovène" + +msgid "sr" +msgstr "Serbe" + +msgid "sv" +msgstr "Suédois" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Turc" + +msgid "ua" +msgstr "ukrainien" + +msgid "vi" +msgstr "Vietnamien" + +msgid "zh" +msgstr "Chinois" diff --git a/translations/php-addressbook-he.po b/translations/php-addressbook-he.po new file mode 100644 index 0000000..402f3ca --- /dev/null +++ b/translations/php-addressbook-he.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:32+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "שפה" + +msgid "auto" +msgstr "ברירת המחדל של הדפדפן (אוטומטית)" + +msgid "USER" +msgstr "משתמש" + +msgid "PASSWORD" +msgstr "סיסמה" + +msgid "LOGIN" +msgstr "התחברות" + +msgid "LOGOUT" +msgstr "התנתקות" + +msgid "JANUARY" +msgstr "ינואר" + +msgid "FEBRUARY" +msgstr "פבואר" + +msgid "MARCH" +msgstr "מרס" + +msgid "APRIL" +msgstr "אפריל" + +msgid "MAY" +msgstr "מאי" + +msgid "JUNE" +msgstr "יוני" + +msgid "JULY" +msgstr "יולי" + +msgid "AUGUST" +msgstr "אוגוסט" + +msgid "SEPTEMBER" +msgstr "ספטמבר" + +msgid "OCTOBER" +msgstr "אוקטובר" + +msgid "NOVEMBER" +msgstr "נובמבר" + +msgid "DECEMBER" +msgstr "דצמבר" + +msgid "ADDRESS_BOOK" +msgstr "פנקס הכתובות" + +msgid "FOR" +msgstr "עבור" + +msgid "SEARCH" +msgstr "חיפוש" + +msgid "HOME" +msgstr "ראשי" + +msgid "NEXT_BIRTHDAYS" +msgstr "ימי הולדת" + +msgid "ADD_NEW" +msgstr "ההערה" + +msgid "PRINT_ALL" +msgstr "הדפס הכל" + +msgid "PRINT_PHONES" +msgstr "רשימת טלפונים" + +msgid "EXPORT_CSV" +msgstr "CSV" + +msgid "EXPORT" +msgstr "יצוא" + +msgid "IMPORT" +msgstr "יבוא" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "קבוצה" + +msgid "GROUPS" +msgstr "קבוצות" + +msgid "MANAGE_GROUPS" +msgstr "קבוצות" + +msgid "NEW_GROUP" +msgstr "הוסף קבוצה" + +msgid "DELETE_GROUPS" +msgstr "Group (s)" + +msgid "EDIT_GROUP" +msgstr "קבוצת אישית" + +msgid "GROUP_NAME" +msgstr "שם הקבוצה" + +msgid "GROUP_HEADER" +msgstr "כותרת (לוגו)" + +msgid "GROUP_FOOTER" +msgstr "כותרת תחתונה (comment)" + +msgid "GROUP_PARENT" +msgstr "האם הקבוצה" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "חיפוש טקסט חופשי" + +msgid "NUMBER_OF_RESULTS" +msgstr "מראה" + +msgid "ALL" +msgstr "הכל" + +msgid "NONE" +msgstr "ללא" + +msgid "SELECT_ALL" +msgstr "הכול" + +msgid "REMOVE_FROM" +msgstr "הסר" + +msgid "MAIL_CLIENT" +msgstr "תוכנית דואר אלקטרוני" + +msgid "SEND_EMAIL" +msgstr "שלח E-Mail" + +msgid "ADD_TO" +msgstr "הוסף" + +msgid "DETAILS" +msgstr "פרטים" + +msgid "EDIT" +msgstr "שינוי" + +msgid "MODIFY" +msgstr "התאמה אישית" + +msgid "PRINT" +msgstr "הדפס" + +msgid "EDIT_ADD_ENTRY" +msgstr "כניסה / שינוי" + +msgid "GUESSED_HOMEPAGE" +msgstr "אפשריות הבית" + +msgid "PREFERENCES" +msgstr "העדפות" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "שם פרטי" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "שם משפחה" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "חברה" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "כתובת" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "טל." + +msgid "PHONE_HOME" +msgstr "פרטי" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "Mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "עסקים" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "פקס" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "דואר אלקטרוני" + +msgid "HOMEPAGE" +msgstr "Homepage" + +msgid "ZIP" +msgstr "מיקוד" + +msgid "CITY" +msgstr "עיר" + +msgid "E_MAIL_HOME" +msgstr "דואר אלקטרוני פרטיות" + +msgid "E_MAIL_OFFICE" +msgstr "E-Mail Office" + +msgid "2ND_ADDRESS" +msgstr "כתובת משנית" + +msgid "2ND_PHONE" +msgstr "שנית טלפון" + +msgid "NOTES" +msgstr "הערות" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "יום הולדת" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "נוצר" + +msgid "MODIFIED" +msgstr "Modified" + +msgid "UPDATE" +msgstr "עדכון" + +msgid "DELETE" +msgstr "למחוק" + +msgid "INVALID" +msgstr "לא חוקי" + +msgid "ENTER" +msgstr "לשמור" + +msgid "MEMBER_OF" +msgstr "חבר" + +msgid "SECONDARY" +msgstr "כתובת משנית" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "ערבית" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "בולגרית" + +msgid "ca" +msgstr "Catalonian" + +msgid "cs" +msgstr "צ 'כית" + +msgid "da" +msgstr "דנית" + +msgid "de" +msgstr "גרמנית" + +msgid "el" +msgstr "יוונית" + +msgid "en" +msgstr "אנגלית" + +msgid "es" +msgstr "ספרדית" + +msgid "fa" +msgstr "פרסית" + +msgid "fi" +msgstr "פינית" + +msgid "fr" +msgstr "צרפתית" + +msgid "he" +msgstr "עברית" + +msgid "hi" +msgstr "הינדית" + +msgid "hu" +msgstr "הונגרי" + +msgid "it" +msgstr "איטלקית" + +msgid "ja" +msgstr "יפני" + +msgid "ko" +msgstr "קוריאנית" + +msgid "nl" +msgstr "הולנדית" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "niemiecki" + +msgid "pt" +msgstr "פורטוגזית" + +msgid "rm" +msgstr "" + +msgid "ru" +msgstr "רוסית" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "סלובנית" + +msgid "sr" +msgstr "הסרבית" + +msgid "sv" +msgstr "שוודית" + +msgid "th" +msgstr "תאילנדית" + +msgid "tr" +msgstr "טורקית" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "ויאטנמית" + +msgid "zh" +msgstr "סינית" diff --git a/translations/php-addressbook-hi.po b/translations/php-addressbook-hi.po new file mode 100644 index 0000000..4e8b1d8 --- /dev/null +++ b/translations/php-addressbook-hi.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-07 17:04+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "भाषा" + +msgid "auto" +msgstr "bowser सेटिंग्स () स्वत:" + +msgid "USER" +msgstr "उपयोगकर्ता" + +msgid "PASSWORD" +msgstr "पासवर्ड" + +msgid "LOGIN" +msgstr "लॉग इन" + +msgid "LOGOUT" +msgstr "लॉगआउट" + +msgid "JANUARY" +msgstr "जनवरी" + +msgid "FEBRUARY" +msgstr "फ़रवरी" + +msgid "MARCH" +msgstr "मार्च" + +msgid "APRIL" +msgstr "अप्रैल" + +msgid "MAY" +msgstr "मई" + +msgid "JUNE" +msgstr "जून" + +msgid "JULY" +msgstr "जुलाई" + +msgid "AUGUST" +msgstr "अगस्त" + +msgid "SEPTEMBER" +msgstr "सितम्बर" + +msgid "OCTOBER" +msgstr "अक्टूबर" + +msgid "NOVEMBER" +msgstr "नवम्बर" + +msgid "DECEMBER" +msgstr "दिसम्बर" + +msgid "ADDRESS_BOOK" +msgstr "पता पुस्तिका" + +msgid "FOR" +msgstr "के लिए" + +msgid "SEARCH" +msgstr "खोज" + +msgid "HOME" +msgstr "घर" + +msgid "NEXT_BIRTHDAYS" +msgstr "अगले जन्मदिन" + +msgid "ADD_NEW" +msgstr "जोड़ नया" + +msgid "PRINT_ALL" +msgstr "सभी मुद्रित" + +msgid "PRINT_PHONES" +msgstr "प्रिंट फोन" + +msgid "EXPORT_CSV" +msgstr "निर्यात csv" + +msgid "EXPORT" +msgstr "निर्यात" + +msgid "IMPORT" +msgstr "आयात" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "समूह" + +msgid "GROUPS" +msgstr "समूह" + +msgid "MANAGE_GROUPS" +msgstr "समूह का प्रबंधन" + +msgid "NEW_GROUP" +msgstr "नया समूह" + +msgid "DELETE_GROUPS" +msgstr "(s) समूह नष्ट" + +msgid "EDIT_GROUP" +msgstr "समूह संपादित करें" + +msgid "GROUP_NAME" +msgstr "समूह नाम" + +msgid "GROUP_HEADER" +msgstr "समूह शीर्षक (लोगो)" + +msgid "GROUP_FOOTER" +msgstr "समूह पाद लेख (टिप्पणी)" + +msgid "GROUP_PARENT" +msgstr "माता पिता के समूह" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "किसी भी पाठ के लिए खोज" + +msgid "NUMBER_OF_RESULTS" +msgstr "परिणामों की संख्या" + +msgid "ALL" +msgstr "सब" + +msgid "NONE" +msgstr "कोई नहीं" + +msgid "SELECT_ALL" +msgstr "सभी का चयन करें" + +msgid "REMOVE_FROM" +msgstr "से निकालें" + +msgid "MAIL_CLIENT" +msgstr "मेल क्लाइंट" + +msgid "SEND_EMAIL" +msgstr "ई भेजें-Mail" + +msgid "ADD_TO" +msgstr "में जोड़ें" + +msgid "DETAILS" +msgstr "विवरण" + +msgid "EDIT" +msgstr "संपादित करें" + +msgid "MODIFY" +msgstr "संशोधित" + +msgid "PRINT" +msgstr "प्रिंट" + +msgid "EDIT_ADD_ENTRY" +msgstr "संपादित करें / पता पुस्तिका प्रविष्टि जोड़ना" + +msgid "GUESSED_HOMEPAGE" +msgstr "अनुमान मुखपृष्ठ" + +msgid "PREFERENCES" +msgstr "वरीयताएँ" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "प्रथम नाम" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "अंतिम नाम" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "कंपनी" + +msgid "DEPT" +msgstr "विभाग" + +msgid "OCCUPATION" +msgstr "नौकरी शीर्षक" + +msgid "TITLES" +msgstr "शीर्षक" + +msgid "ADDRESS" +msgstr "पता" + +msgid "POB" +msgstr "बॉक्स या संख्या" + +msgid "APT" +msgstr "अपार्टमेंट या बंद" + +msgid "STREET" +msgstr "सड़क पत" + +msgid "STATE" +msgstr "राज" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "टेलीफोन" + +msgid "PHONE_HOME" +msgstr "घर" + +msgid "HOME_SHORT" +msgstr "एच" + +msgid "PHONE_MOBILE" +msgstr "मोबाइल" + +msgid "MOBILE_SHORT" +msgstr "एम" + +msgid "PHONE_WORK" +msgstr "काम" + +msgid "WORK_SHORT" +msgstr "काम" + +msgid "FAX" +msgstr "फैक्स" + +msgid "FAX_SHORT" +msgstr "एफ" + +msgid "PHONE2_SHORT" +msgstr "पी" + +msgid "PAGER" +msgstr "पेजर" + +msgid "EMAIL" +msgstr "ईमेल" + +msgid "HOMEPAGE" +msgstr "मुखपृष्ठ" + +msgid "ZIP" +msgstr "ज़िप" + +msgid "CITY" +msgstr "नगर" + +msgid "E_MAIL_HOME" +msgstr "ईमेल घर" + +msgid "E_MAIL_OFFICE" +msgstr "ईमेल कार्यालय" + +msgid "2ND_ADDRESS" +msgstr "दूसरा पता" + +msgid "2ND_PHONE" +msgstr "दूसरे फोन" + +msgid "NOTES" +msgstr "नोट" + +msgid "MISC" +msgstr "विविध" + +msgid "BIRTHDAY" +msgstr "जन्मदिन" + +msgid "ANNIVERSARY" +msgstr "वर्षगांठ" + +msgid "CREATED" +msgstr "निर्मित" + +msgid "MODIFIED" +msgstr "संशोधित" + +msgid "UPDATE" +msgstr "अद्यतन करना" + +msgid "DELETE" +msgstr "मिटाना" + +msgid "INVALID" +msgstr "अवैध" + +msgid "ENTER" +msgstr "दर्ज करें" + +msgid "MEMBER_OF" +msgstr "सदस्य का" + +msgid "SECONDARY" +msgstr "माध्यमिक" + +msgid "CREATE_ACCOUNT" +msgstr "खाता बनाएँ" + +msgid "FORGOT_PASSWORD" +msgstr "पासवर्ड भूल" + +msgid "UPDATED" +msgstr "अद्यतन" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "शीर्षक" + +msgid "MOBILE" +msgstr "मोबाइल" + +msgid "WORK" +msgstr "काम" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "अगले" + +msgid "PHOTO" +msgstr "तस्वीर" + +msgid "ALL_PHONES" +msgstr "सभी फोन" + +msgid "ALL_EMAILS" +msgstr "सभी ई - मेल" + +msgid "SIGN_IN_WITH" +msgstr "साथ में साइन इन" + +msgid "LAST_FIRST" +msgstr "नाम" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "अबखाजियन" + +msgid "ar" +msgstr "अरबी" + +msgid "be" +msgstr "बेलारूस" + +msgid "bg" +msgstr "बल्गेरियन्" + +msgid "ca" +msgstr "Catalonian" + +msgid "cs" +msgstr "चेक़" + +msgid "da" +msgstr "डैनीश" + +msgid "de" +msgstr "जर्मन" + +msgid "el" +msgstr "ग्रीक" + +msgid "en" +msgstr "अंग्रेज़ी" + +msgid "es" +msgstr "स्पैनिश" + +msgid "fa" +msgstr "फारसी" + +msgid "fi" +msgstr "फिनिश" + +msgid "fr" +msgstr "फ़्रेंच" + +msgid "he" +msgstr "हिब्रू" + +msgid "hi" +msgstr "हिन्दी" + +msgid "hu" +msgstr "हंगरी" + +msgid "it" +msgstr "इतालवी" + +msgid "ja" +msgstr "जापानी" + +msgid "ko" +msgstr "कोरियाई" + +msgid "nl" +msgstr "डच्" + +msgid "no" +msgstr "नार्वेजियाई" + +msgid "pl" +msgstr "पोलिश" + +msgid "pt" +msgstr "पुर्तगाली" + +msgid "rm" +msgstr "Rhaeto-रोमांस" + +msgid "ru" +msgstr "रूसी" + +msgid "sk" +msgstr "स्लोवेकिया" + +msgid "sl" +msgstr "स्लोवेनियाई" + +msgid "sr" +msgstr "सर्बियाई" + +msgid "sv" +msgstr "स्विडिश" + +msgid "th" +msgstr "थाई" + +msgid "tr" +msgstr "तुर्की" + +msgid "ua" +msgstr "यूक्रेनी" + +msgid "vi" +msgstr "वियेतनामी" + +msgid "zh" +msgstr "चीनी" diff --git a/translations/php-addressbook-hu.po b/translations/php-addressbook-hu.po new file mode 100644 index 0000000..37081b4 --- /dev/null +++ b/translations/php-addressbook-hu.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:46+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "nyelv" + +msgid "auto" +msgstr "Bowser beállítások (automata)" + +msgid "USER" +msgstr "Felhasználó" + +msgid "PASSWORD" +msgstr "Jelszó" + +msgid "LOGIN" +msgstr "Bejelentkezés" + +msgid "LOGOUT" +msgstr "Kijelentkezés" + +msgid "JANUARY" +msgstr "Január" + +msgid "FEBRUARY" +msgstr "Február" + +msgid "MARCH" +msgstr "Március" + +msgid "APRIL" +msgstr "Április" + +msgid "MAY" +msgstr "Május" + +msgid "JUNE" +msgstr "Június" + +msgid "JULY" +msgstr "Július" + +msgid "AUGUST" +msgstr "Augusztus" + +msgid "SEPTEMBER" +msgstr "Szeptember" + +msgid "OCTOBER" +msgstr "Október" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "Címjegyzék" + +msgid "FOR" +msgstr "miatt" + +msgid "SEARCH" +msgstr "Keresés" + +msgid "HOME" +msgstr "Kezdőlap" + +msgid "NEXT_BIRTHDAYS" +msgstr "Születésnapok" + +msgid "ADD_NEW" +msgstr "Új" + +msgid "PRINT_ALL" +msgstr "Nyomtatás" + +msgid "PRINT_PHONES" +msgstr "Telefonszámok" + +msgid "EXPORT_CSV" +msgstr "csv export" + +msgid "EXPORT" +msgstr "Export" + +msgid "IMPORT" +msgstr "Import" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "csoporthoz" + +msgid "GROUPS" +msgstr "Csoportok" + +msgid "MANAGE_GROUPS" +msgstr "Csoportok" + +msgid "NEW_GROUP" +msgstr "új csoport" + +msgid "DELETE_GROUPS" +msgstr "csoport (ok) törlése" + +msgid "EDIT_GROUP" +msgstr "Csoport szerkesztése" + +msgid "GROUP_NAME" +msgstr "csoport neve" + +msgid "GROUP_HEADER" +msgstr "Group header (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Group footer (Comment)" + +msgid "GROUP_PARENT" +msgstr "Szülő csoport" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Keresés minden szövegben" + +msgid "NUMBER_OF_RESULTS" +msgstr "Találatok száma" + +msgid "ALL" +msgstr "mind" + +msgid "NONE" +msgstr "semmi" + +msgid "SELECT_ALL" +msgstr "select all" + +msgid "REMOVE_FROM" +msgstr "elszállítani" + +msgid "MAIL_CLIENT" +msgstr "e-mail kliens" + +msgid "SEND_EMAIL" +msgstr "Küldj e-mail" + +msgid "ADD_TO" +msgstr "hozzáadni" + +msgid "DETAILS" +msgstr "adatokat" + +msgid "EDIT" +msgstr "Szerkeszt" + +msgid "MODIFY" +msgstr "módosítás" + +msgid "PRINT" +msgstr "Nyomtatás" + +msgid "EDIT_ADD_ENTRY" +msgstr "Szerkesztés / add címjegyzékbejegyzést" + +msgid "GUESSED_HOMEPAGE" +msgstr "Honlap (következtetés)" + +msgid "PREFERENCES" +msgstr "preferenciákat" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "keresztnév" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "vezetéknév" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "társaság" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "cím" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "otthoni" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "munkahelyi" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Honlap" + +msgid "ZIP" +msgstr "irányítószám" + +msgid "CITY" +msgstr "város" + +msgid "E_MAIL_HOME" +msgstr "e-mail otthoni" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail iroda" + +msgid "2ND_ADDRESS" +msgstr "a második cím" + +msgid "2ND_PHONE" +msgstr "második telefonvonal" + +msgid "NOTES" +msgstr "Megjegyzés" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "születésnap" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "készített" + +msgid "MODIFIED" +msgstr "módosítás" + +msgid "UPDATE" +msgstr "frissítés" + +msgid "DELETE" +msgstr "Törlés" + +msgid "INVALID" +msgstr "érvénytelenek" + +msgid "ENTER" +msgstr "lépnie" + +msgid "MEMBER_OF" +msgstr "tagját" + +msgid "SECONDARY" +msgstr "Secondary" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "Frissítés" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arab" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bolgár" + +msgid "ca" +msgstr "Kataloniai" + +msgid "cs" +msgstr "Cseh" + +msgid "da" +msgstr "Dán" + +msgid "de" +msgstr "Német" + +msgid "el" +msgstr "Görög" + +msgid "en" +msgstr "Angol" + +msgid "es" +msgstr "Spanyol" + +msgid "fa" +msgstr "Perzsa" + +msgid "fi" +msgstr "Finn" + +msgid "fr" +msgstr "Francia" + +msgid "he" +msgstr "Héber" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Magyar" + +msgid "it" +msgstr "Olasz" + +msgid "ja" +msgstr "Japán" + +msgid "ko" +msgstr "Koreai" + +msgid "nl" +msgstr "Holland" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Lengyel" + +msgid "pt" +msgstr "Portugál" + +msgid "rm" +msgstr "Réto-román" + +msgid "ru" +msgstr "Orosz" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Szlovén" + +msgid "sr" +msgstr "Szerb" + +msgid "sv" +msgstr "Svéd" + +msgid "th" +msgstr "Thaiföldi" + +msgid "tr" +msgstr "Török" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnámi" + +msgid "zh" +msgstr "Kínai" diff --git a/translations/php-addressbook-it.po b/translations/php-addressbook-it.po new file mode 100644 index 0000000..0f81486 --- /dev/null +++ b/translations/php-addressbook-it.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-07-12 10:02+0000\n" +"Last-Translator: Roberto \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "lingua" + +msgid "auto" +msgstr "browser impostazioni (automatico)" + +msgid "USER" +msgstr "Utente" + +msgid "PASSWORD" +msgstr "Password" + +msgid "LOGIN" +msgstr "Accesso" + +msgid "LOGOUT" +msgstr "Logout" + +msgid "JANUARY" +msgstr "Gennaio" + +msgid "FEBRUARY" +msgstr "Febbraio" + +msgid "MARCH" +msgstr "Marzo" + +msgid "APRIL" +msgstr "Aprile" + +msgid "MAY" +msgstr "Maggio" + +msgid "JUNE" +msgstr "Giugno" + +msgid "JULY" +msgstr "Luglio" + +msgid "AUGUST" +msgstr "Agosto" + +msgid "SEPTEMBER" +msgstr "Settembre" + +msgid "OCTOBER" +msgstr "Ottobre" + +msgid "NOVEMBER" +msgstr "Novembre" + +msgid "DECEMBER" +msgstr "Dicembre" + +msgid "ADDRESS_BOOK" +msgstr "Rubrica" + +msgid "FOR" +msgstr "per" + +msgid "SEARCH" +msgstr "ricerca" + +msgid "HOME" +msgstr "HOME" + +msgid "NEXT_BIRTHDAYS" +msgstr "compleanni" + +msgid "ADD_NEW" +msgstr "nuovo" + +msgid "PRINT_ALL" +msgstr "stampa tutto" + +msgid "PRINT_PHONES" +msgstr "stampa numeri telefonici" + +msgid "EXPORT_CSV" +msgstr "esporta csv" + +msgid "EXPORT" +msgstr "esportazione" + +msgid "IMPORT" +msgstr "importazione" + +msgid "MAP" +msgstr "Mappa" + +msgid "MORE" +msgstr "Altro" + +msgid "GROUP" +msgstr "gruppo" + +msgid "GROUPS" +msgstr "gruppi" + +msgid "MANAGE_GROUPS" +msgstr "gestione gruppi" + +msgid "NEW_GROUP" +msgstr "nuovo gruppo" + +msgid "DELETE_GROUPS" +msgstr "Elimina gruppo (i)" + +msgid "EDIT_GROUP" +msgstr "Modifica gruppo" + +msgid "GROUP_NAME" +msgstr "nome del gruppo" + +msgid "GROUP_HEADER" +msgstr "Intestazione gruppo" + +msgid "GROUP_FOOTER" +msgstr "Pié di pagina gruppo (Commenti)" + +msgid "GROUP_PARENT" +msgstr "Capogruppo" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "ricerca testo" + +msgid "NUMBER_OF_RESULTS" +msgstr "Numero di risultati" + +msgid "ALL" +msgstr "tutti" + +msgid "NONE" +msgstr "nessuno" + +msgid "SELECT_ALL" +msgstr "seleziona tutti" + +msgid "REMOVE_FROM" +msgstr "rimuovi da" + +msgid "MAIL_CLIENT" +msgstr "client e-mail" + +msgid "SEND_EMAIL" +msgstr "Invia e-mail" + +msgid "ADD_TO" +msgstr "aggiungi a" + +msgid "DETAILS" +msgstr "dettagli" + +msgid "EDIT" +msgstr "Modifica" + +msgid "MODIFY" +msgstr "modificare" + +msgid "PRINT" +msgstr "stampa" + +msgid "EDIT_ADD_ENTRY" +msgstr "Modifica / aggiungi voce della rubrica" + +msgid "GUESSED_HOMEPAGE" +msgstr "home page prevista" + +msgid "PREFERENCES" +msgstr "preferenze" + +msgid "NAME_PREFIX" +msgstr "Prefisso Nome" + +msgid "FIRSTNAME" +msgstr "nome / Settore" + +msgid "MIDDLENAME" +msgstr "Sottosettore" + +msgid "LASTNAME" +msgstr "cognome / Ufficio" + +msgid "NAME_SUFFIX" +msgstr "Suffisso Nome" + +msgid "NICKNAME" +msgstr "Riferimento" + +msgid "COMPANY" +msgstr "Società" + +msgid "DEPT" +msgstr "Dipartimento" + +msgid "OCCUPATION" +msgstr "Professione" + +msgid "TITLES" +msgstr "Titoli" + +msgid "ADDRESS" +msgstr "indirizzo" + +msgid "POB" +msgstr "Luogo di nascita" + +msgid "APT" +msgstr "Appartamento" + +msgid "STREET" +msgstr "Strada" + +msgid "STATE" +msgstr "Stato" + +msgid "COUNTRY" +msgstr "Regione" + +msgid "TELEPHONE" +msgstr "telefono" + +msgid "PHONE_HOME" +msgstr "casa" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "cellulare" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "lavoro" + +msgid "WORK_SHORT" +msgstr "numero breve lavoro" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Homepage" + +msgid "ZIP" +msgstr "CAP" + +msgid "CITY" +msgstr "città" + +msgid "E_MAIL_HOME" +msgstr "e-mail casa" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail lavoro" + +msgid "2ND_ADDRESS" +msgstr "secondo indirizzo" + +msgid "2ND_PHONE" +msgstr "secondo telefono" + +msgid "NOTES" +msgstr "Note" + +msgid "MISC" +msgstr "Miscellanea" + +msgid "BIRTHDAY" +msgstr "compleanno" + +msgid "ANNIVERSARY" +msgstr "Onomastico" + +msgid "CREATED" +msgstr "creato" + +msgid "MODIFIED" +msgstr "modificato" + +msgid "UPDATE" +msgstr "aggiorna" + +msgid "DELETE" +msgstr "elimina" + +msgid "INVALID" +msgstr "non valido" + +msgid "ENTER" +msgstr "inserisci" + +msgid "MEMBER_OF" +msgstr "membro di" + +msgid "SECONDARY" +msgstr "Secondaria" + +msgid "CREATE_ACCOUNT" +msgstr "Crea Account" + +msgid "FORGOT_PASSWORD" +msgstr "Password DImenticata" + +msgid "UPDATED" +msgstr "Aggiornato" + +msgid "TRANSLATOR" +msgstr "Traduttore" + +msgid "TITLE" +msgstr "TITOLO" + +msgid "MOBILE" +msgstr "Cellulare" + +msgid "WORK" +msgstr "Lavoro" + +msgid "FIRST_LAST" +msgstr "Nome Cognome / Settore Ufficio" + +msgid "NEXT" +msgstr "Avanti" + +msgid "PHOTO" +msgstr "Foto" + +msgid "ALL_PHONES" +msgstr "Tutti i Numeri" + +msgid "ALL_EMAILS" +msgstr "Tutte le e-mail" + +msgid "SIGN_IN_WITH" +msgstr "Accedi Con" + +msgid "LAST_FIRST" +msgstr "Cognome Nome" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabo" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgaro" + +msgid "ca" +msgstr "Catalano" + +msgid "cs" +msgstr "Ceco" + +msgid "da" +msgstr "Danese" + +msgid "de" +msgstr "Tedesco" + +msgid "el" +msgstr "Greco" + +msgid "en" +msgstr "Inglese" + +msgid "es" +msgstr "Spagnolo" + +msgid "fa" +msgstr "Farsi" + +msgid "fi" +msgstr "Finlandese" + +msgid "fr" +msgstr "Francese" + +msgid "he" +msgstr "Ebraico" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Ungherese" + +msgid "it" +msgstr "Italiano" + +msgid "ja" +msgstr "Giapponese" + +msgid "ko" +msgstr "Coreano" + +msgid "nl" +msgstr "Olandese" + +msgid "no" +msgstr "Norvegese" + +msgid "pl" +msgstr "Polacco" + +msgid "pt" +msgstr "Portoghese" + +msgid "rm" +msgstr "Retoromanzo" + +msgid "ru" +msgstr "Russo" + +msgid "sk" +msgstr "Slovacco" + +msgid "sl" +msgstr "Sloveno" + +msgid "sr" +msgstr "Serbo" + +msgid "sv" +msgstr "Svedese" + +msgid "th" +msgstr "Tailandese" + +msgid "tr" +msgstr "Turco" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamita" + +msgid "zh" +msgstr "Cinese" diff --git a/translations/php-addressbook-ja.po b/translations/php-addressbook-ja.po new file mode 100644 index 0000000..a79167e --- /dev/null +++ b/translations/php-addressbook-ja.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2014-01-28 02:27+0000\n" +"Last-Translator: taka \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "言語" + +msgid "auto" +msgstr "ブラウザー設定 (自動)" + +msgid "USER" +msgstr "ユーザー" + +msgid "PASSWORD" +msgstr "パスワード" + +msgid "LOGIN" +msgstr "ログイン" + +msgid "LOGOUT" +msgstr "ログアウト" + +msgid "JANUARY" +msgstr "1 月" + +msgid "FEBRUARY" +msgstr "2 月" + +msgid "MARCH" +msgstr "3 月" + +msgid "APRIL" +msgstr "4 月" + +msgid "MAY" +msgstr "5 月" + +msgid "JUNE" +msgstr "6 月" + +msgid "JULY" +msgstr "7 月" + +msgid "AUGUST" +msgstr "8 月" + +msgid "SEPTEMBER" +msgstr "9 月" + +msgid "OCTOBER" +msgstr "10 月" + +msgid "NOVEMBER" +msgstr "11 月" + +msgid "DECEMBER" +msgstr "12 月" + +msgid "ADDRESS_BOOK" +msgstr "アドレス帳" + +msgid "FOR" +msgstr "for" + +msgid "SEARCH" +msgstr "検索" + +msgid "HOME" +msgstr "ホーム" + +msgid "NEXT_BIRTHDAYS" +msgstr "誕生日が近いユーザー" + +msgid "ADD_NEW" +msgstr "新規追加" + +msgid "PRINT_ALL" +msgstr "すべて印刷" + +msgid "PRINT_PHONES" +msgstr "電話番号の印刷" + +msgid "EXPORT_CSV" +msgstr "CSV のエクスポート" + +msgid "EXPORT" +msgstr "エクスポート" + +msgid "IMPORT" +msgstr "インポート" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "もっと見る" + +msgid "GROUP" +msgstr "グループ" + +msgid "GROUPS" +msgstr "グループ" + +msgid "MANAGE_GROUPS" +msgstr "グループ管理" + +msgid "NEW_GROUP" +msgstr "新規グループ" + +msgid "DELETE_GROUPS" +msgstr "グループの削除" + +msgid "EDIT_GROUP" +msgstr "グループの編集" + +msgid "GROUP_NAME" +msgstr "グループ名" + +msgid "GROUP_HEADER" +msgstr "グループ ヘッダー (ロゴ)" + +msgid "GROUP_FOOTER" +msgstr "グループ フッター (コメント)" + +msgid "GROUP_PARENT" +msgstr "親グループ" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "テキスト検索" + +msgid "NUMBER_OF_RESULTS" +msgstr "検索の結果" + +msgid "ALL" +msgstr "すべて" + +msgid "NONE" +msgstr "なし" + +msgid "SELECT_ALL" +msgstr "すべて選択" + +msgid "REMOVE_FROM" +msgstr "remove from" + +msgid "MAIL_CLIENT" +msgstr "メール クライアント" + +msgid "SEND_EMAIL" +msgstr "電子メールの送信" + +msgid "ADD_TO" +msgstr "add to" + +msgid "DETAILS" +msgstr "詳細" + +msgid "EDIT" +msgstr "編集" + +msgid "MODIFY" +msgstr "修正" + +msgid "PRINT" +msgstr "印刷" + +msgid "EDIT_ADD_ENTRY" +msgstr "アドレス帳のエントリー 追加 / 編集" + +msgid "GUESSED_HOMEPAGE" +msgstr "Guessed homepage" + +msgid "PREFERENCES" +msgstr "プリファレンス" + +msgid "NAME_PREFIX" +msgstr "敬称(後)" + +msgid "FIRSTNAME" +msgstr "名前" + +msgid "MIDDLENAME" +msgstr "ミドルネーム" + +msgid "LASTNAME" +msgstr "苗字" + +msgid "NAME_SUFFIX" +msgstr "敬称(前)" + +msgid "NICKNAME" +msgstr "ニックネーム" + +msgid "COMPANY" +msgstr "企業名" + +msgid "DEPT" +msgstr "部署" + +msgid "OCCUPATION" +msgstr "職種" + +msgid "TITLES" +msgstr "タイトル" + +msgid "ADDRESS" +msgstr "住所" + +msgid "POB" +msgstr "私書箱" + +msgid "APT" +msgstr "アパート名" + +msgid "STREET" +msgstr "市区町村以下" + +msgid "STATE" +msgstr "市区町村名" + +msgid "COUNTRY" +msgstr "国" + +msgid "TELEPHONE" +msgstr "電話番号" + +msgid "PHONE_HOME" +msgstr "自宅" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "携帯電話番号" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "職場電話番号" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "電話番号2" + +msgid "PAGER" +msgstr "ページャー" + +msgid "EMAIL" +msgstr "電子メール" + +msgid "HOMEPAGE" +msgstr "ホームページ" + +msgid "ZIP" +msgstr "郵便番号" + +msgid "CITY" +msgstr "都市" + +msgid "E_MAIL_HOME" +msgstr "自宅の電子メール" + +msgid "E_MAIL_OFFICE" +msgstr "オフィスの電子メール" + +msgid "2ND_ADDRESS" +msgstr "2 つ目の住所" + +msgid "2ND_PHONE" +msgstr "2 つ目の電話" + +msgid "NOTES" +msgstr "注釈" + +msgid "MISC" +msgstr "その他" + +msgid "BIRTHDAY" +msgstr "誕生日" + +msgid "ANNIVERSARY" +msgstr "記念日" + +msgid "CREATED" +msgstr "created" + +msgid "MODIFIED" +msgstr "modified" + +msgid "UPDATE" +msgstr "更新" + +msgid "DELETE" +msgstr "削除" + +msgid "INVALID" +msgstr "無効" + +msgid "ENTER" +msgstr "入力" + +msgid "MEMBER_OF" +msgstr "所属" + +msgid "SECONDARY" +msgstr "セカンダリ" + +msgid "CREATE_ACCOUNT" +msgstr "アカウントの作成" + +msgid "FORGOT_PASSWORD" +msgstr "パスワードを忘れた" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "SmartScape" + +msgid "TITLE" +msgstr "役職" + +msgid "MOBILE" +msgstr "モバイル" + +msgid "WORK" +msgstr "職場" + +msgid "FIRST_LAST" +msgstr "名前(名,性)" + +msgid "NEXT" +msgstr "次へ" + +msgid "PHOTO" +msgstr "写真" + +msgid "ALL_PHONES" +msgstr "全ての電話番号" + +msgid "ALL_EMAILS" +msgstr "全てのメールアドレス" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "名前(性,名)" + +msgid "GRP_NAME" +msgstr "グループ名" + +msgid "ab" +msgstr "アブハジア語" + +msgid "ar" +msgstr "アラビア語" + +msgid "be" +msgstr "ベルギー語" + +msgid "bg" +msgstr "ブルガリア語" + +msgid "ca" +msgstr "カタロニア語" + +msgid "cs" +msgstr "チェコ語" + +msgid "da" +msgstr "デンマーク語" + +msgid "de" +msgstr "ドイツ語" + +msgid "el" +msgstr "ギリシャ語" + +msgid "en" +msgstr "英語" + +msgid "es" +msgstr "スペイン語" + +msgid "fa" +msgstr "ペルシア語" + +msgid "fi" +msgstr "フィンランド語" + +msgid "fr" +msgstr "フランス語" + +msgid "he" +msgstr "ヘブライ語" + +msgid "hi" +msgstr "ヒンディー語" + +msgid "hu" +msgstr "ハンガリー語" + +msgid "it" +msgstr "イタリア語" + +msgid "ja" +msgstr "日本語" + +msgid "ko" +msgstr "韓国語" + +msgid "nl" +msgstr "オランダ語" + +msgid "no" +msgstr "いいえ" + +msgid "pl" +msgstr "ポーランド語" + +msgid "pt" +msgstr "ポルトガル語" + +msgid "rm" +msgstr "レトロマン語" + +msgid "ru" +msgstr "ロシア語" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "スロヴェニア語" + +msgid "sr" +msgstr "セルビア語" + +msgid "sv" +msgstr "スウェーデン語" + +msgid "th" +msgstr "タイ語" + +msgid "tr" +msgstr "トルコ語" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "ベトナム人" + +msgid "zh" +msgstr "中国語" diff --git a/translations/php-addressbook-ko.po b/translations/php-addressbook-ko.po new file mode 100644 index 0000000..1b724c4 --- /dev/null +++ b/translations/php-addressbook-ko.po @@ -0,0 +1,482 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PHP Address Book\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2014-04-09 13:00+0000\n" +"Last-Translator: Josh Kim \n" +"Language-Team: Josh Kim \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" +"Language: ko\n" +"X-Poedit-SourceCharset: UTF-8\n" + +msgid "LANGUAGE" +msgstr "언어" + +msgid "auto" +msgstr "브라우저 설정(자동)" + +msgid "USER" +msgstr "사용자" + +msgid "PASSWORD" +msgstr "암호" + +msgid "LOGIN" +msgstr "로그인" + +msgid "LOGOUT" +msgstr "로그아웃" + +msgid "JANUARY" +msgstr "1월" + +msgid "FEBRUARY" +msgstr "2월" + +msgid "MARCH" +msgstr "3월" + +msgid "APRIL" +msgstr "4월" + +msgid "MAY" +msgstr "5월" + +msgid "JUNE" +msgstr "6월" + +msgid "JULY" +msgstr "7월" + +msgid "AUGUST" +msgstr "8월" + +msgid "SEPTEMBER" +msgstr "9월" + +msgid "OCTOBER" +msgstr "10월" + +msgid "NOVEMBER" +msgstr "11월" + +msgid "DECEMBER" +msgstr "12월" + +msgid "ADDRESS_BOOK" +msgstr "주소록" + +msgid "FOR" +msgstr "을 위해" + +msgid "SEARCH" +msgstr "검색" + +msgid "HOME" +msgstr "홈" + +msgid "NEXT_BIRTHDAYS" +msgstr "다음 생일" + +msgid "ADD_NEW" +msgstr "새로 추가" + +msgid "PRINT_ALL" +msgstr "모두 인쇄" + +msgid "PRINT_PHONES" +msgstr "전화번호 인쇄" + +msgid "EXPORT_CSV" +msgstr "CSV 내보내기" + +msgid "EXPORT" +msgstr "내보내기" + +msgid "IMPORT" +msgstr "가져오기" + +msgid "MAP" +msgstr "지도" + +msgid "MORE" +msgstr "더보기" + +msgid "GROUP" +msgstr "그룹" + +msgid "GROUPS" +msgstr "그룹" + +msgid "MANAGE_GROUPS" +msgstr "그룹 관리" + +msgid "NEW_GROUP" +msgstr "새 그룹" + +msgid "DELETE_GROUPS" +msgstr "그룹 삭제" + +msgid "EDIT_GROUP" +msgstr "그룹 편집" + +msgid "GROUP_NAME" +msgstr "그룹 이름" + +msgid "GROUP_HEADER" +msgstr "그룹 머리글(로고)" + +msgid "GROUP_FOOTER" +msgstr "그룹 바닥글(댓글)" + +msgid "GROUP_PARENT" +msgstr "상위 그룹" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "모든 텍스트 검색" + +msgid "NUMBER_OF_RESULTS" +msgstr "ê²°ê³¼ 개수" + +msgid "ALL" +msgstr "모두" + +msgid "NONE" +msgstr "없음" + +msgid "SELECT_ALL" +msgstr "모두 선택" + +msgid "REMOVE_FROM" +msgstr "다음으로 부터 제거:" + +msgid "MAIL_CLIENT" +msgstr "메일 클라이언트" + +msgid "SEND_EMAIL" +msgstr "이메일 보내기" + +msgid "ADD_TO" +msgstr "다음에 추가:" + +msgid "DETAILS" +msgstr "상세내용" + +msgid "EDIT" +msgstr "편집" + +msgid "MODIFY" +msgstr "수정" + +msgid "PRINT" +msgstr "인쇄" + +msgid "EDIT_ADD_ENTRY" +msgstr "편집/주소록 입력 추가" + +msgid "GUESSED_HOMEPAGE" +msgstr "추정 홈페이지" + +msgid "PREFERENCES" +msgstr "환경설정" + +msgid "NAME_PREFIX" +msgstr "접두어" + +msgid "FIRSTNAME" +msgstr "이름" + +msgid "MIDDLENAME" +msgstr "중간이름" + +msgid "LASTNAME" +msgstr "성" + +msgid "NAME_SUFFIX" +msgstr "접미어" + +msgid "NICKNAME" +msgstr "별칭" + +msgid "COMPANY" +msgstr "회사" + +msgid "DEPT" +msgstr "부서" + +msgid "OCCUPATION" +msgstr "직책" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "주소" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "아파트" + +msgid "STREET" +msgstr "나머지주소" + +msgid "STATE" +msgstr "시/êµ°/도" + +msgid "COUNTRY" +msgstr "국가" + +msgid "TELEPHONE" +msgstr "전화" + +msgid "PHONE_HOME" +msgstr "집" + +msgid "HOME_SHORT" +msgstr "H" + +msgid "PHONE_MOBILE" +msgstr "휴대전화" + +msgid "MOBILE_SHORT" +msgstr "M" + +msgid "PHONE_WORK" +msgstr "직장" + +msgid "WORK_SHORT" +msgstr "W" + +msgid "FAX" +msgstr "팩스" + +msgid "FAX_SHORT" +msgstr "F" + +msgid "PHONE2_SHORT" +msgstr "P" + +msgid "PAGER" +msgstr "호출기" + +msgid "EMAIL" +msgstr "이메일" + +msgid "HOMEPAGE" +msgstr "홈페이지" + +msgid "ZIP" +msgstr "우편번호" + +msgid "CITY" +msgstr "시/êµ°/구" + +msgid "E_MAIL_HOME" +msgstr "자택 이메일" + +msgid "E_MAIL_OFFICE" +msgstr "직장 이메일" + +msgid "2ND_ADDRESS" +msgstr "두 번째 주소" + +msgid "2ND_PHONE" +msgstr "두 번째 전화" + +msgid "NOTES" +msgstr "메모" + +msgid "MISC" +msgstr "기타" + +msgid "BIRTHDAY" +msgstr "생일" + +msgid "ANNIVERSARY" +msgstr "기념일" + +msgid "CREATED" +msgstr "생성됨" + +msgid "MODIFIED" +msgstr "수정됨" + +msgid "UPDATE" +msgstr "업데이트" + +msgid "DELETE" +msgstr "삭제" + +msgid "INVALID" +msgstr "유효하지 않음" + +msgid "ENTER" +msgstr "입력" + +msgid "MEMBER_OF" +msgstr "다음의 구성원:" + +msgid "SECONDARY" +msgstr "추가" + +msgid "CREATE_ACCOUNT" +msgstr "계정 생성" + +msgid "FORGOT_PASSWORD" +msgstr "암호를 잊음" + +msgid "UPDATED" +msgstr "업데이트됨" + +msgid "TRANSLATOR" +msgstr "번역자" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "휴대전화" + +msgid "WORK" +msgstr "직장" + +msgid "FIRST_LAST" +msgstr "이름 성" + +msgid "NEXT" +msgstr "다음" + +msgid "PHOTO" +msgstr "사진" + +msgid "ALL_PHONES" +msgstr "모든 전화" + +msgid "ALL_EMAILS" +msgstr "모든 이메일" + +msgid "SIGN_IN_WITH" +msgstr "다음으로 로그인:" + +msgid "LAST_FIRST" +msgstr "성 이름" + +msgid "GRP_NAME" +msgstr "그룹명" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "아라비아어" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "불가리아어" + +msgid "ca" +msgstr "카탈로니아" + +msgid "cs" +msgstr "체코어" + +msgid "da" +msgstr "덴마크어" + +msgid "de" +msgstr "독일어" + +msgid "el" +msgstr "그리스어" + +msgid "en" +msgstr "영어" + +msgid "es" +msgstr "스페인어" + +msgid "fa" +msgstr "이란어" + +msgid "fi" +msgstr "핀란드어" + +msgid "fr" +msgstr "프랑스어" + +msgid "he" +msgstr "히브리어" + +msgid "hi" +msgstr "힌디어" + +msgid "hu" +msgstr "헝가리의" + +msgid "it" +msgstr "이탈리아어" + +msgid "ja" +msgstr "일본어" + +msgid "ko" +msgstr "한국어" + +msgid "nl" +msgstr "네덜란드" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "폴란드어" + +msgid "pt" +msgstr "포르투갈어" + +msgid "rm" +msgstr "레토로만어" + +msgid "ru" +msgstr "러시아어" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "슬로베니아" + +msgid "sr" +msgstr "세르비아의" + +msgid "sv" +msgstr "스웨덴어" + +msgid "th" +msgstr "타이어" + +msgid "tr" +msgstr "터키어" + +msgid "ua" +msgstr "우크라이나어" + +msgid "vi" +msgstr "베트남어" + +msgid "zh" +msgstr "중국어" diff --git a/translations/php-addressbook-nl.po b/translations/php-addressbook-nl.po new file mode 100644 index 0000000..3d9dac6 --- /dev/null +++ b/translations/php-addressbook-nl.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:46+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "taal" + +msgid "auto" +msgstr "browser instellingen (automatische)" + +msgid "USER" +msgstr "Gebruiker" + +msgid "PASSWORD" +msgstr "Wachtwoord" + +msgid "LOGIN" +msgstr "Login" + +msgid "LOGOUT" +msgstr "Afmelden" + +msgid "JANUARY" +msgstr "Januari" + +msgid "FEBRUARY" +msgstr "Februari" + +msgid "MARCH" +msgstr "Maart" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Mei" + +msgid "JUNE" +msgstr "Juni" + +msgid "JULY" +msgstr "Juli" + +msgid "AUGUST" +msgstr "Augustus" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "adressenlijst" + +msgid "FOR" +msgstr "voor" + +msgid "SEARCH" +msgstr "zoeken" + +msgid "HOME" +msgstr "thuis" + +msgid "NEXT_BIRTHDAYS" +msgstr "verjaardagen" + +msgid "ADD_NEW" +msgstr "nieuwe" + +msgid "PRINT_ALL" +msgstr "adressen afdrukken" + +msgid "PRINT_PHONES" +msgstr "telefoonnummers afdrukken" + +msgid "EXPORT_CSV" +msgstr "csv export" + +msgid "EXPORT" +msgstr "exporteer" + +msgid "IMPORT" +msgstr "importeer" + +msgid "MAP" +msgstr "kaart" + +msgid "MORE" +msgstr "meer" + +msgid "GROUP" +msgstr "groep" + +msgid "GROUPS" +msgstr "groepen" + +msgid "MANAGE_GROUPS" +msgstr "groepen beheren" + +msgid "NEW_GROUP" +msgstr "nieuwe groep" + +msgid "DELETE_GROUPS" +msgstr "verwijder groep(en)" + +msgid "EDIT_GROUP" +msgstr "bewerk groep" + +msgid "GROUP_NAME" +msgstr "de naam van de groep" + +msgid "GROUP_HEADER" +msgstr "Groep header (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Groep footer (Commentaar)" + +msgid "GROUP_PARENT" +msgstr "Hoofdgroep" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "zoek naar tekst" + +msgid "NUMBER_OF_RESULTS" +msgstr "Aantal resultaten" + +msgid "ALL" +msgstr "alle" + +msgid "NONE" +msgstr "geen" + +msgid "SELECT_ALL" +msgstr "alles selecteren" + +msgid "REMOVE_FROM" +msgstr "verwijderen uit" + +msgid "MAIL_CLIENT" +msgstr "e-mailclient" + +msgid "SEND_EMAIL" +msgstr "Stuur een e-mail" + +msgid "ADD_TO" +msgstr "voeg toe aan" + +msgid "DETAILS" +msgstr "details" + +msgid "EDIT" +msgstr "bewerk" + +msgid "MODIFY" +msgstr "wijzigen" + +msgid "PRINT" +msgstr "print" + +msgid "EDIT_ADD_ENTRY" +msgstr "Edit / add adresboek entry" + +msgid "GUESSED_HOMEPAGE" +msgstr "Geraden homepage" + +msgid "PREFERENCES" +msgstr "voorkeuren" + +msgid "NAME_PREFIX" +msgstr "voorvoegsel" + +msgid "FIRSTNAME" +msgstr "voornaam" + +msgid "MIDDLENAME" +msgstr "tussenvoegsel" + +msgid "LASTNAME" +msgstr "achternaam" + +msgid "NAME_SUFFIX" +msgstr "achtervoegsel" + +msgid "NICKNAME" +msgstr "bijnaam" + +msgid "COMPANY" +msgstr "vennootschap" + +msgid "DEPT" +msgstr "Dept" + +msgid "OCCUPATION" +msgstr "functie" + +msgid "TITLES" +msgstr "titels" + +msgid "ADDRESS" +msgstr "adres" + +msgid "POB" +msgstr "postbus" + +msgid "APT" +msgstr "afdeling" + +msgid "STREET" +msgstr "straat en huisnummer" + +msgid "STATE" +msgstr "staat" + +msgid "COUNTRY" +msgstr "land" + +msgid "TELEPHONE" +msgstr "telefoon" + +msgid "PHONE_HOME" +msgstr "thuis" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobiel" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "werk" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "pieper" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Homepage" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "stad" + +msgid "E_MAIL_HOME" +msgstr "e-mail home" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail kantoor" + +msgid "2ND_ADDRESS" +msgstr "tweede adres" + +msgid "2ND_PHONE" +msgstr "tweede telefoon" + +msgid "NOTES" +msgstr "opmerking" + +msgid "MISC" +msgstr "misc" + +msgid "BIRTHDAY" +msgstr "geboortedatum" + +msgid "ANNIVERSARY" +msgstr "verjaardag" + +msgid "CREATED" +msgstr "gemaakt" + +msgid "MODIFIED" +msgstr "bewerkt" + +msgid "UPDATE" +msgstr "update" + +msgid "DELETE" +msgstr "wissen" + +msgid "INVALID" +msgstr "ongeldig" + +msgid "ENTER" +msgstr "invoeren" + +msgid "MEMBER_OF" +msgstr "lid van" + +msgid "SECONDARY" +msgstr "Secundaire" + +msgid "CREATE_ACCOUNT" +msgstr "Maak een account" + +msgid "FORGOT_PASSWORD" +msgstr "wachtwoord vergeten" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabisch" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgaarse" + +msgid "ca" +msgstr "Catalaans" + +msgid "cs" +msgstr "Tsjechisch" + +msgid "da" +msgstr "Deens" + +msgid "de" +msgstr "Duits" + +msgid "el" +msgstr "Grieks" + +msgid "en" +msgstr "Engels" + +msgid "es" +msgstr "Spaans" + +msgid "fa" +msgstr "Perzisch" + +msgid "fi" +msgstr "Fins" + +msgid "fr" +msgstr "Frans" + +msgid "he" +msgstr "Hebreeuws" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Hongaars" + +msgid "it" +msgstr "Italiaans" + +msgid "ja" +msgstr "Japans" + +msgid "ko" +msgstr "Koreaans" + +msgid "nl" +msgstr "Nederlands" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Pools" + +msgid "pt" +msgstr "Portugees" + +msgid "rm" +msgstr "Retoromaans" + +msgid "ru" +msgstr "Russisch" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Sloveens" + +msgid "sr" +msgstr "Servisch" + +msgid "sv" +msgstr "Zweeds" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Turks" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamees" + +msgid "zh" +msgstr "Chinees" diff --git a/translations/php-addressbook-no.po b/translations/php-addressbook-no.po new file mode 100644 index 0000000..de17724 --- /dev/null +++ b/translations/php-addressbook-no.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-08 12:16+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "sprÃ¥k" + +msgid "auto" +msgstr "nettleser instillinger (automatisk)" + +msgid "USER" +msgstr "Bruker" + +msgid "PASSWORD" +msgstr "Passord" + +msgid "LOGIN" +msgstr "Logg inn" + +msgid "LOGOUT" +msgstr "Logg ut" + +msgid "JANUARY" +msgstr "Januar" + +msgid "FEBRUARY" +msgstr "Februar" + +msgid "MARCH" +msgstr "Mars" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Mai" + +msgid "JUNE" +msgstr "Juni" + +msgid "JULY" +msgstr "Juli" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "Desember" + +msgid "ADDRESS_BOOK" +msgstr "adressebok" + +msgid "FOR" +msgstr "for" + +msgid "SEARCH" +msgstr "søk" + +msgid "HOME" +msgstr "hjem" + +msgid "NEXT_BIRTHDAYS" +msgstr "neste bursdager" + +msgid "ADD_NEW" +msgstr "legg til" + +msgid "PRINT_ALL" +msgstr "skriv ut alle" + +msgid "PRINT_PHONES" +msgstr "skriv ut telefoner" + +msgid "EXPORT_CSV" +msgstr "eksporter csv" + +msgid "EXPORT" +msgstr "eksporter" + +msgid "IMPORT" +msgstr "importer" + +msgid "MAP" +msgstr "kart" + +msgid "MORE" +msgstr "mer" + +msgid "GROUP" +msgstr "gruppe" + +msgid "GROUPS" +msgstr "grupper" + +msgid "MANAGE_GROUPS" +msgstr "grupper" + +msgid "NEW_GROUP" +msgstr "ny gruppe" + +msgid "DELETE_GROUPS" +msgstr "slett gruppe(r)" + +msgid "EDIT_GROUP" +msgstr "editer gruppe" + +msgid "GROUP_NAME" +msgstr "gruppenavn" + +msgid "GROUP_HEADER" +msgstr "Gruppe topptekst (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Gruppe bunntekst (Kommentar)" + +msgid "GROUP_PARENT" +msgstr "Foreldergruppe" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "søk etter tekst" + +msgid "NUMBER_OF_RESULTS" +msgstr "Antall resultater" + +msgid "ALL" +msgstr "alle" + +msgid "NONE" +msgstr "ingen" + +msgid "SELECT_ALL" +msgstr "merk alle" + +msgid "REMOVE_FROM" +msgstr "fjern fra" + +msgid "MAIL_CLIENT" +msgstr "mail klient" + +msgid "SEND_EMAIL" +msgstr "Send e-Pail" + +msgid "ADD_TO" +msgstr "legg til" + +msgid "DETAILS" +msgstr "detaljer" + +msgid "EDIT" +msgstr "editer" + +msgid "MODIFY" +msgstr "modifiser" + +msgid "PRINT" +msgstr "skriv ut" + +msgid "EDIT_ADD_ENTRY" +msgstr "Editer / legg til adressebokoppføring" + +msgid "GUESSED_HOMEPAGE" +msgstr "Guessed homepage" + +msgid "PREFERENCES" +msgstr "preferanser" + +msgid "NAME_PREFIX" +msgstr "prefiks" + +msgid "FIRSTNAME" +msgstr "fornavn" + +msgid "MIDDLENAME" +msgstr "mellomnavn/inital(er)" + +msgid "LASTNAME" +msgstr "etternavn" + +msgid "NAME_SUFFIX" +msgstr "suffiks" + +msgid "NICKNAME" +msgstr "kallenavn" + +msgid "COMPANY" +msgstr "firma" + +msgid "DEPT" +msgstr "Avd." + +msgid "OCCUPATION" +msgstr "arbeidstitel" + +msgid "TITLES" +msgstr "titler" + +msgid "ADDRESS" +msgstr "addresse" + +msgid "POB" +msgstr "box eller number" + +msgid "APT" +msgstr "leilighet" + +msgid "STREET" +msgstr "gateadresse" + +msgid "STATE" +msgstr "fylke" + +msgid "COUNTRY" +msgstr "land" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "hjem" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "arbeid" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "personsøker" + +msgid "EMAIL" +msgstr "e-post" + +msgid "HOMEPAGE" +msgstr "Hjemmeside" + +msgid "ZIP" +msgstr "Postnr." + +msgid "CITY" +msgstr "by" + +msgid "E_MAIL_HOME" +msgstr "e-post home" + +msgid "E_MAIL_OFFICE" +msgstr "e-post kontor" + +msgid "2ND_ADDRESS" +msgstr "Annen addresse" + +msgid "2ND_PHONE" +msgstr "Annen telefon" + +msgid "NOTES" +msgstr "notater" + +msgid "MISC" +msgstr "Div" + +msgid "BIRTHDAY" +msgstr "bursdag" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "opprettet" + +msgid "MODIFIED" +msgstr "modifisert" + +msgid "UPDATE" +msgstr "updater" + +msgid "DELETE" +msgstr "slett" + +msgid "INVALID" +msgstr "ugyldig" + +msgid "ENTER" +msgstr "skriv inn" + +msgid "MEMBER_OF" +msgstr "medlem av" + +msgid "SECONDARY" +msgstr "Sekundær" + +msgid "CREATE_ACCOUNT" +msgstr "Opprett konto" + +msgid "FORGOT_PASSWORD" +msgstr "Glemt passord" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabisk" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgarsk" + +msgid "ca" +msgstr "Catalonian" + +msgid "cs" +msgstr "Tjekkisk" + +msgid "da" +msgstr "Dansk" + +msgid "de" +msgstr "Tysk" + +msgid "el" +msgstr "Gresk" + +msgid "en" +msgstr "English" + +msgid "es" +msgstr "Spansk" + +msgid "fa" +msgstr "Farsi" + +msgid "fi" +msgstr "Finnsk" + +msgid "fr" +msgstr "Fransk" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Ungarsk" + +msgid "it" +msgstr "Italiensk" + +msgid "ja" +msgstr "Japansk" + +msgid "ko" +msgstr "Koreansk" + +msgid "nl" +msgstr "Nederlandsk" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Polsk" + +msgid "pt" +msgstr "Portugesisk" + +msgid "rm" +msgstr "Rhaeto-Romance" + +msgid "ru" +msgstr "Russisk" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovensk" + +msgid "sr" +msgstr "Serbisk" + +msgid "sv" +msgstr "Svensk" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Tyrkisk" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamesisk" + +msgid "zh" +msgstr "Kinesisk" diff --git a/translations/php-addressbook-pl.po b/translations/php-addressbook-pl.po new file mode 100644 index 0000000..c39de43 --- /dev/null +++ b/translations/php-addressbook-pl.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-08 17:46+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "język" + +msgid "auto" +msgstr "ustawienia przeglądarki (automatycznie)" + +msgid "USER" +msgstr "Użytkownik" + +msgid "PASSWORD" +msgstr "Hasło" + +msgid "LOGIN" +msgstr "Login" + +msgid "LOGOUT" +msgstr "Wyloguj się" + +msgid "JANUARY" +msgstr "styczeń" + +msgid "FEBRUARY" +msgstr "luty" + +msgid "MARCH" +msgstr "marzec" + +msgid "APRIL" +msgstr "kwiecień" + +msgid "MAY" +msgstr "maj" + +msgid "JUNE" +msgstr "czerwiec" + +msgid "JULY" +msgstr "lipiec" + +msgid "AUGUST" +msgstr "sierpień" + +msgid "SEPTEMBER" +msgstr "wrzesień" + +msgid "OCTOBER" +msgstr "październik" + +msgid "NOVEMBER" +msgstr "listopad" + +msgid "DECEMBER" +msgstr "grudzień" + +msgid "ADDRESS_BOOK" +msgstr "książka adresowa" + +msgid "FOR" +msgstr "do" + +msgid "SEARCH" +msgstr "szukaj" + +msgid "HOME" +msgstr "strona główna" + +msgid "NEXT_BIRTHDAYS" +msgstr "następne urodziny" + +msgid "ADD_NEW" +msgstr "nowy wpis" + +msgid "PRINT_ALL" +msgstr "drukuj wszystko" + +msgid "PRINT_PHONES" +msgstr "drukuj telefony" + +msgid "EXPORT_CSV" +msgstr "eksportuj do csv" + +msgid "EXPORT" +msgstr "export" + +msgid "IMPORT" +msgstr "import" + +msgid "MAP" +msgstr "carte" + +msgid "MORE" +msgstr "plus" + +msgid "GROUP" +msgstr "grupa" + +msgid "GROUPS" +msgstr "grupy" + +msgid "MANAGE_GROUPS" +msgstr "zarządzaj grupami" + +msgid "NEW_GROUP" +msgstr "nowa grupa" + +msgid "DELETE_GROUPS" +msgstr "usuń grupę" + +msgid "EDIT_GROUP" +msgstr "edytuj grupę" + +msgid "GROUP_NAME" +msgstr "nazwa grupy" + +msgid "GROUP_HEADER" +msgstr "Nagłówek grupy (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Stopka grupy (komentarz)" + +msgid "GROUP_PARENT" +msgstr "Grupa nadrzędna" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "wyszukaj dowolny tekst" + +msgid "NUMBER_OF_RESULTS" +msgstr "Liczba trafień" + +msgid "ALL" +msgstr "wszystkie" + +msgid "NONE" +msgstr "żadne" + +msgid "SELECT_ALL" +msgstr "wybierz wszystkie" + +msgid "REMOVE_FROM" +msgstr "usuń z" + +msgid "MAIL_CLIENT" +msgstr "program pocztowy" + +msgid "SEND_EMAIL" +msgstr "wyslij e-mail" + +msgid "ADD_TO" +msgstr "dodaj do" + +msgid "DETAILS" +msgstr "szczegóły" + +msgid "EDIT" +msgstr "edytuj" + +msgid "MODIFY" +msgstr "zmień" + +msgid "PRINT" +msgstr "drukuj" + +msgid "EDIT_ADD_ENTRY" +msgstr "edytuj / dodaj wpis do książki adresowej" + +msgid "GUESSED_HOMEPAGE" +msgstr "MOżliwa strona domowa" + +msgid "PREFERENCES" +msgstr "właściwości" + +msgid "NAME_PREFIX" +msgstr "préfixe" + +msgid "FIRSTNAME" +msgstr "imię" + +msgid "MIDDLENAME" +msgstr "prénom / inital (s)" + +msgid "LASTNAME" +msgstr "nazwisko" + +msgid "NAME_SUFFIX" +msgstr "suffixe" + +msgid "NICKNAME" +msgstr "surnom" + +msgid "COMPANY" +msgstr "Firma" + +msgid "DEPT" +msgstr "Département" + +msgid "OCCUPATION" +msgstr "titre d'emploi" + +msgid "TITLES" +msgstr "titres" + +msgid "ADDRESS" +msgstr "adres" + +msgid "POB" +msgstr "boîte ou plusieurs" + +msgid "APT" +msgstr "appartement ou d'arrêt" + +msgid "STREET" +msgstr "Adresse rue" + +msgid "STATE" +msgstr "état" + +msgid "COUNTRY" +msgstr "pays" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "prywatny" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "komórkowy" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "praca" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "pager" + +msgid "EMAIL" +msgstr "e-Mail" + +msgid "HOMEPAGE" +msgstr "Strona główna" + +msgid "ZIP" +msgstr "kod pocztowy" + +msgid "CITY" +msgstr "miasto" + +msgid "E_MAIL_HOME" +msgstr "e-mail prywatny" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail służbowy" + +msgid "2ND_ADDRESS" +msgstr "drugi adres" + +msgid "2ND_PHONE" +msgstr "drugi telefon" + +msgid "NOTES" +msgstr "uwagi" + +msgid "MISC" +msgstr "Divers" + +msgid "BIRTHDAY" +msgstr "urodziny" + +msgid "ANNIVERSARY" +msgstr "anniversaire" + +msgid "CREATED" +msgstr "stworzony" + +msgid "MODIFIED" +msgstr "zmodyfikowane" + +msgid "UPDATE" +msgstr "aktualizuj" + +msgid "DELETE" +msgstr "usuń" + +msgid "INVALID" +msgstr "nieprawidłowy" + +msgid "ENTER" +msgstr "akceptuj" + +msgid "MEMBER_OF" +msgstr "członek grupy" + +msgid "SECONDARY" +msgstr "drugi adres" + +msgid "CREATE_ACCOUNT" +msgstr "Créer un compte" + +msgid "FORGOT_PASSWORD" +msgstr "Mot de passe oublié" + +msgid "UPDATED" +msgstr "mis à jour" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "titre" + +msgid "MOBILE" +msgstr "komórkowy" + +msgid "WORK" +msgstr "praca" + +msgid "FIRST_LAST" +msgstr "Imię, Nazwisko" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabski" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bułgarski" + +msgid "ca" +msgstr "Katalońska" + +msgid "cs" +msgstr "polský" + +msgid "da" +msgstr "Duński" + +msgid "de" +msgstr "Polnisch" + +msgid "el" +msgstr "Grecki" + +msgid "en" +msgstr "polish" + +msgid "es" +msgstr "Hiszpański" + +msgid "fa" +msgstr "Perski" + +msgid "fi" +msgstr "Fiński" + +msgid "fr" +msgstr "Francuski" + +msgid "he" +msgstr "Hebrajski" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Hongroise" + +msgid "it" +msgstr "Włoski" + +msgid "ja" +msgstr "Japoński" + +msgid "ko" +msgstr "Koreańczyk" + +msgid "nl" +msgstr "Holenderski" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "polski" + +msgid "pt" +msgstr "Portugalski" + +msgid "rm" +msgstr "Retoromański" + +msgid "ru" +msgstr "Rosyjski" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Słoweński" + +msgid "sr" +msgstr "Serbski" + +msgid "sv" +msgstr "Szwedzki" + +msgid "th" +msgstr "Tajski" + +msgid "tr" +msgstr "Turecki" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Wietnamski" + +msgid "zh" +msgstr "Chiński" diff --git a/translations/php-addressbook-pt.po b/translations/php-addressbook-pt.po new file mode 100644 index 0000000..68414de --- /dev/null +++ b/translations/php-addressbook-pt.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:46+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "linguagem" + +msgid "auto" +msgstr "bowser configurações (automático)" + +msgid "USER" +msgstr "Usuário" + +msgid "PASSWORD" +msgstr "Senha" + +msgid "LOGIN" +msgstr "Login" + +msgid "LOGOUT" +msgstr "Sair" + +msgid "JANUARY" +msgstr "Janeiro" + +msgid "FEBRUARY" +msgstr "Fevereiro" + +msgid "MARCH" +msgstr "Março" + +msgid "APRIL" +msgstr "Abril" + +msgid "MAY" +msgstr "Maio" + +msgid "JUNE" +msgstr "Junho" + +msgid "JULY" +msgstr "Julho" + +msgid "AUGUST" +msgstr "Agosto" + +msgid "SEPTEMBER" +msgstr "Setembro" + +msgid "OCTOBER" +msgstr "Outubro" + +msgid "NOVEMBER" +msgstr "Novembro" + +msgid "DECEMBER" +msgstr "Dezembro" + +msgid "ADDRESS_BOOK" +msgstr "Catálogo de Endereços" + +msgid "FOR" +msgstr "para" + +msgid "SEARCH" +msgstr "pesquisa" + +msgid "HOME" +msgstr "Início" + +msgid "NEXT_BIRTHDAYS" +msgstr "Próximos Aniversários" + +msgid "ADD_NEW" +msgstr "Adicionar Contato" + +msgid "PRINT_ALL" +msgstr "Imprimir Tudo" + +msgid "PRINT_PHONES" +msgstr "Imprimir Telefones" + +msgid "EXPORT_CSV" +msgstr "Exportar para o CSV" + +msgid "EXPORT" +msgstr "exportação" + +msgid "IMPORT" +msgstr "importação" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "grupo" + +msgid "GROUPS" +msgstr "Grupos" + +msgid "MANAGE_GROUPS" +msgstr "gerenciar grupos" + +msgid "NEW_GROUP" +msgstr "novo grupo" + +msgid "DELETE_GROUPS" +msgstr "apagar grupo (s)" + +msgid "EDIT_GROUP" +msgstr "Editar grupo" + +msgid "GROUP_NAME" +msgstr "Nome do grupo" + +msgid "GROUP_HEADER" +msgstr "Grupo header (Logotipo)" + +msgid "GROUP_FOOTER" +msgstr "Grupo de rodapé (Comment)" + +msgid "GROUP_PARENT" +msgstr "grupo Parent" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "pesquisa qualquer texto" + +msgid "NUMBER_OF_RESULTS" +msgstr "Número de resultados" + +msgid "ALL" +msgstr "todos" + +msgid "NONE" +msgstr "nenhum" + +msgid "SELECT_ALL" +msgstr "Selecionar Todos" + +msgid "REMOVE_FROM" +msgstr "remover a partir de" + +msgid "MAIL_CLIENT" +msgstr "cliente de e-mail" + +msgid "SEND_EMAIL" +msgstr "Enviar e-mail" + +msgid "ADD_TO" +msgstr "adicionar" + +msgid "DETAILS" +msgstr "detalhes" + +msgid "EDIT" +msgstr "editar" + +msgid "MODIFY" +msgstr "modificar" + +msgid "PRINT" +msgstr "Imprimir" + +msgid "EDIT_ADD_ENTRY" +msgstr "Editar / Adicionar um Contato" + +msgid "GUESSED_HOMEPAGE" +msgstr "homepage" + +msgid "PREFERENCES" +msgstr "preferências" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "NOME" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "sobrenome" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "Empresa" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "endereço" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "TELEFONE" + +msgid "PHONE_HOME" +msgstr "Residencial" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "Celular" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "Comercial" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "E-MAIL" + +msgid "HOMEPAGE" +msgstr "Site" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "cidade" + +msgid "E_MAIL_HOME" +msgstr "e-mail home" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail escritório" + +msgid "2ND_ADDRESS" +msgstr "segundo endereço" + +msgid "2ND_PHONE" +msgstr "Segundo Telefone" + +msgid "NOTES" +msgstr "notas" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "aniversário" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "criado" + +msgid "MODIFIED" +msgstr "modificado" + +msgid "UPDATE" +msgstr "atualizar" + +msgid "DELETE" +msgstr "excluir" + +msgid "INVALID" +msgstr "inválido" + +msgid "ENTER" +msgstr "enviar" + +msgid "MEMBER_OF" +msgstr "membro do" + +msgid "SECONDARY" +msgstr "Secundário" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "atualizado" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Árabe" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Búlgaro" + +msgid "ca" +msgstr "Catalunha" + +msgid "cs" +msgstr "Checo" + +msgid "da" +msgstr "Dinamarquês" + +msgid "de" +msgstr "Alemão" + +msgid "el" +msgstr "Grego" + +msgid "en" +msgstr "Inglês" + +msgid "es" +msgstr "Espanhol" + +msgid "fa" +msgstr "Persa" + +msgid "fi" +msgstr "Finlandês" + +msgid "fr" +msgstr "Francês" + +msgid "he" +msgstr "Hebraico" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Húngaro" + +msgid "it" +msgstr "Italiano" + +msgid "ja" +msgstr "Japonês" + +msgid "ko" +msgstr "Coreano" + +msgid "nl" +msgstr "Holandês" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Polonês" + +msgid "pt" +msgstr "Português" + +msgid "rm" +msgstr "Reto-romance" + +msgid "ru" +msgstr "Russo" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Esloveno" + +msgid "sr" +msgstr "Sérvio" + +msgid "sv" +msgstr "Sueco" + +msgid "th" +msgstr "Tailândia" + +msgid "tr" +msgstr "Turco" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamita" + +msgid "zh" +msgstr "Chinês" diff --git a/translations/php-addressbook-ro.po b/translations/php-addressbook-ro.po new file mode 100644 index 0000000..f2cc555 --- /dev/null +++ b/translations/php-addressbook-ro.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-03-15 06:58+0000\n" +"Last-Translator: Daniel \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "Limba" + +msgid "auto" +msgstr "Browser (automatic)" + +msgid "USER" +msgstr "Utilizator" + +msgid "PASSWORD" +msgstr "Parola" + +msgid "LOGIN" +msgstr "Login" + +msgid "LOGOUT" +msgstr "Logout" + +msgid "JANUARY" +msgstr "Ianuarie" + +msgid "FEBRUARY" +msgstr "Februarie" + +msgid "MARCH" +msgstr "Martie" + +msgid "APRIL" +msgstr "Aprilie" + +msgid "MAY" +msgstr "Mai" + +msgid "JUNE" +msgstr "Iunie" + +msgid "JULY" +msgstr "Iulie" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "Septembrie" + +msgid "OCTOBER" +msgstr "Octombrie" + +msgid "NOVEMBER" +msgstr "Noiembrie" + +msgid "DECEMBER" +msgstr "Decembrie" + +msgid "ADDRESS_BOOK" +msgstr "Agenda telefonică" + +msgid "FOR" +msgstr "pentru" + +msgid "SEARCH" +msgstr "Caută" + +msgid "HOME" +msgstr "Acasă" + +msgid "NEXT_BIRTHDAYS" +msgstr "dată naștere" + +msgid "ADD_NEW" +msgstr "Adaugă" + +msgid "PRINT_ALL" +msgstr "Imprimă" + +msgid "PRINT_PHONES" +msgstr "Imprimă numere telefoane" + +msgid "EXPORT_CSV" +msgstr "exportă în CSV" + +msgid "EXPORT" +msgstr "Exportă" + +msgid "IMPORT" +msgstr "Importă" + +msgid "MAP" +msgstr "Hartă" + +msgid "MORE" +msgstr "mai multe" + +msgid "GROUP" +msgstr "Grup" + +msgid "GROUPS" +msgstr "Grupuri" + +msgid "MANAGE_GROUPS" +msgstr "Administrează grupuri" + +msgid "NEW_GROUP" +msgstr "Adaugă grup" + +msgid "DELETE_GROUPS" +msgstr "Șterge grup" + +msgid "EDIT_GROUP" +msgstr "Editează grup" + +msgid "GROUP_NAME" +msgstr "Nume grup" + +msgid "GROUP_HEADER" +msgstr "Grup (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Footer (Comentarii)" + +msgid "GROUP_PARENT" +msgstr "Group Parent" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Caută orice text" + +msgid "NUMBER_OF_RESULTS" +msgstr "Număr de rezultate" + +msgid "ALL" +msgstr "tot" + +msgid "NONE" +msgstr "nici unul" + +msgid "SELECT_ALL" +msgstr "Selectează tot" + +msgid "REMOVE_FROM" +msgstr "Șterge de la" + +msgid "MAIL_CLIENT" +msgstr "Client Email" + +msgid "SEND_EMAIL" +msgstr "Trimite Email" + +msgid "ADD_TO" +msgstr "Adaugă la" + +msgid "DETAILS" +msgstr "Detalii" + +msgid "EDIT" +msgstr "Editează" + +msgid "MODIFY" +msgstr "Modifică" + +msgid "PRINT" +msgstr "Imprimă" + +msgid "EDIT_ADD_ENTRY" +msgstr "Editează sau adaugă număr de telefon în agendă" + +msgid "GUESSED_HOMEPAGE" +msgstr "Pagina principală gasită" + +msgid "PREFERENCES" +msgstr "Preferințe" + +msgid "NAME_PREFIX" +msgstr "Nume_Prefix" + +msgid "FIRSTNAME" +msgstr "Nume" + +msgid "MIDDLENAME" +msgstr "Nume Mijlociu" + +msgid "LASTNAME" +msgstr "Prenume" + +msgid "NAME_SUFFIX" +msgstr "Nume_Suffix" + +msgid "NICKNAME" +msgstr "Nickname" + +msgid "COMPANY" +msgstr "Firmă" + +msgid "DEPT" +msgstr "Dept" + +msgid "OCCUPATION" +msgstr "Ocupație" + +msgid "TITLES" +msgstr "Titlu" + +msgid "ADDRESS" +msgstr "Adresă" + +msgid "POB" +msgstr "Comuna" + +msgid "APT" +msgstr "Sat" + +msgid "STREET" +msgstr "Strada" + +msgid "STATE" +msgstr "Județ" + +msgid "COUNTRY" +msgstr "Țara" + +msgid "TELEPHONE" +msgstr "Telefon" + +msgid "PHONE_HOME" +msgstr "Telefon fix" + +msgid "HOME_SHORT" +msgstr "Acasă_scurt" + +msgid "PHONE_MOBILE" +msgstr "Telefon mobil" + +msgid "MOBILE_SHORT" +msgstr "Mobil_scurt" + +msgid "PHONE_WORK" +msgstr "Telefon serviciu" + +msgid "WORK_SHORT" +msgstr "Serviciu_scurt" + +msgid "FAX" +msgstr "Fax" + +msgid "FAX_SHORT" +msgstr "Fax_scurt" + +msgid "PHONE2_SHORT" +msgstr "Telefon2_scurt" + +msgid "PAGER" +msgstr "Pager" + +msgid "EMAIL" +msgstr "Email" + +msgid "HOMEPAGE" +msgstr "Home" + +msgid "ZIP" +msgstr "Cod poștal" + +msgid "CITY" +msgstr "Oraș" + +msgid "E_MAIL_HOME" +msgstr "E-mail personal" + +msgid "E_MAIL_OFFICE" +msgstr "E-mail serviciu" + +msgid "2ND_ADDRESS" +msgstr "A doua adresă" + +msgid "2ND_PHONE" +msgstr "Alt telefon" + +msgid "NOTES" +msgstr "Notă" + +msgid "MISC" +msgstr "Misc" + +msgid "BIRTHDAY" +msgstr "Zi de naștere" + +msgid "ANNIVERSARY" +msgstr "Aniversare" + +msgid "CREATED" +msgstr "Creat" + +msgid "MODIFIED" +msgstr "Modificat" + +msgid "UPDATE" +msgstr "Actualizare" + +msgid "DELETE" +msgstr "Șterge" + +msgid "INVALID" +msgstr "Invalid" + +msgid "ENTER" +msgstr "Adaugă" + +msgid "MEMBER_OF" +msgstr "Membru de" + +msgid "SECONDARY" +msgstr "Al doilea" + +msgid "CREATE_ACCOUNT" +msgstr "Crează cont" + +msgid "FORGOT_PASSWORD" +msgstr "Am uitat parola" + +msgid "UPDATED" +msgstr "Actualizat" + +msgid "TRANSLATOR" +msgstr "Traducător" + +msgid "TITLE" +msgstr "Titlu" + +msgid "MOBILE" +msgstr "Mobil" + +msgid "WORK" +msgstr "Serviciu" + +msgid "FIRST_LAST" +msgstr "Primul_Ultimul" + +msgid "NEXT" +msgstr "Următor" + +msgid "PHOTO" +msgstr "Fotografie" + +msgid "ALL_PHONES" +msgstr "Toate telefoanele" + +msgid "ALL_EMAILS" +msgstr "Toate adresele de email" + +msgid "SIGN_IN_WITH" +msgstr "Acces site cu" + +msgid "LAST_FIRST" +msgstr "Ultimul_Primul" + +msgid "GRP_NAME" +msgstr "GRP Nume" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabă" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgară" + +msgid "ca" +msgstr "Catalană" + +msgid "cs" +msgstr "Cehă" + +msgid "da" +msgstr "Daneză" + +msgid "de" +msgstr "Germană" + +msgid "el" +msgstr "Greacă" + +msgid "en" +msgstr "Engleză" + +msgid "es" +msgstr "" + +msgid "fa" +msgstr "Persană" + +msgid "fi" +msgstr "Finlandeză" + +msgid "fr" +msgstr "Franceză" + +msgid "he" +msgstr "Ebraică" + +msgid "hi" +msgstr "Hindu" + +msgid "hu" +msgstr "Ungară" + +msgid "it" +msgstr "Italiană" + +msgid "ja" +msgstr "Japoneză" + +msgid "ko" +msgstr "Coreană" + +msgid "nl" +msgstr "Olandeză" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Poloneză" + +msgid "pt" +msgstr "Portugheză" + +msgid "rm" +msgstr "Retorromanică" + +msgid "ru" +msgstr "Rusă" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenă" + +msgid "sr" +msgstr "Sârbă" + +msgid "sv" +msgstr "Suedeză" + +msgid "th" +msgstr "Tailandeză" + +msgid "tr" +msgstr "Turcă" + +msgid "ua" +msgstr "ua" + +msgid "vi" +msgstr "Vietnameză" + +msgid "zh" +msgstr "Chineză" diff --git a/translations/php-addressbook-ru.po b/translations/php-addressbook-ru.po new file mode 100644 index 0000000..1bdd65f --- /dev/null +++ b/translations/php-addressbook-ru.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2015-01-04 20:16+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "язык" + +msgid "auto" +msgstr "параметры браузера (автоматически)" + +msgid "USER" +msgstr "Пользователь" + +msgid "PASSWORD" +msgstr "Пароль" + +msgid "LOGIN" +msgstr "Войти" + +msgid "LOGOUT" +msgstr "Выйти" + +msgid "JANUARY" +msgstr "Январь" + +msgid "FEBRUARY" +msgstr "Февраль" + +msgid "MARCH" +msgstr "Март" + +msgid "APRIL" +msgstr "Апрель" + +msgid "MAY" +msgstr "Май" + +msgid "JUNE" +msgstr "Июнь" + +msgid "JULY" +msgstr "Июль" + +msgid "AUGUST" +msgstr "Август" + +msgid "SEPTEMBER" +msgstr "Сентябрь" + +msgid "OCTOBER" +msgstr "Октябрь" + +msgid "NOVEMBER" +msgstr "Ноябрь" + +msgid "DECEMBER" +msgstr "Декабрь" + +msgid "ADDRESS_BOOK" +msgstr "Адресная книга" + +msgid "FOR" +msgstr "для" + +msgid "SEARCH" +msgstr "Поиск" + +msgid "HOME" +msgstr "Главная" + +msgid "NEXT_BIRTHDAYS" +msgstr "Дни рождения" + +msgid "ADD_NEW" +msgstr "Добавить контакт" + +msgid "PRINT_ALL" +msgstr "Распечатать всё" + +msgid "PRINT_PHONES" +msgstr "Распечатать номера телефонов" + +msgid "EXPORT_CSV" +msgstr "CSV экспорт" + +msgid "EXPORT" +msgstr "Экспорт" + +msgid "IMPORT" +msgstr "Импорт" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "группа" + +msgid "GROUPS" +msgstr "Группы" + +msgid "MANAGE_GROUPS" +msgstr "Управление группами" + +msgid "NEW_GROUP" +msgstr "Новая группа" + +msgid "DELETE_GROUPS" +msgstr "Удалить группу (ы)" + +msgid "EDIT_GROUP" +msgstr "Изменить группу" + +msgid "GROUP_NAME" +msgstr "Имя группы" + +msgid "GROUP_HEADER" +msgstr "Название группы (шапка)" + +msgid "GROUP_FOOTER" +msgstr "Подпись группы (комментарий)" + +msgid "GROUP_PARENT" +msgstr "Родительская группа" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Поиск по тексту" + +msgid "NUMBER_OF_RESULTS" +msgstr "Количество результатов" + +msgid "ALL" +msgstr "все" + +msgid "NONE" +msgstr "не выбрано" + +msgid "SELECT_ALL" +msgstr "Выбрать все" + +msgid "REMOVE_FROM" +msgstr "Удалить из" + +msgid "MAIL_CLIENT" +msgstr "Почтовый клиент" + +msgid "SEND_EMAIL" +msgstr "Отправить электронное письмо" + +msgid "ADD_TO" +msgstr "Скопировать в" + +msgid "DETAILS" +msgstr "Детально..." + +msgid "EDIT" +msgstr "Изменить" + +msgid "MODIFY" +msgstr "Изменить" + +msgid "PRINT" +msgstr "Версия для печати" + +msgid "EDIT_ADD_ENTRY" +msgstr "Изменить / добавить запись адресной книги" + +msgid "GUESSED_HOMEPAGE" +msgstr "Предположительный сайт" + +msgid "PREFERENCES" +msgstr "Предпочтения" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "Имя" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "Фамилия" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "Прозвище" + +msgid "COMPANY" +msgstr "компания" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "Адрес" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "Домашний тел." + +msgid "PHONE_HOME" +msgstr "Домашний тел." + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "мобильный тел." + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "рабочий тел." + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "Факс" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Сайт" + +msgid "ZIP" +msgstr "индекс" + +msgid "CITY" +msgstr "город" + +msgid "E_MAIL_HOME" +msgstr "электронный адрес (домашний)" + +msgid "E_MAIL_OFFICE" +msgstr "электронный адрес (офисный)" + +msgid "2ND_ADDRESS" +msgstr "второй адрес" + +msgid "2ND_PHONE" +msgstr "второй телефон" + +msgid "NOTES" +msgstr "Примечание" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "день рождения" + +msgid "ANNIVERSARY" +msgstr "Годовщина" + +msgid "CREATED" +msgstr "создано" + +msgid "MODIFIED" +msgstr "Изменения" + +msgid "UPDATE" +msgstr "обновить" + +msgid "DELETE" +msgstr "удалить" + +msgid "INVALID" +msgstr "недействительный" + +msgid "ENTER" +msgstr "Сохранить" + +msgid "MEMBER_OF" +msgstr "Состоит в группах" + +msgid "SECONDARY" +msgstr "Дополнительная информация" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "Обновлено" + +msgid "TRANSLATOR" +msgstr "Переводчик" + +msgid "TITLE" +msgstr "Должность" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "ВСЕ_ТЕЛЕФОНЫ" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "Название группы" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Арабский" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Болгарский" + +msgid "ca" +msgstr "Каталонии" + +msgid "cs" +msgstr "Чешский" + +msgid "da" +msgstr "Датский" + +msgid "de" +msgstr "Немецкий" + +msgid "el" +msgstr "Греческая" + +msgid "en" +msgstr "Английский" + +msgid "es" +msgstr "Испанский" + +msgid "fa" +msgstr "Персидский" + +msgid "fi" +msgstr "Финский" + +msgid "fr" +msgstr "Французский" + +msgid "he" +msgstr "Иврит" + +msgid "hi" +msgstr "Хинди" + +msgid "hu" +msgstr "Венгерский" + +msgid "it" +msgstr "Итальянский" + +msgid "ja" +msgstr "Японский" + +msgid "ko" +msgstr "Корейский" + +msgid "nl" +msgstr "Голландский" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Польский" + +msgid "pt" +msgstr "Португальский" + +msgid "rm" +msgstr "Ретороманский" + +msgid "ru" +msgstr "Русский" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Словенский" + +msgid "sr" +msgstr "Сербский" + +msgid "sv" +msgstr "Шведский" + +msgid "th" +msgstr "Тайский" + +msgid "tr" +msgstr "Турецкий" + +msgid "ua" +msgstr "Украинский" + +msgid "vi" +msgstr "Вьетнамский" + +msgid "zh" +msgstr "Китайский" diff --git a/translations/php-addressbook-sk.po b/translations/php-addressbook-sk.po new file mode 100644 index 0000000..44711c8 --- /dev/null +++ b/translations/php-addressbook-sk.po @@ -0,0 +1,480 @@ +# Slovak translation for php-addressbook +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the php-addressbook package. +# FIRST AUTHOR , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: php-addressbook\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:32+0000\n" +"Last-Translator: DodoG \n" +"Language-Team: Slovak \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "Jazyk" + +msgid "auto" +msgstr "automaticky" + +msgid "USER" +msgstr "Používateľ" + +msgid "PASSWORD" +msgstr "Heslo" + +msgid "LOGIN" +msgstr "Prihlásenie" + +msgid "LOGOUT" +msgstr "ODHLÁSENIE" + +msgid "JANUARY" +msgstr "Január" + +msgid "FEBRUARY" +msgstr "Február" + +msgid "MARCH" +msgstr "Marec" + +msgid "APRIL" +msgstr "Apríl" + +msgid "MAY" +msgstr "Máj" + +msgid "JUNE" +msgstr "Jún" + +msgid "JULY" +msgstr "Júl" + +msgid "AUGUST" +msgstr "August" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Október" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "Adresár" + +msgid "FOR" +msgstr "pre" + +msgid "SEARCH" +msgstr "HľadaÅ¥" + +msgid "HOME" +msgstr "Domov" + +msgid "NEXT_BIRTHDAYS" +msgstr "Nasledujúce narodeniny" + +msgid "ADD_NEW" +msgstr "PridaÅ¥ novú" + +msgid "PRINT_ALL" +msgstr "VytlačiÅ¥ vÅ¡etko" + +msgid "PRINT_PHONES" +msgstr "Vytlačit tel. čísla" + +msgid "EXPORT_CSV" +msgstr "ExportovaÅ¥ CSV" + +msgid "EXPORT" +msgstr "ExportovaÅ¥" + +msgid "IMPORT" +msgstr "ImportovaÅ¥" + +msgid "MAP" +msgstr "Mapa" + +msgid "MORE" +msgstr "Viac" + +msgid "GROUP" +msgstr "Skupina" + +msgid "GROUPS" +msgstr "Skupiny" + +msgid "MANAGE_GROUPS" +msgstr "SpravovaÅ¥ skupiny" + +msgid "NEW_GROUP" +msgstr "Nová skupina" + +msgid "DELETE_GROUPS" +msgstr "VymazaÅ¥ skupiny" + +msgid "EDIT_GROUP" +msgstr "UpraviÅ¥ skupinu" + +msgid "GROUP_NAME" +msgstr "Názov skupiny" + +msgid "GROUP_HEADER" +msgstr "Hlavička skupiny" + +msgid "GROUP_FOOTER" +msgstr "Päta skupiny (poznámka)" + +msgid "GROUP_PARENT" +msgstr "Nadradená skupina" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "HľadaÅ¥ vÅ¡ade" + +msgid "NUMBER_OF_RESULTS" +msgstr "Počet výsledkov" + +msgid "ALL" +msgstr "VÅ¡etko" + +msgid "NONE" +msgstr "žiadne" + +msgid "SELECT_ALL" +msgstr "VybraÅ¥ vÅ¡etko" + +msgid "REMOVE_FROM" +msgstr "OdstrániÅ¥ z" + +msgid "MAIL_CLIENT" +msgstr "Emailový klient" + +msgid "SEND_EMAIL" +msgstr "PoslaÅ¥ email" + +msgid "ADD_TO" +msgstr "PridaÅ¥ do" + +msgid "DETAILS" +msgstr "Podrobnosti" + +msgid "EDIT" +msgstr "UpraviÅ¥" + +msgid "MODIFY" +msgstr "ZmeniÅ¥" + +msgid "PRINT" +msgstr "VytlačiÅ¥" + +msgid "EDIT_ADD_ENTRY" +msgstr "UpraviÅ¥ alebo pridaÅ¥ položku" + +msgid "GUESSED_HOMEPAGE" +msgstr "Odhadovaná webstránka" + +msgid "PREFERENCES" +msgstr "Prevoľby" + +msgid "NAME_PREFIX" +msgstr "Predpona" + +msgid "FIRSTNAME" +msgstr "Krstné meno" + +msgid "MIDDLENAME" +msgstr "Stredné meno" + +msgid "LASTNAME" +msgstr "Priezvisko" + +msgid "NAME_SUFFIX" +msgstr "Prípona" + +msgid "NICKNAME" +msgstr "Prezývka" + +msgid "COMPANY" +msgstr "SpoločnosÅ¥" + +msgid "DEPT" +msgstr "Oddelenie" + +msgid "OCCUPATION" +msgstr "Zamestnanie" + +msgid "TITLES" +msgstr "Tituly" + +msgid "ADDRESS" +msgstr "Adresa" + +msgid "POB" +msgstr "Číslo" + +msgid "APT" +msgstr "Apartmán" + +msgid "STREET" +msgstr "Ulica" + +msgid "STATE" +msgstr "Stav" + +msgid "COUNTRY" +msgstr "Krajina" + +msgid "TELEPHONE" +msgstr "Telefón" + +msgid "PHONE_HOME" +msgstr "Telefón domov" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "Mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "Telefón práca" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "Fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "Pager" + +msgid "EMAIL" +msgstr "Email" + +msgid "HOMEPAGE" +msgstr "Webstránka" + +msgid "ZIP" +msgstr "PSČ" + +msgid "CITY" +msgstr "Mesto" + +msgid "E_MAIL_HOME" +msgstr "Email domov" + +msgid "E_MAIL_OFFICE" +msgstr "Email práca" + +msgid "2ND_ADDRESS" +msgstr "Druhá adresa" + +msgid "2ND_PHONE" +msgstr "Druhý telefón" + +msgid "NOTES" +msgstr "Poznámky" + +msgid "MISC" +msgstr "Rôzne" + +msgid "BIRTHDAY" +msgstr "Narodeniny" + +msgid "ANNIVERSARY" +msgstr "Výročie" + +msgid "CREATED" +msgstr "Vytvorené" + +msgid "MODIFIED" +msgstr "Upravené" + +msgid "UPDATE" +msgstr "AktualizovaÅ¥" + +msgid "DELETE" +msgstr "VymazaÅ¥" + +msgid "INVALID" +msgstr "Neplatné" + +msgid "ENTER" +msgstr "VložiÅ¥" + +msgid "MEMBER_OF" +msgstr "Člen skupiny" + +msgid "SECONDARY" +msgstr "Sekundárne" + +msgid "CREATE_ACCOUNT" +msgstr "VytvoriÅ¥ konto" + +msgid "FORGOT_PASSWORD" +msgstr "Zabudnuté heslo" + +msgid "UPDATED" +msgstr "Aktualizované" + +msgid "TRANSLATOR" +msgstr "Prekladateľ" + +msgid "TITLE" +msgstr "Titul" + +msgid "MOBILE" +msgstr "Mobil" + +msgid "WORK" +msgstr "Práca" + +msgid "FIRST_LAST" +msgstr "FIRST_LAST" + +msgid "NEXT" +msgstr "Ďalší" + +msgid "PHOTO" +msgstr "Fotografia" + +msgid "ALL_PHONES" +msgstr "VÅ¡etky tel. čísla" + +msgid "ALL_EMAILS" +msgstr "VÅ¡etky tel. čísla" + +msgid "SIGN_IN_WITH" +msgstr "PrihlásiÅ¥ sa cez" + +msgid "LAST_FIRST" +msgstr "LAST_FIRST" + +msgid "GRP_NAME" +msgstr "Názov skupiny" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabský" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulharský" + +msgid "ca" +msgstr "Katalánsky" + +msgid "cs" +msgstr "Český" + +msgid "da" +msgstr "Dánsky" + +msgid "de" +msgstr "Nemecký" + +msgid "el" +msgstr "Grécky" + +msgid "en" +msgstr "Anglický" + +msgid "es" +msgstr "Å panielsky" + +msgid "fa" +msgstr "Perzský (Farsi)" + +msgid "fi" +msgstr "Fínsky" + +msgid "fr" +msgstr "Francúzsky" + +msgid "he" +msgstr "Hebrejský" + +msgid "hi" +msgstr "Hindský" + +msgid "hu" +msgstr "Maďarský" + +msgid "it" +msgstr "Taliansky" + +msgid "ja" +msgstr "Japonský" + +msgid "ko" +msgstr "Kórejský" + +msgid "nl" +msgstr "Holandský" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Poľský" + +msgid "pt" +msgstr "Portugalský" + +msgid "rm" +msgstr "Rétorománsky" + +msgid "ru" +msgstr "Ruský" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovinský" + +msgid "sr" +msgstr "Srbský" + +msgid "sv" +msgstr "Å védsky" + +msgid "th" +msgstr "Thajský" + +msgid "tr" +msgstr "Turecký" + +msgid "ua" +msgstr "Ukrajinský" + +msgid "vi" +msgstr "Vietnamský" + +msgid "zh" +msgstr "Čínsky" diff --git a/translations/php-addressbook-sl.po b/translations/php-addressbook-sl.po new file mode 100644 index 0000000..6028a36 --- /dev/null +++ b/translations/php-addressbook-sl.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2015-01-04 20:32+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "Jezik" + +msgid "auto" +msgstr "Nastavitve brskalnika (avtomatsko)" + +msgid "USER" +msgstr "Uporabnik" + +msgid "PASSWORD" +msgstr "Geslo" + +msgid "LOGIN" +msgstr "Prijava" + +msgid "LOGOUT" +msgstr "Odjava" + +msgid "JANUARY" +msgstr "Januar" + +msgid "FEBRUARY" +msgstr "Februar" + +msgid "MARCH" +msgstr "Marec" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Maj" + +msgid "JUNE" +msgstr "Junij" + +msgid "JULY" +msgstr "Julij" + +msgid "AUGUST" +msgstr "Avgust" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "Imenik" + +msgid "FOR" +msgstr "za" + +msgid "SEARCH" +msgstr "iskanje" + +msgid "HOME" +msgstr "Domov" + +msgid "NEXT_BIRTHDAYS" +msgstr "Rojstni dan" + +msgid "ADD_NEW" +msgstr "Nov vnos" + +msgid "PRINT_ALL" +msgstr "Natisni vse podatke" + +msgid "PRINT_PHONES" +msgstr "Natisni tel. Å¡tevilke" + +msgid "EXPORT_CSV" +msgstr "CSV-izvoz" + +msgid "EXPORT" +msgstr "export" + +msgid "IMPORT" +msgstr "import" + +msgid "MAP" +msgstr "Zemljevid" + +msgid "MORE" +msgstr "Več" + +msgid "GROUP" +msgstr "Skupina" + +msgid "GROUPS" +msgstr "Skupine" + +msgid "MANAGE_GROUPS" +msgstr "Uredi skupine" + +msgid "NEW_GROUP" +msgstr "Dodaj skupino" + +msgid "DELETE_GROUPS" +msgstr "BriÅ¡i skupino(e)" + +msgid "EDIT_GROUP" +msgstr "Uredi skupino" + +msgid "GROUP_NAME" +msgstr "Ime skupine" + +msgid "GROUP_HEADER" +msgstr "Glava skupine (slika)" + +msgid "GROUP_FOOTER" +msgstr "Noga skupine (komentar)" + +msgid "GROUP_PARENT" +msgstr "Materská skupina" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Poišči besedilo" + +msgid "NUMBER_OF_RESULTS" +msgstr "Å tevilo zadetkov" + +msgid "ALL" +msgstr "Vsi" + +msgid "NONE" +msgstr "- brez -" + +msgid "SELECT_ALL" +msgstr "Izberi vse" + +msgid "REMOVE_FROM" +msgstr "IzbriÅ¡i iz" + +msgid "MAIL_CLIENT" +msgstr "PoÅ¡tni program" + +msgid "SEND_EMAIL" +msgstr "PoÅ¡lji E-Mail" + +msgid "ADD_TO" +msgstr "Dodaj izbrane v skupino" + +msgid "DETAILS" +msgstr "Podrobno" + +msgid "EDIT" +msgstr "uredi" + +msgid "MODIFY" +msgstr "spremeni" + +msgid "PRINT" +msgstr "natisni" + +msgid "EDIT_ADD_ENTRY" +msgstr "Uredi / dodaj v imenik" + +msgid "GUESSED_HOMEPAGE" +msgstr "Možna spletna stran" + +msgid "PREFERENCES" +msgstr "Nastavitve" + +msgid "NAME_PREFIX" +msgstr "redpona imena" + +msgid "FIRSTNAME" +msgstr "ime" + +msgid "MIDDLENAME" +msgstr "Srednje ime" + +msgid "LASTNAME" +msgstr "priimek" + +msgid "NAME_SUFFIX" +msgstr "za imenom" + +msgid "NICKNAME" +msgstr "Vzdevek" + +msgid "COMPANY" +msgstr "podjetje" + +msgid "DEPT" +msgstr "Oddelek" + +msgid "OCCUPATION" +msgstr "Poklic" + +msgid "TITLES" +msgstr "Naziv" + +msgid "ADDRESS" +msgstr "naslov" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "Ulica" + +msgid "STATE" +msgstr "Dežela" + +msgid "COUNTRY" +msgstr "Država" + +msgid "TELEPHONE" +msgstr "Telefon" + +msgid "PHONE_HOME" +msgstr "doma" + +msgid "HOME_SHORT" +msgstr "Doma - kratko" + +msgid "PHONE_MOBILE" +msgstr "mobilni" + +msgid "MOBILE_SHORT" +msgstr "Mobilna - kratka" + +msgid "PHONE_WORK" +msgstr "službeni" + +msgid "WORK_SHORT" +msgstr "Delo - kratka" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "Fax - kratka" + +msgid "PHONE2_SHORT" +msgstr "Telefon 2 - Kratka" + +msgid "PAGER" +msgstr "Pager" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "spletna stran" + +msgid "ZIP" +msgstr "poÅ¡tna Å¡t." + +msgid "CITY" +msgstr "mesto" + +msgid "E_MAIL_HOME" +msgstr "privatni e-mail" + +msgid "E_MAIL_OFFICE" +msgstr "Službeni e-mail" + +msgid "2ND_ADDRESS" +msgstr "drugi naslov" + +msgid "2ND_PHONE" +msgstr "drugi telefon" + +msgid "NOTES" +msgstr "zaznamek" + +msgid "MISC" +msgstr "Obletnica" + +msgid "BIRTHDAY" +msgstr "rojstni dan" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "ustvarjen" + +msgid "MODIFIED" +msgstr "spremenjen" + +msgid "UPDATE" +msgstr "posodobi" + +msgid "DELETE" +msgstr "izbriÅ¡i" + +msgid "INVALID" +msgstr "neveljavno" + +msgid "ENTER" +msgstr "Nov vnos" + +msgid "MEMBER_OF" +msgstr "član" + +msgid "SECONDARY" +msgstr "Drugi naslov" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "Pozabljeno geslo" + +msgid "UPDATED" +msgstr "Obnovi" + +msgid "TRANSLATOR" +msgstr "Prevajalnik" + +msgid "TITLE" +msgstr "Naziv" + +msgid "MOBILE" +msgstr "Mobilna" + +msgid "WORK" +msgstr "Delo" + +msgid "FIRST_LAST" +msgstr "Ime in priimek" + +msgid "NEXT" +msgstr "Naprej" + +msgid "PHOTO" +msgstr "Slika" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "e-mail naslovi" + +msgid "SIGN_IN_WITH" +msgstr "Prijava z" + +msgid "LAST_FIRST" +msgstr "Priimek in ime" + +msgid "GRP_NAME" +msgstr "Ime skupine" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabski" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bolgarski" + +msgid "ca" +msgstr "Katalonski" + +msgid "cs" +msgstr "ČeÅ¡ko" + +msgid "da" +msgstr "Danski" + +msgid "de" +msgstr "NemÅ¡ko" + +msgid "el" +msgstr "GrÅ¡ki" + +msgid "en" +msgstr "AngleÅ¡ko" + +msgid "es" +msgstr "Å panski" + +msgid "fa" +msgstr "PerzÅ¡tina" + +msgid "fi" +msgstr "Finski" + +msgid "fr" +msgstr "Francoski" + +msgid "he" +msgstr "Hebrejski" + +msgid "hi" +msgstr "Indijski / Hindi" + +msgid "hu" +msgstr "Maďarský" + +msgid "it" +msgstr "Italijanski" + +msgid "ja" +msgstr "Japonski" + +msgid "ko" +msgstr "Korejski" + +msgid "nl" +msgstr "Nizozemski" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Poljsko" + +msgid "pt" +msgstr "Portuglski" + +msgid "rm" +msgstr "Retoromanski" + +msgid "ru" +msgstr "Ruski" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenski" + +msgid "sr" +msgstr "Srbský" + +msgid "sv" +msgstr "Å vedski" + +msgid "th" +msgstr "Tajski" + +msgid "tr" +msgstr "TurÅ¡ki" + +msgid "ua" +msgstr "Ukrajina" + +msgid "vi" +msgstr "Vietnamski" + +msgid "zh" +msgstr "Kitajski" diff --git a/translations/php-addressbook-sr.po b/translations/php-addressbook-sr.po new file mode 100644 index 0000000..cfa9638 --- /dev/null +++ b/translations/php-addressbook-sr.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:47+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "jezik" + +msgid "auto" +msgstr "podeÅ¡avanja browsera (automatsko)" + +msgid "USER" +msgstr "Korisničko ime" + +msgid "PASSWORD" +msgstr "Lozinka" + +msgid "LOGIN" +msgstr "Prijava" + +msgid "LOGOUT" +msgstr "Odjava" + +msgid "JANUARY" +msgstr "Januar" + +msgid "FEBRUARY" +msgstr "Februar" + +msgid "MARCH" +msgstr "Mart" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Maj" + +msgid "JUNE" +msgstr "Jun" + +msgid "JULY" +msgstr "Jul" + +msgid "AUGUST" +msgstr "Avgust" + +msgid "SEPTEMBER" +msgstr "Septembar" + +msgid "OCTOBER" +msgstr "Oktobar" + +msgid "NOVEMBER" +msgstr "Novembar" + +msgid "DECEMBER" +msgstr "Decembar" + +msgid "ADDRESS_BOOK" +msgstr "adresar" + +msgid "FOR" +msgstr "za" + +msgid "SEARCH" +msgstr "pretraga" + +msgid "HOME" +msgstr "Početak" + +msgid "NEXT_BIRTHDAYS" +msgstr "Predstojeći rođendani" + +msgid "ADD_NEW" +msgstr "Dodaj stavku" + +msgid "PRINT_ALL" +msgstr "Å tampaj sve" + +msgid "PRINT_PHONES" +msgstr "Å tampaj telefone" + +msgid "EXPORT_CSV" +msgstr "Eksportuj csv" + +msgid "EXPORT" +msgstr "izvoz" + +msgid "IMPORT" +msgstr "uvoz" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "grupa" + +msgid "GROUPS" +msgstr "Grupe" + +msgid "MANAGE_GROUPS" +msgstr "uredi grupe" + +msgid "NEW_GROUP" +msgstr "nova grupa" + +msgid "DELETE_GROUPS" +msgstr "briÅ¡i grup(u/e)" + +msgid "EDIT_GROUP" +msgstr "izmeni grupu" + +msgid "GROUP_NAME" +msgstr "ime grupe" + +msgid "GROUP_HEADER" +msgstr "Zaglavlje grupe (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Fusnota grupe (Komentar)" + +msgid "GROUP_PARENT" +msgstr "Roditelj grupa" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "traži bilo koji tekst" + +msgid "NUMBER_OF_RESULTS" +msgstr "Broj rezultata" + +msgid "ALL" +msgstr "sve" + +msgid "NONE" +msgstr "nijedna" + +msgid "SELECT_ALL" +msgstr "izaberi sve" + +msgid "REMOVE_FROM" +msgstr "ukloni iz" + +msgid "MAIL_CLIENT" +msgstr "mail klijent" + +msgid "SEND_EMAIL" +msgstr "PoÅ¡alji e-mail" + +msgid "ADD_TO" +msgstr "dodaj u" + +msgid "DETAILS" +msgstr "detalji" + +msgid "EDIT" +msgstr "izmeni" + +msgid "MODIFY" +msgstr "modifikuj" + +msgid "PRINT" +msgstr "Å¡tampaj" + +msgid "EDIT_ADD_ENTRY" +msgstr "Izmeni / dodaj stavku u adresar" + +msgid "GUESSED_HOMEPAGE" +msgstr "Pretpostavljena web stranica" + +msgid "PREFERENCES" +msgstr "lična podeÅ¡avanja" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "ime" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "prezime" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "firma" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "adresa" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "kućni telefon" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "Mobilni" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "na poslu" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "faks" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Web stranica" + +msgid "ZIP" +msgstr "PoÅ¡tanski broj" + +msgid "CITY" +msgstr "mesto" + +msgid "E_MAIL_HOME" +msgstr "e-mail privatni" + +msgid "E_MAIL_OFFICE" +msgstr "e-mail službeni" + +msgid "2ND_ADDRESS" +msgstr "druga adresa" + +msgid "2ND_PHONE" +msgstr "drugi telefon" + +msgid "NOTES" +msgstr "napomene" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "rođendan" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "kreirano" + +msgid "MODIFIED" +msgstr "modifikovano" + +msgid "UPDATE" +msgstr "ažuriraj" + +msgid "DELETE" +msgstr "briÅ¡i" + +msgid "INVALID" +msgstr "nevažeće" + +msgid "ENTER" +msgstr "unesi" + +msgid "MEMBER_OF" +msgstr "član grupe" + +msgid "SECONDARY" +msgstr "Sekundarni podaci" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arapski" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bugarski" + +msgid "ca" +msgstr "Katalonski" + +msgid "cs" +msgstr "ČeÅ¡ki" + +msgid "da" +msgstr "Danski" + +msgid "de" +msgstr "Nemački" + +msgid "el" +msgstr "Grčki" + +msgid "en" +msgstr "Engleski" + +msgid "es" +msgstr "Å panski" + +msgid "fa" +msgstr "PersiJski" + +msgid "fi" +msgstr "Finski" + +msgid "fr" +msgstr "Francuski" + +msgid "he" +msgstr "Hebrejski" + +msgid "hi" +msgstr "Hindu" + +msgid "hu" +msgstr "Mađarski" + +msgid "it" +msgstr "Italijanski" + +msgid "ja" +msgstr "Japanski" + +msgid "ko" +msgstr "Korejski" + +msgid "nl" +msgstr "Holandski" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Poljski" + +msgid "pt" +msgstr "Portugalski" + +msgid "rm" +msgstr "Rhaeto-Romance" + +msgid "ru" +msgstr "Ruski" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenački" + +msgid "sr" +msgstr "Srpski" + +msgid "sv" +msgstr "Å vedski" + +msgid "th" +msgstr "Tai" + +msgid "tr" +msgstr "Turski" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vijetnamski" + +msgid "zh" +msgstr "Kineski" diff --git a/translations/php-addressbook-sv.po b/translations/php-addressbook-sv.po new file mode 100644 index 0000000..5d774a2 --- /dev/null +++ b/translations/php-addressbook-sv.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-04 20:36+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "sprÃ¥k" + +msgid "auto" +msgstr "Bowser inställningar (automatiska)" + +msgid "USER" +msgstr "Användare" + +msgid "PASSWORD" +msgstr "Lösenord" + +msgid "LOGIN" +msgstr "Inloggning" + +msgid "LOGOUT" +msgstr "Logga ut" + +msgid "JANUARY" +msgstr "Januari" + +msgid "FEBRUARY" +msgstr "Februari" + +msgid "MARCH" +msgstr "Mars" + +msgid "APRIL" +msgstr "April" + +msgid "MAY" +msgstr "Maj" + +msgid "JUNE" +msgstr "Juni" + +msgid "JULY" +msgstr "Juli" + +msgid "AUGUST" +msgstr "Aug" + +msgid "SEPTEMBER" +msgstr "September" + +msgid "OCTOBER" +msgstr "Oktober" + +msgid "NOVEMBER" +msgstr "November" + +msgid "DECEMBER" +msgstr "December" + +msgid "ADDRESS_BOOK" +msgstr "Adressbok" + +msgid "FOR" +msgstr "för" + +msgid "SEARCH" +msgstr "söka" + +msgid "HOME" +msgstr "hem" + +msgid "NEXT_BIRTHDAYS" +msgstr "kommande födelsedagar" + +msgid "ADD_NEW" +msgstr "lägga till nya" + +msgid "PRINT_ALL" +msgstr "Skriv ut alla" + +msgid "PRINT_PHONES" +msgstr "Skriv ut telefoner" + +msgid "EXPORT_CSV" +msgstr "Exportera CSV" + +msgid "EXPORT" +msgstr "export" + +msgid "IMPORT" +msgstr "import" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "grupp" + +msgid "GROUPS" +msgstr "grupper" + +msgid "MANAGE_GROUPS" +msgstr "hantera grupper" + +msgid "NEW_GROUP" +msgstr "ny grupp" + +msgid "DELETE_GROUPS" +msgstr "Radera grupp (er)" + +msgid "EDIT_GROUP" +msgstr "Redigera grupp" + +msgid "GROUP_NAME" +msgstr "Gruppens namn" + +msgid "GROUP_HEADER" +msgstr "Grupp huvudet (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Grupp sidfot (Kommentera)" + +msgid "GROUP_PARENT" +msgstr "Föräldragrupp" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "söka efter text" + +msgid "NUMBER_OF_RESULTS" +msgstr "Antal resultat" + +msgid "ALL" +msgstr "alla" + +msgid "NONE" +msgstr "ingen" + +msgid "SELECT_ALL" +msgstr "Välj alla" + +msgid "REMOVE_FROM" +msgstr "Ta bort frÃ¥n" + +msgid "MAIL_CLIENT" +msgstr "e-postklient" + +msgid "SEND_EMAIL" +msgstr "Skicka e-post" + +msgid "ADD_TO" +msgstr "Lägg till" + +msgid "DETAILS" +msgstr "information" + +msgid "EDIT" +msgstr "Redigera" + +msgid "MODIFY" +msgstr "ändra" + +msgid "PRINT" +msgstr "Skriv ut" + +msgid "EDIT_ADD_ENTRY" +msgstr "Ändra / lägg till adressbok post" + +msgid "GUESSED_HOMEPAGE" +msgstr "Gissade hemsida" + +msgid "PREFERENCES" +msgstr "preferenser" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "förnamn" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "efternamn" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "företag" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "adress" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "hem" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "mobil" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "arbete" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-post" + +msgid "HOMEPAGE" +msgstr "Hemsida" + +msgid "ZIP" +msgstr "ZIP" + +msgid "CITY" +msgstr "stad" + +msgid "E_MAIL_HOME" +msgstr "e-post hem" + +msgid "E_MAIL_OFFICE" +msgstr "e-post kontor" + +msgid "2ND_ADDRESS" +msgstr "andra adress" + +msgid "2ND_PHONE" +msgstr "andra telefonen" + +msgid "NOTES" +msgstr "anteckningar" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "födelsedag" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "skapade" + +msgid "MODIFIED" +msgstr "modifierade" + +msgid "UPDATE" +msgstr "uppdatera" + +msgid "DELETE" +msgstr "ta bort" + +msgid "INVALID" +msgstr "ogiltig" + +msgid "ENTER" +msgstr "Ange" + +msgid "MEMBER_OF" +msgstr "medlem av" + +msgid "SECONDARY" +msgstr "Sekundära" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arabiska" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgariska" + +msgid "ca" +msgstr "Katalanska" + +msgid "cs" +msgstr "Tjeckiska" + +msgid "da" +msgstr "Danska" + +msgid "de" +msgstr "Tyska" + +msgid "el" +msgstr "Grekiska" + +msgid "en" +msgstr "Engelska" + +msgid "es" +msgstr "Spanska" + +msgid "fa" +msgstr "Persiska" + +msgid "fi" +msgstr "Finska" + +msgid "fr" +msgstr "Franska" + +msgid "he" +msgstr "Hebreiska" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Ungerska" + +msgid "it" +msgstr "Italienska" + +msgid "ja" +msgstr "Japanska" + +msgid "ko" +msgstr "Koreanska" + +msgid "nl" +msgstr "Holländska" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Polska" + +msgid "pt" +msgstr "Portugisiska" + +msgid "rm" +msgstr "Rätoromanska" + +msgid "ru" +msgstr "Ryska" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Slovenian" + +msgid "sr" +msgstr "Serbiska" + +msgid "sv" +msgstr "Svenska" + +msgid "th" +msgstr "Thai" + +msgid "tr" +msgstr "Turkiska" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamesiska" + +msgid "zh" +msgstr "Kinesiska" diff --git a/translations/php-addressbook-th.po b/translations/php-addressbook-th.po new file mode 100644 index 0000000..d95b7b6 --- /dev/null +++ b/translations/php-addressbook-th.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-05-01 14:45+0000\n" +"Last-Translator: Atawit Somsiri \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "ADDRESS_BOOK" +msgstr "Address Book" + +msgid "ADD_NEW" +msgstr "เพิ่มรายการใหม่" + +msgid "PRINT_ALL" +msgstr "พิมพ์ทั้งหมด" + +msgid "PRINT_PHONES" +msgstr "พิมพ์รายการโทรศัพท์" + +msgid "IMPORT" +msgstr "นำเข้าข้อมูล" + +msgid "GROUPS" +msgstr "กลุ่ม" + +msgid "MANAGE_GROUPS" +msgstr "จัดการกลุ่ม" + +msgid "NEW_GROUP" +msgstr "เพิ่มกลุ่มใหม่" + +msgid "DELETE_GROUPS" +msgstr "ลบกลุ่มย่อย" + +msgid "LANGUAGE" +msgstr "ภาษา" + +msgid "auto" +msgstr "อัตโนมัติ" + +msgid "USER" +msgstr "ผู้ใช้งาน" + +msgid "PASSWORD" +msgstr "รหัสผ่าน" + +msgid "LOGIN" +msgstr "เข้าสู่ระบบ" + +msgid "LOGOUT" +msgstr "ออกจากระบบ" + +msgid "JANUARY" +msgstr "มกราคม" + +msgid "FEBRUARY" +msgstr "กุมภาพันธ์" + +msgid "MARCH" +msgstr "มีนาคม" + +msgid "APRIL" +msgstr "เมษายน" + +msgid "MAY" +msgstr "พฤษภาคม" + +msgid "JUNE" +msgstr "มิถุนายน" + +msgid "JULY" +msgstr "กรกฎาคม" + +msgid "AUGUST" +msgstr "สิงหาคม" + +msgid "SEPTEMBER" +msgstr "กันยายน" + +msgid "OCTOBER" +msgstr "ตุลาคม" + +msgid "NOVEMBER" +msgstr "พฤศจิกายน" + +msgid "DECEMBER" +msgstr "ธันวาคม" + +msgid "FOR" +msgstr "สำหรับ" + +msgid "SEARCH" +msgstr "ค้นหา" + +msgid "HOME" +msgstr "หน้าแรก" + +msgid "NEXT_BIRTHDAYS" +msgstr "วันเกิด" + +msgid "EXPORT_CSV" +msgstr "ส่งออกรูปแบบ CSV" + +msgid "EXPORT" +msgstr "ส่งออก" + +msgid "MAP" +msgstr "แผนที่" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "กลุ่ม" + +msgid "EDIT_GROUP" +msgstr "แก้ไขรายการกลุ่มย่อย" + +msgid "GROUP_NAME" +msgstr "ชื่อกลุ่ม" + +msgid "GROUP_HEADER" +msgstr "ชื่อกลุ่มหลัก" + +msgid "GROUP_FOOTER" +msgstr "" + +msgid "GROUP_PARENT" +msgstr "กลุ่มผู้ปกครอง" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "ค้นหาข้อความใดๆ" + +msgid "NUMBER_OF_RESULTS" +msgstr "ผลสอบ" + +msgid "ALL" +msgstr "ทั้งนั้น" + +msgid "NONE" +msgstr "หมดตูด" + +msgid "SELECT_ALL" +msgstr "ทั้งนั้น" + +msgid "REMOVE_FROM" +msgstr "ลบ" + +msgid "MAIL_CLIENT" +msgstr "" + +msgid "SEND_EMAIL" +msgstr "ส่งจดหมาย จดหมายอิเล็กทรอนิกส์" + +msgid "ADD_TO" +msgstr "ต่อเติม" + +msgid "DETAILS" +msgstr "ขยายความใน" + +msgid "EDIT" +msgstr "เกลา" + +msgid "MODIFY" +msgstr "ก้าวไกล" + +msgid "PRINT" +msgstr "พิมพ์หนังสือ" + +msgid "EDIT_ADD_ENTRY" +msgstr "ประพันธ์" + +msgid "GUESSED_HOMEPAGE" +msgstr "คาด หน้าแรก" + +msgid "PREFERENCES" +msgstr "การตั้งตัว" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "ตั้งชื่อ" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "ตั้งชื่อ" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "บริษัท" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "ที่อยู่" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "เบอร์โทร" + +msgid "PHONE_HOME" +msgstr "เอกชน" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "มือถือ" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "กิจการ" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "แฟกซ์" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "จดหมายอิเล็กทรอนิกส์" + +msgid "HOMEPAGE" +msgstr "หน้าแรก" + +msgid "ZIP" +msgstr "รหัสไปรษณีย์" + +msgid "CITY" +msgstr "บุรี" + +msgid "E_MAIL_HOME" +msgstr "จดหมายอิเล็กทรอนิกส์ เอกชน" + +msgid "E_MAIL_OFFICE" +msgstr "จดหมายอิเล็กทรอนิกส์ กิจการ" + +msgid "2ND_ADDRESS" +msgstr "ที่สอง ที่อยู่" + +msgid "2ND_PHONE" +msgstr "ที่สอง เบอร์" + +msgid "NOTES" +msgstr "บันทึกย่อ" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "วันเกิด" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "สร้างขึ้น" + +msgid "MODIFIED" +msgstr "แก้ไข" + +msgid "UPDATE" +msgstr "ก้าวไกล" + +msgid "DELETE" +msgstr "ขีดฆ่า" + +msgid "INVALID" +msgstr "โมฆะ" + +msgid "ENTER" +msgstr "เก็บ" + +msgid "MEMBER_OF" +msgstr "ภาคี" + +msgid "SECONDARY" +msgstr "ครึ่งหลัง" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "ภาษาอาหรับ" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "บัลแกเรีย" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "" + +msgid "da" +msgstr "ภาษาเดนมาร์ก" + +msgid "de" +msgstr "ภาษาเยอรมัน" + +msgid "el" +msgstr "กรีก" + +msgid "en" +msgstr "ภาษาอังกฤษ" + +msgid "es" +msgstr "สเปน" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "ฟินแลนด์" + +msgid "fr" +msgstr "ฝรั่งเศส" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "ภาษาฮินดี" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "ภาษาอิตาลี" + +msgid "ja" +msgstr "ภาษาญี่ปุ่น" + +msgid "ko" +msgstr "เกาหลี" + +msgid "nl" +msgstr "ดัตช์" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "" + +msgid "pt" +msgstr "ภาษาโปรตุเกส" + +msgid "rm" +msgstr "เรโต - Romance" + +msgid "ru" +msgstr "คนรัสเซีย" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "ภาษาสโลเวเนีย" + +msgid "sr" +msgstr "เซอร์เบีย" + +msgid "sv" +msgstr "สวีเดน" + +msgid "th" +msgstr "ภาษาไทย" + +msgid "tr" +msgstr "ภาษาตุรกี" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "เวียตนาม" + +msgid "zh" +msgstr "ภาษาจีน" diff --git a/translations/php-addressbook-tr.po b/translations/php-addressbook-tr.po new file mode 100644 index 0000000..3570fa9 --- /dev/null +++ b/translations/php-addressbook-tr.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:47+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "dil" + +msgid "auto" +msgstr "tarayıcı ayarları (otomatik)" + +msgid "USER" +msgstr "Kullanıcı" + +msgid "PASSWORD" +msgstr "Şifre" + +msgid "LOGIN" +msgstr "Giriş" + +msgid "LOGOUT" +msgstr "Çıkış" + +msgid "JANUARY" +msgstr "Ocak" + +msgid "FEBRUARY" +msgstr "Şubat" + +msgid "MARCH" +msgstr "Mart" + +msgid "APRIL" +msgstr "Nisan" + +msgid "MAY" +msgstr "Mayıs" + +msgid "JUNE" +msgstr "Haziran" + +msgid "JULY" +msgstr "Temmuz" + +msgid "AUGUST" +msgstr "Ağustos" + +msgid "SEPTEMBER" +msgstr "Eylül" + +msgid "OCTOBER" +msgstr "Ekim" + +msgid "NOVEMBER" +msgstr "Kasım" + +msgid "DECEMBER" +msgstr "Aralık" + +msgid "ADDRESS_BOOK" +msgstr "adres rehberi" + +msgid "FOR" +msgstr "için" + +msgid "SEARCH" +msgstr "aramak" + +msgid "HOME" +msgstr "Anasayfa" + +msgid "NEXT_BIRTHDAYS" +msgstr "En yakın doğum günleri" + +msgid "ADD_NEW" +msgstr "Yeni Kayıt" + +msgid "PRINT_ALL" +msgstr "Tümünü yazdır" + +msgid "PRINT_PHONES" +msgstr "Telefonları yazdır" + +msgid "EXPORT_CSV" +msgstr "csv aktar" + +msgid "EXPORT" +msgstr "ihracat" + +msgid "IMPORT" +msgstr "ithalat" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "grup" + +msgid "GROUPS" +msgstr "Gruplar" + +msgid "MANAGE_GROUPS" +msgstr "Grupları yönet" + +msgid "NEW_GROUP" +msgstr "yeni grup" + +msgid "DELETE_GROUPS" +msgstr "Grubu sil" + +msgid "EDIT_GROUP" +msgstr "grubu düzenle" + +msgid "GROUP_NAME" +msgstr "grup adı" + +msgid "GROUP_HEADER" +msgstr "grup başlığı (Logo)" + +msgid "GROUP_FOOTER" +msgstr "Grup altlığı (yorum)" + +msgid "GROUP_PARENT" +msgstr "Ana grup" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Metin arama" + +msgid "NUMBER_OF_RESULTS" +msgstr "Bulunan sonuç sayısı" + +msgid "ALL" +msgstr "tümü" + +msgid "NONE" +msgstr "hiçbiri" + +msgid "SELECT_ALL" +msgstr "tümünü seç" + +msgid "REMOVE_FROM" +msgstr "gruptan çıkart" + +msgid "MAIL_CLIENT" +msgstr "posta hesabı" + +msgid "SEND_EMAIL" +msgstr "e-posta gönder" + +msgid "ADD_TO" +msgstr "gruba ekle" + +msgid "DETAILS" +msgstr "detaylar" + +msgid "EDIT" +msgstr "düzenle" + +msgid "MODIFY" +msgstr "düzenle" + +msgid "PRINT" +msgstr "yazdır" + +msgid "EDIT_ADD_ENTRY" +msgstr "yeni kayıt ekle / düzenle" + +msgid "GUESSED_HOMEPAGE" +msgstr "web sitesi" + +msgid "PREFERENCES" +msgstr "tercihler" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "adı" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "soyadı" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "Şirket" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "adres" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "telefon" + +msgid "PHONE_HOME" +msgstr "ev" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "GSM" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "iş" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "fax" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "e-posta" + +msgid "HOMEPAGE" +msgstr "web sayfası" + +msgid "ZIP" +msgstr "posta kodu" + +msgid "CITY" +msgstr "şehir" + +msgid "E_MAIL_HOME" +msgstr "e-posta ev" + +msgid "E_MAIL_OFFICE" +msgstr "e-posta iş" + +msgid "2ND_ADDRESS" +msgstr "ikinci adres" + +msgid "2ND_PHONE" +msgstr "ikinci telefon" + +msgid "NOTES" +msgstr "notlar" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "doğum günü" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "hazırlandı" + +msgid "MODIFIED" +msgstr "olarak" + +msgid "UPDATE" +msgstr "güncelleştir" + +msgid "DELETE" +msgstr "sil" + +msgid "INVALID" +msgstr "geçersiz" + +msgid "ENTER" +msgstr "kaydet" + +msgid "MEMBER_OF" +msgstr "Grup" + +msgid "SECONDARY" +msgstr "alternatif;" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Arapça" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "Bulgarca" + +msgid "ca" +msgstr "Catalonyaca" + +msgid "cs" +msgstr "Çek" + +msgid "da" +msgstr "Danimarkalı" + +msgid "de" +msgstr "Almanca" + +msgid "el" +msgstr "Yunanca" + +msgid "en" +msgstr "Ä°ngilizce" + +msgid "es" +msgstr "Ä°spanyolca" + +msgid "fa" +msgstr "Farsça" + +msgid "fi" +msgstr "Fince" + +msgid "fr" +msgstr "Fransızca" + +msgid "he" +msgstr "Hebrew" + +msgid "hi" +msgstr "Hintçe" + +msgid "hu" +msgstr "Macar" + +msgid "it" +msgstr "Ä°talyanca" + +msgid "ja" +msgstr "Japonca" + +msgid "ko" +msgstr "Korece" + +msgid "nl" +msgstr "Almanca" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "Alman parlatmak" + +msgid "pt" +msgstr "Portekizce" + +msgid "rm" +msgstr "Romanca" + +msgid "ru" +msgstr "Rusca" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "Sloven" + +msgid "sr" +msgstr "Sırp" + +msgid "sv" +msgstr "Ä°sveçce" + +msgid "th" +msgstr "Thaice" + +msgid "tr" +msgstr "Türk" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Vietnamca" + +msgid "zh" +msgstr "Çince" diff --git a/translations/php-addressbook-ua.po b/translations/php-addressbook-ua.po new file mode 100644 index 0000000..ce3ec34 --- /dev/null +++ b/translations/php-addressbook-ua.po @@ -0,0 +1,385 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +msgid "DIR" +msgstr "" + +msgid "LANGUAGE" +msgstr "мова" + +msgid "auto" +msgstr "параметри браузера (автоматично)" + +msgid "JANUARY" +msgstr "Січень" + +msgid "FEBRUARY" +msgstr "Лютий" + +msgid "MARCH" +msgstr "Березень" + +msgid "APRIL" +msgstr "Квітень" + +msgid "MAY" +msgstr "Травень" + +msgid "JUNE" +msgstr "Червень" + +msgid "JULY" +msgstr "Липень" + +msgid "AUGUST" +msgstr "Серпень" + +msgid "SEPTEMBER" +msgstr "Вересень" + +msgid "OCTOBER" +msgstr "Жовтень" + +msgid "NOVEMBER" +msgstr "Листопад" + +msgid "DECEMBER" +msgstr "Грудень" + +msgid "ADDRESS_BOOK" +msgstr "Адресна книга" + +msgid "FOR" +msgstr "для" + +msgid "SEARCH" +msgstr "Пошук" + +msgid "HOME" +msgstr "Головна" + +msgid "NEXT_BIRTHDAYS" +msgstr "Дні народження" + +msgid "ADD_NEW" +msgstr "Додати контакт" + +msgid "PRINT_ALL" +msgstr "Роздрукувати все" + +msgid "PRINT_PHONES" +msgstr "Роздрукувати номера телефонів" + +msgid "EXPORT_CSV" +msgstr "CSV експорт" + +msgid "GROUPS" +msgstr "Групи" + +msgid "MANAGE_GROUPS" +msgstr "Керування групами" + +msgid "NEW_GROUP" +msgstr "Нова група" + +msgid "DELETE_GROUPS" +msgstr "Видалити групу (и)" + +msgid "EDIT_GROUP" +msgstr "Редагувати групу" + +msgid "GROUP_NAME" +msgstr "Назва групи" + +msgid "GROUP_HEADER" +msgstr "Назва групи (шапка)" + +msgid "GROUP_FOOTER" +msgstr "Підпис групи (коментар)" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Пошук по тексту" + +msgid "NUMBER_OF_RESULTS" +msgstr "Кількість результатів" + +msgid "ALL" +msgstr "все" + +msgid "NONE" +msgstr "не вибрано" + +msgid "SELECT_ALL" +msgstr "Вибрати все" + +msgid "REMOVE_FROM" +msgstr "Видалити з" + +msgid "MAIL_CLIENT" +msgstr "Поштовий клієнт" + +msgid "SEND_EMAIL" +msgstr "Надіслати електронний лист" + +msgid "ADD_TO" +msgstr "Скопіювати у " + +msgid "DETAILS" +msgstr "Детально..." + +msgid "EDIT" +msgstr "Редагувати" + +msgid "MODIFY" +msgstr "Змінити" + +msgid "PRINT" +msgstr "Версія для друку" + +msgid "EDIT_ADD_ENTRY" +msgstr "Змінити / додати запис адресної книги" + +msgid "GUESSED_HOMEPAGE" +msgstr "Гаданий сайт" + +msgid "PREFERENCES" +msgstr "Вподобання" + +msgid "FIRSTNAME" +msgstr "Ім'я" + +msgid "LASTNAME" +msgstr "Прізвище" + +msgid "ADDRESS" +msgstr "Адреса" + +msgid "TELEPHONE" +msgstr "Домашній тел." + +msgid "PHONE_HOME" +msgstr "домашній тел." + +msgid "PHONE_MOBILE" +msgstr "мобільний тел." + +msgid "PHONE_WORK" +msgstr "робочий тел." + +msgid "EMAIL" +msgstr "e-mail " + +msgid "NICKNAME" +msgstr "Прізвисько" + +msgid "TITLE" +msgstr "Посада" + +msgid "ANNIVERSARY" +msgstr "Річниця" + +msgid "ZIP" +msgstr "індекс" + +msgid "CITY" +msgstr "місто" + +msgid "E_MAIL_HOME" +msgstr "електронна адреса (домашня)" + +msgid "E_MAIL_OFFICE" +msgstr "електронна адреса (офісна)" + +msgid "2ND_ADDRESS" +msgstr "друга адреса" + +msgid "2ND_PHONE" +msgstr "другий телефон" + +msgid "BIRTHDAY" +msgstr "день народження" + +msgid "UPDATE" +msgstr "оновити" + +msgid "DELETE" +msgstr "видалити" + +msgid "INVALID" +msgstr "недійсний" + +msgid "ENTER" +msgstr "Зберегти" + +msgid "MEMBER_OF" +msgstr "Перебуває у групах" + +msgid "SECONDARY" +msgstr "Додаткова інформація" + +msgid "GRP_NAME" +msgstr "Назва групи" + +msgid "GROUP" +msgstr "група" + +msgid "COMPANY" +msgstr "компанія" + +msgid "FAX" +msgstr "Факс" + +msgid "F:" +msgstr "Факс:" + +msgid "HOMEPAGE" +msgstr "Сайт" + +msgid "NOTES" +msgstr "Примітка" + +msgid "CREATED" +msgstr "створено" + +msgid "MODIFIED" +msgstr "Змінено" + +msgid "USER" +msgstr "Користувач" + +msgid "PASSWORD" +msgstr "Пароль" + +msgid "LOGIN" +msgstr "Увійти" + +msgid "LOGOUT" +msgstr "Вийти" + +msgid "UPDATED" +msgstr "Оновлено" + +msgid "EXPORT" +msgstr "Експорт" + +msgid "IMPORT" +msgstr "Імпорт" + +msgid "GROUP_PARENT" +msgstr "Батьківська група" + +msgid "TRANSLATOR" +msgstr "Перекладач" + +msgid "H:" +msgstr "Домашній телефон:" + +msgid "M:" +msgstr "Мобільний телефон:" + +msgid "W:" +msgstr "Робочий телефон:" + +msgid "en" +msgstr "Англійська" + +msgid "cs" +msgstr "Чешська" + +msgid "de" +msgstr "Німецька" + +msgid "pl" +msgstr "Польська" + +msgid "ar" +msgstr "Арабська" + +msgid "el" +msgstr "Грецька" + +msgid "es" +msgstr "Іспанська" + +msgid "fr" +msgstr "Французька" + +msgid "zh" +msgstr "Китайська" + +msgid "he" +msgstr "Іврит" + +msgid "hi" +msgstr "Хінді" + +msgid "ko" +msgstr "Корейська" + +msgid "pt" +msgstr "Португальська" + +msgid "ua" +msgstr "Українська" + +msgid "ja" +msgstr "Японська" + +msgid "th" +msgstr "Тайська" + +msgid "vi" +msgstr "В'єтнамська" + +msgid "ca" +msgstr "Каталонська" + +msgid "it" +msgstr "Італійська" + +msgid "nl" +msgstr "Нідерландська" + +msgid "sv" +msgstr "Шведська" + +msgid "bg" +msgstr "Болгарська" + +msgid "rm" +msgstr "Ретороманська" + +msgid "tr" +msgstr "Турецька" + +msgid "da" +msgstr "Датська" + +msgid "fi" +msgstr "Фінська" + +msgid "sr" +msgstr "Сербська" + +msgid "sl" +msgstr "Словенська" + +msgid "fa" +msgstr "Персидська" + +msgid "hu" +msgstr "Венгерська" diff --git a/translations/php-addressbook-uk.po b/translations/php-addressbook-uk.po new file mode 100644 index 0000000..4b108b2 --- /dev/null +++ b/translations/php-addressbook-uk.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-01-23 22:22+0000\n" +"Last-Translator: Oleksiy Petrov \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "мова" + +msgid "auto" +msgstr "параметри браузера (автоматично)" + +msgid "USER" +msgstr "Користувач" + +msgid "PASSWORD" +msgstr "Пароль" + +msgid "LOGIN" +msgstr "Увійти" + +msgid "LOGOUT" +msgstr "Вийти" + +msgid "JANUARY" +msgstr "Січень" + +msgid "FEBRUARY" +msgstr "Лютий" + +msgid "MARCH" +msgstr "Березень" + +msgid "APRIL" +msgstr "Квітень" + +msgid "MAY" +msgstr "Травень" + +msgid "JUNE" +msgstr "Червень" + +msgid "JULY" +msgstr "Липень" + +msgid "AUGUST" +msgstr "Серпень" + +msgid "SEPTEMBER" +msgstr "Вересень" + +msgid "OCTOBER" +msgstr "Жовтень" + +msgid "NOVEMBER" +msgstr "Листопад" + +msgid "DECEMBER" +msgstr "Грудень" + +msgid "ADDRESS_BOOK" +msgstr "Адресна книга" + +msgid "FOR" +msgstr "для" + +msgid "SEARCH" +msgstr "Пошук" + +msgid "HOME" +msgstr "Головна" + +msgid "NEXT_BIRTHDAYS" +msgstr "Дні народження" + +msgid "ADD_NEW" +msgstr "Додати контакт" + +msgid "PRINT_ALL" +msgstr "Роздрукувати все" + +msgid "PRINT_PHONES" +msgstr "Роздрукувати номера телефонів" + +msgid "EXPORT_CSV" +msgstr "CSV експорт" + +msgid "EXPORT" +msgstr "Експорт" + +msgid "IMPORT" +msgstr "Імпорт" + +msgid "MAP" +msgstr "МАПА" + +msgid "MORE" +msgstr "БІЛЬШЕ" + +msgid "GROUP" +msgstr "група" + +msgid "GROUPS" +msgstr "Групи" + +msgid "MANAGE_GROUPS" +msgstr "Керування групами" + +msgid "NEW_GROUP" +msgstr "Нова група" + +msgid "DELETE_GROUPS" +msgstr "Видалити групу (и)" + +msgid "EDIT_GROUP" +msgstr "Редагувати групу" + +msgid "GROUP_NAME" +msgstr "Назва групи" + +msgid "GROUP_HEADER" +msgstr "Назва групи (шапка)" + +msgid "GROUP_FOOTER" +msgstr "Підпис групи (коментар)" + +msgid "GROUP_PARENT" +msgstr "Батьківська група" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "Пошук по тексту" + +msgid "NUMBER_OF_RESULTS" +msgstr "Кількість результатів" + +msgid "ALL" +msgstr "все" + +msgid "NONE" +msgstr "не вибрано" + +msgid "SELECT_ALL" +msgstr "Вибрати все" + +msgid "REMOVE_FROM" +msgstr "Видалити з" + +msgid "MAIL_CLIENT" +msgstr "Поштовий клієнт" + +msgid "SEND_EMAIL" +msgstr "Надіслати електронний лист" + +msgid "ADD_TO" +msgstr "Скопіювати у" + +msgid "DETAILS" +msgstr "Детально..." + +msgid "EDIT" +msgstr "Редагувати" + +msgid "MODIFY" +msgstr "Змінити" + +msgid "PRINT" +msgstr "Версія для друку" + +msgid "EDIT_ADD_ENTRY" +msgstr "Змінити / додати запис адресної книги" + +msgid "GUESSED_HOMEPAGE" +msgstr "Гаданий сайт" + +msgid "PREFERENCES" +msgstr "Вподобання" + +msgid "NAME_PREFIX" +msgstr "Префікс імені" + +msgid "FIRSTNAME" +msgstr "Ім'я" + +msgid "MIDDLENAME" +msgstr "По батькові" + +msgid "LASTNAME" +msgstr "Прізвище" + +msgid "NAME_SUFFIX" +msgstr "Суфікс імені" + +msgid "NICKNAME" +msgstr "Прізвисько" + +msgid "COMPANY" +msgstr "компанія" + +msgid "DEPT" +msgstr "Відділ" + +msgid "OCCUPATION" +msgstr "Професія" + +msgid "TITLES" +msgstr "Посада" + +msgid "ADDRESS" +msgstr "Адреса" + +msgid "POB" +msgstr "Поштова скринька" + +msgid "APT" +msgstr "Квартира" + +msgid "STREET" +msgstr "Вулиця" + +msgid "STATE" +msgstr "Штат" + +msgid "COUNTRY" +msgstr "Країна" + +msgid "TELEPHONE" +msgstr "Домашній тел." + +msgid "PHONE_HOME" +msgstr "домашній тел." + +msgid "HOME_SHORT" +msgstr "Дом. тел." + +msgid "PHONE_MOBILE" +msgstr "мобільний тел." + +msgid "MOBILE_SHORT" +msgstr "Моб. тел." + +msgid "PHONE_WORK" +msgstr "робочий тел." + +msgid "WORK_SHORT" +msgstr "Роб. тел." + +msgid "FAX" +msgstr "Факс" + +msgid "FAX_SHORT" +msgstr "Факс" + +msgid "PHONE2_SHORT" +msgstr "Тел. 2" + +msgid "PAGER" +msgstr "ПЕЙДЖЕР" + +msgid "EMAIL" +msgstr "e-mail" + +msgid "HOMEPAGE" +msgstr "Сайт" + +msgid "ZIP" +msgstr "індекс" + +msgid "CITY" +msgstr "місто" + +msgid "E_MAIL_HOME" +msgstr "електронна адреса (домашня)" + +msgid "E_MAIL_OFFICE" +msgstr "електронна адреса (офісна)" + +msgid "2ND_ADDRESS" +msgstr "друга адреса" + +msgid "2ND_PHONE" +msgstr "другий телефон" + +msgid "NOTES" +msgstr "Примітка" + +msgid "MISC" +msgstr "РІЗНЕ" + +msgid "BIRTHDAY" +msgstr "день народження" + +msgid "ANNIVERSARY" +msgstr "Річниця" + +msgid "CREATED" +msgstr "створено" + +msgid "MODIFIED" +msgstr "Змінено" + +msgid "UPDATE" +msgstr "оновити" + +msgid "DELETE" +msgstr "видалити" + +msgid "INVALID" +msgstr "недійсний" + +msgid "ENTER" +msgstr "Зберегти" + +msgid "MEMBER_OF" +msgstr "Перебуває у групах" + +msgid "SECONDARY" +msgstr "Додаткова інформація" + +msgid "CREATE_ACCOUNT" +msgstr "Створити обліковий запис" + +msgid "FORGOT_PASSWORD" +msgstr "Забули пароль" + +msgid "UPDATED" +msgstr "Оновлено" + +msgid "TRANSLATOR" +msgstr "Перекладач" + +msgid "TITLE" +msgstr "Посада" + +msgid "MOBILE" +msgstr "Мобільний" + +msgid "WORK" +msgstr "Робочий" + +msgid "FIRST_LAST" +msgstr "Ім'я, Прізвище" + +msgid "NEXT" +msgstr "Далі" + +msgid "PHOTO" +msgstr "Світлина" + +msgid "ALL_PHONES" +msgstr "Усі тел. номера" + +msgid "ALL_EMAILS" +msgstr "Усі поштові адреси" + +msgid "SIGN_IN_WITH" +msgstr "Зареєструватися використовуючи" + +msgid "LAST_FIRST" +msgstr "Прізвище, І'мя" + +msgid "GRP_NAME" +msgstr "Назва групи" + +msgid "ab" +msgstr "Абхазька" + +msgid "ar" +msgstr "Арабська" + +msgid "be" +msgstr "Білоруська" + +msgid "bg" +msgstr "Болгарська" + +msgid "ca" +msgstr "Каталонська" + +msgid "cs" +msgstr "Чешська" + +msgid "da" +msgstr "Датська" + +msgid "de" +msgstr "Німецька" + +msgid "el" +msgstr "Грецька" + +msgid "en" +msgstr "Англійська" + +msgid "es" +msgstr "Іспанська" + +msgid "fa" +msgstr "Персидська" + +msgid "fi" +msgstr "Фінська" + +msgid "fr" +msgstr "Французька" + +msgid "he" +msgstr "Іврит" + +msgid "hi" +msgstr "Хінді" + +msgid "hu" +msgstr "Венгерська" + +msgid "it" +msgstr "Італійська" + +msgid "ja" +msgstr "Японська" + +msgid "ko" +msgstr "Корейська" + +msgid "nl" +msgstr "Нідерландська" + +msgid "no" +msgstr "Норвезька" + +msgid "pl" +msgstr "Польська" + +msgid "pt" +msgstr "Португальська" + +msgid "rm" +msgstr "Ретороманська" + +msgid "ru" +msgstr "Російська" + +msgid "sk" +msgstr "Словацька" + +msgid "sl" +msgstr "Словенська" + +msgid "sr" +msgstr "Сербська" + +msgid "sv" +msgstr "Шведська" + +msgid "th" +msgstr "Тайська" + +msgid "tr" +msgstr "Турецька" + +msgid "ua" +msgstr "Українська" + +msgid "vi" +msgstr "В'єтнамська" + +msgid "zh" +msgstr "Китайська" diff --git a/translations/php-addressbook-vi.po b/translations/php-addressbook-vi.po new file mode 100644 index 0000000..abe64d0 --- /dev/null +++ b/translations/php-addressbook-vi.po @@ -0,0 +1,478 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under BSD and AGPL +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2013-11-20 09:20+0000\n" +"Last-Translator: Vu Huy Phuong \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "NGÔN NGá»®" + +msgid "auto" +msgstr "tá»± động" + +msgid "USER" +msgstr "NGƯỜI DÙNG" + +msgid "PASSWORD" +msgstr "MẬT KHẨU" + +msgid "LOGIN" +msgstr "ĐĂNG NHẬP" + +msgid "LOGOUT" +msgstr "ĐĂNG XUẤT" + +msgid "JANUARY" +msgstr "THÁNG MỘT" + +msgid "FEBRUARY" +msgstr "THÁNG HAI" + +msgid "MARCH" +msgstr "THÁNG BA" + +msgid "APRIL" +msgstr "THÁNG TƯ" + +msgid "MAY" +msgstr "THÁNG NĂM" + +msgid "JUNE" +msgstr "THÁNG SÁU" + +msgid "JULY" +msgstr "THÁNG BẢY" + +msgid "AUGUST" +msgstr "THÁNG TÁM" + +msgid "SEPTEMBER" +msgstr "THÁNG CHÍN" + +msgid "OCTOBER" +msgstr "THÁNG MƯỜI" + +msgid "NOVEMBER" +msgstr "THÁNG MƯỜI MỘT" + +msgid "DECEMBER" +msgstr "THÁNG MƯỜI HAI" + +msgid "ADDRESS_BOOK" +msgstr "SỔ ĐỊA CHỈ" + +msgid "FOR" +msgstr "ĐỂ" + +msgid "SEARCH" +msgstr "TÌM KIẾM" + +msgid "HOME" +msgstr "TRANG CHỦ" + +msgid "NEXT_BIRTHDAYS" +msgstr "NGÀY SINH NHẬT KẾ TIẾP" + +msgid "ADD_NEW" +msgstr "THÊM MỚI" + +msgid "PRINT_ALL" +msgstr "IN TẤT CẢ" + +msgid "PRINT_PHONES" +msgstr "IN SỐ ĐIỆN THOẠI" + +msgid "EXPORT_CSV" +msgstr "XUẤT CSV" + +msgid "EXPORT" +msgstr "XUẤT" + +msgid "IMPORT" +msgstr "NHẬP" + +msgid "MAP" +msgstr "BẢN ĐỒ" + +msgid "MORE" +msgstr "THÊM" + +msgid "GROUP" +msgstr "NHÓM" + +msgid "GROUPS" +msgstr "NHÓM" + +msgid "MANAGE_GROUPS" +msgstr "QUẢN LÝ NHÓM" + +msgid "NEW_GROUP" +msgstr "TẠO NHÓM MỚI" + +msgid "DELETE_GROUPS" +msgstr "XÓA NHÓM" + +msgid "EDIT_GROUP" +msgstr "SỬA NHÓM" + +msgid "GROUP_NAME" +msgstr "TÊN NHÓM" + +msgid "GROUP_HEADER" +msgstr "HEADER NHÓM" + +msgid "GROUP_FOOTER" +msgstr "FOOTER NHÓM" + +msgid "GROUP_PARENT" +msgstr "NHÓM CHA" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "TÌM BẤT KỲ" + +msgid "NUMBER_OF_RESULTS" +msgstr "SỐ KẾT QUẢ" + +msgid "ALL" +msgstr "TẤT CẢ" + +msgid "NONE" +msgstr "KHÔNG" + +msgid "SELECT_ALL" +msgstr "CHỌN TẤT CẢ" + +msgid "REMOVE_FROM" +msgstr "XÓA KHỎI" + +msgid "MAIL_CLIENT" +msgstr "TRÌNH EMAIL" + +msgid "SEND_EMAIL" +msgstr "GỬI EMAIL" + +msgid "ADD_TO" +msgstr "THÊM VÀO" + +msgid "DETAILS" +msgstr "CHI TIẾT" + +msgid "EDIT" +msgstr "SỬA" + +msgid "MODIFY" +msgstr "SỬA" + +msgid "PRINT" +msgstr "IN" + +msgid "EDIT_ADD_ENTRY" +msgstr "SỬA MỤC THÊM" + +msgid "GUESSED_HOMEPAGE" +msgstr "WEBSITE" + +msgid "PREFERENCES" +msgstr "THIẾT LẬP" + +msgid "NAME_PREFIX" +msgstr "DANH XƯNG" + +msgid "FIRSTNAME" +msgstr "TÊN" + +msgid "MIDDLENAME" +msgstr "ĐỆM" + +msgid "LASTNAME" +msgstr "HỌ" + +msgid "NAME_SUFFIX" +msgstr "TÊN HẬU" + +msgid "NICKNAME" +msgstr "TÊN THƯỜNG GỌI" + +msgid "COMPANY" +msgstr "ĐƠN VỊ" + +msgid "DEPT" +msgstr "PHÒNG BAN" + +msgid "OCCUPATION" +msgstr "NGHỀ NGHIỆP" + +msgid "TITLES" +msgstr "CHỨC DANH" + +msgid "ADDRESS" +msgstr "ĐỊA CHỈ" + +msgid "POB" +msgstr "ĐỊA CHỈ HỘP THƯ" + +msgid "APT" +msgstr "APT" + +msgid "STREET" +msgstr "ĐƯỜNG" + +msgid "STATE" +msgstr "TỈNH" + +msgid "COUNTRY" +msgstr "QUỐC GIA" + +msgid "TELEPHONE" +msgstr "ĐIỆN THOẠI" + +msgid "PHONE_HOME" +msgstr "SỐ NHÀ" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "SỐ DI ĐỘNG" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "SỐ CÆ  QUAN" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "FAX" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "EMAIL" + +msgid "HOMEPAGE" +msgstr "TRANG CHỦ" + +msgid "ZIP" +msgstr "Mà VÙNG" + +msgid "CITY" +msgstr "THÀNH PHỐ" + +msgid "E_MAIL_HOME" +msgstr "EMAIL CÁ NHÂN" + +msgid "E_MAIL_OFFICE" +msgstr "EMAIL CÆ  QUAN" + +msgid "2ND_ADDRESS" +msgstr "e-mail văn phòng" + +msgid "2ND_PHONE" +msgstr "Địa chỉ thứ hai" + +msgid "NOTES" +msgstr "GHI CHÚ" + +msgid "MISC" +msgstr "KHÁC" + +msgid "BIRTHDAY" +msgstr "SINH NHẬT" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "ĐƯỢC TẠO" + +msgid "MODIFIED" +msgstr "Đà SỬA" + +msgid "UPDATE" +msgstr "CẬP NHẬT" + +msgid "DELETE" +msgstr "XÓA" + +msgid "INVALID" +msgstr "KHÔNG HỢP LỆ" + +msgid "ENTER" +msgstr "NHẬP" + +msgid "MEMBER_OF" +msgstr "THÀNH VIÊN CỦA" + +msgid "SECONDARY" +msgstr "THÔNG TIN PHỤ" + +msgid "CREATE_ACCOUNT" +msgstr "TẠO TÀI KHOẢN" + +msgid "FORGOT_PASSWORD" +msgstr "QUÊN MẬT KHẨU" + +msgid "UPDATED" +msgstr "Đà CẬP NHẬT" + +msgid "TRANSLATOR" +msgstr "NGƯỜI DỊCH" + +msgid "TITLE" +msgstr "CHỨC DANH" + +msgid "MOBILE" +msgstr "DI DỘNG" + +msgid "WORK" +msgstr "Số cÆ¡ quan" + +msgid "FIRST_LAST" +msgstr "Tên họ" + +msgid "NEXT" +msgstr "Kê tiếp" + +msgid "PHOTO" +msgstr "Ảnh" + +msgid "ALL_PHONES" +msgstr "Điện thoại" + +msgid "ALL_EMAILS" +msgstr "Email" + +msgid "SIGN_IN_WITH" +msgstr "Đăng nhập với" + +msgid "LAST_FIRST" +msgstr "Họ tên" + +msgid "GRP_NAME" +msgstr "Tên nhóm" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "Argentina" + +msgid "be" +msgstr "Bỉ" + +msgid "bg" +msgstr "Bungari" + +msgid "ca" +msgstr "Canada" + +msgid "cs" +msgstr "Tiếng Anh" + +msgid "da" +msgstr "Đan Mạch" + +msgid "de" +msgstr "Đức" + +msgid "el" +msgstr "Tiếng Bồ Đào Nha" + +msgid "en" +msgstr "tiếng Anh" + +msgid "es" +msgstr "Tây Ban Nha" + +msgid "fa" +msgstr "Tiếng Farsi" + +msgid "fi" +msgstr "Phần Lan" + +msgid "fr" +msgstr "Pháp" + +msgid "he" +msgstr "Do Thái" + +msgid "hi" +msgstr "Hindi" + +msgid "hu" +msgstr "Hungary" + +msgid "it" +msgstr "Ý" + +msgid "ja" +msgstr "Nhật Bản" + +msgid "ko" +msgstr "Hàn Quốc" + +msgid "nl" +msgstr "Hà Lan" + +msgid "no" +msgstr "không" + +msgid "pl" +msgstr "Ba Lan" + +msgid "pt" +msgstr "Bồ Đào Nha" + +msgid "rm" +msgstr "Rhaeto-Romance" + +msgid "ru" +msgstr "Nga" + +msgid "sk" +msgstr "Slovakia" + +msgid "sl" +msgstr "Slovenia" + +msgid "sr" +msgstr "Serbia" + +msgid "sv" +msgstr "Thụy Điển" + +msgid "th" +msgstr "Thái" + +msgid "tr" +msgstr "Thổ NhÄ© Kỳ" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "Việt" + +msgid "zh" +msgstr "Tiếng Pháp" diff --git a/translations/php-addressbook-zh.po b/translations/php-addressbook-zh.po new file mode 100644 index 0000000..3109c43 --- /dev/null +++ b/translations/php-addressbook-zh.po @@ -0,0 +1,480 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-11-09 01:32+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "語言" + +msgid "auto" +msgstr "瀏覽器設定(自動)" + +msgid "USER" +msgstr "用户" + +msgid "PASSWORD" +msgstr "密码" + +msgid "LOGIN" +msgstr "注册" + +msgid "LOGOUT" +msgstr "注销" + +msgid "JANUARY" +msgstr "一月" + +msgid "FEBRUARY" +msgstr "二月" + +msgid "MARCH" +msgstr "三月" + +msgid "APRIL" +msgstr "四月" + +msgid "MAY" +msgstr "五月" + +msgid "JUNE" +msgstr "六月" + +msgid "JULY" +msgstr "七月" + +msgid "AUGUST" +msgstr "八月" + +msgid "SEPTEMBER" +msgstr "九月" + +msgid "OCTOBER" +msgstr "十月" + +msgid "NOVEMBER" +msgstr "十一月" + +msgid "DECEMBER" +msgstr "十二月" + +msgid "ADDRESS_BOOK" +msgstr "地址簿" + +msgid "FOR" +msgstr "為了" + +msgid "SEARCH" +msgstr "搜索" + +msgid "HOME" +msgstr "首頁" + +msgid "NEXT_BIRTHDAYS" +msgstr "下次生日" + +msgid "ADD_NEW" +msgstr "新增" + +msgid "PRINT_ALL" +msgstr "全部打印" + +msgid "PRINT_PHONES" +msgstr "打印" + +msgid "EXPORT_CSV" +msgstr "匯出" + +msgid "EXPORT" +msgstr "出口" + +msgid "IMPORT" +msgstr "进口" + +msgid "MAP" +msgstr "地圖" + +msgid "MORE" +msgstr "更多" + +msgid "GROUP" +msgstr "組" + +msgid "GROUPS" +msgstr "組別" + +msgid "MANAGE_GROUPS" +msgstr "組別管理" + +msgid "NEW_GROUP" +msgstr "新組別" + +msgid "DELETE_GROUPS" +msgstr "刪除組別" + +msgid "EDIT_GROUP" +msgstr "組別編輯" + +msgid "GROUP_NAME" +msgstr "組別名稱" + +msgid "GROUP_HEADER" +msgstr "組別標題" + +msgid "GROUP_FOOTER" +msgstr "組別附注" + +msgid "GROUP_PARENT" +msgstr "家长组" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "搜索 任何文字" + +msgid "NUMBER_OF_RESULTS" +msgstr "搜索結果 數量" + +msgid "ALL" +msgstr "全部" + +msgid "NONE" +msgstr "無" + +msgid "SELECT_ALL" +msgstr "全選" + +msgid "REMOVE_FROM" +msgstr "刪除" + +msgid "MAIL_CLIENT" +msgstr "郵件客戶端" + +msgid "SEND_EMAIL" +msgstr "發送電郵" + +msgid "ADD_TO" +msgstr "增加" + +msgid "DETAILS" +msgstr "詳細" + +msgid "EDIT" +msgstr "編輯" + +msgid "MODIFY" +msgstr "修改" + +msgid "PRINT" +msgstr "打印" + +msgid "EDIT_ADD_ENTRY" +msgstr "編輯/添加" + +msgid "GUESSED_HOMEPAGE" +msgstr "猜到首頁" + +msgid "PREFERENCES" +msgstr "設定" + +msgid "NAME_PREFIX" +msgstr "字首" + +msgid "FIRSTNAME" +msgstr "名" + +msgid "MIDDLENAME" +msgstr "中間名/頭文字(S)" + +msgid "LASTNAME" +msgstr "姓氏" + +msgid "NAME_SUFFIX" +msgstr "後綴" + +msgid "NICKNAME" +msgstr "暱稱" + +msgid "COMPANY" +msgstr "公司" + +msgid "DEPT" +msgstr "部" + +msgid "OCCUPATION" +msgstr "職稱" + +msgid "TITLES" +msgstr "標題" + +msgid "ADDRESS" +msgstr "地址" + +msgid "POB" +msgstr "框或數字" + +msgid "APT" +msgstr "公寓或停止" + +msgid "STREET" +msgstr "街道地址" + +msgid "STATE" +msgstr "州" + +msgid "COUNTRY" +msgstr "國家" + +msgid "TELEPHONE" +msgstr "電話" + +msgid "PHONE_HOME" +msgstr "住家電話" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "移動電話" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "辦公電話" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "傳真" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "呼叫器" + +msgid "EMAIL" +msgstr "電子郵件" + +msgid "HOMEPAGE" +msgstr "首頁" + +msgid "ZIP" +msgstr "郵編" + +msgid "CITY" +msgstr "城市" + +msgid "E_MAIL_HOME" +msgstr "住家電郵" + +msgid "E_MAIL_OFFICE" +msgstr "辦公電郵" + +msgid "2ND_ADDRESS" +msgstr "地址 2" + +msgid "2ND_PHONE" +msgstr "電話 2" + +msgid "NOTES" +msgstr "附注" + +msgid "MISC" +msgstr "雜項" + +msgid "BIRTHDAY" +msgstr "生日" + +msgid "ANNIVERSARY" +msgstr "週年" + +msgid "CREATED" +msgstr "創建" + +msgid "MODIFIED" +msgstr "修改" + +msgid "UPDATE" +msgstr "更新" + +msgid "DELETE" +msgstr "刪除" + +msgid "INVALID" +msgstr "無效" + +msgid "ENTER" +msgstr "進入" + +msgid "MEMBER_OF" +msgstr "成員" + +msgid "SECONDARY" +msgstr "次檔" + +msgid "CREATE_ACCOUNT" +msgstr "創建賬戶" + +msgid "FORGOT_PASSWORD" +msgstr "忘記密碼" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "阿拉伯語" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "保加利亞文" + +msgid "ca" +msgstr "加泰隆" + +msgid "cs" +msgstr "捷克語" + +msgid "da" +msgstr "丹麥文" + +msgid "de" +msgstr "德語" + +msgid "el" +msgstr "希臘語" + +msgid "en" +msgstr "英語" + +msgid "es" +msgstr "西班牙語" + +msgid "fa" +msgstr "波斯语" + +msgid "fi" +msgstr "芬蘭文" + +msgid "fr" +msgstr "法語" + +msgid "he" +msgstr "希伯來語" + +msgid "hi" +msgstr "北印度語" + +msgid "hu" +msgstr "匈牙利" + +msgid "it" +msgstr "意大利語" + +msgid "ja" +msgstr "日語" + +msgid "ko" +msgstr "韓文" + +msgid "nl" +msgstr "荷蘭文" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "波蘭語" + +msgid "pt" +msgstr "葡萄牙語" + +msgid "rm" +msgstr "托羅曼斯文" + +msgid "ru" +msgstr "俄語" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "斯洛文尼亚" + +msgid "sr" +msgstr "塞尔维亚" + +msgid "sv" +msgstr "瑞典文" + +msgid "th" +msgstr "泰國" + +msgid "tr" +msgstr "土耳其文" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "越南文" + +msgid "zh" +msgstr "中國" diff --git a/translations/php-addressbook.pot b/translations/php-addressbook.pot new file mode 100644 index 0000000..ebaab51 --- /dev/null +++ b/translations/php-addressbook.pot @@ -0,0 +1,476 @@ +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-10-25 14:49+0200\n" +"PO-Revision-Date: 2012-10-27 11:47+0000\n" +"Last-Translator: chatelao \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2015-01-04 20:36+0000\n" +"X-Generator: Launchpad (build 17286)\n" + +msgid "LANGUAGE" +msgstr "" + +msgid "auto" +msgstr "" + +msgid "USER" +msgstr "" + +msgid "PASSWORD" +msgstr "" + +msgid "LOGIN" +msgstr "" + +msgid "LOGOUT" +msgstr "" + +msgid "JANUARY" +msgstr "" + +msgid "FEBRUARY" +msgstr "" + +msgid "MARCH" +msgstr "" + +msgid "APRIL" +msgstr "" + +msgid "MAY" +msgstr "" + +msgid "JUNE" +msgstr "" + +msgid "JULY" +msgstr "" + +msgid "AUGUST" +msgstr "" + +msgid "SEPTEMBER" +msgstr "" + +msgid "OCTOBER" +msgstr "" + +msgid "NOVEMBER" +msgstr "" + +msgid "DECEMBER" +msgstr "" + +msgid "ADDRESS_BOOK" +msgstr "" + +msgid "FOR" +msgstr "" + +msgid "SEARCH" +msgstr "" + +msgid "HOME" +msgstr "" + +msgid "NEXT_BIRTHDAYS" +msgstr "" + +msgid "ADD_NEW" +msgstr "" + +msgid "PRINT_ALL" +msgstr "" + +msgid "PRINT_PHONES" +msgstr "" + +msgid "EXPORT_CSV" +msgstr "" + +msgid "EXPORT" +msgstr "" + +msgid "IMPORT" +msgstr "" + +msgid "MAP" +msgstr "" + +msgid "MORE" +msgstr "" + +msgid "GROUP" +msgstr "" + +msgid "GROUPS" +msgstr "" + +msgid "MANAGE_GROUPS" +msgstr "" + +msgid "NEW_GROUP" +msgstr "" + +msgid "DELETE_GROUPS" +msgstr "" + +msgid "EDIT_GROUP" +msgstr "" + +msgid "GROUP_NAME" +msgstr "" + +msgid "GROUP_HEADER" +msgstr "" + +msgid "GROUP_FOOTER" +msgstr "" + +msgid "GROUP_PARENT" +msgstr "" + +msgid "SEARCH_FOR_ANY_TEXT" +msgstr "" + +msgid "NUMBER_OF_RESULTS" +msgstr "" + +msgid "ALL" +msgstr "" + +msgid "NONE" +msgstr "" + +msgid "SELECT_ALL" +msgstr "" + +msgid "REMOVE_FROM" +msgstr "" + +msgid "MAIL_CLIENT" +msgstr "" + +msgid "SEND_EMAIL" +msgstr "" + +msgid "ADD_TO" +msgstr "" + +msgid "DETAILS" +msgstr "" + +msgid "EDIT" +msgstr "" + +msgid "MODIFY" +msgstr "" + +msgid "PRINT" +msgstr "" + +msgid "EDIT_ADD_ENTRY" +msgstr "" + +msgid "GUESSED_HOMEPAGE" +msgstr "" + +msgid "PREFERENCES" +msgstr "" + +msgid "NAME_PREFIX" +msgstr "" + +msgid "FIRSTNAME" +msgstr "" + +msgid "MIDDLENAME" +msgstr "" + +msgid "LASTNAME" +msgstr "" + +msgid "NAME_SUFFIX" +msgstr "" + +msgid "NICKNAME" +msgstr "" + +msgid "COMPANY" +msgstr "" + +msgid "DEPT" +msgstr "" + +msgid "OCCUPATION" +msgstr "" + +msgid "TITLES" +msgstr "" + +msgid "ADDRESS" +msgstr "" + +msgid "POB" +msgstr "" + +msgid "APT" +msgstr "" + +msgid "STREET" +msgstr "" + +msgid "STATE" +msgstr "" + +msgid "COUNTRY" +msgstr "" + +msgid "TELEPHONE" +msgstr "" + +msgid "PHONE_HOME" +msgstr "" + +msgid "HOME_SHORT" +msgstr "" + +msgid "PHONE_MOBILE" +msgstr "" + +msgid "MOBILE_SHORT" +msgstr "" + +msgid "PHONE_WORK" +msgstr "" + +msgid "WORK_SHORT" +msgstr "" + +msgid "FAX" +msgstr "" + +msgid "FAX_SHORT" +msgstr "" + +msgid "PHONE2_SHORT" +msgstr "" + +msgid "PAGER" +msgstr "" + +msgid "EMAIL" +msgstr "" + +msgid "HOMEPAGE" +msgstr "" + +msgid "ZIP" +msgstr "" + +msgid "CITY" +msgstr "" + +msgid "E_MAIL_HOME" +msgstr "" + +msgid "E_MAIL_OFFICE" +msgstr "" + +msgid "2ND_ADDRESS" +msgstr "" + +msgid "2ND_PHONE" +msgstr "" + +msgid "NOTES" +msgstr "" + +msgid "MISC" +msgstr "" + +msgid "BIRTHDAY" +msgstr "" + +msgid "ANNIVERSARY" +msgstr "" + +msgid "CREATED" +msgstr "" + +msgid "MODIFIED" +msgstr "" + +msgid "UPDATE" +msgstr "" + +msgid "DELETE" +msgstr "" + +msgid "INVALID" +msgstr "" + +msgid "ENTER" +msgstr "" + +msgid "MEMBER_OF" +msgstr "" + +msgid "SECONDARY" +msgstr "" + +msgid "CREATE_ACCOUNT" +msgstr "" + +msgid "FORGOT_PASSWORD" +msgstr "" + +msgid "UPDATED" +msgstr "" + +msgid "TRANSLATOR" +msgstr "" + +msgid "TITLE" +msgstr "" + +msgid "MOBILE" +msgstr "" + +msgid "WORK" +msgstr "" + +msgid "FIRST_LAST" +msgstr "" + +msgid "NEXT" +msgstr "" + +msgid "PHOTO" +msgstr "" + +msgid "ALL_PHONES" +msgstr "" + +msgid "ALL_EMAILS" +msgstr "" + +msgid "SIGN_IN_WITH" +msgstr "" + +msgid "LAST_FIRST" +msgstr "" + +msgid "GRP_NAME" +msgstr "" + +msgid "ab" +msgstr "" + +msgid "ar" +msgstr "" + +msgid "be" +msgstr "" + +msgid "bg" +msgstr "" + +msgid "ca" +msgstr "" + +msgid "cs" +msgstr "" + +msgid "da" +msgstr "" + +msgid "de" +msgstr "" + +msgid "el" +msgstr "" + +msgid "en" +msgstr "" + +msgid "es" +msgstr "" + +msgid "fa" +msgstr "" + +msgid "fi" +msgstr "" + +msgid "fr" +msgstr "" + +msgid "he" +msgstr "" + +msgid "hi" +msgstr "" + +msgid "hu" +msgstr "" + +msgid "it" +msgstr "" + +msgid "ja" +msgstr "" + +msgid "ko" +msgstr "" + +msgid "nl" +msgstr "" + +msgid "no" +msgstr "" + +msgid "pl" +msgstr "" + +msgid "pt" +msgstr "" + +msgid "rm" +msgstr "" + +msgid "ru" +msgstr "" + +msgid "sk" +msgstr "" + +msgid "sl" +msgstr "" + +msgid "sr" +msgstr "" + +msgid "sv" +msgstr "" + +msgid "th" +msgstr "" + +msgid "tr" +msgstr "" + +msgid "ua" +msgstr "" + +msgid "vi" +msgstr "" + +msgid "zh" +msgstr "" diff --git a/vcard.php b/vcard.php new file mode 100644 index 0000000..32d4e23 --- /dev/null +++ b/vcard.php @@ -0,0 +1,22 @@ + diff --git a/view.php b/view.php new file mode 100644 index 0000000..7652e2a --- /dev/null +++ b/view.php @@ -0,0 +1,115 @@ +Address book <?php echo ($group_name != "" ? "($group_name)":""); ?>Address book <?php echo ($group_name != "" ? "($group_name)":""); ?><?php echo $r["firstname"].(isset($r["middlename"]) ? " ".$r["middlename"] : "")." ".$r["lastname"]." ".($group_name != "" ? "($group_name)":"")."\n"; ?>
    '; + // echo ''; + } +} + + +if ($id) { +if($resultsnumber == 0) { + + echo "
    Please select a valid entry.
    "; + +} else { + +include "include/view.w.php"; +showOneEntry($r); + +?> +
    +
    + +
    + + + + +
    + +
    + + +
    + +".ucfmsg('ADDRESS_BOOK').($group ? " ".msg('FOR')." $group" : ""); + ?> + + + hasPhone() || !$only_phones) { + if( ($cnt % (2*$addr_per_line)) == 0) + echo ""; + if( ($cnt % (2*$addr_per_line)) == $addr_per_line) + echo ""; + + echo ""; + + $cnt++; + if( ($cnt % $addr_per_line) == 0) + echo ""; + } + } + while(($cnt % $addr_per_line) != 0) + { + echo ""; + $cnt++; + } + ?> + +
    "; + showOneEntry($r, $only_phones); + echo "
    .
    +Please select a valid entry.
    "; + +} +include ("include/footer.inc.php"); +?> \ No newline at end of file diff --git a/view.php.bak b/view.php.bak new file mode 100644 index 0000000..7652e2a --- /dev/null +++ b/view.php.bak @@ -0,0 +1,115 @@ +Address book <?php echo ($group_name != "" ? "($group_name)":""); ?>Address book <?php echo ($group_name != "" ? "($group_name)":""); ?><?php echo $r["firstname"].(isset($r["middlename"]) ? " ".$r["middlename"] : "")." ".$r["lastname"]." ".($group_name != "" ? "($group_name)":"")."\n"; ?>
    '; + // echo ''; + } +} + + +if ($id) { +if($resultsnumber == 0) { + + echo "
    Please select a valid entry.
    "; + +} else { + +include "include/view.w.php"; +showOneEntry($r); + +?> +
    +
    + +
    + + + + +
    + +
    + + +
    + +".ucfmsg('ADDRESS_BOOK').($group ? " ".msg('FOR')." $group" : ""); + ?> + + + hasPhone() || !$only_phones) { + if( ($cnt % (2*$addr_per_line)) == 0) + echo ""; + if( ($cnt % (2*$addr_per_line)) == $addr_per_line) + echo ""; + + echo ""; + + $cnt++; + if( ($cnt % $addr_per_line) == 0) + echo ""; + } + } + while(($cnt % $addr_per_line) != 0) + { + echo ""; + $cnt++; + } + ?> + +
    "; + showOneEntry($r, $only_phones); + echo "
    .
    +Please select a valid entry.
    "; + +} +include ("include/footer.inc.php"); +?> \ No newline at end of file diff --git a/z-push/INSTALL b/z-push/INSTALL new file mode 100644 index 0000000..21cc45b --- /dev/null +++ b/z-push/INSTALL @@ -0,0 +1,171 @@ +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 + + +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 don't set this up correctly, you will not be +able to login correctly via z-push. + +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. + + +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. \ No newline at end of file diff --git a/z-push/LICENSE b/z-push/LICENSE new file mode 100644 index 0000000..bc3f2e0 --- /dev/null +++ b/z-push/LICENSE @@ -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 - 2011 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 . \ No newline at end of file diff --git a/z-push/backend/combined/combined.php b/z-push/backend/combined/combined.php new file mode 100644 index 0000000..baa5dc9 --- /dev/null +++ b/z-push/backend/combined/combined.php @@ -0,0 +1,419 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +// default backend +include_once('lib/default/backend.php'); + +//include the CombinedBackend's own config file +require_once("backend/combined/config.php"); +require_once("backend/combined/importer.php"); +require_once("backend/combined/exporter.php"); + +class BackendCombined extends Backend { + public $config; + public $backends; + private $activeBackend; + private $activeBackendID; + + /** + * Constructor of the combined backend + * + * @access public + */ + public function BackendCombined() { + parent::Backend(); + $this->config = BackendCombinedConfig::GetBackendCombinedConfig(); + + foreach ($this->config['backends'] as $i => $b){ + // load and instatiate backend + ZPush::IncludeBackend($b['name']); + $this->backends[$i] = new $b['name']($b['config']); + } + ZLog::Write(LOGLEVEL_INFO, sprintf("Combined %d backends loaded.", count($this->backends))); + } + + /** + * Authenticates the user on each backend + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + */ + public function Logon($username, $domain, $password) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon('%s', '%s',***))", $username, $domain)); + if(!is_array($this->backends)){ + return false; + } + foreach ($this->backends as $i => $b){ + $u = $username; + $d = $domain; + $p = $password; + if(isset($this->config['backends'][$i]['users'])){ + if(!isset($this->config['backends'][$i]['users'][$username])){ + unset($this->backends[$i]); + continue; + } + if(isset($this->config['backends'][$i]['users'][$username]['username'])) + $u = $this->config['backends'][$i]['users'][$username]['username']; + if(isset($this->config['backends'][$i]['users'][$username]['password'])) + $p = $this->config['backends'][$i]['users'][$username]['password']; + if(isset($this->config['backends'][$i]['users'][$username]['domain'])) + $d = $this->config['backends'][$i]['users'][$username]['domain']; + } + if($this->backends[$i]->Logon($u, $d, $p) == false){ + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Logon() failed on %s ", $this->config['backends'][$i]['name'])); + return false; + } + } + ZLog::Write(LOGLEVEL_INFO, "Combined->Logon() success"); + return true; + } + + /** + * Setup the backend to work on a specific store or checks ACLs there. + * If only the $store is submitted, all Import/Export/Fetch/Etc operations should be + * performed on this store (switch operations store). + * If the ACL check is enabled, this operation should just indicate the ACL status on + * the submitted store, without changing the store for operations. + * For the ACL status, the currently logged on user MUST have access rights on + * - the entire store - admin access if no folderid is sent, or + * - on a specific folderid in the store (secretary/full access rights) + * + * The ACLcheck MUST fail if a folder of the authenticated user is checked! + * + * @param string $store target store, could contain a "domain\user" value + * @param boolean $checkACLonly if set to true, Setup() should just check ACLs + * @param string $folderid if set, only ACLs on this folderid are relevant + * + * @access public + * @return boolean + */ + public function Setup($store, $checkACLonly = false, $folderid = false) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Setup('%s', '%s', '%s')", $store, Utils::PrintAsString($checkACLonly), $folderid)); + if(!is_array($this->backends)){ + return false; + } + foreach ($this->backends as $i => $b){ + $u = $store; + if(isset($this->config['backends'][$i]['users']) && isset($this->config['backends'][$i]['users'][$store]['username'])){ + $u = $this->config['backends'][$i]['users'][$store]['username']; + } + if($this->backends[$i]->Setup($u, $checkACLonly, $folderid) == false){ + ZLog::Write(LOGLEVEL_WARN, "Combined->Setup() failed"); + return false; + } + } + ZLog::Write(LOGLEVEL_INFO, "Combined->Setup() success"); + return true; + } + + /** + * Logs off each backend + * + * @access public + * @return boolean + */ + public function Logoff() { + ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logoff()"); + foreach ($this->backends as $i => $b){ + $this->backends[$i]->Logoff(); + } + ZLog::Write(LOGLEVEL_DEBUG, "Combined->Logoff() success"); + return true; + } + + /** + * Returns an array of SyncFolder types with the entire folder hierarchy + * from all backends combined + * + * provides AS 1.0 compatibility + * + * @access public + * @return array SYNC_FOLDER + */ + public function GetHierarchy(){ + ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetHierarchy()"); + $ha = array(); + foreach ($this->backends as $i => $b){ + if(!empty($this->config['backends'][$i]['subfolder'])){ + $f = new SyncFolder(); + $f->serverid = $i.$this->config['delimiter'].'0'; + $f->parentid = '0'; + $f->displayname = $this->config['backends'][$i]['subfolder']; + $f->type = SYNC_FOLDER_TYPE_OTHER; + $ha[] = $f; + } + $h = $this->backends[$i]->GetHierarchy(); + if(is_array($h)){ + foreach($h as $j => $f){ + $h[$j]->serverid = $i.$this->config['delimiter'].$h[$j]->serverid; + if($h[$j]->parentid != '0' || !empty($this->config['backends'][$i]['subfolder'])){ + $h[$j]->parentid = $i.$this->config['delimiter'].$h[$j]->parentid; + } + if(isset($this->config['folderbackend'][$h[$j]->type]) && $this->config['folderbackend'][$h[$j]->type] != $i){ + $h[$j]->type = SYNC_FOLDER_TYPE_OTHER; + } + } + $ha = array_merge($ha, $h); + } + } + ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetHierarchy() success"); + return $ha; + } + + /** + * Returns the importer to process changes from the mobile + * + * @param string $folderid (opt) + * + * @access public + * @return object(ImportChanges) + */ + public function GetImporter($folderid = false) { + if($folderid !== false) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetImporter() Content: ImportChangesCombined:('%s')", $folderid)); + + // get the contents importer from the folder in a backend + // the importer is wrapped to check foldernames in the ImportMessageMove function + $backend = $this->GetBackend($folderid); + if($backend === false) + return false; + $importer = $backend->GetImporter($this->GetBackendFolder($folderid)); + if($importer){ + return new ImportChangesCombined($this, $folderid, $importer); + } + return false; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetImporter() -> Hierarchy: ImportChangesCombined()"); + //return our own hierarchy importer which send each change to the right backend + return new ImportChangesCombined($this); + } + } + + /** + * Returns the exporter to send changes to the mobile + * the exporter from right backend for contents exporter and our own exporter for hierarchy exporter + * + * @param string $folderid (opt) + * + * @access public + * @return object(ExportChanges) + */ + public function GetExporter($folderid = false){ + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetExporter('%s')", $folderid)); + if($folderid){ + $backend = $this->GetBackend($folderid); + if($backend == false) + return false; + return $backend->GetExporter($this->GetBackendFolder($folderid)); + } + return new ExportChangesCombined($this); + } + + /** + * Sends an e-mail + * This messages needs to be saved into the 'sent items' folder + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + public function SendMail($sm) { + ZLog::Write(LOGLEVEL_DEBUG, "Combined->SendMail()"); + foreach ($this->backends as $i => $b){ + if($this->backends[$i]->SendMail($sm) == true){ + return true; + } + } + return false; + } + + /** + * Returns all available data of a single message + * + * @param string $folderid + * @param string $id + * @param ContentParameters $contentparameters flag + * + * @access public + * @return object(SyncObject) + * @throws StatusException + */ + public function Fetch($folderid, $id, $contentparameters) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->Fetch('%s', '%s', CPO)", $folderid, $id)); + $backend = $this->GetBackend($folderid); + if($backend == false) + return false; + return $backend->Fetch($this->GetBackendFolder($folderid), $id, $contentparameters); + } + + /** + * Returns the waste basket + * If the wastebasket is set to one backend, return the wastebasket of that backend + * else return the first waste basket we can find + * + * @access public + * @return string + */ + function GetWasteBasket(){ + ZLog::Write(LOGLEVEL_DEBUG, "Combined->GetWasteBasket()"); + + if (isset($this->activeBackend)) { + if (!$this->activeBackend->GetWasteBasket()) + return false; + else + return $this->activeBackendID . $this->config['delimiter'] . $this->activeBackend->GetWasteBasket(); + } + + return false; + } + + /** + * Returns the content of the named attachment as stream. + * There is no way to tell which backend the attachment is from, so we try them all + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined->GetAttachmentData('%s')", $attname)); + foreach ($this->backends as $i => $b) { + try { + $attachment = $this->backends[$i]->GetAttachmentData($attname); + if ($attachment instanceof SyncItemOperationsAttachment) + return $attachment; + } + catch (StatusException $s) { + // backends might throw StatusExceptions if it's not their attachment + } + } + throw new StatusException("Combined->GetAttachmentData(): no backend found", SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + } + + /** + * Processes a response to a meeting request. + * + * @param string $requestid id of the object containing the request + * @param string $folderid id of the parent folder of $requestid + * @param string $response + * + * @access public + * @return string id of the created/updated calendar obj + * @throws StatusException + */ + public function MeetingResponse($requestid, $folderid, $error) { + $backend = $this->GetBackend($folderid); + if($backend === false) + return false; + return $backend->MeetingResponse($requestid, $this->GetBackendFolder($folderid), $error); + } + + /** + * Finds the correct backend for a folder + * + * @param string $folderid combinedid of the folder + * + * @access public + * @return object + */ + public function GetBackend($folderid){ + $pos = strpos($folderid, $this->config['delimiter']); + if($pos === false) + return false; + $id = substr($folderid, 0, $pos); + if(!isset($this->backends[$id])) + return false; + + $this->activeBackend = $this->backends[$id]; + $this->activeBackendID = $id; + return $this->backends[$id]; + } + + /** + * Returns an understandable folderid for the backend + * + * @param string $folderid combinedid of the folder + * + * @access public + * @return string + */ + public function GetBackendFolder($folderid){ + $pos = strpos($folderid, $this->config['delimiter']); + if($pos === false) + return false; + return substr($folderid,$pos + strlen($this->config['delimiter'])); + } + + /** + * Returns backend id for a folder + * + * @param string $folderid combinedid of the folder + * + * @access public + * @return object + */ + public function GetBackendId($folderid){ + $pos = strpos($folderid, $this->config['delimiter']); + if($pos === false) + return false; + return substr($folderid,0,$pos); + } +} +?> \ No newline at end of file diff --git a/z-push/backend/combined/config.php b/z-push/backend/combined/config.php new file mode 100644 index 0000000..5305e4a --- /dev/null +++ b/z-push/backend/combined/config.php @@ -0,0 +1,151 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class BackendCombinedConfig { + + // ************************* + // BackendZarafa settings + // ************************* + public static $BackendZarafa_config = array('MAPI_SERVER' => MAPI_SERVER); + + // ************************* + // BackendIMAP settings + // ************************* + public static $BackendIMAP_config = array( + // Defines the server to which we want to connect + 'IMAP_SERVER' => IMAP_SERVER, + // connecting to default port (143) + 'IMAP_PORT' => IMAP_PORT, + // best cross-platform compatibility (see http://php.net/imap_open for options) + 'IMAP_OPTIONS' => IMAP_OPTIONS, + // overwrite the "from" header if it isn't set when sending emails + // options: 'username' - the username will be set (usefull if your login is equal to your emailaddress) + // 'domain' - the value of the "domain" field is used + // '@mydomain.com' - the username is used and the given string will be appended + 'IMAP_DEFAULTFROM' => IMAP_DEFAULTFROM, + // copy outgoing mail to this folder. If not set z-push will try the default folders + 'IMAP_SENTFOLDER' => IMAP_SENTFOLDER, + // forward messages inline (default false - as attachment) + 'IMAP_INLINE_FORWARD' => IMAP_INLINE_FORWARD, + // use imap_mail() to send emails (default) - if false mail() is used + 'IMAP_USE_IMAPMAIL' => IMAP_USE_IMAPMAIL, + ); + + // ************************* + // BackendMaildir settings + // ************************* + public static $BackendMaildir_config = array( + 'MAILDIR_BASE' => MAILDIR_BASE, + 'MAILDIR_SUBDIR' => MAILDIR_SUBDIR, + ); + + // ************************* + // BackendVCardDir settings + // ************************* + public static $BackendVCardDir_config = array('VCARDDIR_DIR' => VCARDDIR_DIR); + + // ************************* + // BackendCombined settings + // ************************* + /** + * Returns the configuration of the combined backend + * + * @access public + * @return array + * + */ + public static function GetBackendCombinedConfig() { + //use a function for it because php does not allow + //assigning variables to the class members (expecting T_STRING) + return array( + //the order in which the backends are loaded. + //login only succeeds if all backend return true on login + //sending mail: the mail is sent with first backend that is able to send the mail + 'backends' => array( + 'i' => array( + 'name' => 'BackendIMAP', + 'config' => self::$BackendIMAP_config, + ), + 'z' => array( + 'name' => 'BackendZarafa', + 'config' => self::$BackendZarafa_config + ), + 'm' => array( + 'name' => 'BackendMaildir', + 'config' => self::$BackendMaildir_config, + ), + 'v' => array( + 'name' => 'BackendVCardDir', + 'config' => self::$BackendVCardDir_config, + ), + ), + 'delimiter' => '/', + //force one type of folder to one backend + //it must match one of the above defined backends + 'folderbackend' => array( + SYNC_FOLDER_TYPE_INBOX => 'i', + SYNC_FOLDER_TYPE_DRAFTS => 'i', + SYNC_FOLDER_TYPE_WASTEBASKET => 'i', + SYNC_FOLDER_TYPE_SENTMAIL => 'i', + SYNC_FOLDER_TYPE_OUTBOX => 'i', + SYNC_FOLDER_TYPE_TASK => 'z', + SYNC_FOLDER_TYPE_APPOINTMENT => 'z', + SYNC_FOLDER_TYPE_CONTACT => 'z', + SYNC_FOLDER_TYPE_NOTE => 'z', + SYNC_FOLDER_TYPE_JOURNAL => 'z', + SYNC_FOLDER_TYPE_OTHER => 'i', + SYNC_FOLDER_TYPE_USER_MAIL => 'i', + SYNC_FOLDER_TYPE_USER_APPOINTMENT => 'z', + SYNC_FOLDER_TYPE_USER_CONTACT => 'z', + SYNC_FOLDER_TYPE_USER_TASK => 'z', + SYNC_FOLDER_TYPE_USER_JOURNAL => 'z', + SYNC_FOLDER_TYPE_USER_NOTE => 'z', + SYNC_FOLDER_TYPE_UNKNOWN => 'z', + ), + //creating a new folder in the root folder should create a folder in one backend + 'rootcreatefolderbackend' => 'i', + ); + } +} +?> \ No newline at end of file diff --git a/z-push/backend/combined/exporter.php b/z-push/backend/combined/exporter.php new file mode 100644 index 0000000..fb8cc15 --- /dev/null +++ b/z-push/backend/combined/exporter.php @@ -0,0 +1,184 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +/** + * the ExportChangesCombined class is returned from GetExporter for changes. + * It combines the changes from all backends and prepends all folderids with the backendid + */ + +class ExportChangesCombined implements IExportChanges { + private $backend; + private $syncstates; + private $exporters; + private $importer; + private $importwraps; + + public function ExportChangesCombined(&$backend) { + $this->backend =& $backend; + $this->exporters = array(); + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined constructed"); + } + + /** + * Initializes the state and flags + * + * @param string $state + * @param int $flags + * + * @access public + * @return boolean status flag + */ + public function Config($syncstate, $flags = 0) { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Config(...)"); + $this->syncstates = $syncstate; + if(!is_array($this->syncstates)){ + $this->syncstates = array(); + } + + foreach($this->backend->backends as $i => $b){ + if(isset($this->syncstates[$i])){ + $state = $this->syncstates[$i]; + } else { + $state = ''; + } + + $this->exporters[$i] = $this->backend->backends[$i]->GetExporter(); + $this->exporters[$i]->Config($state, $flags); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Config() success"); + } + + /** + * Returns the amount of changes to be exported + * + * @access public + * @return int + */ + public function GetChangeCount() { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetChangeCount()"); + $c = 0; + foreach($this->exporters as $i => $e){ + $c += $this->exporters[$i]->GetChangeCount(); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetChangeCount() success"); + return $c; + } + + /** + * Synchronizes a change to the configured importer + * + * @access public + * @return array with status information + */ + public function Synchronize() { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Synchronize()"); + foreach($this->exporters as $i => $e){ + if(!empty($this->backend->config['backends'][$i]['subfolder']) && !isset($this->syncstates[$i])){ + // first sync and subfolder backend + $f = new SyncFolder(); + $f->serverid = $i.$this->backend->config['delimiter'].'0'; + $f->parentid = '0'; + $f->displayname = $this->backend->config['backends'][$i]['subfolder']; + $f->type = SYNC_FOLDER_TYPE_OTHER; + $this->importer->ImportFolderChange($f); + } + while(is_array($this->exporters[$i]->Synchronize())); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->Synchronize() success"); + return true; + } + + /** + * Reads and returns the current state + * + * @access public + * @return string + */ + public function GetState() { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetState()"); + foreach($this->exporters as $i => $e){ + $this->syncstates[$i] = $this->exporters[$i]->GetState(); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->GetState() success"); + return $this->syncstates; + } + + /** + * Configures additional parameters used for content synchronization + * + * @param ContentParameters $contentparameters + * + * @access public + * @return boolean + * @throws StatusException + */ + public function ConfigContentParameters($contentparameters) { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->ConfigContentParameters()"); + foreach($this->exporters as $i => $e){ + //call the ConfigContentParameters() of each exporter + $e->ConfigContentParameters($contentparameters); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->ConfigContentParameters() success"); + } + + /** + * Sets the importer where the exporter will sent its changes to + * This exporter should also be ready to accept calls after this + * + * @param object &$importer Implementation of IImportChanges + * + * @access public + * @return boolean + */ + public function InitializeExporter(&$importer) { + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...)"); + foreach ($this->exporters as $i => $e) { + if(!isset($this->_importwraps[$i])){ + $this->importwraps[$i] = new ImportHierarchyChangesCombinedWrap($i, $this->backend, $importer); + } + $e->InitializeExporter($this->importwraps[$i]); + } + ZLog::Write(LOGLEVEL_DEBUG, "ExportChangesCombined->InitializeExporter(...) success"); + } +} +?> \ No newline at end of file diff --git a/z-push/backend/combined/importer.php b/z-push/backend/combined/importer.php new file mode 100644 index 0000000..4ed129b --- /dev/null +++ b/z-push/backend/combined/importer.php @@ -0,0 +1,333 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class ImportChangesCombined implements IImportChanges { + private $backend; + private $folderid; + private $icc; + + /** + * Constructor of the ImportChangesCombined class + * + * @param object $backend + * @param string $folderid + * @param object $importer + * + * @access public + */ + public function ImportChangesCombined(&$backend, $folderid = false, $icc = false) { + $this->backend = $backend; + $this->folderid = $folderid; + $this->icc = &$icc; + } + + /** + * Loads objects which are expected to be exported with the state + * Before importing/saving the actual message from the mobile, a conflict detection should be done + * + * @param ContentParameters $contentparameters class of objects + * @param string $state + * + * @access public + * @return boolean + * @throws StatusException + */ + public function LoadConflicts($contentparameters, $state) { + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->LoadConflicts() icc not configured"); + return false; + } + $this->icc->LoadConflicts($contentparameters, $state); + } + + /** + * Imports a single message + * + * @param string $id + * @param SyncObject $message + * + * @access public + * @return boolean/string failure / id of message + */ + public function ImportMessageChange($id, $message) { + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageChange() icc not configured"); + return false; + } + return $this->icc->ImportMessageChange($id, $message); + } + + /** + * Imports a deletion. This may conflict if the local object has been modified + * + * @param string $id + * + * @access public + * @return boolean + */ + public function ImportMessageDeletion($id) { + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageDeletion() icc not configured"); + return false; + } + return $this->icc->ImportMessageDeletion($id); + } + + /** + * Imports a change in 'read' flag + * This can never conflict + * + * @param string $id + * @param int $flags + * + * @access public + * @return boolean + */ + public function ImportMessageReadFlag($id, $flags) { + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageReadFlag() icc not configured"); + return false; + } + return $this->icc->ImportMessageReadFlag($id, $flags); + } + + /** + * Imports a move of a message. This occurs when a user moves an item to another folder + * + * @param string $id + * @param string $newfolder + * + * @access public + * @return boolean + */ + public function ImportMessageMove($id, $newfolder) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportMessageMove('%s', '%s')", $id, $newfolder)); + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageMove icc not configured"); + return false; + } + if($this->backend->GetBackendId($this->folderid) != $this->backend->GetBackendId($newfolder)){ + ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportMessageMove() cannot move message between two backends"); + return false; + } + return $this->icc->ImportMessageMove($id, $this->backend->GetBackendFolder($newfolder)); + } + + + /**---------------------------------------------------------------------------------------------------------- + * Methods to import hierarchy + */ + + /** + * Imports a change on a folder + * + * @param object $folder SyncFolder + * + * @access public + * @return boolean/string status/id of the folder + */ + public function ImportFolderChange($folder) { + $id = $folder->serverid; + $parent = $folder->parentid; + ZLog::Write(LOGLEVEL_DEBUG, "ImportChangesCombined->ImportFolderChange() ".print_r($folder, 1)); + if($parent == '0') { + if($id) { + $backendid = $this->backend->GetBackendId($id); + } + else { + $backendid = $this->backend->config['rootcreatefolderbackend']; + } + } + else { + $backendid = $this->backend->GetBackendId($parent); + $parent = $this->backend->GetBackendFolder($parent); + } + + if(!empty($this->backend->config['backends'][$backendid]['subfolder']) && $id == $backendid.$this->backend->config['delimiter'].'0') { + ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderChange() cannot change static folder"); + return false; + } + + if($id != false) { + if($backendid != $this->backend->GetBackendId($id)) { + ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderChange() cannot move folder between two backends"); + return false; + } + $id = $this->backend->GetBackendFolder($id); + } + + $this->icc = $this->backend->getBackend($backendid)->GetImporter(); + $res = $this->icc->ImportFolderChange($folder); + ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->ImportFolderChange() success'); + return $backendid.$this->backend->config['delimiter'].$res; + } + + /** + * Imports a folder deletion + * + * @param string $id + * @param string $parent id + * + * @access public + * @return boolean/int success/SYNC_FOLDERHIERARCHY_STATUS + */ + public function ImportFolderDeletion($id, $parent = false) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportFolderDeletion('%s', '%s'), $id, $parent")); + $backendid = $this->backend->GetBackendId($id); + if(!empty($this->backend->config['backends'][$backendid]['subfolder']) && $id == $backendid.$this->backend->config['delimiter'].'0') { + ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportFolderDeletion() cannot change static folder"); + return false; //we can not change a static subfolder + } + + $backend = $this->backend->GetBackend($id); + $id = $this->backend->GetBackendFolder($id); + + if($parent != '0') + $parent = $this->backend->GetBackendFolder($parent); + + $this->icc = $backend->GetImporter(); + $res = $this->icc->ImportFolderDeletion($id, $parent); + ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->ImportFolderDeletion() success'); + return $res; + } + + + /** + * Initializes the state and flags + * + * @param string $state + * @param int $flags + * + * @access public + * @return boolean status flag + */ + public function Config($state, $flags = 0) { + ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->Config(...)'); + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->Config() icc not configured"); + return false; + } + $this->icc->Config($state, $flags); + ZLog::Write(LOGLEVEL_DEBUG, 'ImportChangesCombined->Config() success'); + } + + /** + * Reads and returns the current state + * + * @access public + * @return string + */ + public function GetState() { + if (!$this->icc) { + ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->GetState() icc not configured"); + return false; + } + return $this->icc->GetState(); + } +} + + +/** + * The ImportHierarchyChangesCombinedWrap class wraps the importer given in ExportChangesCombined->Config. + * It prepends the backendid to all folderids and checks foldertypes. + */ + +class ImportHierarchyChangesCombinedWrap { + private $ihc; + private $backend; + private $backendid; + + /** + * Constructor of the ImportChangesCombined class + * + * @param string $backendid + * @param object $backend + * @param object $ihc + * + * @access public + */ + public function ImportHierarchyChangesCombinedWrap($backendid, &$backend, &$ihc) { + ZLog::Write(LOGLEVEL_DEBUG, "ImportHierarchyChangesCombinedWrap->ImportHierarchyChangesCombinedWrap('$backendid',...)"); + $this->backendid = $backendid; + $this->backend =& $backend; + $this->ihc = &$ihc; + } + + /** + * Imports a change on a folder + * + * @param object $folder SyncFolder + * + * @access public + * @return boolean/string status/id of the folder + */ + public function ImportFolderChange($folder) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportHierarchyChangesCombinedWrap->ImportFolderChange('%s')", $folder->serverid)); + $folder->serverid = $this->backendid.$this->backend->config['delimiter'].$folder->serverid; + if($folder->parentid != '0' || !empty($this->backend->config['backends'][$this->backendid]['subfolder'])){ + $folder->parentid = $this->backendid.$this->backend->config['delimiter'].$folder->parentid; + } + if(isset($this->backend->config['folderbackend'][$folder->type]) && $this->backend->config['folderbackend'][$folder->type] != $this->backendid){ + ZLog::Write(LOGLEVEL_DEBUG, sprintf("not using folder: '%s' ('%s')", $folder->displayname, $folder->serverid)); + return true; + } + ZLog::Write(LOGLEVEL_DEBUG, "ImportHierarchyChangesCombinedWrap->ImportFolderChange() success"); + return $this->ihc->ImportFolderChange($folder); + } + + /** + * Imports a folder deletion + * + * @param string $id + * + * @access public + * + * @return boolean/int success/SYNC_FOLDERHIERARCHY_STATUS + */ + public function ImportFolderDeletion($id) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportHierarchyChangesCombinedWrap->ImportFolderDeletion('%s')", $id)); + return $this->ihc->ImportFolderDeletion($this->backendid.$this->backend->config['delimiter'].$id); + } +} + +?> \ No newline at end of file diff --git a/z-push/backend/imap.php b/z-push/backend/imap.php new file mode 100644 index 0000000..bb80de8 --- /dev/null +++ b/z-push/backend/imap.php @@ -0,0 +1,1603 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +include_once('lib/default/diffbackend/diffbackend.php'); +include_once('include/mimeDecode.php'); +require_once('include/z_RFC822.php'); + + +class BackendIMAP extends BackendDiff { + protected $wasteID; + protected $sentID; + protected $server; + protected $mbox; + protected $mboxFolder; + protected $username; + protected $domain; + protected $serverdelimiter; + protected $sinkfolders; + protected $sinkstates; + + /**---------------------------------------------------------------------------------------------------------- + * default backend methods + */ + + /** + * Authenticates the user + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + * @throws FatalException if php-imap module can not be found + */ + public function Logon($username, $domain, $password) { + $this->wasteID = false; + $this->sentID = false; + $this->server = "{" . IMAP_SERVER . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}"; + + if (!function_exists("imap_open")) + throw new FatalException("BackendIMAP(): php-imap module is not installed", 0, null, LOGLEVEL_FATAL); + + // open the IMAP-mailbox + $this->mbox = @imap_open($this->server , $username, $password, OP_HALFOPEN); + $this->mboxFolder = ""; + + if ($this->mbox) { + ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->Logon(): User '%s' is authenticated on IMAP",$username)); + $this->username = $username; + $this->domain = $domain; + // set serverdelimiter + $this->serverdelimiter = $this->getServerDelimiter(); + return true; + } + else { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->Logon(): can't connect: " . imap_last_error()); + return false; + } + } + + /** + * Logs off + * Called before shutting down the request to close the IMAP connection + * writes errors to the log + * + * @access public + * @return boolean + */ + public function Logoff() { + if ($this->mbox) { + // list all errors + $errors = imap_errors(); + if (is_array($errors)) { + foreach ($errors as $e) + if (stripos($e, "fail") !== false) + $level = LOGLEVEL_WARN; + else + $level = LOGLEVEL_DEBUG; + + ZLog::Write($level, "BackendIMAP->Logoff(): IMAP said: " . $e); + } + @imap_close($this->mbox); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->Logoff(): IMAP connection closed"); + } + $this->SaveStorages(); + } + + /** + * Sends an e-mail + * This messages needs to be saved into the 'sent items' folder + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + // TODO implement , $saveInSent = true + public function SendMail($sm) { + $forward = $reply = (isset($sm->source->itemid) && $sm->source->itemid) ? $sm->source->itemid : false; + $parent = false; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", + strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), + Utils::PrintAsString((isset($sm->source->folderid) ? $sm->source->folderid : false)), + Utils::PrintAsString(($sm->saveinsent)), Utils::PrintAsString(isset($sm->replacemime)) )); + + if (isset($sm->source->folderid) && $sm->source->folderid) + // convert parent folder id back to work on an imap-id + $parent = $this->getImapIdFromFolderId($sm->source->folderid); + + + // by splitting the message in several lines we can easily grep later + foreach(preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) + ZLog::Write(LOGLEVEL_WBXML, "RFC822: ". $rfc822line); + + $mobj = new Mail_mimeDecode($sm->mime); + $message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + + $Mail_RFC822 = new Mail_RFC822(); + $toaddr = $ccaddr = $bccaddr = ""; + if(isset($message->headers["to"])) + $toaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["to"])); + if(isset($message->headers["cc"])) + $ccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["cc"])); + if(isset($message->headers["bcc"])) + $bccaddr = $this->parseAddr($Mail_RFC822->parseAddressList($message->headers["bcc"])); + + // save some headers when forwarding mails (content type & transfer-encoding) + $headers = ""; + $forward_h_ct = ""; + $forward_h_cte = ""; + $envelopefrom = ""; + + $use_orgbody = false; + + // clean up the transmitted headers + // remove default headers because we are using imap_mail + $changedfrom = false; + $returnPathSet = false; + $body_base64 = false; + $org_charset = ""; + $org_boundary = false; + $multipartmixed = false; + foreach($message->headers as $k => $v) { + if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc") + continue; + + if ($k == "content-type") { + // if the message is a multipart message, then we should use the sent body + if (preg_match("/multipart/i", $v)) { + $use_orgbody = true; + $org_boundary = $message->ctype_parameters["boundary"]; + } + + // save the original content-type header for the body part when forwarding + if ($sm->forwardflag && !$use_orgbody) { + $forward_h_ct = $v; + continue; + } + + // set charset always to utf-8 + $org_charset = $v; + $v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v); + } + + if ($k == "content-transfer-encoding") { + // if the content was base64 encoded, encode the body again when sending + if (trim($v) == "base64") $body_base64 = true; + + // save the original encoding header for the body part when forwarding + if ($sm->forwardflag) { + $forward_h_cte = $v; + continue; + } + } + + // check if "from"-header is set, do nothing if it's set + // else set it to IMAP_DEFAULTFROM + if ($k == "from") { + if (trim($v)) { + $changedfrom = true; + } elseif (! trim($v) && IMAP_DEFAULTFROM) { + $changedfrom = true; + if (IMAP_DEFAULTFROM == 'username') $v = $this->username; + else if (IMAP_DEFAULTFROM == 'domain') $v = $this->domain; + else $v = $this->username . IMAP_DEFAULTFROM; + $envelopefrom = "-f$v"; + } + } + + // check if "Return-Path"-header is set + if ($k == "return-path") { + $returnPathSet = true; + if (! trim($v) && IMAP_DEFAULTFROM) { + if (IMAP_DEFAULTFROM == 'username') $v = $this->username; + else if (IMAP_DEFAULTFROM == 'domain') $v = $this->domain; + else $v = $this->username . IMAP_DEFAULTFROM; + } + } + + // all other headers stay + if ($headers) $headers .= "\n"; + $headers .= ucfirst($k) . ": ". $v; + } + + // set "From" header if not set on the device + if(IMAP_DEFAULTFROM && !$changedfrom){ + if (IMAP_DEFAULTFROM == 'username') $v = $this->username; + else if (IMAP_DEFAULTFROM == 'domain') $v = $this->domain; + else $v = $this->username . IMAP_DEFAULTFROM; + if ($headers) $headers .= "\n"; + $headers .= 'From: '.$v; + $envelopefrom = "-f$v"; + } + + // set "Return-Path" header if not set on the device + if(IMAP_DEFAULTFROM && !$returnPathSet){ + if (IMAP_DEFAULTFROM == 'username') $v = $this->username; + else if (IMAP_DEFAULTFROM == 'domain') $v = $this->domain; + else $v = $this->username . IMAP_DEFAULTFROM; + if ($headers) $headers .= "\n"; + $headers .= 'Return-Path: '.$v; + } + + // if this is a multipart message with a boundary, we must use the original body + if ($use_orgbody) { + list(,$body) = $mobj->_splitBodyHeader($sm->mime); + $repl_body = $this->getBody($message); + } + else + $body = $this->getBody($message); + + // reply + if ($sm->replyflag && $parent) { + $this->imap_reopenFolder($parent); + // receive entire mail (header + body) to decode body correctly + $origmail = @imap_fetchheader($this->mbox, $reply, FT_UID) . @imap_body($this->mbox, $reply, FT_PEEK | FT_UID); + if (!$origmail) + throw new StatusException(sprintf("BackendIMAP->SendMail(): Could not open message id '%s' in folder id '%s' to be replied: %s", $reply, $parent, imap_last_error()), SYNC_COMMONSTATUS_ITEMNOTFOUND); + + $mobj2 = new Mail_mimeDecode($origmail); + // receive only body + $body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8'))); + // unset mimedecoder & origmail - free memory + unset($mobj2); + unset($origmail); + } + + // encode the body to base64 if it was sent originally in base64 by the pda + // contrib - chunk base64 encoded body + if ($body_base64 && !$sm->forwardflag) $body = chunk_split(base64_encode($body)); + + + // forward + if ($sm->forwardflag && $parent) { + $this->imap_reopenFolder($parent); + // receive entire mail (header + body) + $origmail = @imap_fetchheader($this->mbox, $forward, FT_UID) . @imap_body($this->mbox, $forward, FT_PEEK | FT_UID); + + if (!$origmail) + throw new StatusException(sprintf("BackendIMAP->SendMail(): Could not open message id '%s' in folder id '%s' to be forwarded: %s", $forward, $parent, imap_last_error()), SYNC_COMMONSTATUS_ITEMNOTFOUND); + + if (!defined('IMAP_INLINE_FORWARD') || IMAP_INLINE_FORWARD === false) { + // contrib - chunk base64 encoded body + if ($body_base64) $body = chunk_split(base64_encode($body)); + //use original boundary if it's set + $boundary = ($org_boundary) ? $org_boundary : false; + // build a new mime message, forward entire old mail as file + list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte,$boundary); + // add boundary headers + $headers .= "\n" . $aheader; + + } + else { + $mobj2 = new Mail_mimeDecode($origmail); + $mess2 = $mobj2->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + + if (!$use_orgbody) + $nbody = $body; + else + $nbody = $repl_body; + + $nbody .= "\r\n\r\n"; + $nbody .= "-----Original Message-----\r\n"; + if(isset($mess2->headers['from'])) + $nbody .= "From: " . $mess2->headers['from'] . "\r\n"; + if(isset($mess2->headers['to']) && strlen($mess2->headers['to']) > 0) + $nbody .= "To: " . $mess2->headers['to'] . "\r\n"; + if(isset($mess2->headers['cc']) && strlen($mess2->headers['cc']) > 0) + $nbody .= "Cc: " . $mess2->headers['cc'] . "\r\n"; + if(isset($mess2->headers['date'])) + $nbody .= "Sent: " . $mess2->headers['date'] . "\r\n"; + if(isset($mess2->headers['subject'])) + $nbody .= "Subject: " . $mess2->headers['subject'] . "\r\n"; + $nbody .= "\r\n"; + $nbody .= $this->getBody($mess2); + + if ($body_base64) { + // contrib - chunk base64 encoded body + $nbody = chunk_split(base64_encode($nbody)); + if ($use_orgbody) + // contrib - chunk base64 encoded body + $repl_body = chunk_split(base64_encode($repl_body)); + } + + if ($use_orgbody) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): -------------------"); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): old:\n'$repl_body'\nnew:\n'$nbody'\nund der body:\n'$body'"); + //$body is quoted-printable encoded while $repl_body and $nbody are plain text, + //so we need to decode $body in order replace to take place + $body = str_replace($repl_body, $nbody, quoted_printable_decode($body)); + } + else + $body = $nbody; + + + if(isset($mess2->parts)) { + $attached = false; + + if ($org_boundary) { + $att_boundary = $org_boundary; + // cut end boundary from body + $body = substr($body, 0, strrpos($body, "--$att_boundary--")); + } + else { + $att_boundary = strtoupper(md5(uniqid(time()))); + // add boundary headers + $headers .= "\n" . "Content-Type: multipart/mixed; boundary=$att_boundary"; + $multipartmixed = true; + } + + foreach($mess2->parts as $part) { + if(isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) { + + if(isset($part->d_parameters['filename'])) + $attname = $part->d_parameters['filename']; + else if(isset($part->ctype_parameters['name'])) + $attname = $part->ctype_parameters['name']; + else if(isset($part->headers['content-description'])) + $attname = $part->headers['content-description']; + else $attname = "unknown attachment"; + + // ignore html content + if ($part->ctype_primary == "text" && $part->ctype_secondary == "html") { + continue; + } + // + if ($use_orgbody || $attached) { + $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary); + } + // first attachment + else { + $encmail = $body; + $attached = true; + $body = $this->enc_multipart($att_boundary, $body, $forward_h_ct, $forward_h_cte); + $body .= $this->enc_attach_file($att_boundary, $attname, strlen($part->body),$part->body, $part->ctype_primary ."/". $part->ctype_secondary); + } + } + } + if ($multipartmixed && strpos(strtolower($mess2->headers['content-type']), "alternative") !== false) { + //this happens if a multipart/alternative message is forwarded + //then it's a multipart/mixed message which consists of: + //1. text/plain part which was written on the mobile + //2. multipart/alternative part which is the original message + $body = "This is a message with multiple parts in MIME format.\n--". + $att_boundary. + "\nContent-Type: $forward_h_ct\nContent-Transfer-Encoding: $forward_h_cte\n\n". + (($body_base64) ? chunk_split(base64_encode($message->body)) : rtrim($message->body)). + "\n--".$att_boundary. + "\nContent-Type: {$mess2->headers['content-type']}\n\n". + @imap_body($this->mbox, $forward, FT_PEEK | FT_UID)."\n\n"; + } + $body .= "--$att_boundary--\n\n"; + } + + unset($mobj2); + } + + // unset origmail - free memory + unset($origmail); + + } + + // remove carriage-returns from body + $body = str_replace("\r\n", "\n", $body); + + if (!$multipartmixed) { + if (!empty($forward_h_ct)) $headers .= "\nContent-Type: $forward_h_ct"; + if (!empty($forward_h_cte)) $headers .= "\nContent-Transfer-Encoding: $forward_h_cte"; + // if body was quoted-printable, convert it again + if (isset($message->headers["content-transfer-encoding"]) && strtolower($message->headers["content-transfer-encoding"]) == "quoted-printable") { + $body = quoted_printable_encode($body); + } + } + + // more debugging + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): parsed message: ". print_r($message,1)); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): headers: $headers"); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): subject: {$message->headers["subject"]}"); + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): body: $body"); + + if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) { + $send = @imap_mail ( $toaddr, $message->headers["subject"], $body, $headers, $ccaddr, $bccaddr); + } + else { + if (!empty($ccaddr)) $headers .= "\nCc: $ccaddr"; + if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr"; + $send = @mail ( $toaddr, $message->headers["subject"], $body, $headers, $envelopefrom ); + } + + // email sent? + if (!$send) + throw new StatusException(sprintf("BackendIMAP->SendMail(): The email could not be sent. Last IMAP-error: %s", imap_last_error()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); + + // add message to the sent folder + // build complete headers + $headers .= "\nTo: $toaddr"; + $headers .= "\nSubject: " . $message->headers["subject"]; + + if (!defined('IMAP_USE_IMAPMAIL') || IMAP_USE_IMAPMAIL == true) { + if (!empty($ccaddr)) $headers .= "\nCc: $ccaddr"; + if (!empty($bccaddr)) $headers .= "\nBcc: $bccaddr"; + } + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): complete headers: $headers"); + + $asf = false; + if ($this->sentID) { + $asf = $this->addSentMessage($this->sentID, $headers, $body); + } + else if (IMAP_SENTFOLDER) { + $asf = $this->addSentMessage(IMAP_SENTFOLDER, $headers, $body); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SendMail(): Outgoing mail saved in configured 'Sent' folder '%s': %s", IMAP_SENTFOLDER, Utils::PrintAsString($asf))); + } + // No Sent folder set, try defaults + else { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): No Sent mailbox set"); + if($this->addSentMessage("INBOX.Sent", $headers, $body)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'INBOX.Sent'"); + $asf = true; + } + else if ($this->addSentMessage("Sent", $headers, $body)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail(): Outgoing mail saved in 'Sent'"); + $asf = true; + } + else if ($this->addSentMessage("Sent Items", $headers, $body)) { + ZLog::Write(LOGLEVEL_DEBUG, "BackendIMAP->SendMail():IMAP-SendMail: Outgoing mail saved in 'Sent Items'"); + $asf = true; + } + } + + if (!$asf) { + ZLog::Write(LOGLEVEL_ERROR, "BackendIMAP->SendMail(): The email could not be saved to Sent Items folder. Check your configuration."); + } + + return $send; + } + + /** + * Returns the waste basket + * + * @access public + * @return string + */ + public function GetWasteBasket() { + // TODO this could be retrieved from the DeviceFolderCache + if ($this->wasteID == false) { + //try to get the waste basket without doing complete hierarchy sync + $wastebaskt = @imap_getmailboxes($this->mbox, $this->server, "Trash"); + if (isset($wastebaskt[0])) { + $this->wasteID = $this->convertImapId(substr($wastebaskt[0]->name, strlen($this->server))); + return $this->wasteID; + } + //try get waste id from hierarchy if it wasn't possible with above for some reason + $this->GetHierarchy(); + } + return $this->wasteID; + } + + /** + * Returns the content of the named attachment as stream. The passed attachment identifier is + * the exact string that is returned in the 'AttName' property of an SyncAttachment. + * Any information necessary to find the attachment must be encoded in that 'attname' property. + * Data is written directly (with print $data;) + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetAttachmentData('%s')", $attname)); + + list($folderid, $id, $part) = explode(":", $attname); + + if (!$folderid || !$id || !$part) + throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, attachment name key can not be parsed", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + + // convert back to work on an imap-id + $folderImapid = $this->getImapIdFromFolderId($folderid); + + $this->imap_reopenFolder($folderImapid); + $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID); + + $mobj = new Mail_mimeDecode($mail); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + + if (!isset($message->parts[$part]->body)) + throw new StatusException(sprintf("BackendIMAP->GetAttachmentData('%s'): Error, requested part key can not be found: '%d'", $attname, $part), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + + // unset mimedecoder & mail + unset($mobj); + unset($mail); + + include_once('include/stringstreamwrapper.php'); + $attachment = new SyncItemOperationsAttachment(); + $attachment->data = StringStreamWrapper::Open($message->parts[$part]->body); + if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary)) + $attachment->contenttype = $message->parts[$part]->ctype_primary .'/'.$message->parts[$part]->ctype_secondary; + + return $attachment; + } + + /** + * Indicates if the backend has a ChangesSink. + * A sink is an active notification mechanism which does not need polling. + * The IMAP backend simulates a sink by polling status information of the folder + * + * @access public + * @return boolean + */ + public function HasChangesSink() { + $this->sinkfolders = array(); + $this->sinkstates = array(); + return true; + } + + /** + * 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 found can not be found + */ + public function ChangesSinkInitialize($folderid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("IMAPBackend->ChangesSinkInitialize(): folderid '%s'", $folderid)); + + $imapid = $this->getImapIdFromFolderId($folderid); + + if ($imapid) { + $this->sinkfolders[] = $imapid; + return true; + } + + 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) { + $notifications = array(); + $stopat = time() + $timeout - 1; + + while($stopat > time() && empty($notifications)) { + foreach ($this->sinkfolders as $imapid) { + $this->imap_reopenFolder($imapid); + + // courier-imap only cleares the status cache after checking + @imap_check($this->mbox); + + $status = @imap_status($this->mbox, $this->server . $imapid, SA_ALL); + if (!$status) { + ZLog::Write(LOGLEVEL_WARN, sprintf("ChangesSink: could not stat folder '%s': %s ", $this->getFolderIdFromImapId($imapid), imap_last_error())); + } + else { + $newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen; + + if (! isset($this->sinkstates[$imapid]) ) + $this->sinkstates[$imapid] = $newstate; + + if ($this->sinkstates[$imapid] != $newstate) { + $notifications[] = $this->getFolderIdFromImapId($imapid); + $this->sinkstates[$imapid] = $newstate; + } + } + } + + if (empty($notifications)) + sleep(5); + } + + return $notifications; + } + + + /**---------------------------------------------------------------------------------------------------------- + * implemented DiffBackend methods + */ + + + /** + * Returns a list (array) of folders. + * + * @access public + * @return array/boolean false if the list could not be retrieved + */ + public function GetFolderList() { + $folders = array(); + + $list = @imap_getmailboxes($this->mbox, $this->server, "*"); + if (is_array($list)) { + // reverse list to obtain folders in right order + $list = array_reverse($list); + + foreach ($list as $val) { + $box = array(); + // cut off serverstring + $imapid = substr($val->name, strlen($this->server)); + $box["id"] = $this->convertImapId($imapid); + + $fhir = explode($val->delimiter, $imapid); + if (count($fhir) > 1) { + $this->getModAndParentNames($fhir, $box["mod"], $imapparent); + $box["parent"] = $this->convertImapId($imapparent); + } + else { + $box["mod"] = $imapid; + $box["parent"] = "0"; + } + $folders[]=$box; + } + } + else { + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->GetFolderList(): imap_list failed: " . imap_last_error()); + return false; + } + + return $folders; + } + + /** + * Returns an actual SyncFolder object + * + * @param string $id id of the folder + * + * @access public + * @return object SyncFolder with information + */ + public function GetFolder($id) { + $folder = new SyncFolder(); + $folder->serverid = $id; + + // convert back to work on an imap-id + $imapid = $this->getImapIdFromFolderId($id); + + // explode hierarchy + $fhir = explode($this->serverdelimiter, $imapid); + + // compare on lowercase strings + $lid = strtolower($imapid); +// TODO WasteID or SentID could be saved for later ussage + if($lid == "inbox") { + $folder->parentid = "0"; // Root + $folder->displayname = "Inbox"; + $folder->type = SYNC_FOLDER_TYPE_INBOX; + } + // Zarafa IMAP-Gateway outputs + else if($lid == "drafts") { + $folder->parentid = "0"; + $folder->displayname = "Drafts"; + $folder->type = SYNC_FOLDER_TYPE_DRAFTS; + } + else if($lid == "trash") { + $folder->parentid = "0"; + $folder->displayname = "Trash"; + $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET; + $this->wasteID = $id; + } + else if($lid == "sent" || $lid == "sent items" || $lid == IMAP_SENTFOLDER) { + $folder->parentid = "0"; + $folder->displayname = "Sent"; + $folder->type = SYNC_FOLDER_TYPE_SENTMAIL; + $this->sentID = $id; + } + // courier-imap outputs and cyrus-imapd outputs + else if($lid == "inbox.drafts" || $lid == "inbox/drafts") { + $folder->parentid = $this->convertImapId($fhir[0]); + $folder->displayname = "Drafts"; + $folder->type = SYNC_FOLDER_TYPE_DRAFTS; + } + else if($lid == "inbox.trash" || $lid == "inbox/trash") { + $folder->parentid = $this->convertImapId($fhir[0]); + $folder->displayname = "Trash"; + $folder->type = SYNC_FOLDER_TYPE_WASTEBASKET; + $this->wasteID = $id; + } + else if($lid == "inbox.sent" || $lid == "inbox/sent") { + $folder->parentid = $this->convertImapId($fhir[0]); + $folder->displayname = "Sent"; + $folder->type = SYNC_FOLDER_TYPE_SENTMAIL; + $this->sentID = $id; + } + + // define the rest as other-folders + else { + if (count($fhir) > 1) { + $this->getModAndParentNames($fhir, $folder->displayname, $imapparent); + $folder->parentid = $this->convertImapId($imapparent); + $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($folder->displayname)); + } + else { + $folder->displayname = Utils::Utf7_to_utf8(Utils::Utf7_iconv_decode($imapid)); + $folder->parentid = "0"; + } + $folder->type = SYNC_FOLDER_TYPE_OTHER; + } + + //advanced debugging + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetFolder('%s'): '%s'", $id, $folder)); + + return $folder; + } + + /** + * Returns folder stats. An associative array with properties is expected. + * + * @param string $id id of the folder + * + * @access public + * @return array + */ + public function StatFolder($id) { + $folder = $this->GetFolder($id); + + $stat = array(); + $stat["id"] = $id; + $stat["parent"] = $folder->parentid; + $stat["mod"] = $folder->displayname; + + return $stat; + } + + /** + * Creates or modifies a folder + * The folder type is ignored in IMAP, as all folders are Email folders + * + * @param string $folderid id of the parent folder + * @param string $oldid if empty -> new folder created, else folder is to be renamed + * @param string $displayname new folder name (to be created, or to be renamed to) + * @param int $type folder type + * + * @access public + * @return boolean status + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function ChangeFolder($folderid, $oldid, $displayname, $type){ + ZLog::Write(LOGLEVEL_INFO, sprintf("BackendIMAP->ChangeFolder('%s','%s','%s','%s')", $folderid, $oldid, $displayname, $type)); + + // go to parent mailbox + $this->imap_reopenFolder($folderid); + + // build name for new mailboxBackendMaildir + $displayname = Utils::Utf7_iconv_encode(Utils::Utf8_to_utf7($displayname)); + $newname = $this->server . $folderid . $this->serverdelimiter . $displayname; + + $csts = false; + // if $id is set => rename mailbox, otherwise create + if ($oldid) { + // rename doesn't work properly with IMAP + // the activesync client doesn't support a 'changing ID' + // TODO this would be solved by implementing hex ids (Mantis #459) + //$csts = imap_renamemailbox($this->mbox, $this->server . imap_utf7_encode(str_replace(".", $this->serverdelimiter, $oldid)), $newname); + } + else { + $csts = @imap_createmailbox($this->mbox, $newname); + } + if ($csts) { + return $this->StatFolder($folderid . $this->serverdelimiter . $displayname); + } + else + return false; + } + + /** + * Deletes a folder + * + * @param string $id + * @param string $parent is normally false + * + * @access public + * @return boolean status - false if e.g. does not exist + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function DeleteFolder($id, $parentid){ + // TODO implement + return false; + } + + /** + * Returns a list (array) of messages + * + * @param string $folderid id of the parent folder + * @param long $cutoffdate timestamp in the past from which on messages should be returned + * + * @access public + * @return array/false array with messages or false if folder is not available + */ + public function GetMessageList($folderid, $cutoffdate) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList('%s','%s')", $folderid, $cutoffdate)); + + $folderid = $this->getImapIdFromFolderId($folderid); + + if ($folderid == false) + throw new StatusException("Folderid not found in cache", SYNC_STATUS_FOLDERHIERARCHYCHANGED); + + $messages = array(); + $this->imap_reopenFolder($folderid, true); + + $sequence = "1:*"; + if ($cutoffdate > 0) { + $search = @imap_search($this->mbox, "SINCE ". date("d-M-Y", $cutoffdate)); + if ($search !== false) + $sequence = implode(",", $search); + } + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessageList(): searching with sequence '%s'", $sequence)); + $overviews = @imap_fetch_overview($this->mbox, $sequence); + + if (!$overviews || !is_array($overviews)) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->GetMessageList('%s','%s'): Failed to retrieve overview: %s",$folderid, $cutoffdate, imap_last_error())); + return $messages; + } + + foreach($overviews as $overview) { + $date = ""; + $vars = get_object_vars($overview); + if (array_key_exists( "date", $vars)) { + // message is out of range for cutoffdate, ignore it + if ($this->cleanupDate($overview->date) < $cutoffdate) continue; + $date = $overview->date; + } + + // cut of deleted messages + if (array_key_exists( "deleted", $vars) && $overview->deleted) + continue; + + if (array_key_exists( "uid", $vars)) { + $message = array(); + $message["mod"] = $date; + $message["id"] = $overview->uid; + // 'seen' aka 'read' is the only flag we want to know about + $message["flags"] = 0; + + if(array_key_exists( "seen", $vars) && $overview->seen) + $message["flags"] = 1; + + array_push($messages, $message); + } + } + return $messages; + } + + /** + * Returns the actual SyncXXX object type. + * + * @param string $folderid id of the parent folder + * @param string $id id of the message + * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) + * + * @access public + * @return object/false false if the message could not be retrieved + */ + public function GetMessage($folderid, $id, $contentparameters) { + $truncsize = Utils::GetTruncSize($contentparameters->GetTruncation()); + $mimesupport = $contentparameters->GetMimeSupport(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->GetMessage('%s','%s')", $folderid, $id)); + + $folderImapid = $this->getImapIdFromFolderId($folderid); + + // Get flags, etc + $stat = $this->StatMessage($folderid, $id); + + if ($stat) { + $this->imap_reopenFolder($folderImapid); + $mail = @imap_fetchheader($this->mbox, $id, FT_UID) . @imap_body($this->mbox, $id, FT_PEEK | FT_UID); + + $mobj = new Mail_mimeDecode($mail); + $message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'charset' => 'utf-8')); + + $output = new SyncMail(); + + $body = $this->getBody($message); + $output->bodysize = strlen($body); + + // truncate body, if requested + if(strlen($body) > $truncsize) { + $body = Utils::Utf8_truncate($body, $truncsize); + $output->bodytruncated = 1; + } else { + $body = $body; + $output->bodytruncated = 0; + } + $body = str_replace("\n","\r\n", str_replace("\r","",$body)); + + $output->body = $body; + $output->datereceived = isset($message->headers["date"]) ? $this->cleanupDate($message->headers["date"]) : null; + $output->messageclass = "IPM.Note"; + $output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : ""; + $output->read = $stat["flags"]; + $output->from = isset($message->headers["from"]) ? $message->headers["from"] : null; + + $Mail_RFC822 = new Mail_RFC822(); + $toaddr = $ccaddr = $replytoaddr = array(); + if(isset($message->headers["to"])) + $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]); + if(isset($message->headers["cc"])) + $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]); + if(isset($message->headers["reply_to"])) + $replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]); + + $output->to = array(); + $output->cc = array(); + $output->reply_to = array(); + foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) { + foreach($addrlist as $addr) { + $address = $addr->mailbox . "@" . $addr->host; + $name = $addr->personal; + + if (!isset($output->displayto) && $name != "") + $output->displayto = $name; + + if($name == "" || $name == $address) + $fulladdr = w2u($address); + else { + if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') { + $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">"; + } + else { + $fulladdr = w2u($name) ." <" . w2u($address) . ">"; + } + } + + array_push($output->$type, $fulladdr); + } + } + + // convert mime-importance to AS-importance + if (isset($message->headers["x-priority"])) { + $mimeImportance = preg_replace("/\D+/", "", $message->headers["x-priority"]); + if ($mimeImportance > 3) + $output->importance = 0; + if ($mimeImportance == 3) + $output->importance = 1; + if ($mimeImportance < 3) + $output->importance = 2; + } + + // Attachments are only searched in the top-level part + if(isset($message->parts)) { + $mparts = $message->parts; + for ($i=0; $ictype_primary == "multipart" && ($part->ctype_secondary == "mixed" || $part->ctype_secondary == "alternative" || $part->ctype_secondary == "related")) { + foreach($part->parts as $spart) + $mparts[] = $spart; + continue; + } + //add part as attachment if it's disposition indicates so or if it is not a text part + if ((isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline")) || + (isset($part->ctype_primary) && $part->ctype_primary != "text")) { + + if (!isset($output->attachments) || !is_array($output->attachments)) + $output->attachments = array(); + + $attachment = new SyncAttachment(); + + if (isset($part->body)) + $attachment->attsize = strlen($part->body); + + if(isset($part->d_parameters['filename'])) + $attname = $part->d_parameters['filename']; + else if(isset($part->ctype_parameters['name'])) + $attname = $part->ctype_parameters['name']; + else if(isset($part->headers['content-description'])) + $attname = $part->headers['content-description']; + else $attname = "unknown attachment"; + + $attachment->displayname = $attname; + $attachment->attname = $folderid . ":" . $id . ":" . $i; + $attachment->attmethod = 1; + $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : ""; + array_push($output->attachments, $attachment); + } + + } + } + // unset mimedecoder & mail + unset($mobj); + unset($mail); + return $output; + } + + return false; + } + + /** + * 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/boolean + */ + public function StatMessage($folderid, $id) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->StatMessage('%s','%s')", $folderid, $id)); + $folderImapid = $this->getImapIdFromFolderId($folderid); + + $this->imap_reopenFolder($folderImapid); + $overview = @imap_fetch_overview( $this->mbox , $id , FT_UID); + + if (!$overview) { + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->StatMessage('%s','%s'): Failed to retrieve overview: %s", $folderid, $id, imap_last_error())); + return false; + } + + // check if variables for this overview object are available + $vars = get_object_vars($overview[0]); + + // without uid it's not a valid message + if (! array_key_exists( "uid", $vars)) return false; + + $entry = array(); + $entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : ""; + $entry["id"] = $overview[0]->uid; + // 'seen' aka 'read' is the only flag we want to know about + $entry["flags"] = 0; + + if(array_key_exists( "seen", $vars) && $overview[0]->seen) + $entry["flags"] = 1; + + return $entry; + } + + /** + * Called when a message has been changed on the mobile. + * This functionality is not available for emails. + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param SyncXXX $message the SyncObject containing a message + * + * @access public + * @return array same return value as StatMessage() + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function ChangeMessage($folderid, $id, $message) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->ChangeMessage('%s','%s','%s')", $folderid, $id, get_class($message))); + // TODO recheck implementation + // TODO this could throw several StatusExceptions like e.g. SYNC_STATUS_OBJECTNOTFOUND, SYNC_STATUS_SYNCCANNOTBECOMPLETED + return false; + } + + /** + * Changes the 'read' flag of a message on disk + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param int $flags read flag of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function SetReadFlag($folderid, $id, $flags) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->SetReadFlag('%s','%s','%s')", $folderid, $id, $flags)); + $folderImapid = $this->getImapIdFromFolderId($folderid); + + $this->imap_reopenFolder($folderImapid); + + if ($flags == 0) { + // set as "Unseen" (unread) + $status = @imap_clearflag_full ( $this->mbox, $id, "\\Seen", ST_UID); + } else { + // set as "Seen" (read) + $status = @imap_setflag_full($this->mbox, $id, "\\Seen",ST_UID); + } + + return $status; + } + + /** + * Called when the user has requested to delete (really delete) a message + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function DeleteMessage($folderid, $id) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s')", $folderid, $id)); + $folderImapid = $this->getImapIdFromFolderId($folderid); + + $this->imap_reopenFolder($folderImapid); + $s1 = @imap_delete ($this->mbox, $id, FT_UID); + $s11 = @imap_setflag_full($this->mbox, $id, "\\Deleted", FT_UID); + $s2 = @imap_expunge($this->mbox); + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->DeleteMessage('%s','%s'): result: s-delete: '%s' s-expunge: '%s' setflag: '%s'", $folderid, $id, $s1, $s2, $s11)); + + return ($s1 && $s2 && $s11); + } + + /** + * Called when the user moves an item on the PDA from one folder to another + * + * @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 function MoveMessage($folderid, $id, $newfolderid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s')", $folderid, $id, $newfolderid)); + $folderImapid = $this->getImapIdFromFolderId($folderid); + $newfolderImapid = $this->getImapIdFromFolderId($newfolderid); + + + $this->imap_reopenFolder($folderImapid); + + // TODO this should throw a StatusExceptions on errors like SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST,SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID,SYNC_MOVEITEMSSTATUS_CANNOTMOVE + + // read message flags + $overview = @imap_fetch_overview ( $this->mbox , $id, FT_UID); + + if (!$overview) + throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to retrieve overview of source message: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); + else { + // get next UID for destination folder + // when moving a message we have to announce through ActiveSync the new messageID in the + // destination folder. This is a "guessing" mechanism as IMAP does not inform that value. + // when lots of simultaneous operations happen in the destination folder this could fail. + // in the worst case the moved message is displayed twice on the mobile. + $destStatus = imap_status($this->mbox, $this->server . $newfolderImapid, SA_ALL); + if (!$destStatus) + throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, unable to open destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); + + $newid = $destStatus->uidnext; + + // move message + $s1 = imap_mail_move($this->mbox, $id, $newfolderImapid, CP_UID); + if (! $s1) + throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, copy to destination folder failed: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); + + + // delete message in from-folder + $s2 = imap_expunge($this->mbox); + + // open new folder + $stat = $this->imap_reopenFolder($newfolderImapid); + if (! $s1) + throw new StatusException(sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): Error, openeing the destination folder: %s", $folderid, $id, $newfolderid, imap_last_error()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); + + + // remove all flags + $s3 = @imap_clearflag_full ($this->mbox, $newid, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID); + $newflags = ""; + if ($overview[0]->seen) $newflags .= "\\Seen"; + if ($overview[0]->flagged) $newflags .= " \\Flagged"; + if ($overview[0]->answered) $newflags .= " \\Answered"; + $s4 = @imap_setflag_full ($this->mbox, $newid, $newflags, FT_UID); + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->MoveMessage('%s','%s','%s'): result s-move: '%s' s-expunge: '%s' unset-Flags: '%s' set-Flags: '%s'", $folderid, $id, $newfolderid, Utils::PrintAsString($s1), Utils::PrintAsString($s2), Utils::PrintAsString($s3), Utils::PrintAsString($s4))); + + // return the new id "as string"" + return $newid . ""; + } + } + + + /**---------------------------------------------------------------------------------------------------------- + * protected IMAP methods + */ + + /** + * Unmasks a hex folderid and returns the imap folder id + * + * @param string $folderid hex folderid generated by convertImapId() + * + * @access protected + * @return string imap folder id + */ + protected function getImapIdFromFolderId($folderid) { + $this->InitializePermanentStorage(); + + if (isset($this->permanentStorage->fmFidFimap)) { + if (isset($this->permanentStorage->fmFidFimap[$folderid])) { + $imapId = $this->permanentStorage->fmFidFimap[$folderid]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, $imapId)); + return $imapId; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not found')); + return false; + } + } + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getImapIdFromFolderId('%s') = %s", $folderid, 'not initialized!')); + return false; + } + + /** + * Retrieves a hex folderid previousily masked imap + * + * @param string $imapid Imap folder id + * + * @access protected + * @return string hex folder id + */ + protected function getFolderIdFromImapId($imapid) { + $this->InitializePermanentStorage(); + + if (isset($this->permanentStorage->fmFimapFid)) { + if (isset($this->permanentStorage->fmFimapFid[$imapid])) { + $folderid = $this->permanentStorage->fmFimapFid[$imapid]; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, $folderid)); + return $folderid; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not found')); + return false; + } + } + ZLog::Write(LOGLEVEL_WARN, sprintf("BackendIMAP->getFolderIdFromImapId('%s') = %s", $imapid, 'not initialized!')); + return false; + } + + /** + * Masks a imap folder id into a generated hex folderid + * The method getFolderIdFromImapId() is consulted so that an + * imapid always returns the same hex folder id + * + * @param string $imapid Imap folder id + * + * @access protected + * @return string hex folder id + */ + protected function convertImapId($imapid) { + $this->InitializePermanentStorage(); + + // check if this imap id was converted before + $folderid = $this->getFolderIdFromImapId($imapid); + + // nothing found, so generate a new id and put it in the cache + if (!$folderid) { + // generate folderid and add it to the mapping + $folderid = sprintf('%04x%04x', mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )); + + // folderId to folderImap mapping + if (!isset($this->permanentStorage->fmFidFimap)) + $this->permanentStorage->fmFidFimap = array(); + + $a = $this->permanentStorage->fmFidFimap; + $a[$folderid] = $imapid; + $this->permanentStorage->fmFidFimap = $a; + + // folderImap to folderid mapping + if (!isset($this->permanentStorage->fmFimapFid)) + $this->permanentStorage->fmFimapFid = array(); + + $b = $this->permanentStorage->fmFimapFid; + $b[$imapid] = $folderid; + $this->permanentStorage->fmFimapFid = $b; + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("BackendIMAP->convertImapId('%s') = %s", $imapid, $folderid)); + + return $folderid; + } + + + /** + * Parses the message and return only the plaintext body + * + * @param string $message html message + * + * @access protected + * @return string plaintext message + */ + protected function getBody($message) { + $body = ""; + $htmlbody = ""; + + $this->getBodyRecursive($message, "plain", $body); + + if($body === "") { + $this->getBodyRecursive($message, "html", $body); + // remove css-style tags + $body = preg_replace("//is", "", $body); + // remove all other html + $body = strip_tags($body); + } + + return $body; + } + + /** + * Get all parts in the message with specified type and concatenate them together, unless the + * Content-Disposition is 'attachment', in which case the text is apparently an attachment + * + * @param string $message mimedecode message(part) + * @param string $message message subtype + * @param string &$body body reference + * + * @access protected + * @return + */ + protected function getBodyRecursive($message, $subtype, &$body) { + if(!isset($message->ctype_primary)) return; + if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body)) + $body .= $message->body; + + if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) { + foreach($message->parts as $part) { + if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment")) { + $this->getBodyRecursive($part, $subtype, $body); + } + } + } + } + + /** + * Returns the serverdelimiter for folder parsing + * + * @access protected + * @return string delimiter + */ + protected function getServerDelimiter() { + $list = @imap_getmailboxes($this->mbox, $this->server, "*"); + if (is_array($list)) { + $val = $list[0]; + + return $val->delimiter; + } + return "."; // default "." + } + + /** + * Helper to re-initialize the folder to speed things up + * Remember what folder is currently open and only change if necessary + * + * @param string $folderid id of the folder + * @param boolean $force re-open the folder even if currently opened + * + * @access protected + * @return + */ + protected function imap_reopenFolder($folderid, $force = false) { + // to see changes, the folder has to be reopened! + if ($this->mboxFolder != $folderid || $force) { + $s = @imap_reopen($this->mbox, $this->server . $folderid); + // TODO throw status exception + if (!$s) { + ZLog::Write(LOGLEVEL_WARN, "BackendIMAP->imap_reopenFolder('%s'): failed to change folder: ",$folderid, implode(", ", imap_errors())); + return false; + } + $this->mboxFolder = $folderid; + } + } + + + /** + * Build a multipart RFC822, embedding body and one file (for attachments) + * + * @param string $filenm name of the file to be attached + * @param long $filesize size of the file to be attached + * @param string $file_cont content of the file + * @param string $body current body + * @param string $body_ct content-type + * @param string $body_cte content-transfer-encoding + * @param string $boundary optional existing boundary + * + * @access protected + * @return array with [0] => $mail_header and [1] => $mail_body + */ + protected function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte, $boundary = false) { + if (!$boundary) $boundary = strtoupper(md5(uniqid(time()))); + + //remove the ending boundary because we will add it at the end + $body = str_replace("--$boundary--", "", $body); + + $mail_header = "Content-Type: multipart/mixed; boundary=$boundary\n"; + + // build main body with the sumitted type & encoding from the pda + $mail_body = $this->enc_multipart($boundary, $body, $body_ct, $body_cte); + $mail_body .= $this->enc_attach_file($boundary, $filenm, $filesize, $file_cont); + + $mail_body .= "--$boundary--\n\n"; + return array($mail_header, $mail_body); + } + + /** + * Helper for mail_attach() + * + * @param string $boundary boundary + * @param string $body current body + * @param string $body_ct content-type + * @param string $body_cte content-transfer-encoding + * + * @access protected + * @return string message body + */ + protected function enc_multipart($boundary, $body, $body_ct, $body_cte) { + $mail_body = "This is a multi-part message in MIME format\n\n"; + $mail_body .= "--$boundary\n"; + $mail_body .= "Content-Type: $body_ct\n"; + $mail_body .= "Content-Transfer-Encoding: $body_cte\n\n"; + $mail_body .= "$body\n\n"; + + return $mail_body; + } + + /** + * Helper for mail_attach() + * + * @param string $boundary boundary + * @param string $filenm name of the file to be attached + * @param long $filesize size of the file to be attached + * @param string $file_cont content of the file + * @param string $content_type optional content-type + * + * @access protected + * @return string message body + */ + protected function enc_attach_file($boundary, $filenm, $filesize, $file_cont, $content_type = "") { + if (!$content_type) $content_type = "text/plain"; + $mail_body = "--$boundary\n"; + $mail_body .= "Content-Type: $content_type; name=\"$filenm\"\n"; + $mail_body .= "Content-Transfer-Encoding: base64\n"; + $mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n"; + $mail_body .= "Content-Description: $filenm\n\n"; + //contrib - chunk base64 encoded attachments + $mail_body .= chunk_split(base64_encode($file_cont)) . "\n\n"; + + return $mail_body; + } + + /** + * Adds a message with seen flag to a specified folder (used for saving sent items) + * + * @param string $folderid id of the folder + * @param string $header header of the message + * @param long $body body of the message + * + * @access protected + * @return boolean status + */ + protected function addSentMessage($folderid, $header, $body) { + $header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body)); + + return @imap_append($this->mbox, $this->server . $folderid, $header_body, "\\Seen"); + } + + /** + * Parses an mimedecode address array back to a simple "," separated string + * + * @param array $ad addresses array + * + * @access protected + * @return string mail address(es) string + */ + protected function parseAddr($ad) { + $addr_string = ""; + if (isset($ad) && is_array($ad)) { + foreach($ad as $addr) { + if ($addr_string) $addr_string .= ","; + $addr_string .= $addr->mailbox . "@" . $addr->host; + } + } + return $addr_string; + } + + /** + * Recursive way to get mod and parent - repeat until only one part is left + * or the folder is identified as an IMAP folder + * + * @param string $fhir folder hierarchy string + * @param string &$displayname reference of the displayname + * @param long &$parent reference of the parent folder + * + * @access protected + * @return + */ + protected function getModAndParentNames($fhir, &$displayname, &$parent) { + // if mod is already set add the previous part to it as it might be a folder which has + // delimiter in its name + $displayname = (isset($displayname) && strlen($displayname) > 0) ? $displayname = array_pop($fhir).$this->serverdelimiter.$displayname : array_pop($fhir); + $parent = implode($this->serverdelimiter, $fhir); + + if (count($fhir) == 1 || $this->checkIfIMAPFolder($parent)) { + return; + } + //recursion magic + $this->getModAndParentNames($fhir, $displayname, $parent); + } + + /** + * Checks if a specified name is a folder in the IMAP store + * + * @param string $foldername a foldername + * + * @access protected + * @return boolean + */ + protected function checkIfIMAPFolder($folderName) { + $parent = imap_list($this->mbox, $this->server, $folderName); + if ($parent === false) return false; + return true; + } + + /** + * Removes parenthesis (comments) from the date string because + * strtotime returns false if received date has them + * + * @param string $receiveddate a date as a string + * + * @access protected + * @return string + */ + protected function cleanupDate($receiveddate) { + $receiveddate = strtotime(preg_replace("/\(.*\)/", "", $receiveddate)); + if ($receiveddate == false || $receiveddate == -1) { + debugLog("Received date is false. Message might be broken."); + return null; + } + + return $receiveddate; + } + +} + +?> \ No newline at end of file diff --git a/z-push/backend/maildir.php b/z-push/backend/maildir.php new file mode 100644 index 0000000..d003a43 --- /dev/null +++ b/z-push/backend/maildir.php @@ -0,0 +1,699 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +include_once('lib/default/diffbackend/diffbackend.php'); +include_once('include/mimeDecode.php'); +require_once('include/z_RFC822.php'); + +class BackendMaildir extends BackendDiff { + /**---------------------------------------------------------------------------------------------------------- + * default backend methods + */ + + /** + * Authenticates the user - NOT EFFECTIVELY IMPLEMENTED + * Normally some kind of password check would be done here. + * Alternatively, the password could be ignored and an Apache + * authentication via mod_auth_* could be done + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + */ + public function Logon($username, $domain, $password) { + return true; + } + + /** + * Logs off + * + * @access public + * @return boolean + */ + public function Logoff() { + return true; + } + + /** + * Sends an e-mail + * Not implemented here + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + public function SendMail($sm) { + return false; + } + + /** + * Returns the waste basket + * + * @access public + * @return string + */ + public function GetWasteBasket() { + return false; + } + + /** + * Returns the content of the named attachment as stream. The passed attachment identifier is + * the exact string that is returned in the 'AttName' property of an SyncAttachment. + * Any information necessary to find the attachment must be encoded in that 'attname' property. + * Data is written directly (with print $data;) + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + list($id, $part) = explode(":", $attname); + + $fn = $this->findMessage($id); + if ($fn == false) + throw new StatusException(sprintf("BackendMaildir->GetAttachmentData('%s'): Error, requested message/attachment can not be found", $attname), SYNC_ITEMOPERATIONSSTATUS_INVALIDATT); + + // Parse e-mail + $rfc822 = file_get_contents($this->getPath() . "/$fn"); + + $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8')); + + include_once('include/stringstreamwrapper.php'); + $attachment = new SyncItemOperationsAttachment(); + $attachment->data = StringStreamWrapper::Open($message->parts[$part]->body); + if (isset($message->parts[$part]->ctype_primary) && isset($message->parts[$part]->ctype_secondary)) + $attachment->contenttype = $message->parts[$part]->ctype_primary .'/'.$message->parts[$part]->ctype_secondary; + + return $attachment; + } + + /**---------------------------------------------------------------------------------------------------------- + * implemented DiffBackend methods + */ + + + /** + * Returns a list (array) of folders. + * In simple implementations like this one, probably just one folder is returned. + * + * @access public + * @return array + */ + public function GetFolderList() { + $folders = array(); + + $inbox = array(); + $inbox["id"] = "root"; + $inbox["parent"] = "0"; + $inbox["mod"] = "Inbox"; + + $folders[]=$inbox; + + $sub = array(); + $sub["id"] = "sub"; + $sub["parent"] = "root"; + $sub["mod"] = "Sub"; + +// $folders[]=$sub; + + return $folders; + } + + /** + * Returns an actual SyncFolder object + * + * @param string $id id of the folder + * + * @access public + * @return object SyncFolder with information + */ + public function GetFolder($id) { + if($id == "root") { + $inbox = new SyncFolder(); + + $inbox->serverid = $id; + $inbox->parentid = "0"; // Root + $inbox->displayname = "Inbox"; + $inbox->type = SYNC_FOLDER_TYPE_INBOX; + + return $inbox; + } else if($id == "sub") { + $inbox = new SyncFolder(); + $inbox->serverid = $id; + $inbox->parentid = "root"; + $inbox->displayname = "Sub"; + $inbox->type = SYNC_FOLDER_TYPE_OTHER; + + return $inbox; + } else { + return false; + } + } + + + /** + * Returns folder stats. An associative array with properties is expected. + * + * @param string $id id of the folder + * + * @access public + * @return array + */ + public function StatFolder($id) { + $folder = $this->GetFolder($id); + + $stat = array(); + $stat["id"] = $id; + $stat["parent"] = $folder->parentid; + $stat["mod"] = $folder->displayname; + + return $stat; + } + + + /** + * Creates or modifies a folder + * not implemented + * + * @param string $folderid id of the parent folder + * @param string $oldid if empty -> new folder created, else folder is to be renamed + * @param string $displayname new folder name (to be created, or to be renamed to) + * @param int $type folder type + * + * @access public + * @return boolean status + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function ChangeFolder($folderid, $oldid, $displayname, $type){ + return false; + } + + /** + * Deletes a folder + * + * @param string $id + * @param string $parent is normally false + * + * @access public + * @return boolean status - false if e.g. does not exist + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function DeleteFolder($id, $parentid){ + return false; + } + + /** + * Returns a list (array) of messages + * + * @param string $folderid id of the parent folder + * @param long $cutoffdate timestamp in the past from which on messages should be returned + * + * @access public + * @return array/false array with messages or false if folder is not available + */ + public function GetMessageList($folderid, $cutoffdate) { + $this->moveNewToCur(); + + if($folderid != "root") + return false; + + // return stats of all messages in a dir. We can do this faster than + // just calling statMessage() on each message; We still need fstat() + // information though, so listing 10000 messages is going to be + // rather slow (depending on filesystem, etc) + + // we also have to filter by the specified cutoffdate so only the + // last X days are retrieved. Normally, this would mean that we'd + // have to open each message, get the Received: header, and check + // whether that is in the filter range. Because this is much too slow, we + // are depending on the creation date of the message instead, which should + // normally be just about the same, unless you just did some kind of import. + + $messages = array(); + $dirname = $this->getPath(); + + $dir = opendir($dirname); + + if(!$dir) + return false; + + while($entry = readdir($dir)) { + if($entry{0} == ".") + continue; + + $message = array(); + + $stat = stat("$dirname/$entry"); + + if($stat["mtime"] < $cutoffdate) { + // message is out of range for curoffdate, ignore it + continue; + } + + $message["mod"] = $stat["mtime"]; + + $matches = array(); + + // Flags according to http://cr.yp.to/proto/maildir.html (pretty authoritative - qmail author's website) + if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$entry,$matches)) + continue; + $message["id"] = $matches[1]; + $message["flags"] = 0; + + if(strpos($matches[2],"S") !== false) { + $message["flags"] |= 1; // 'seen' aka 'read' is the only flag we want to know about + } + + array_push($messages, $message); + } + + return $messages; + } + + /** + * Returns the actual SyncXXX object type. + * + * @param string $folderid id of the parent folder + * @param string $id id of the message + * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) + * + * @access public + * @return object/false false if the message could not be retrieved + */ + public function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) { + if($folderid != 'root') + return false; + + $fn = $this->findMessage($id); + + // Get flags, etc + $stat = $this->StatMessage($folderid, $id); + + // Parse e-mail + $rfc822 = file_get_contents($this->getPath() . "/" . $fn); + + $message = Mail_mimeDecode::decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8')); + + $output = new SyncMail(); + + $output->body = str_replace("\n", "\r\n", $this->getBody($message)); + $output->bodysize = strlen($output->body); + $output->bodytruncated = 0; // We don't implement truncation in this backend + $output->datereceived = $this->parseReceivedDate($message->headers["received"][0]); + $output->messageclass = "IPM.Note"; + $output->subject = $message->headers["subject"]; + $output->read = $stat["flags"]; + $output->from = $message->headers["from"]; + + $Mail_RFC822 = new Mail_RFC822(); + $toaddr = $ccaddr = $replytoaddr = array(); + if(isset($message->headers["to"])) + $toaddr = $Mail_RFC822->parseAddressList($message->headers["to"]); + if(isset($message->headers["cc"])) + $ccaddr = $Mail_RFC822->parseAddressList($message->headers["cc"]); + if(isset($message->headers["reply_to"])) + $replytoaddr = $Mail_RFC822->parseAddressList($message->headers["reply_to"]); + + $output->to = array(); + $output->cc = array(); + $output->reply_to = array(); + foreach(array("to" => $toaddr, "cc" => $ccaddr, "reply_to" => $replytoaddr) as $type => $addrlist) { + foreach($addrlist as $addr) { + $address = $addr->mailbox . "@" . $addr->host; + $name = $addr->personal; + + if (!isset($output->displayto) && $name != "") + $output->displayto = $name; + + if($name == "" || $name == $address) + $fulladdr = w2u($address); + else { + if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') { + $fulladdr = "\"" . w2u($name) ."\" <" . w2u($address) . ">"; + } + else { + $fulladdr = w2u($name) ." <" . w2u($address) . ">"; + } + } + + array_push($output->$type, $fulladdr); + } + } + + // convert mime-importance to AS-importance + if (isset($message->headers["x-priority"])) { + $mimeImportance = preg_replace("/\D+/", "", $message->headers["x-priority"]); + if ($mimeImportance > 3) + $output->importance = 0; + if ($mimeImportance == 3) + $output->importance = 1; + if ($mimeImportance < 3) + $output->importance = 2; + } + + // Attachments are only searched in the top-level part + $n = 0; + if(isset($message->parts)) { + foreach($message->parts as $part) { + if($part->ctype_primary == "application") { + $attachment = new SyncAttachment(); + $attachment->attsize = strlen($part->body); + + if(isset($part->d_parameters['filename'])) + $attname = $part->d_parameters['filename']; + else if(isset($part->ctype_parameters['name'])) + $attname = $part->ctype_parameters['name']; + else if(isset($part->headers['content-description'])) + $attname = $part->headers['content-description']; + else $attname = "unknown attachment"; + + $attachment->displayname = $attname; + $attachment->attname = $id . ":" . $n; + $attachment->attmethod = 1; + $attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : ""; + + array_push($output->attachments, $attachment); + } + $n++; + } + } + + return $output; + } + + /** + * Returns message stats, analogous to the folder stats from StatFolder(). + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return array + */ + public function StatMessage($folderid, $id) { + $dirname = $this->getPath(); + $fn = $this->findMessage($id); + if(!$fn) + return false; + + $stat = stat("$dirname/$fn"); + + $entry = array(); + $entry["id"] = $id; + $entry["flags"] = 0; + + if(strpos($fn,"S")) + $entry["flags"] |= 1; + $entry["mod"] = $stat["mtime"]; + + return $entry; + } + + /** + * Called when a message has been changed on the mobile. + * This functionality is not available for emails. + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param SyncXXX $message the SyncObject containing a message + * + * @access public + * @return array same return value as StatMessage() + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function ChangeMessage($folderid, $id, $message) { + return false; + } + + /** + * Changes the 'read' flag of a message on disk + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param int $flags read flag of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function SetReadFlag($folderid, $id, $flags) { + if($folderid != 'root') + return false; + + $fn = $this->findMessage($id); + + if(!$fn) + return true; // message may have been deleted + + if(!preg_match("/([^:]+):2,([PRSTDF]*)/",$fn,$matches)) + return false; + + // remove 'seen' (S) flag + if(!$flags) { + $newflags = str_replace("S","",$matches[2]); + } else { + // make sure we don't double add the 'S' flag + $newflags = str_replace("S","",$matches[2]) . "S"; + } + + $newfn = $matches[1] . ":2," . $newflags; + // rename if required + if($fn != $newfn) + rename($this->getPath() ."/$fn", $this->getPath() . "/$newfn"); + + return true; + } + + /** + * Called when the user has requested to delete (really delete) a message + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function DeleteMessage($folderid, $id) { + if($folderid != 'root') + return false; + + $fn = $this->findMessage($id); + + if(!$fn) + return true; // success because message has been deleted already + + if(!unlink($this->getPath() . "/$fn")) { + return true; // success - message may have been deleted in the mean time (since findMessage) + } + + return true; + } + + /** + * Called when the user moves an item on the PDA from one folder to another + * not implemented + * + * @param string $folderid id of the source folder + * @param string $id id of the message + * @param string $newfolderid id of the destination folder + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions + */ + public function MoveMessage($folderid, $id, $newfolderid) { + return false; + } + + + /**---------------------------------------------------------------------------------------------------------- + * private maildir-specific internals + */ + + /** + * Searches for the message + * + * @param string $id id of the message + * + * @access private + * @return string + */ + private function findMessage($id) { + // We could use 'this->folderid' for path info but we currently + // only support a single INBOX. We also have to use a glob '*' + // because we don't know the flags of the message we're looking for. + + $dirname = $this->getPath(); + $dir = opendir($dirname); + + while($entry = readdir($dir)) { + if(strpos($entry,$id) === 0) + return $entry; + } + return false; // not found + } + + /** + * Parses the message and return only the plaintext body + * + * @param string $message html message + * + * @access private + * @return string plaintext message + */ + private function getBody($message) { + $body = ""; + $htmlbody = ""; + + $this->getBodyRecursive($message, "plain", $body); + + if(!isset($body) || $body === "") { + $this->getBodyRecursive($message, "html", $body); + // remove css-style tags + $body = preg_replace("//is", "", $body); + // remove all other html + $body = strip_tags($body); + } + + return $body; + } + + /** + * Get all parts in the message with specified type and concatenate them together, unless the + * Content-Disposition is 'attachment', in which case the text is apparently an attachment + * + * @param string $message mimedecode message(part) + * @param string $message message subtype + * @param string &$body body reference + * + * @access private + * @return + */ + private function getBodyRecursive($message, $subtype, &$body) { + if(!isset($message->ctype_primary)) return; + if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body)) + $body .= $message->body; + + if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) { + foreach($message->parts as $part) { + if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment")) { + $this->getBodyRecursive($part, $subtype, $body); + } + } + } + } + + /** + * Parses the received date + * + * @param string $received received date string + * + * @access private + * @return long + */ + private function parseReceivedDate($received) { + $pos = strpos($received, ";"); + if(!$pos) + return false; + + $datestr = substr($received, $pos+1); + $datestr = ltrim($datestr); + + return strtotime($datestr); + } + + /** + * Moves everything in Maildir/new/* to Maildir/cur/ + * + * @access private + * @return + */ + private function moveNewToCur() { + $newdirname = MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/new"; + + $newdir = opendir($newdirname); + + while($newentry = readdir($newdir)) { + if($newentry{0} == ".") + continue; + + // link/unlink == move. This is the way to move the message according to cr.yp.to + link($newdirname . "/" . $newentry, $this->getPath() . "/" . $newentry . ":2,"); + unlink($newdirname . "/" . $newentry); + } + } + + /** + * The path we're working on + * + * @access private + * @return string + */ + private function getPath() { + return MAILDIR_BASE . "/" . $this->store . "/" . MAILDIR_SUBDIR . "/cur"; + } +} + +?> \ No newline at end of file diff --git a/z-push/backend/phpaddressbook/address.class.php b/z-push/backend/phpaddressbook/address.class.php new file mode 100644 index 0000000..1060945 --- /dev/null +++ b/z-push/backend/phpaddressbook/address.class.php @@ -0,0 +1,369 @@ + $val) { + $res[$key] = trim($val); + } + return $res; +} + +function echoIfSet($addr_array, $key) { + echo getIfSetFromAddr($addr_array, $key); +} + + +function deleteAddresses($part_sql) { + + global $keep_history, $domain_id, $base_from_where, $table, $table_grp_adr, $table_groups; + + $sql = "SELECT * FROM $base_from_where AND ".$part_sql; + $result = mysqli_query($db,$sql); + $resultsnumber = mysqli_num_rows($result); + + $is_valid = $resultsnumber > 0; + + if($is_valid) { + if($keep_history) { + $sql = "UPDATE $table + SET deprecated = now() + WHERE deprecated is null AND ".$part_sql." AND domain_id = ".$domain_id; + mysqli_query($db,$sql); + $sql = "UPDATE $table_grp_adr + SET deprecated = now() + WHERE deprecated is null AND ".$part_sql." AND domain_id = ".$domain_id; + mysqli_query($db,$sql); + } else { + $sql = "DELETE FROM $table_grp_adr WHERE ".$part_sql." AND domain_id = ".$domain_id; + mysqli_query($db,$sql); + $sql = "DELETE FROM $table WHERE ".$part_sql." AND domain_id = ".$domain_id; + mysqli_query($db,$sql); + } + } + + return $is_valid; +} + +function saveAddress($addr_array, $group_name = "") { + + global $domain_id, $table, $table_grp_adr, $table_groups, $month_lookup, $base_from_where; + + if(isset($addr_array['id'])) { + $set_id = "'".$addr_array['id']."'"; + $src_tbl = $month_lookup." WHERE bmonth_num = 1"; + } else { + $set_id = "ifnull(max(id),0)+1"; // '0' is a bad ID + $src_tbl = $table; + } + + $sql = "INSERT INTO $table ( domain_id, id, firstname, lastname, nickname, company, title, address, home, mobile, work, fax, email, email2, email3, homepage, aday, amonth, ayear, bday, bmonth, byear, address2, phone2, photo, notes, created, modified) + SELECT $domain_id domain_id + , ".$set_id." id + , '".getIfSetFromAddr($addr_array, 'firstname')."' firstname + , '".getIfSetFromAddr($addr_array, 'lastname')."' lastname + , '".getIfSetFromAddr($addr_array, 'nickname')."' nickname + , '".getIfSetFromAddr($addr_array, 'company')."' company + , '".getIfSetFromAddr($addr_array, 'title')."' title + , '".getIfSetFromAddr($addr_array, 'address')."' address + , '".getIfSetFromAddr($addr_array, 'home')."' home + , '".getIfSetFromAddr($addr_array, 'mobile')."' mobile + , '".getIfSetFromAddr($addr_array, 'work')."' work + , '".getIfSetFromAddr($addr_array, 'fax')."' fax + , '".getIfSetFromAddr($addr_array, 'email')."' email + , '".getIfSetFromAddr($addr_array, 'email2')."' email2 + , '".getIfSetFromAddr($addr_array, 'email3')."' email3 + , '".getIfSetFromAddr($addr_array, 'homepage')."' homepage + , '".getIfSetFromAddr($addr_array, 'aday')."' aday + , '".getIfSetFromAddr($addr_array, 'amonth')."' amonth + , '".getIfSetFromAddr($addr_array, 'ayear')."' ayear + , '".getIfSetFromAddr($addr_array, 'bday')."' bday + , '".getIfSetFromAddr($addr_array, 'bmonth')."' bmonth + , '".getIfSetFromAddr($addr_array, 'byear')."' byear + , '".getIfSetFromAddr($addr_array, 'address2')."' address2 + , '".getIfSetFromAddr($addr_array, 'phone2')."' phone2 + , '".getIfSetFromAddr($addr_array, 'photo')."' photo + , '".getIfSetFromAddr($addr_array, 'notes')."' notes + , now(), now() + FROM ".$src_tbl; + $result = mysqli_query($db,$sql); + + $sql = "SELECT max(id) max_id from $table"; + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $id = $rec['max_id']; + + if(!isset($addr_array['id']) && $group_name) { + $sql = "INSERT INTO $table_grp_adr SELECT $domain_id domain_id, $id id, group_id, now(), now(), NULL FROM $table_groups WHERE group_name = '$group_name'"; + $result = mysqli_query($db,$sql); + } + + return $id; +} + +function updateAddress($addr, $keep_photo = true) { + + global $keep_history, $domain_id, $base_from_where, $table, $table_grp_adr, $table_groups; + + $addresses = new Addresses($addr['id']); + $resultsnumber = $addresses->count(); + + $homepage = str_replace('http://', '', $addr['homepage']); + + $is_valid = $resultsnumber > 0; + + if($is_valid) + { + if($keep_history) { + + // Get current photo, if "$keep_photo" + if($keep_photo) { + $r = $addresses->nextAddress()->getData(); + $addr['photo'] = $r['photo']; + } + + $sql = "UPDATE $table + SET deprecated = now() + WHERE deprecated is null + AND id = '".$addr['id']."' + AND domain_id = '".$domain_id."';"; + $result = mysqli_query($db,$sql); + + saveAddress($addr); + } else { + $sql = "UPDATE $table SET firstname = '".$addr['firstname']."' + , lastname = '".$addr['lastname']."' + , nickname = '".$addr['nickname']."' + , company = '".$addr['company']."' + , title = '".$addr['title']."' + , address = '".$addr['address']."' + , home = '".$addr['home']."' + , mobile = '".$addr['mobile']."' + , work = '".$addr['work']."' + , fax = '".$addr['fax']."' + , email = '".$addr['email']."' + , email2 = '".$addr['email2']."' + , email3 = '".$addr['email3']."' + , homepage = '".$addr['homepage']."' + , aday = '".$addr['aday']."' + , amonth = '".$addr['amonth']."' + , ayear = '".$addr['ayear']."' + , bday = '".$addr['bday']."' + , bmonth = '".$addr['bmonth']."' + , byear = '".$addr['byear']."' + , address2 = '".$addr['address2']."' + , phone2 = '".$addr['phone2']."' + , notes = '".$addr['notes']."' + ".($keep_photo ? "" : ", photo = '".$addr['photo']."'")." + , modified = now() + WHERE id = '".$addr['id']."' + AND domain_id = '$domain_id';"; + $result = mysqli_query($db,$sql); + } + // header("Location: view?id=$id"); + } + + return $is_valid; +} + +$phone_delims = array("'", '/', "-", " ", "(", ")", "."); + +class Address { + + private $address; // mother of all data + + private $phones; + private $emails; + + function __construct($data) { + $this->address = $data; + $this->phones = $this->getPhones(); + $this->emails = $this->getEMails(); + } + + public function getData() { + return $this->address; + } + + public function getEMails() { + + $result = array(); + if($this->address["email"] != "") $result[] = $this->address["email"]; + if($this->address["email2"] != "") $result[] = $this->address["email2"]; + if($this->address["email3"] != "") $result[] = $this->address["email3"]; + return $result; + } + + public function firstEMail() { + return (!empty($this->emails) ? $this->emails[0] : ""); + } + + // + // Phone order home->mobile->work->phone2 + // + public function getPhones() { + + $phones = array(); + if($this->address["home"] != "") $phones[] = $this->address["home"]; + if($this->address["mobile"] != "") $phones[] = $this->address["mobile"]; + if($this->address["work"] != "") $phones[] = $this->address["work"]; + if($this->address["phone2"] != "") $phones[] = $this->address["phone2"]; + return $phones; + } + + public function hasPhone() { + + return !empty($this->phones); + } + + public function firstPhone() { + return (!empty($this->phones) ? $this->phones[0] : ""); + } + + // + // Create a unified format for comparision an display. + // + public function unifyPhone( $prefix = "" + , $remove_prefix = false ) { + + global $intl_prefix_reg, $default_provider, $phone_delims; + + // Remove all optical delimiters + $phone = $this->firstPhone(); + foreach($phone_delims as $phone_delim) { + $phone = str_replace($phone_delim, "", $phone); + } + + if($prefix != "" || $remove_prefix = true) { + + // Replace 00xxx => +xx + $phone = preg_replace('/^00/', "+", $phone); + + // Replace 0 with $prefix (00 is already "+") + if($prefix != "") { + $phone = preg_replace('/^0/', $prefix, $phone); + } + + // Replace xx (0) yy => xxyy + $phone = preg_replace("/^(".$intl_prefix_reg.")0/", '${1}', $phone); + + // Replace +xx with 0 + if($remove_prefix) { + $phone = preg_replace("/^(".$intl_prefix_reg.")/", "0", $phone); + } + } + + return $phone; + + } + + // + // Show the phone number in the shortes readable format. + // + public function shortPhone() { + return $this->unifyPhone(); + } + +} + +class Addresses { + + private $result; + + function likePhone($row, $searchword) { + + global $phone_delims; + + $replace = $row; + $like = "'$searchword'"; + foreach($phone_delims as $phone_delim) { + $replace = "replace(".$replace.", '".mysqli_real_escape_string($phone_delim)."','')"; + $like = "replace(".$like. ", '".mysqli_real_escape_string($phone_delim)."','')"; + } + return $replace." LIKE CONCAT('%',".$like.",'%')"; + } + + function __construct($searchstring, $alphabet = "") { + + global $base_from_where, $table; + + $sql = "SELECT DISTINCT $table.* FROM $base_from_where"; + + if(preg_match("/^([0-9])+$/",$searchstring)) { + + $sql .= " AND $table.id='$searchstring'"; + + } elseif ($searchstring) { + + $searchwords = explode(" ", $searchstring); + + foreach($searchwords as $searchword) { + $sql .= "AND ( lastname LIKE '%$searchword%' + OR firstname LIKE '%$searchword%' + OR nickname LIKE '%$searchword%' + OR company LIKE '%$searchword%' + OR address LIKE '%$searchword%' + OR ".$this->likePhone('home', $searchword)." + OR ".$this->likePhone('work', $searchword)." + OR ".$this->likePhone('mobile', $searchword)." + OR ".$this->likePhone('fax', $searchword)." + OR email LIKE '%$searchword%' + OR email2 LIKE '%$searchword%' + OR email3 LIKE '%$searchword%' + OR address2 LIKE '%$searchword%' + OR notes LIKE '%$searchword%' + )"; + } + } + if($alphabet) { + $sql .= "AND ( lastname LIKE '$alphabet%' + OR nickname LIKE '$alphabet%' + OR firstname LIKE '$alphabet%' + )"; + } + + if(true) { + $sql .= "ORDER BY lastname, firstname ASC"; + } else { + $sql .= "ORDER BY firstname, lastname ASC"; + } + + // Paging + // $page = 1; + // $pagesize = 2200; + // if($pagesize > 0) { + // $sql .= " LIMIT ".($page-1)*$pagesize.",".$pagesize; + // } + // + $this->result = mysqli_query($db,$sql); + } + + public function nextAddress() { + + $myrow = mysqli_fetch_array($this->result); + if($myrow) { + return new Address(trimAll($myrow)); + } else { + return false; + } + } + + public function getResults() { + return $this->result; + } + + public function count() { + return mysqli_num_rows($this->getResults()); + } +} +?> \ No newline at end of file diff --git a/z-push/backend/phpaddressbook/birthday.class.php b/z-push/backend/phpaddressbook/birthday.class.php new file mode 100644 index 0000000..b6f3e13 --- /dev/null +++ b/z-push/backend/phpaddressbook/birthday.class.php @@ -0,0 +1,185 @@ +day = -1; + $this->month = -1; + $this->year = -1; + + $this->today = time(); + + // + // Three different constructors + // + $num = func_num_args(); + $args = func_get_args(); + switch($num){ + case 0: + break; + case 1: + $this->setDate(func_get_arg(0)); + break; + case 2: + $arg0 = func_get_arg(0); + $arg1 = func_get_arg(1); + $this->setDate($arg0, $arg1); + break; + case 3: + $arg0 = func_get_arg(0); + $arg1 = func_get_arg(1); + $arg2 = func_get_arg(2); + $this->setDate($arg0, $arg1, $arg2); + break; + default: + throw new Exception(); + } + } + + function toDate() { + return mktime(0, 0, 0, $this->month, $this->day, $this->year); + } + + + public static function tryDelimDate($str, $delim) { + + preg_match('/([0-9]{4})'.$delim.'([0-9]{1,2})'.$delim.'([0-9]{1,2})/', $str, $matches); + + return $matches; + } + + public static function isEmptyVal($val) { + + return in_array($val, array(-1, 0, "", "-")); + + } + + function setDay($day) { + + $val = intval($day); + if(1 <= $val && $val <= 31) { + $this->day = $val; + return true; + } else { + $this->day = -1; + return self::isEmptyVal($day); + } + } + + function setMonth($month) { + + $val = intval($month); + if(1 <= $val && $val <= 12) { + $this->month = $val; + return true; + } elseif(in_array($month, $this->name_of_months)) { + $this->month = array_search($month, $this->name_of_months)+1; + } else { + $this->month = -1; + return self::isEmptyVal($month); + } + } + + function setYear($year) { + + $val = intval($year); + if(1800 <= $val && $val <= 2200) { + $this->year = $val; + return true; + } else { + $this->year = -1; + return self::isEmptyVal($year); + } + } + + function setDate($val0, $val1 = "", $val2 = "") { + + if($val0 != "" && $val1 != "" && $val2 != "") { + $this->setDay ($val0); + $this->setMonth($val1); + $this->setYear ($val2); + } + + if(is_array($val0)) { + $this->prefix = $val1; + $this->setDay( $val0[$this->prefix.'day']); + $this->setMonth( $val0[$this->prefix.'month']); + $this->setYear( $val0[$this->prefix.'year']); + + return true; + } + + if( is_int($val0) && $val0 > 0 + && $val1 == "" && $val2 == "") { + + $this->setDay(date("d", $val0)); + $this->setMonth(date("m", $val0)); + $this->setYear(date("Y", $val0)); + + return true; + } + // + // vCard: xxxx-yy-zz + // + $parse_date = self::tryDelimDate($val0, "-"); + if(count($parse_date) == 4) { + $this->setDay ($parse_date[3]); + $this->setMonth($parse_date[2]); + $this->setYear ($parse_date[1]); + + return true; + } + } + + function setPrefix($prefix) { + + $this->prefix = $prefix; + return $this; + } + + function addToAddr($addr) { + + if($this->day != -1) { + $addr[$this->prefix."day"] = $this->day; + } + + if($this->month != -1) { + $addr[$this->prefix."month"] = $this->name_of_months[$this->month - 1]; + } + + if($this->year != -1) { + $addr[$this->prefix."year"] = $this->year; + } + + return $addr; + + } + + function getAge() { + + $age = date("Y", $this->today) - $this->year; + if( (date("m", $this->today) < $this->month) + ||((date("m", $this->today) == $this->month) && (date("d", $this->today) < $this->day))) + { + $age--; + } + + return ($age < 150 ? $age : -1); + } + +} +?> \ No newline at end of file diff --git a/z-push/backend/phpaddressbook/login.inc.php b/z-push/backend/phpaddressbook/login.inc.php new file mode 100644 index 0000000..687c590 --- /dev/null +++ b/z-push/backend/phpaddressbook/login.inc.php @@ -0,0 +1,437 @@ +hasRoles(array($role)); +} + + +// +//---------------- Implementations --------------- +// +class AuthUserConfig implements AuthUser { + + private $name; + private $config; + + function __construct($username, $config) { + $this->name = $username; + $this->config = $config; + } + + function getConfig() { + return $this->config; + } + + function hasRole($rolename) { + + $config = $this->config; + + if( isset($this->config['role']) + && $rolename == $this->config['role']) { + return true; + } + if( isset($this->config['roles']) + && in_array($rolename, $this->config['roles'])) { + return true; + } + return false; + } + + function getDomain() { + if(isset($this->config['domain'])) { + return $this->config['domain']; + } else { + return 0; // the default domain + } + } + function getName() { + return $this->name; + } + + function getGroup() { + if(isset($this->config['group'])) { + return $this->config['group']; + } else { + return ""; // no group + } + } +} + +// +// Login implementations +// +class AuthLoginFactory { + + + static function getBestLogin($required_roles = array()) { + + global $iplist, $blacklist, $userlist, $db, $usertable; + + if(isset($iplist)) { + if(isset($blacklist)) { + $login = new AuthLoginIP($iplist, $blacklist); + } else { + $login = new AuthLoginIP($iplist); + } + } + if((!isset($login) || !$login->hasRoles()) && isset($userlist)) { + $login = new AuthLoginUserList($userlist); + } + if((!isset($login) || !$login->hasRoles()) && isset($usertable)) { + $login = new AuthLoginDb($db, $usertable); + } + if(!isset($iplist) && !isset($userlist)) { + $login = new AuthLoginAlways(); + } + + return $login; + } +} + +abstract class AuthLoginImpl implements AuthLogin { + + protected $user_id; + + function __construct() { + $this->user_id = -1; + } + + public function hasValidUserPass() { + return $this->user_id != -1; + } +} + +class AuthLoginAlways extends AuthLoginImpl { + + function __construct() { + parent::__construct(); + } + + function hasValidUserPass() { + return true; + } + + public function hasRoles($roles = array()) { + return (count($roles) == 0); + } + + public function getUser() { + return new AuthUserConfig("", array()); + } + + public function hasLogout() { + return false; + } +} + +class AuthLoginIP extends AuthLoginImpl { + + private $whitelist; + private $blacklist; + private $ip; + + function __construct($whitelist, $blacklist = array()) { + + parent::__construct(); + + $this->ip = $_SERVER['REMOTE_ADDR']; + $this->whitelist = $whitelist; + $this->blacklist = $blacklist; + } + + function calcMin($sub_range) { + + $sub_range_elements = explode('-',$sub_range); + if(count($sub_range_elements) == 2) { + return $sub_range_elements[0]; + } elseif($sub_range == "*") { + return 0; + } else { + return $sub_range; + } + } + + function calcMax($sub_range) { + + $sub_range_elements = explode('-',$sub_range); + if(count($sub_range_elements) == 2) { + return $sub_range_elements[1]; + } elseif($sub_range == "*") { + return 255; + } else { + return $sub_range; + } + } + + function getIpValue() { + + $result = 0; + $sub_ranges = explode(".", $this->ip); + foreach($sub_ranges as $sub_range) { + $result *= 256; + $result += $sub_range; + } + return $result; + } + + function isInIpRange($range) { + + $sub_ranges = explode(".", $range); + $min = 0; + $max = 0; + foreach($sub_ranges as $sub_range) { + $min = $min * 256; + $min = $min + $this->calcMin($sub_range); + $max = $max * 256; + $max = $max + $this->calcMax($sub_range); + } + return ($this->getIpValue() >= $min) && ($this->getIpValue() <= $max); + } + + function isInIpRanges($ranges) { + + $result = false; + foreach($ranges as $range => $config) { + $result = $this->isInIpRange($range) || $result; + } + return $result; + } + + function getConfigFromIpRange($ranges) { + + $result = false; + foreach($ranges as $range => $config) { + if($this->isInIpRange($range)) { + return $config; + } + } + return $result; + } + + function hasValidUserPass() { + return $this->isInIpRanges($this->whitelist) + && !$this->isInIpRanges($this->blacklist); + } + + function hasRoles($roles = array()) { + if(count($roles) == 0) { + return $this->hasValidUserPass(); +// return hasValidUserPass(); + } + } + + public function getUser() { + return new AuthUserConfig($this->ip, $this->getConfigFromIpRange($this->whitelist)); + } + + public function hasLogout() { + return false; + } +} + +abstract class AuthLoginUserPass extends AuthLoginImpl { + + // Authentication stuff + private $ip_date; + private $uin; + protected $username; + protected $md5_pass; + protected $user_cfg; + + function __construct() { + + parent::__construct(); + + $this->ip_date = $_SERVER['REMOTE_ADDR']."_".date('Y-m'); + $this->uin = (isset($_COOKIE['uin']) ? $_COOKIE['uin'] : ""); + + // + // Handle the logout + // + if(isset($_POST['logout'])) { + setcookie("uin", "logged-out", 0); + $this->uin = "logged-out"; + } + } + + function finishConstruct() { + $this->uin = $this->genUIN($this->username, $this->md5_pass); + setcookie("uin", $this->getUIN(), 0); + } + + // Create a locally unique, monthly changing cookie value. + function genUIN($username, $md5_pass) { + return md5($username.$md5_pass.$this->ip_date); + } + function getUIN() { + return $this->uin; + } + + function getM5P() { + return $this->md5_pass; + } + + function getIpDate() { + return $this->ip_date; + } + + public function getUserName() { + $username = (isset($_POST['user']) ? $_POST['user'] + : (isset($_GET['user']) ? $_GET['user'] + : (isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] + : ""))); + + return $username; + } + + public function getPassWord() { + + $password = (isset($_POST['pass']) ? $_POST['pass'] + : (isset($_GET['pass']) ? $_GET['pass'] + : (isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] + : ""))); + + return $password; + } + + public function hasLogout() { + return true; + } + + public function hasRoles($roles = array()) { + + if($this->hasValidUserPass()) { + if(count($roles) == 0) { + return true; + } elseif(isset($this->user_cfg['role'])) { + return in_array($this->user_cfg['role'], $roles); + } elseif(isset($this->user_cfg['roles'])) { + return in_array($this->user_cfg['roles'], $roles); + } else { + return false; + } + } else { + return false; + } + } + + function getUser() { + + if(isset($this->user_cfg)) { + return new AuthUserConfig($this->username, $this->user_cfg); + } else { + return ""; + } + } +} + +class AuthLoginUserList extends AuthLoginUserPass { + + private $userlist; + + function __construct($userlist) { + parent::__construct(); + + $this->userlist = $userlist; + + // + // Search with UIN + // + if($this->getUIN() != "") { + foreach($this->userlist as $username => $config) { + if( array_key_exists('pass', $config) + && $this->genUIN($username, md5($config['pass'])) == $this->getUIN()) { + $this->user_id = $username; + } + } + } + + // + // Check the new user/pass + // + $username = $this->getUserName(); + if(!$this->hasValidUserPass() && $username != "") { + if(array_key_exists($username, $this->userlist) + && $this->userlist[$username]['pass'] == $this->getPassWord()) { + $this->user_id = $username; + } + } + + if($this->user_id != -1) { + $this->user_cfg = $this->userlist[$this->user_id]; + $this->username = $this->user_id; + $this->md5_pass = md5($this->user_cfg['pass']); + } + + $this->finishConstruct(); + } +} + +class AuthLoginDb extends AuthLoginUserPass { + + // return md5($username.$md5_pass.$this->ip_date); + + function __construct($db_conn, $table) { + + parent::__construct(); + + // + // Check if UIN is valid in DB. + // + $cnt = 0; + if($this->getUIN() != "") { + $uin = $this->getUIN(); + $sql = "select * from ".$table + ." where md5(concat(username,md5_pass,'".$this->getIpDate()."'))" + ." = '".mysqli_real_escape_string($uin)."'"; + + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $cnt = mysqli_num_rows($result); + } + + // + // Check if user is valid in DB. + // + if($cnt == 0 && $this->getUserName() != "") { + $username = $this->getUserName(); + $username_lower = strtolower($this->getUserName()); + $md5_pass = md5($this->getPassWord()); + $md5_pass_lower = md5(strtolower($this->getPassWord())); + + $sql = "select user_id, domain_id, username, md5_pass from ".$table + ." where username in ('".mysqli_real_escape_string($username)."','" + .mysqli_real_escape_string($username_lower)."')" + ." and md5_pass in ('".$md5_pass."','".$md5_pass_lower."');"; + + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); + $cnt = mysqli_num_rows($result); + } + + if($cnt == 1) { + $this->user_id = $rec['user_id']; + $this->username = $rec['username']; + $this->md5_pass = $rec['md5_pass']; + $this->user_cfg = array('domain' => $rec['domain_id']); + } + + $this->finishConstruct(); + } +} +?> \ No newline at end of file diff --git a/z-push/backend/phpaddressbook/phpaddressbook.php b/z-push/backend/phpaddressbook/phpaddressbook.php new file mode 100644 index 0000000..c64eed5 --- /dev/null +++ b/z-push/backend/phpaddressbook/phpaddressbook.php @@ -0,0 +1,923 @@ + 0) { + $zip = $zips[2][0]; + for($i = 0; $i < $cnt_lines; $i++) { + if(FALSE !== strpos($addr_lines[$i], $zip)) { + $city_line = $i; + $city = trim(str_replace($zip, "", $addr_lines[$i]), ", "); + } + } + } else { + if($cnt_lines >= 2) { + $city_line = $cnt_lines-1; + $city = $addr_lines[$city_line]; + if(strlen($city) <= 2) { + $city_line = $cnt_lines-2; + $city = $addr_lines[$city_line]; + } + } elseif($cnt_lines == 1) { + $city_line = 0; + $city = $addr_lines[$city_line]; + } + } + + //-------------------------------------------------------- + // Find the name of the street: + // + $addr = ""; + $street = ""; + $street_nr = ""; + if(count($street_nrs[2]) > 0) { + $street_nr = $street_nrs[2][0]; + for($i = 0; $i < $cnt_lines; $i++) { + preg_match_all('/(^|[^\d])'.$street_nr.'([^\d]|$)/', $addr_lines[$i], $matches); + if(count($matches[0]) > 0) { + $addr_line = $i; + $addr = $addr_lines[$addr_line]; + $street = trim(str_replace($street_nr, "", $addr_lines[$i]), ", "); + } + } + } elseif(isset($city_line) && $city_line >= 1) { + $addr_line = $city_line-1; + $addr = $addr_lines[$addr_line]; + $street = $addr; + } + + //-------------------------------------------------------- + // Find the extension: + // + $exta = ""; + if(isset($addr_line) && $addr_line >= 1) { + $exta = $addr_lines[$addr_line-1]; + } + + //-------------------------------------------------------- + // Find the name of the country: + // + $country = ""; + if(isset($city_line) && $city_line < $cnt_lines-1) { + $country = $addr_lines[$city_line+1]; + } + + $addr_struc['pbox'] = ""; // post office box + $addr_struc['exta'] = $exta; // the extended address; the street + $addr_struc['street'] = $street; + $addr_struc['street_nr'] = $street_nr; + $addr_struc['addr'] = $addr; // address + $addr_struc['city'] = $city; // the locality (e.g., city) + $addr_struc['region'] = ""; // the region (e.g., state or province) + $addr_struc['zip'] = $zip; // the postal code + $addr_struc['country'] = $country; // the country name + + return $addr_struc; + +} + +// +// END: export.vcard.php +// + +// +// FROM: dbconnect.php +// +class PhpAddr { + + public function connect() { + + global $dbserver, $dbuser, $dbpass, $dbname, $db; + + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::connect()'.$dbserver.' - '.$dbuser.' - '.$dbname); + + // --- Connect to DB, retry 5 times --- + for ($i = 0; $i < 5; $i++) { + + $db = mysqli_connect("$dbserver", "$dbuser", "$dbpass"); + $errno = mysqli_errno(); + if ($errno == 1040 || $errno == 1226 || $errno == 1203) { + sleep(1); + } else { + break; + } + } + mysqli_select_db("$dbname", $db); + mysqli_query($db, 'set character set utf8;'); + mysqli_query($db, "SET NAMES `utf8`"); + + return $db; + + } + + public function disconnect() { + + global $db; + + if(mysqli_ping($db)) { + mysqli_close($db); + } + return true; + } +} + +include_once("login.inc.php"); +include_once("address.class.php"); + +// +// END: address.class.php +// + +class BackendPhpaddressbook extends BackendDiff { + /**---------------------------------------------------------------------------------------------------------- + * default backend methods + */ + + var $_user; + var $_login; + var $_phpaddr; + + /** + * Authenticates the user + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + */ + public function Logon($username, $domain, $password) { + + global $db, $usertable, $userlist,$base_where, $base_from, $base_from_where,$table,$domain_id; + + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::Logon()'.$username); + + + $_POST['user'] = $username; + $_POST['pass'] = $password; + $_COOKIE['uin'] = ""; + + $this->_user = $username; + + $this->_phpaddr = new PhpAddr(); + $db = $this->_phpaddr->connect(); + +//1 $userlist['admin']['pass'] = "nimda"; +//1 $userlist['admin']['role'] = "root"; + + $this->_login = AuthLoginFactory::getBestLogin(); + + if($this->_login->hasRoles()) { + $domain_id = $this->_login->getUser()->getDomain(); + $base_where = "$table.domain_id = $domain_id "; + $base_where .= "AND $table.deprecated is null "; + $base_from_where = "$base_from WHERE $base_where "; + + return true; + } else { + return false; + } + } + + /** + * Logs off + * + * @access public + * @return boolean + */ + public function Logoff() { + return $this->_phpaddr->disconnect(); + } + + /** + * Sends an e-mail + * Not implemented here + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + public function SendMail($sm) { + return false; + } + + /** + * Returns the waste basket + * + * @access public + * @return string + */ + public function GetWasteBasket() { + return false; + } + + /** + * Returns the content of the named attachment as stream + * not implemented + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + return false; + } + + /**---------------------------------------------------------------------------------------------------------- + * implemented DiffBackend methods + */ + + /** + * Returns a list (array) of folders. + * In simple implementations like this one, probably just one folder is returned. + * + * @access public + * @return array + */ + public function GetFolderList() { + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::GetFolderList()'); + $contacts = array(); + $folder = $this->StatFolder("root"); + $contacts[] = $folder; + + return $contacts; + } + + /** + * Returns an actual SyncFolder object + * + * @param string $id id of the folder + * + * @access public + * @return object SyncFolder with information + */ + public function GetFolder($id) { + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::GetFolder('.$id.')'); + if($id == "root") { + $folder = new SyncFolder(); + $folder->serverid = $id; + $folder->parentid = "0"; + $folder->displayname = "Contacts"; + $folder->type = SYNC_FOLDER_TYPE_CONTACT; + + return $folder; + } else return false; + } + + /** + * Returns folder stats. An associative array with properties is expected. + * + * @param string $id id of the folder + * + * @access public + * @return array + */ + public function StatFolder($id) { + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::StatFolder('.$id.')'); + $folder = $this->GetFolder($id); + + $stat = array(); + $stat["id"] = $id; + $stat["parent"] = $folder->parentid; + $stat["mod"] = $folder->displayname; + + return $stat; + } + + /** + * Creates or modifies a folder + * not implemented + * + * @param string $folderid id of the parent folder + * @param string $oldid if empty -> new folder created, else folder is to be renamed + * @param string $displayname new folder name (to be created, or to be renamed to) + * @param int $type folder type + * + * @access public + * @return boolean status + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function ChangeFolder($folderid, $oldid, $displayname, $type){ + return false; + } + + /** + * Deletes a folder + * + * @param string $id + * @param string $parent is normally false + * + * @access public + * @return boolean status - false if e.g. does not exist + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function DeleteFolder($id, $parentid){ + return false; + } + + /** + * Returns a list (array) of messages + * + * @param string $folderid id of the parent folder + * @param long $cutoffdate timestamp in the past from which on messages should be returned + * + * @access public + * @return array/false array with messages or false if folder is not available + */ + public function GetMessageList($folderid, $cutoffdate) { + + global $db, $base_from_where; + + debugLog('PhpAddr::GetMessageList('.$folderid.')'); + $messages = array(); + + $sql = "SELECT id, modified FROM $base_from_where"; + + $result = mysqli_query($db,$sql); + while($rec = mysqli_fetch_array($result)) { + $message = array(); + $message["id"] = $rec['id']; + $message["mod"] = $rec["modified"]; + $message["flags"] = 1; // always 'read' + + $messages[] = $message; + } + + debugLog('PhpAddr::GetMessageList('.$folderid.', found: '.count($messages).' items)'); + + return $messages; + } + + /** + * Returns the actual SyncXXX object type. + * + * @param string $folderid id of the parent folder + * @param string $id id of the message + * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) + * + * @access public + * @return object/false false if the message could not be retrieved + */ + public function GetMessage($folderid, $id, $contentparameters) { + + global $db, $base_from_where, $domain_id; + + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::GetMessage('.$folderid.', '.$id.', ..)'); + if($folderid != "root") + return false; + + $types = array ('dom' => 'type', 'intl' => 'type', 'postal' => 'type', 'parcel' => 'type', 'home' => 'type', 'work' => 'type', + 'pref' => 'type', 'voice' => 'type', 'fax' => 'type', 'msg' => 'type', 'cell' => 'type', 'pager' => 'type', + 'bbs' => 'type', 'modem' => 'type', 'car' => 'type', 'isdn' => 'type', 'video' => 'type', + 'aol' => 'type', 'applelink' => 'type', 'attmail' => 'type', 'cis' => 'type', 'eworld' => 'type', + 'internet' => 'type', 'ibmmail' => 'type', 'mcimail' => 'type', + 'powershare' => 'type', 'prodigy' => 'type', 'tlx' => 'type', 'x400' => 'type', + 'gif' => 'type', 'cgm' => 'type', 'wmf' => 'type', 'bmp' => 'type', 'met' => 'type', 'pmb' => 'type', 'dib' => 'type', + 'pict' => 'type', 'tiff' => 'type', 'pdf' => 'type', 'ps' => 'type', 'jpeg' => 'type', 'qtime' => 'type', + 'mpeg' => 'type', 'mpeg2' => 'type', 'avi' => 'type', + 'wave' => 'type', 'aiff' => 'type', 'pcm' => 'type', + 'x509' => 'type', 'pgp' => 'type', 'text' => 'value', 'inline' => 'value', 'url' => 'value', 'cid' => 'value', 'content-id' => 'value', + '7bit' => 'encoding', '8bit' => 'encoding', 'quoted-printable' => 'encoding', 'base64' => 'encoding', + ); + + + $message = new SyncContact(); + + $sql = "SELECT * FROM $base_from_where AND id = ".intval($id); + $result = mysqli_query($db,$sql); + $addr = mysqli_fetch_array($result); + + if(isset($addr['email'])) + $message->email1address = $addr['email']; + if(isset($addr['email2'])) + $message->email2address = $addr['email2']; + if(isset($addr['email3'])) + $message->email3address = $addr['email3']; + + if(isset($addr['address']) && trim($addr['address']) != "") { + $addr_parts = label2adr($addr['address']); + $message->homestreet = $addr_parts['street']." ".$addr_parts['street_nr']; + $message->homecity = $addr_parts['city']; + $message->homepostalcode = $addr_parts['zip']; + // $message->homestate = ?; + $message->homecountry = $addr_parts['country']; + } + + if(isset($addr['address2']) && trim($addr['address2']) != "") { + $addr_parts = label2adr($addr['address2']); + $message->businessstreet = $addr_parts['street']." ".$addr_parts['street_nr']; + $message->businesscity = $addr_parts['city']; + $message->businesspostalcode = $addr_parts['zip']; + // $message->businessstate = ?; + $message->businesscountry = $addr_parts['country']; + } +/* + if(isset($vcard['tel'])){ + foreach($vcard['tel'] as $tel) { + if(!isset($tel['type'])){ + $tel['type'] = array(); + } + if(in_array('car', $tel['type'])){ + $message->carphonenumber = $tel['val'][0]; + }elseif(in_array('pager', $tel['type'])){ + $message->pagernumber = $tel['val'][0]; + }elseif(in_array('cell', $tel['type'])){ + $message->mobilephonenumber = $tel['val'][0]; + }elseif(in_array('home', $tel['type'])){ + if(in_array('fax', $tel['type'])){ + $message->homefaxnumber = $tel['val'][0]; + }elseif(empty($message->homephonenumber)){ + $message->homephonenumber = $tel['val'][0]; + }else{ + $message->home2phonenumber = $tel['val'][0]; + } + }elseif(in_array('work', $tel['type'])){ + if(in_array('fax', $tel['type'])){ + $message->businessfaxnumber = $tel['val'][0]; + }elseif(empty($message->businessphonenumber)){ + $message->businessphonenumber = $tel['val'][0]; + }else{ + $message->business2phonenumber = $tel['val'][0]; + } + }elseif(empty($message->homephonenumber)){ + $message->homephonenumber = $tel['val'][0]; + }elseif(empty($message->home2phonenumber)){ + $message->home2phonenumber = $tel['val'][0]; + }else{ + $message->radiophonenumber = $tel['val'][0]; + } + } + } + //;;street;city;state;postalcode;country + if(isset($vcard['adr'])){ + foreach($vcard['adr'] as $adr) { + if(empty($adr['type'])){ + $a = 'other'; + }elseif(in_array('home', $adr['type'])){ + $a = 'home'; + }elseif(in_array('work', $adr['type'])){ + $a = 'business'; + }else{ + $a = 'other'; + } + if(!empty($adr['val'][2])){ + $b=$a.'street'; + $message->$b = w2ui($adr['val'][2]); + } + if(!empty($adr['val'][3])){ + $b=$a.'city'; + $message->$b = w2ui($adr['val'][3]); + } + if(!empty($adr['val'][4])){ + $b=$a.'state'; + $message->$b = w2ui($adr['val'][4]); + } + if(!empty($adr['val'][5])){ + $b=$a.'postalcode'; + $message->$b = w2ui($adr['val'][5]); + } + if(!empty($adr['val'][6])){ + $b=$a.'country'; + $message->$b = w2ui($adr['val'][6]); + } + } + } + */ + $message->fileas = ( $addr['firstname']." ".$addr['lastname'] != "" + ? $addr['firstname']." ".$addr['lastname'] != "" + : $addr['company']); + + $message->lastname = $addr['lastname']; + $message->firstname = $addr['firstname']; + $message->nickname = $addr['nickname']; + $message->jobtitle = $addr['title']; + $message->companyname = $addr['company']; + + $message->homephonenumber = $addr['home']; + $message->mobilephonenumber = $addr['mobile']; + $message->businessphonenumber = $addr['work']; + $message->businessfaxnumber = $addr['fax']; + + $message->home2phonenumber = $addr['phone2']; + + $message->body = $addr['notes']; + + $message->picture = $addr['photo']; + + if( isset($addr['bday']) && $addr['bday'] != "" + && isset($addr['bmonth']) && $addr['bmonth'] != "" + && isset($addr['byear']) && $addr['byear'] != "" + ) + { + $message->birthday = strtotime($addr['bday']." ".$addr['bmonth']." ".$addr['byear']); + } + + if( isset($addr['aday']) && $addr['aday'] != "" + && isset($addr['amonth']) && $addr['amonth'] != "" + && isset($addr['ayear']) && $addr['ayear'] != "" + ) + { + $message->anniversary = strtotime($addr['aday']." ".$addr['amonth']." ".$addr['ayear']); + } + // $message->birthday = strtotime($vcard['bday'][0]['val'][0]); +/* + if(!empty($vcard['n'][0]['val'][2])) + $message->middlename = w2ui($vcard['n'][0]['val'][2]); + if(!empty($vcard['n'][0]['val'][3])) + $message->title = w2ui($vcard['n'][0]['val'][3]); + if(!empty($vcard['n'][0]['val'][4])) + $message->suffix = w2ui($vcard['n'][0]['val'][4]); + if(!empty($vcard['bday'][0]['val'][0])){ + $tz = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $message->birthday = strtotime($vcard['bday'][0]['val'][0]); + date_default_timezone_set($tz); + } + if(!empty($vcard['org'][0]['val'][0])) + $message->companyname = w2ui($vcard['org'][0]['val'][0]); + if(!empty($vcard['note'][0]['val'][0])){ + $message->body = w2ui($vcard['note'][0]['val'][0]); + $message->bodysize = strlen($vcard['note'][0]['val'][0]); + $message->bodytruncated = 0; + } + if(!empty($vcard['role'][0]['val'][0])) + $message->jobtitle = w2ui($vcard['role'][0]['val'][0]);//$vcard['title'][0]['val'][0] + if(!empty($vcard['url'][0]['val'][0])) + $message->webpage = w2ui($vcard['url'][0]['val'][0]); + if(!empty($vcard['categories'][0]['val'])) + $message->categories = $vcard['categories'][0]['val']; + + if(!empty($vcard['photo'][0]['val'][0])) + $message->picture = base64_encode($vcard['photo'][0]['val'][0]); +*/ + return $message; + } + + /** + * Returns message stats, analogous to the folder stats from StatFolder(). + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return array + */ + public function StatMessage($folderid, $id) { + + global $db, $base_from_where; + + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::StatMessage('.$folderid.', '.$id.')'); + if($folderid != "root") + return false; + + $sql = "SELECT id, modified FROM $base_from_where AND id = ".intval($id); + +//2 $this->_phpaddr->connect(); + $result = mysqli_query($db,$sql); + $rec = mysqli_fetch_array($result); +//2 $this->_phpaddr->disconnect(); + + $message = array(); + $message["id"] = $rec['id']; + $message["mod"] = $rec["modified"]; + $message["flags"] = 1; // always 'read' + + return $message; + } + + function _getOneToOneMapping() { + + $mapping = array(); + + // + // $mapping[ActiveSyncKey] = PhpAddrKey (if not the same); + // + $mapping['lastname'] = ""; + $mapping['firstname'] = ""; + $mapping['companyname'] = "company"; + + $mapping['homephonenumber'] = "home"; + $mapping['home2phonenumber'] = "phone2"; + $mapping['mobilephonenumber'] = "mobile"; + $mapping['businessphonenumber'] = "work"; + $mapping['businessfaxnumber'] = "fax"; + + $mapping['email1address'] = "email"; + $mapping['email2address'] = "email2"; + $mapping['email3address'] = "email3"; + + $mapping['webpage'] = "homepage"; + + $mapping['body'] = "notes"; + + return $mapping; + } + + /** + * Called when a message has been changed on the mobile. + * This functionality is not available for emails. + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param SyncXXX $message the SyncObject containing a message + * + * @access public + * @return array same return value as StatMessage() + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function ChangeMessage($folderid, $id, $message) { + ZLog::Write(LOGLEVEL_DEBUG, 'PhpAddr::ChangeMessage('.$folderid.', '.$id.', ..)'); + + debugLog('PhpAddr::ChangeMessage(FolderID: '.$folderid.', ID: '.$id.', ..)'.json_encode($message)); + + global $keep_history, $domain_id; + + + $addr['firstname'] = ( $message->title != "" + ? $message->title." " + : ""); + $addr['firstname'] .= $message->firstname. + ( $message->middlename != "" + ? " ".$message->middlename : ""); + + $addr['firstname'] .= ( $message->suffix != "" + ? " ".$message->suffix : ""); + + $addr['lastname'] = $message->lastname; + $addr['nickname'] = $message->nickname; + $addr['company'] = $message->companyname; + $addr['title'] = $message->jobtitle; + // 'jobtitle' => 'ROLE', + + // ';;homestreet;homecity;homestate;homepostalcode;homecountry' => 'ADR;HOME', + + $addr['home'] = $message->homephonenumber; + $addr['mobile'] = $message->mobilephonenumber; + $addr['work'] = $message->businessphonenumber; + $addr['fax'] = ( $message->businessfaxnumber != "" + ? $message->businessfaxnumber + : $message->homefaxnumber); + + $addr['phone2'] = $message->home2phonenumber; + + // 'business2phonenumber' => 'TEL;WORK', + // 'businessfaxnumber' => 'TEL;WORK;FAX', + // 'home2phonenumber' => 'TEL;HOME', + // 'homefaxnumber' => 'TEL;HOME;FAX', + // 'carphonenumber' => 'TEL;CAR', + // 'pagernumber' => 'TEL;PAGER', + + if(!empty($message->picture)) { + $addr['photo'] .= 'PHOTO;ENCODING=BASE64;TYPE=JPEG:'."\n\t".substr(chunk_split($message->picture, 50, "\n\t"), 0, -1); + } + + $addr['email'] = $message->email1address; + $addr['email2'] = $message->email2address; + $addr['email3'] = $message->email3address; + + $addr['homepage'] = $message->webpage; + + // if($message->birthday !== FALSE) { + if($message->birthday != 0) { + $addr['bday'] = date('j', $message->birthday); + $addr['bmonth'] = date('F', $message->birthday); + $addr['byear'] = date('Y', $message->birthday); + + } + + // if($message->anniversary !== FALSE) { + if($message->anniversary != 0) { + $addr['aday'] = date('j', $message->anniversary); + $addr['amonth'] = date('F', $message->anniversary); + $addr['ayear'] = date('Y', $message->anniversary); + } + // ->anniversary +/* + // if($message->birthday > 0) { + debugLog('PhpAddr::Birthday(Birthday: '.$message->birthday.', Day: '.$addr['bday']); + } + debugLog('PhpAddr::Birthday(Birthday: '.$message->birthday); +*/ + + // ';;businessstreet;businesscity;businessstate;businesspostalcode;businesscountry' => 'ADR;WORK', + // ';;otherstreet;othercity;otherstate;otherpostalcode;othercountry' => 'ADR', + $addr['notes'] = $message->body; + + // assistantname, assistnamephonenumber, children, department, officelocation, radiophonenumber, spouse, rtf + $addr['address'] = $message->homestreet."\n" + .$message->homepostalcode." ".$message->homecity."\n" + .$message->homecountry; + // $message->homestate = $addr_parts['city']; + + $addr['address2'] = $message->businessstreet."\n" + .$message->businesspostalcode." ".$message->businesscity."\n" + .$message->businesscountry; + // $message->businessstate = $addr_parts['city']; + +//2 $this->_phpaddr->connect(); + if($id == "") { + $id = saveAddress($addr); + debugLog('PhpAddr::ChangedMessage(FolderID: '.$folderid.', ID: '.$id.', ..)'); + } else { + $addr['id'] = intval($id); + updateAddress($addr); + } +//2 $this->_phpaddr->disconnect(); + +/* + $mapping = array( + ); + $data = "BEGIN:VCARD\nVERSION:2.1\nPRODID:Z-Push\n"; + foreach($mapping as $k => $v){ + $val = ''; + $ks = explode(';', $k); + foreach($ks as $i){ + if(!empty($message->$i)) + $val .= $this->escape($message->$i); + $val.=';'; + } + if(empty($val)) + continue; + $val = substr($val,0,-1); + if(strlen($val)>50){ + $data .= $v.":\n\t".substr(chunk_split($val, 50, "\n\t"), 0, -1); + }else{ + $data .= $v.':'.$val."\n"; + } + } + if(!empty($message->categories)) + $data .= 'CATEGORIES:'.implode(',', $this->escape($message->categories))."\n"; + if(!empty($message->picture)) + $data .= 'PHOTO;ENCODING=BASE64;TYPE=JPEG:'."\n\t".substr(chunk_split($message->picture, 50, "\n\t"), 0, -1); + if(isset($message->birthday)) + $data .= 'BDAY:'.date('Y-m-d', $message->birthday)."\n"; + $data .= "END:VCARD"; + +// not supported: anniversary, assistantname, assistnamephonenumber, children, department, officelocation, radiophonenumber, spouse, rtf + + if(!$id){ + if(!empty($message->fileas)){ + $name = u2wi($message->fileas); + }elseif(!empty($message->lastname)){ + $name = $name = u2wi($message->lastname); + }elseif(!empty($message->firstname)){ + $name = $name = u2wi($message->firstname); + }elseif(!empty($message->companyname)){ + $name = $name = u2wi($message->companyname); + }else{ + $name = 'unknown'; + } + $name = preg_replace('/[^a-z0-9 _-]/i', '', $name); + $id = $name.'.vcf'; + $i = 0; + while(file_exists($this->getPath().'/'.$id)){ + $i++; + $id = $name.$i.'.vcf'; + } + } + // file_put_contents($this->getPath().'/'.$id, $data); + */ + return $this->StatMessage($folderid, $id); + } + + /** + * Changes the 'read' flag of a message on disk + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param int $flags read flag of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function SetReadFlag($folderid, $id, $flags) { + return false; + } + + /** + * Called when the user has requested to delete (really delete) a message + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function DeleteMessage($folderid, $id) { + + debugLog('PhpAddr::DeleteMessage(FolderID: '.$folderid.', ID: '.$id.')'); +//2 $this->_phpaddr->connect(); + $deleted = deleteAddresses(" id = '".intval($id)."'"); +//2 $this->_phpaddr->disconnect(); + + return $deleted; + } + + /** + * Called when the user moves an item on the PDA from one folder to another + * not implemented + * + * @param string $folderid id of the source folder + * @param string $id id of the message + * @param string $newfolderid id of the destination folder + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions + */ + public function MoveMessage($folderid, $id, $newfolderid) { + return false; + } + + + /**---------------------------------------------------------------------------------------------------------- + * private vcard-specific internals + */ + + /** + * The path we're working on + * + * @access private + * @return string + */ + private function getPath() { + return str_replace('%u', $this->store, VCARDDIR_DIR); + } + + /** + * Escapes a string + * + * @param string $data string to be escaped + * + * @access private + * @return string + */ + function escape($data){ + if (is_array($data)) { + foreach ($data as $key => $val) { + $data[$key] = $this->escape($val); + } + return $data; + } + $data = str_replace("\r\n", "\n", $data); + $data = str_replace("\r", "\n", $data); + $data = str_replace(array('\\', ';', ',', "\n"), array('\\\\', '\\;', '\\,', '\\n'), $data); + return u2wi($data); + } + + /** + * Un-escapes a string + * + * @param string $data string to be un-escaped + * + * @access private + * @return string + */ + function unescape($data){ + $data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data); + return $data; + } +}; +?> \ No newline at end of file diff --git a/z-push/backend/searchldap/config.php b/z-push/backend/searchldap/config.php new file mode 100644 index 0000000..a9d9823 --- /dev/null +++ b/z-push/backend/searchldap/config.php @@ -0,0 +1,75 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +// LDAP host and port +define("LDAP_HOST", "ldap://127.0.0.1/"); +define("LDAP_PORT", "389"); + +// Set USER and PASSWORD if not using anonymous bind +define("ANONYMOUS_BIND", true); +define("LDAP_BIND_USER", "cn=searchuser,dc=test,dc=net"); +define("LDAP_BIND_PASSWORD", ""); + +// Search base & filter +// the SEARCHVALUE string is substituded by the value inserted into the search field +define("LDAP_SEARCH_BASE", "ou=global,dc=test,dc=net"); +define("LDAP_SEARCH_FILTER", "(|(cn=*SEARCHVALUE*)(mail=*SEARCHVALUE*))"); + +// LDAP field mapping. +// values correspond to an inetOrgPerson class +global $ldap_field_map; +$ldap_field_map = array( + SYNC_GAL_DISPLAYNAME => 'cn', + SYNC_GAL_PHONE => 'telephonenumber', + SYNC_GAL_OFFICE => '', + SYNC_GAL_TITLE => 'title', + SYNC_GAL_COMPANY => 'ou', + SYNC_GAL_ALIAS => 'uid', + SYNC_GAL_FIRSTNAME => 'givenname', + SYNC_GAL_LASTNAME => 'sn', + SYNC_GAL_HOMEPHONE => 'homephone', + SYNC_GAL_MOBILEPHONE => 'mobile', + SYNC_GAL_EMAILADDRESS => 'mail', + ); +?> \ No newline at end of file diff --git a/z-push/backend/searchldap/searchldap.php b/z-push/backend/searchldap/searchldap.php new file mode 100644 index 0000000..22f72fb --- /dev/null +++ b/z-push/backend/searchldap/searchldap.php @@ -0,0 +1,198 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +require_once("backend/searchldap/config.php"); + +class BackendSearchLDAP implements ISearchProvider { + private $connection; + + /** + * Initializes the backend to perform the search + * Connects to the LDAP server using the values from the configuration + * + * + * @access public + * @return + * @throws StatusException + */ + public function BackendSearchLDAP() { + if (!function_exists("ldap_connect")) + throw new StatusException("BackendSearchLDAP(): php-ldap is not installed. Search aborted.", SYNC_SEARCHSTATUS_STORE_SERVERERROR, null, LOGLEVEL_FATAL); + + // connect to LDAP + $this->connection = @ldap_connect(LDAP_HOST, LDAP_PORT); + @ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3); + + // Authenticate + if (constant('ANONYMOUS_BIND') === true) { + if(! @ldap_bind($this->connection)) { + $this->connection = false; + throw new StatusException("BackendSearchLDAP(): Could not bind anonymously to server! Search aborted.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + else if (constant('LDAP_BIND_USER') != "") { + if(! @ldap_bind($this->connection, LDAP_BIND_USER, LDAP_BIND_PASSWORD)) { + $this->connection = false; + throw new StatusException(sprintf("BackendSearchLDAP(): Could not bind to server with user '%s' and specified password! Search aborted.", LDAP_BIND_USER), SYNC_SEARCHSTATUS_STORE_ACCESSDENIED, null, LOGLEVEL_ERROR); + } + } + else { + // it would be possible to use the users login and password to authenticate on the LDAP server + // the main $backend has to keep these values so they could be used here + $this->connection = false; + throw new StatusException("BackendSearchLDAP(): neither anonymous nor default bind enabled. Other options not implemented.", SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED, null, LOGLEVEL_ERROR); + } + } + + /** + * Indicates if a search type is supported by this SearchProvider + * Currently only the type ISearchProvider::SEARCH_GAL (Global Address List) is implemented + * + * @param string $searchtype + * + * @access public + * @return boolean + */ + public function SupportsType($searchtype) { + return ($searchtype == ISearchProvider::SEARCH_GAL); + } + + + /** + * Queries the LDAP backend + * + * @param string $searchquery string to be searched for + * @param string $searchrange specified searchrange + * + * @access public + * @return array search results + */ + public function GetGALSearchResults($searchquery, $searchrange) { + global $ldap_field_map; + if (isset($this->connection) && $this->connection !== false) { + $searchfilter = str_replace("SEARCHVALUE", $searchquery, LDAP_SEARCH_FILTER); + $result = @ldap_search($this->connection, LDAP_SEARCH_BASE, $searchfilter); + if (!$result) { + ZLog::Write(LOGLEVEL_ERROR, "BackendSearchLDAP: Error in search query. Search aborted"); + return false; + } + + // get entry data as array + $searchresult = ldap_get_entries($this->connection, $result); + + // range for the search results, default symbian range end is 50, wm 99, + // so we'll use that of nokia + $rangestart = 0; + $rangeend = 50; + + if ($searchrange != '0') { + $pos = strpos($searchrange, '-'); + $rangestart = substr($searchrange, 0, $pos); + $rangeend = substr($searchrange, ($pos + 1)); + } + $items = array(); + + // TODO the limiting of the searchresults could be refactored into Utils as it's probably used more than once + $querycnt = $searchresult['count']; + //do not return more results as requested in range + $querylimit = (($rangeend + 1) < $querycnt) ? ($rangeend + 1) : $querycnt; + $items['range'] = $rangestart.'-'.($querycnt-1); + $items['searchtotal'] = $querycnt; + + $rc = 0; + for ($i = $rangestart; $i < $querylimit; $i++) { + foreach ($ldap_field_map as $key=>$value ) { + if (isset($searchresult[$i][$value])) { + if (is_array($searchresult[$i][$value])) + $items[$rc][$key] = $searchresult[$i][$value][0]; + else + $items[$rc][$key] = $searchresult[$i][$value]; + } + } + $rc++; + } + + return $items; + } + else + return false; + } + + /** + * Searches for the emails on the server + * + * @param ContentParameter $cpo + * + * @return array + */ + public function GetMailboxSearchResults($cpo) { + return array(); + } + + /** + * Terminates a search for a given PID + * + * @param int $pid + * + * @return boolean + */ + public function TerminateSearch($pid) { + return true; + } + + /** + * Disconnects from LDAP + * + * @access public + * @return boolean + */ + public function Disconnect() { + if ($this->connection) + @ldap_close($this->connection); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/backend/vcarddir.php b/z-push/backend/vcarddir.php new file mode 100644 index 0000000..4f06f4f --- /dev/null +++ b/z-push/backend/vcarddir.php @@ -0,0 +1,673 @@ +. +* +* Consult LICENSE file for details +************************************************/ +include_once('lib/default/diffbackend/diffbackend.php'); + +class BackendVCardDir extends BackendDiff { + /**---------------------------------------------------------------------------------------------------------- + * default backend methods + */ + + /** + * Authenticates the user - NOT EFFECTIVELY IMPLEMENTED + * Normally some kind of password check would be done here. + * Alternatively, the password could be ignored and an Apache + * authentication via mod_auth_* could be done + * + * @param string $username + * @param string $domain + * @param string $password + * + * @access public + * @return boolean + */ + public function Logon($username, $domain, $password) { + return true; + } + + /** + * Logs off + * + * @access public + * @return boolean + */ + public function Logoff() { + return true; + } + + /** + * Sends an e-mail + * Not implemented here + * + * @param SyncSendMail $sm SyncSendMail object + * + * @access public + * @return boolean + * @throws StatusException + */ + public function SendMail($sm) { + return false; + } + + /** + * Returns the waste basket + * + * @access public + * @return string + */ + public function GetWasteBasket() { + return false; + } + + /** + * Returns the content of the named attachment as stream + * not implemented + * + * @param string $attname + * + * @access public + * @return SyncItemOperationsAttachment + * @throws StatusException + */ + public function GetAttachmentData($attname) { + return false; + } + + /**---------------------------------------------------------------------------------------------------------- + * implemented DiffBackend methods + */ + + /** + * Returns a list (array) of folders. + * In simple implementations like this one, probably just one folder is returned. + * + * @access public + * @return array + */ + public function GetFolderList() { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolderList()'); + $contacts = array(); + $folder = $this->StatFolder("root"); + $contacts[] = $folder; + + return $contacts; + } + + /** + * Returns an actual SyncFolder object + * + * @param string $id id of the folder + * + * @access public + * @return object SyncFolder with information + */ + public function GetFolder($id) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetFolder('.$id.')'); + if($id == "root") { + $folder = new SyncFolder(); + $folder->serverid = $id; + $folder->parentid = "0"; + $folder->displayname = "Contacts"; + $folder->type = SYNC_FOLDER_TYPE_CONTACT; + + return $folder; + } else return false; + } + + /** + * Returns folder stats. An associative array with properties is expected. + * + * @param string $id id of the folder + * + * @access public + * @return array + */ + public function StatFolder($id) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatFolder('.$id.')'); + $folder = $this->GetFolder($id); + + $stat = array(); + $stat["id"] = $id; + $stat["parent"] = $folder->parentid; + $stat["mod"] = $folder->displayname; + + return $stat; + } + + /** + * Creates or modifies a folder + * not implemented + * + * @param string $folderid id of the parent folder + * @param string $oldid if empty -> new folder created, else folder is to be renamed + * @param string $displayname new folder name (to be created, or to be renamed to) + * @param int $type folder type + * + * @access public + * @return boolean status + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function ChangeFolder($folderid, $oldid, $displayname, $type){ + return false; + } + + /** + * Deletes a folder + * + * @param string $id + * @param string $parent is normally false + * + * @access public + * @return boolean status - false if e.g. does not exist + * @throws StatusException could throw specific SYNC_FSSTATUS_* exceptions + * + */ + public function DeleteFolder($id, $parentid){ + return false; + } + + /** + * Returns a list (array) of messages + * + * @param string $folderid id of the parent folder + * @param long $cutoffdate timestamp in the past from which on messages should be returned + * + * @access public + * @return array/false array with messages or false if folder is not available + */ + public function GetMessageList($folderid, $cutoffdate) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessageList('.$folderid.')'); + $messages = array(); + + $dir = opendir($this->getPath()); + if(!$dir) + return false; + + while($entry = readdir($dir)) { + if(is_dir($this->getPath() .'/'.$entry)) + continue; + + $message = array(); + $message["id"] = $entry; + $stat = stat($this->getPath() .'/'.$entry); + $message["mod"] = $stat["mtime"]; + $message["flags"] = 1; // always 'read' + + $messages[] = $message; + } + + return $messages; + } + + /** + * Returns the actual SyncXXX object type. + * + * @param string $folderid id of the parent folder + * @param string $id id of the message + * @param ContentParameters $contentparameters parameters of the requested message (truncation, mimesupport etc) + * + * @access public + * @return object/false false if the message could not be retrieved + */ + public function GetMessage($folderid, $id, $contentparameters) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::GetMessage('.$folderid.', '.$id.', ..)'); + if($folderid != "root") + return; + + $types = array ('dom' => 'type', 'intl' => 'type', 'postal' => 'type', 'parcel' => 'type', 'home' => 'type', 'work' => 'type', + 'pref' => 'type', 'voice' => 'type', 'fax' => 'type', 'msg' => 'type', 'cell' => 'type', 'pager' => 'type', + 'bbs' => 'type', 'modem' => 'type', 'car' => 'type', 'isdn' => 'type', 'video' => 'type', + 'aol' => 'type', 'applelink' => 'type', 'attmail' => 'type', 'cis' => 'type', 'eworld' => 'type', + 'internet' => 'type', 'ibmmail' => 'type', 'mcimail' => 'type', + 'powershare' => 'type', 'prodigy' => 'type', 'tlx' => 'type', 'x400' => 'type', + 'gif' => 'type', 'cgm' => 'type', 'wmf' => 'type', 'bmp' => 'type', 'met' => 'type', 'pmb' => 'type', 'dib' => 'type', + 'pict' => 'type', 'tiff' => 'type', 'pdf' => 'type', 'ps' => 'type', 'jpeg' => 'type', 'qtime' => 'type', + 'mpeg' => 'type', 'mpeg2' => 'type', 'avi' => 'type', + 'wave' => 'type', 'aiff' => 'type', 'pcm' => 'type', + 'x509' => 'type', 'pgp' => 'type', 'text' => 'value', 'inline' => 'value', 'url' => 'value', 'cid' => 'value', 'content-id' => 'value', + '7bit' => 'encoding', '8bit' => 'encoding', 'quoted-printable' => 'encoding', 'base64' => 'encoding', + ); + + + // Parse the vcard + $message = new SyncContact(); + + $data = file_get_contents($this->getPath() . "/" . $id); + $data = str_replace("\x00", '', $data); + $data = str_replace("\r\n", "\n", $data); + $data = str_replace("\r", "\n", $data); + $data = preg_replace('/(\n)([ \t])/i', '', $data); + + $lines = explode("\n", $data); + + $vcard = array(); + foreach($lines as $line) { + if (trim($line) == '') + continue; + $pos = strpos($line, ':'); + if ($pos === false) + continue; + + $field = trim(substr($line, 0, $pos)); + $value = trim(substr($line, $pos+1)); + + $fieldparts = preg_split('/(? $v){ + $val[$i] = quoted_printable_decode($v); + } + break; + case 'b': + case 'base64': + foreach($val as $i => $v){ + $val[$i] = base64_decode($v); + } + break; + } + }else{ + foreach($val as $i => $v){ + $val[$i] = $this->unescape($v); + } + } + $fieldvalue['val'] = $val; + $vcard[$type][] = $fieldvalue; + } + + if(isset($vcard['email'][0]['val'][0])) + $message->email1address = $vcard['email'][0]['val'][0]; + if(isset($vcard['email'][1]['val'][0])) + $message->email2address = $vcard['email'][1]['val'][0]; + if(isset($vcard['email'][2]['val'][0])) + $message->email3address = $vcard['email'][2]['val'][0]; + + if(isset($vcard['tel'])){ + foreach($vcard['tel'] as $tel) { + if(!isset($tel['type'])){ + $tel['type'] = array(); + } + if(in_array('car', $tel['type'])){ + $message->carphonenumber = $tel['val'][0]; + }elseif(in_array('pager', $tel['type'])){ + $message->pagernumber = $tel['val'][0]; + }elseif(in_array('cell', $tel['type'])){ + $message->mobilephonenumber = $tel['val'][0]; + }elseif(in_array('home', $tel['type'])){ + if(in_array('fax', $tel['type'])){ + $message->homefaxnumber = $tel['val'][0]; + }elseif(empty($message->homephonenumber)){ + $message->homephonenumber = $tel['val'][0]; + }else{ + $message->home2phonenumber = $tel['val'][0]; + } + }elseif(in_array('work', $tel['type'])){ + if(in_array('fax', $tel['type'])){ + $message->businessfaxnumber = $tel['val'][0]; + }elseif(empty($message->businessphonenumber)){ + $message->businessphonenumber = $tel['val'][0]; + }else{ + $message->business2phonenumber = $tel['val'][0]; + } + }elseif(empty($message->homephonenumber)){ + $message->homephonenumber = $tel['val'][0]; + }elseif(empty($message->home2phonenumber)){ + $message->home2phonenumber = $tel['val'][0]; + }else{ + $message->radiophonenumber = $tel['val'][0]; + } + } + } + //;;street;city;state;postalcode;country + if(isset($vcard['adr'])){ + foreach($vcard['adr'] as $adr) { + if(empty($adr['type'])){ + $a = 'other'; + }elseif(in_array('home', $adr['type'])){ + $a = 'home'; + }elseif(in_array('work', $adr['type'])){ + $a = 'business'; + }else{ + $a = 'other'; + } + if(!empty($adr['val'][2])){ + $b=$a.'street'; + $message->$b = w2ui($adr['val'][2]); + } + if(!empty($adr['val'][3])){ + $b=$a.'city'; + $message->$b = w2ui($adr['val'][3]); + } + if(!empty($adr['val'][4])){ + $b=$a.'state'; + $message->$b = w2ui($adr['val'][4]); + } + if(!empty($adr['val'][5])){ + $b=$a.'postalcode'; + $message->$b = w2ui($adr['val'][5]); + } + if(!empty($adr['val'][6])){ + $b=$a.'country'; + $message->$b = w2ui($adr['val'][6]); + } + } + } + + if(!empty($vcard['fn'][0]['val'][0])) + $message->fileas = w2ui($vcard['fn'][0]['val'][0]); + if(!empty($vcard['n'][0]['val'][0])) + $message->lastname = w2ui($vcard['n'][0]['val'][0]); + if(!empty($vcard['n'][0]['val'][1])) + $message->firstname = w2ui($vcard['n'][0]['val'][1]); + if(!empty($vcard['n'][0]['val'][2])) + $message->middlename = w2ui($vcard['n'][0]['val'][2]); + if(!empty($vcard['n'][0]['val'][3])) + $message->title = w2ui($vcard['n'][0]['val'][3]); + if(!empty($vcard['n'][0]['val'][4])) + $message->suffix = w2ui($vcard['n'][0]['val'][4]); + if(!empty($vcard['bday'][0]['val'][0])){ + $tz = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $message->birthday = strtotime($vcard['bday'][0]['val'][0]); + date_default_timezone_set($tz); + } + if(!empty($vcard['org'][0]['val'][0])) + $message->companyname = w2ui($vcard['org'][0]['val'][0]); + if(!empty($vcard['note'][0]['val'][0])){ + $message->body = w2ui($vcard['note'][0]['val'][0]); + $message->bodysize = strlen($vcard['note'][0]['val'][0]); + $message->bodytruncated = 0; + } + if(!empty($vcard['role'][0]['val'][0])) + $message->jobtitle = w2ui($vcard['role'][0]['val'][0]);//$vcard['title'][0]['val'][0] + if(!empty($vcard['url'][0]['val'][0])) + $message->webpage = w2ui($vcard['url'][0]['val'][0]); + if(!empty($vcard['categories'][0]['val'])) + $message->categories = $vcard['categories'][0]['val']; + + if(!empty($vcard['photo'][0]['val'][0])) + $message->picture = base64_encode($vcard['photo'][0]['val'][0]); + + return $message; + } + + /** + * Returns message stats, analogous to the folder stats from StatFolder(). + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return array + */ + public function StatMessage($folderid, $id) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::StatMessage('.$folderid.', '.$id.')'); + if($folderid != "root") + return false; + + $stat = stat($this->getPath() . "/" . $id); + + $message = array(); + $message["mod"] = $stat["mtime"]; + $message["id"] = $id; + $message["flags"] = 1; + + return $message; + } + + /** + * Called when a message has been changed on the mobile. + * This functionality is not available for emails. + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param SyncXXX $message the SyncObject containing a message + * + * @access public + * @return array same return value as StatMessage() + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function ChangeMessage($folderid, $id, $message) { + ZLog::Write(LOGLEVEL_DEBUG, 'VCDir::ChangeMessage('.$folderid.', '.$id.', ..)'); + $mapping = array( + 'fileas' => 'FN', + 'lastname;firstname;middlename;title;suffix' => 'N', + 'email1address' => 'EMAIL;INTERNET', + 'email2address' => 'EMAIL;INTERNET', + 'email3address' => 'EMAIL;INTERNET', + 'businessphonenumber' => 'TEL;WORK', + 'business2phonenumber' => 'TEL;WORK', + 'businessfaxnumber' => 'TEL;WORK;FAX', + 'homephonenumber' => 'TEL;HOME', + 'home2phonenumber' => 'TEL;HOME', + 'homefaxnumber' => 'TEL;HOME;FAX', + 'mobilephonenumber' => 'TEL;CELL', + 'carphonenumber' => 'TEL;CAR', + 'pagernumber' => 'TEL;PAGER', + ';;businessstreet;businesscity;businessstate;businesspostalcode;businesscountry' => 'ADR;WORK', + ';;homestreet;homecity;homestate;homepostalcode;homecountry' => 'ADR;HOME', + ';;otherstreet;othercity;otherstate;otherpostalcode;othercountry' => 'ADR', + 'companyname' => 'ORG', + 'body' => 'NOTE', + 'jobtitle' => 'ROLE', + 'webpage' => 'URL', + ); + $data = "BEGIN:VCARD\nVERSION:2.1\nPRODID:Z-Push\n"; + foreach($mapping as $k => $v){ + $val = ''; + $ks = explode(';', $k); + foreach($ks as $i){ + if(!empty($message->$i)) + $val .= $this->escape($message->$i); + $val.=';'; + } + if(empty($val)) + continue; + $val = substr($val,0,-1); + if(strlen($val)>50){ + $data .= $v.":\n\t".substr(chunk_split($val, 50, "\n\t"), 0, -1); + }else{ + $data .= $v.':'.$val."\n"; + } + } + if(!empty($message->categories)) + $data .= 'CATEGORIES:'.implode(',', $this->escape($message->categories))."\n"; + if(!empty($message->picture)) + $data .= 'PHOTO;ENCODING=BASE64;TYPE=JPEG:'."\n\t".substr(chunk_split($message->picture, 50, "\n\t"), 0, -1); + if(isset($message->birthday)) + $data .= 'BDAY:'.date('Y-m-d', $message->birthday)."\n"; + $data .= "END:VCARD"; + +// not supported: anniversary, assistantname, assistnamephonenumber, children, department, officelocation, radiophonenumber, spouse, rtf + + if(!$id){ + if(!empty($message->fileas)){ + $name = u2wi($message->fileas); + }elseif(!empty($message->lastname)){ + $name = $name = u2wi($message->lastname); + }elseif(!empty($message->firstname)){ + $name = $name = u2wi($message->firstname); + }elseif(!empty($message->companyname)){ + $name = $name = u2wi($message->companyname); + }else{ + $name = 'unknown'; + } + $name = preg_replace('/[^a-z0-9 _-]/i', '', $name); + $id = $name.'.vcf'; + $i = 0; + while(file_exists($this->getPath().'/'.$id)){ + $i++; + $id = $name.$i.'.vcf'; + } + } + file_put_contents($this->getPath().'/'.$id, $data); + return $this->StatMessage($folderid, $id); + } + + /** + * Changes the 'read' flag of a message on disk + * + * @param string $folderid id of the folder + * @param string $id id of the message + * @param int $flags read flag of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function SetReadFlag($folderid, $id, $flags) { + return false; + } + + /** + * Called when the user has requested to delete (really delete) a message + * + * @param string $folderid id of the folder + * @param string $id id of the message + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_STATUS_* exceptions + */ + public function DeleteMessage($folderid, $id) { + return unlink($this->getPath() . '/' . $id); + } + + /** + * Called when the user moves an item on the PDA from one folder to another + * not implemented + * + * @param string $folderid id of the source folder + * @param string $id id of the message + * @param string $newfolderid id of the destination folder + * + * @access public + * @return boolean status of the operation + * @throws StatusException could throw specific SYNC_MOVEITEMSSTATUS_* exceptions + */ + public function MoveMessage($folderid, $id, $newfolderid) { + return false; + } + + + /**---------------------------------------------------------------------------------------------------------- + * private vcard-specific internals + */ + + /** + * The path we're working on + * + * @access private + * @return string + */ + private function getPath() { + return str_replace('%u', $this->store, VCARDDIR_DIR); + } + + /** + * Escapes a string + * + * @param string $data string to be escaped + * + * @access private + * @return string + */ + function escape($data){ + if (is_array($data)) { + foreach ($data as $key => $val) { + $data[$key] = $this->escape($val); + } + return $data; + } + $data = str_replace("\r\n", "\n", $data); + $data = str_replace("\r", "\n", $data); + $data = str_replace(array('\\', ';', ',', "\n"), array('\\\\', '\\;', '\\,', '\\n'), $data); + return u2wi($data); + } + + /** + * Un-escapes a string + * + * @param string $data string to be un-escaped + * + * @access private + * @return string + */ + function unescape($data){ + $data = str_replace(array('\\\\', '\\;', '\\,', '\\n','\\N'),array('\\', ';', ',', "\n", "\n"),$data); + return $data; + } +}; +?> \ No newline at end of file diff --git a/z-push/config.php b/z-push/config.php new file mode 100644 index 0000000..fb7d68c --- /dev/null +++ b/z-push/config.php @@ -0,0 +1,308 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +/********************************************************************************** + * Default settings + */ + // Defines the default time zone, change e.g. to "Europe/London" if necessary + define('TIMEZONE', ''); + + // Defines the base path on the server + define('BASE_PATH', dirname($_SERVER['SCRIPT_FILENAME']). '/'); + + // Try to set unlimited timeout + define('SCRIPT_TIMEOUT', 0); + + //Max size of attachments to display inline. Default is 2 MB + define('MAX_EMBEDDED_SIZE', 2097152); + + +/********************************************************************************** + * Include the php-addressbook config.php + */ + include(dirname(__FILE__).DIRECTORY_SEPARATOR.".." + .DIRECTORY_SEPARATOR."config" + .DIRECTORY_SEPARATOR."config.php"); + include(dirname(__FILE__).DIRECTORY_SEPARATOR.".." + .DIRECTORY_SEPARATOR."config" + .DIRECTORY_SEPARATOR."cfg.zpush.php"); + +/********************************************************************************** + * Default FileStateMachine settings + */ + define('STATE_DIR', $zpush_states_dir); + + +/********************************************************************************** + * Logging settings + */ + define('LOGFILEDIR', $zpush_logs_dir); + define('LOGFILE', LOGFILEDIR . 'z-push.log'); + define('LOGERRORFILE', LOGFILEDIR . 'z-push-error.log'); + define('LOGLEVEL', $zpush_logs_level); + define('LOGAUTHFAIL', false); + + + // To save e.g. WBXML data only for selected users, add the usernames to the array + // The data will be saved into a dedicated file per user in the LOGFILEDIR + define('LOGUSERLEVEL', $zpush_log_users_level); + $specialLogUsers = $zpush_log_users; + + +/********************************************************************************** + * Mobile settings + */ + // Device Provisioning + define('PROVISIONING', true); + + // This option allows the 'loose enforcement' of the provisioning policies for older + // devices which don't support provisioning (like WM 5 and HTC Android Mail) - dw2412 contribution + // false (default) - Enforce provisioning for all devices + // true - allow older devices, but enforce policies on devices which support it + define('LOOSE_PROVISIONING', false); + + // Default conflict preference + // Some devices allow to set if the server or PIM (mobile) + // should win in case of a synchronization conflict + // SYNC_CONFLICT_OVERWRITE_SERVER - Server is overwritten, PIM wins + // SYNC_CONFLICT_OVERWRITE_PIM - PIM is overwritten, Server wins (default) + define('SYNC_CONFLICT_DEFAULT', SYNC_CONFLICT_OVERWRITE_PIM); + + // Global limitation of items to be synchronized + // The mobile can define a sync back period for calendar and email items + // For large stores with many items the time period could be limited to a max value + // If the mobile transmits a wider time period, the defined max value is used + // Applicable values: + // SYNC_FILTERTYPE_ALL (default, no limitation) + // SYNC_FILTERTYPE_1DAY, SYNC_FILTERTYPE_3DAYS, SYNC_FILTERTYPE_1WEEK, SYNC_FILTERTYPE_2WEEKS, + // SYNC_FILTERTYPE_1MONTH, SYNC_FILTERTYPE_3MONTHS, SYNC_FILTERTYPE_6MONTHS + define('SYNC_FILTERTIME_MAX', SYNC_FILTERTYPE_ALL); + + // Interval in seconds before checking if there are changes on the server when in Ping. + // It means the highest time span before a change is pushed to a mobile. Set it to + // a higher value if you have a high load on the server. + define('PING_INTERVAL', 30); + + + // Set the fileas order contacts. Possible values are: + // SYNC_FILEAS_FIRSTLAST - fileas will be "Firstname Middlename Lastname" + // SYNC_FILEAS_LASTFIRST - fileas will be "Lastname, Firstname Middlename" + // SYNC_FILEAS_COMPANYONLY - fileas will be "Company" + // SYNC_FILEAS_COMPANYLAST - fileas will be "Company (Lastname, Firstname Middlename)" + // SYNC_FILEAS_COMPANYFIRST - fileas will be "Company (Firstname Middlename Lastname)" + // SYNC_FILEAS_LASTCOMPANY - fileas will be "Lastname, Firstname Middlename (Company)" + // SYNC_FILEAS_FIRSTCOMPANY - fileas will be "Firstname Middlename Lastname (Company)" + // The company-fileas will only be set if a contact has a company set. If one of + // company-fileas is selected and a contact doesn't have a company set, it will default + // to SYNC_FILEAS_FIRSTLAST or SYNC_FILEAS_LASTFIRST (depending on if last or first + // option is selected for company). + // If SYNC_FILEAS_COMPANYONLY is selected and company of the contact is not set + // SYNC_FILEAS_FIRSTLAST will be used + define('FILEAS_ORDER', SYNC_FILEAS_FIRSTLAST); + +/********************************************************************************** + * Backend settings + */ + // The data providers that we are using (see configuration below) + define('BACKEND_PROVIDER', "BackendPhpaddressbook"); +// define('BACKEND_PROVIDER', "BackendVCardDir"); +// define('BACKEND_PROVIDER', "BackendZarafa"); +// +// +// // ************************ +// // BackendZarafa settings +// // ************************ +// // Defines the server to which we want to connect +// define('MAPI_SERVER', 'file:///var/run/zarafa'); +// +// +// // ************************ +// // BackendIMAP settings +// // ************************ +// // Defines the server to which we want to connect +// define('IMAP_SERVER', 'localhost'); +// // connecting to default port (143) +// define('IMAP_PORT', 143); +// // best cross-platform compatibility (see http://php.net/imap_open for options) +// define('IMAP_OPTIONS', '/notls/norsh'); +// // overwrite the "from" header if it isn't set when sending emails +// // options: 'username' - the username will be set (usefull if your login is equal to your emailaddress) +// // 'domain' - the value of the "domain" field is used +// // '@mydomain.com' - the username is used and the given string will be appended +// define('IMAP_DEFAULTFROM', ''); +// // copy outgoing mail to this folder. If not set z-push will try the default folders +// define('IMAP_SENTFOLDER', ''); +// // forward messages inline (default false - as attachment) +// define('IMAP_INLINE_FORWARD', false); +// // use imap_mail() to send emails (default) - if false mail() is used +// define('IMAP_USE_IMAPMAIL', true); +// +// +// // ************************ +// // BackendMaildir settings +// // ************************ +// define('MAILDIR_BASE', '/tmp'); +// define('MAILDIR_SUBDIR', 'Maildir'); +// +// // ********************** +// // BackendVCardDir settings +// // ********************** +// define('VCARDDIR_DIR', '/home/www/z-push_vcards'); + + + // ********************** + // BackendPhpaddressbook settings + // ********************** + + // + // Define the tablenames, + // if not defined in "config.php" + if(!isset($table)) $table = "addressbook"; + if(!isset($month_lookup)) $month_lookup = "month_lookup"; + if(!isset($table_groups)) $table_groups = "group_list"; + if(!isset($table_grp_adr)) $table_grp_adr = "address_in_groups"; + if(!isset($usertable)) $usertable = "users"; + +// Apply the table prefix, if available +$table = $table_prefix.$table; +$month_lookup = $table_prefix.$month_lookup; +$table_groups = $table_prefix.$table_groups; +$table_grp_adr = $table_prefix.$table_grp_adr; +$usertable = $table_prefix.$usertable; + +// Assemble the statements +if(true || $group_name == "") { + $base_select = " * "; + $base_from = $table; + } else { + + if($group_name == "[none]" || $group_name == "[no group]") { + $base_select = " * "; + $base_from = "$table"; + $base_where .= "AND $table.id not in (select distinct id from $table_grp_adr)"; + } elseif(isset($_REQUEST['nosubgroups']) ) { + $base_select = " * "; + $base_from = "$table_grp_adr, $table_groups, $table"; + $base_where .= "AND $table.id = $table_grp_adr.id " + ."AND $table_grp_adr.group_id = $table_groups.group_id " + ."AND $table_groups.group_name = '$group_name'"; + } else { + $base_select = "DISTINCT $table.*"; + $base_from = "$table_grp_adr, $sql_from, $table"; + $base_where .= "AND $table.id = $table_grp_adr.id " + ."AND $table_grp_adr.group_id = g0.group_id " + ."AND ($sql_where)"; + } + } +$domain_id = 0; + + +/********************************************************************************** + * Search provider settings + * + * Alternative backend to perform SEARCH requests (GAL search) + * By default the main Backend defines the preferred search functionality. + * If set, the Search Provider will always be preferred. + * Use 'BackendSearchLDAP' to search in a LDAP directory (see backend/searchldap/config.php) + */ + define('SEARCH_PROVIDER', ''); + // Time in seconds for the server search. Setting it too high might result in timeout. + // Setting it too low might not return all results. Default is 10. + define('SEARCH_WAIT', 10); + // The maximum number of results to send to the client. Setting it too high + // might result in timeout. Default is 10. + define('SEARCH_MAXRESULTS', 10); + + +/********************************************************************************** + * Synchronize additional folders to all mobiles + * + * With this feature, special folders can be synchronized to all mobiles. + * This is useful for e.g. global company contacts. + * + * This feature is supported only by certain devices, like iPhones. + * Check the compatibility list for supported devices: + * http://z-push.sf.net/compatibility + * + * To synchronize a folder, add a section setting all parameters as below: + * store: the ressource where the folder is located. + * Zarafa users use 'SYSTEM' for the 'Public Folder' + * folderid: folder id of the folder to be synchronized + * name: name to be displayed on the mobile device + * type: supported types are: + * SYNC_FOLDER_TYPE_USER_CONTACT + * SYNC_FOLDER_TYPE_USER_APPOINTMENT + * SYNC_FOLDER_TYPE_USER_TASK + * SYNC_FOLDER_TYPE_USER_MAIL + * + * Additional notes: + * - on Zarafa systems use backend/zarafa/listfolders.php script to get a list + * of available folders + * + * - all Z-Push users must have full writing permissions (secretary rights) so + * the configured folders can be synchronized to the mobile + * + * - this feature is only partly suitable for multi-tenancy environments, + * as ALL users from ALL tenents need access to the configured store & folder. + * When configuring a public folder, this will cause problems, as each user has + * a different public folder in his tenant, so the folder are not available. + + * - changing this configuration could cause HIGH LOAD on the system, as all + * connected devices will be updated and load the data contained in the + * added/modified folders. + */ + + $additionalFolders = array( + // demo entry for the synchronization of contacts from the public folder. + // uncomment (remove '/*' '*/') and fill in the folderid +/* + array( + 'store' => "SYSTEM", + 'folderid' => "", + 'name' => "Public Contacts", + 'type' => SYNC_FOLDER_TYPE_USER_CONTACT, + ), +*/ + ); + +?> \ No newline at end of file diff --git a/z-push/include/mimeDecode.php b/z-push/include/mimeDecode.php new file mode 100644 index 0000000..23a9367 --- /dev/null +++ b/z-push/include/mimeDecode.php @@ -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$ + * @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 \ No newline at end of file diff --git a/z-push/include/stringstreamwrapper.php b/z-push/include/stringstreamwrapper.php new file mode 100644 index 0000000..7b26c7c --- /dev/null +++ b/z-push/include/stringstreamwrapper.php @@ -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") + +?> \ No newline at end of file diff --git a/z-push/include/z_RFC822.php b/z-push/include/z_RFC822.php new file mode 100644 index 0000000..3d02d3d --- /dev/null +++ b/z-push/include/z_RFC822.php @@ -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$ + * @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; + } +} \ No newline at end of file diff --git a/z-push/index.php b/z-push/index.php new file mode 100644 index 0000000..8abd2c2 --- /dev/null +++ b/z-push/index.php @@ -0,0 +1,284 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +ob_start(null, 1048576); + +// ignore user abortions because this can lead to weird errors - see ZP-239 +ignore_user_abort(true); + +include_once('lib/exceptions/exceptions.php'); +include_once('lib/utils/utils.php'); +include_once('lib/utils/compat.php'); +include_once('lib/utils/timezoneutil.php'); +include_once('lib/core/zpushdefs.php'); +include_once('lib/core/stateobject.php'); +include_once('lib/core/interprocessdata.php'); +include_once('lib/core/pingtracking.php'); +include_once('lib/core/topcollector.php'); +include_once('lib/core/loopdetection.php'); +include_once('lib/core/asdevice.php'); +include_once('lib/core/statemanager.php'); +include_once('lib/core/devicemanager.php'); +include_once('lib/core/zpush.php'); +include_once('lib/core/zlog.php'); +include_once('lib/core/paddingfilter.php'); +include_once('lib/interface/ibackend.php'); +include_once('lib/interface/ichanges.php'); +include_once('lib/interface/iexportchanges.php'); +include_once('lib/interface/iimportchanges.php'); +include_once('lib/interface/isearchprovider.php'); +include_once('lib/interface/istatemachine.php'); +include_once('lib/core/streamer.php'); +include_once('lib/core/streamimporter.php'); +include_once('lib/core/synccollections.php'); +include_once('lib/core/hierarchycache.php'); +include_once('lib/core/changesmemorywrapper.php'); +include_once('lib/core/syncparameters.php'); +include_once('lib/core/bodypreference.php'); +include_once('lib/core/contentparameters.php'); +include_once('lib/wbxml/wbxmldefs.php'); +include_once('lib/wbxml/wbxmldecoder.php'); +include_once('lib/wbxml/wbxmlencoder.php'); +include_once('lib/syncobjects/syncobject.php'); +include_once('lib/syncobjects/syncbasebody.php'); +include_once('lib/syncobjects/syncbaseattachment.php'); +include_once('lib/syncobjects/syncmailflags.php'); +include_once('lib/syncobjects/syncrecurrence.php'); +include_once('lib/syncobjects/syncappointment.php'); +include_once('lib/syncobjects/syncappointmentexception.php'); +include_once('lib/syncobjects/syncattachment.php'); +include_once('lib/syncobjects/syncattendee.php'); +include_once('lib/syncobjects/syncmeetingrequestrecurrence.php'); +include_once('lib/syncobjects/syncmeetingrequest.php'); +include_once('lib/syncobjects/syncmail.php'); +include_once('lib/syncobjects/syncnote.php'); +include_once('lib/syncobjects/synccontact.php'); +include_once('lib/syncobjects/syncfolder.php'); +include_once('lib/syncobjects/syncprovisioning.php'); +include_once('lib/syncobjects/synctaskrecurrence.php'); +include_once('lib/syncobjects/synctask.php'); +include_once('lib/syncobjects/syncoofmessage.php'); +include_once('lib/syncobjects/syncoof.php'); +include_once('lib/syncobjects/syncuserinformation.php'); +include_once('lib/syncobjects/syncdeviceinformation.php'); +include_once('lib/syncobjects/syncdevicepassword.php'); +include_once('lib/syncobjects/syncitemoperationsattachment.php'); +include_once('lib/syncobjects/syncsendmail.php'); +include_once('lib/syncobjects/syncsendmailsource.php'); +include_once('lib/default/backend.php'); +include_once('lib/default/searchprovider.php'); +include_once('lib/request/request.php'); +include_once('lib/request/requestprocessor.php'); + +include_once('config.php'); +include_once('version.php'); + + + // Attempt to set maximum execution time + ini_set('max_execution_time', SCRIPT_TIMEOUT); + set_time_limit(SCRIPT_TIMEOUT); + + try { + // check config & initialize the basics + ZPush::CheckConfig(); + Request::Initialize(); + ZLog::Initialize(); + + ZLog::Write(LOGLEVEL_DEBUG,"-------- Start"); + ZLog::Write(LOGLEVEL_INFO, + sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='%s' devId='%s' devType='%s'", + @constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(), + Request::GetCommand(), Request::GetGETUser(), Request::GetDeviceID(), Request::GetDeviceType())); + + // Stop here if this is an OPTIONS request + if (Request::IsMethodOPTIONS()) + throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); + + ZPush::CheckAdvancedConfig(); + + // Process request headers and look for AS headers + Request::ProcessHeaders(); + + // Check required GET parameters + if(Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetGETUser() || !Request::GetDeviceID() || !Request::GetDeviceType())) + throw new FatalException("Requested the Z-Push URL without the required GET parameters"); + + // Load the backend + $backend = ZPush::GetBackend(); + + // always request the authorization header + if (! Request::AuthenticationInfo()) + throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); + + // check the provisioning information + if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) && + ((Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0) || ZPush::GetDeviceManager()->ProvisioningRequired(Request::GetPolicyKey())) && + (LOOSE_PROVISIONING === false || + (LOOSE_PROVISIONING === true && Request::WasPolicyKeySent()))) + //TODO for AS 14 send a wbxml response + throw new ProvisioningRequiredException(); + + // most commands require an authenticated user + if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode())) + RequestProcessor::Authenticate(); + + // Do the actual processing of the request + if (Request::IsMethodGET()) + throw new NoPostRequestException("This is the Z-Push location and can only be accessed by Microsoft ActiveSync-capable devices", NoPostRequestException::GET_REQUEST); + + // Do the actual request + header(ZPush::GetServerHeader()); + + // announce the supported AS versions (if not already sent to device) + if (ZPush::GetDeviceManager()->AnnounceASVersion()) { + $versions = ZPush::GetSupportedProtocolVersions(true); + ZLog::Write(LOGLEVEL_INFO, sprintf("Announcing latest AS version to device: %s", $versions)); + header("X-MS-RP: ". $versions); + } + + RequestProcessor::Initialize(); + if(!RequestProcessor::HandleRequest()) + throw new WBXMLException(ZLog::GetWBXMLDebugInfo()); + + // stream the data + $len = ob_get_length(); + $data = ob_get_contents(); + ob_end_clean(); + + // log amount of data transferred + // TODO check $len when streaming more data (e.g. Attachments), as the data will be send chunked + ZPush::GetDeviceManager()->SentData($len); + + // Unfortunately, even though Z-Push can stream the data to the client + // with a chunked encoding, using chunked encoding breaks the progress bar + // on the PDA. So the data is de-chunk here, written a content-length header and + // data send as a 'normal' packet. If the output packet exceeds 1MB (see ob_start) + // then it will be sent as a chunked packet anyway because PHP will have to flush + // the buffer. + if(!headers_sent()) + header("Content-Length: $len"); + + // send vnd.ms-sync.wbxml content type header if there is no content + // otherwise text/html content type is added which might break some devices + if ($len == 0) + header("Content-Type: application/vnd.ms-sync.wbxml"); + + print $data; + + // destruct backend after all data is on the stream + $backend->Logoff(); + } + + catch (NoPostRequestException $nopostex) { + if ($nopostex->getCode() == NoPostRequestException::OPTIONS_REQUEST) { + header(ZPush::GetServerHeader()); + header(ZPush::GetSupportedProtocolVersions()); + header(ZPush::GetSupportedCommands()); + ZLog::Write(LOGLEVEL_INFO, $nopostex->getMessage()); + } + else if ($nopostex->getCode() == NoPostRequestException::GET_REQUEST) { + if (Request::GetUserAgent()) + ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); + if (!headers_sent() && $nopostex->showLegalNotice()) + ZPush::PrintZPushLegal('GET not supported', $nopostex->getMessage()); + } + } + + catch (Exception $ex) { + if (Request::GetUserAgent()) + ZLog::Write(LOGLEVEL_INFO, sprintf("User-agent: '%s'", Request::GetUserAgent())); + $exclass = get_class($ex); + + if(!headers_sent()) { + if ($ex instanceof ZPushException) { + header('HTTP/1.1 '. $ex->getHTTPCodeString()); + foreach ($ex->getHTTPHeaders() as $h) + header($h); + } + // something really unexpected happened! + else + header('HTTP/1.1 500 Internal Server Error'); + } + else + ZLog::Write(LOGLEVEL_FATAL, "Exception: ($exclass) - headers were already sent. Message: ". $ex->getMessage()); + + if ($ex instanceof AuthenticationRequiredException) { + ZPush::PrintZPushLegal($exclass, sprintf('
    %s
    ',$ex->getMessage())); + + // log the failed login attemt e.g. for fail2ban + if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false) + ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser()? Request::GetAuthUser(): Request::GetGETUser() )); + } + + // This could be a WBXML problem.. try to get the complete request + else if ($ex instanceof WBXMLException) { + ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this."); + } + + // Try to output some kind of error information. This is only possible if + // the output had not started yet. If it has started already, we can't show the user the error, and + // the device will give its own (useless) error message. + else if (!($ex instanceof ZPushException) || $ex->showLegalNotice()) { + $cmdinfo = (Request::GetCommand())? sprintf(" processing command %s", Request::GetCommand()): ""; + $extrace = $ex->getTrace(); + $trace = (!empty($extrace))? "\n\nTrace:\n". print_r($extrace,1):""; + ZPush::PrintZPushLegal($exclass . $cmdinfo, sprintf('
    %s
    ',$ex->getMessage() . $trace)); + } + + // Announce exception to process loop detection + if (ZPush::GetDeviceManager(false)) + ZPush::GetDeviceManager()->AnnounceProcessException($ex); + + // Announce exception if the TopCollector if available + ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); + } + + // save device data if the DeviceManager is available + if (ZPush::GetDeviceManager(false)) + ZPush::GetDeviceManager()->Save(); + + // end gracefully + ZLog::Write(LOGLEVEL_DEBUG, '-------- End'); +?> \ No newline at end of file diff --git a/z-push/lib/core/asdevice.php b/z-push/lib/core/asdevice.php new file mode 100644 index 0000000..62d960e --- /dev/null +++ b/z-push/lib/core/asdevice.php @@ -0,0 +1,641 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class ASDevice extends StateObject { + const UNDEFINED = -1; + // content data + const FOLDERUUID = 1; + const FOLDERTYPE = 2; + const FOLDERSUPPORTEDFIELDS = 3; + + // expected values for not set member variables + protected $unsetdata = array( + 'useragenthistory' => array(), + 'hierarchyuuid' => false, + 'contentdata' => array(), + 'wipestatus' => SYNC_PROVISION_RWSTATUS_NA, + 'wiperequestedby' => false, + 'wiperequestedon' => false, + 'wipeactionon' => false, + 'lastupdatetime' => 0, + 'conversationmode' => false, + 'policies' => array(), + 'policykey' => self::UNDEFINED, + 'forcesave' => false, + 'asversion' => false, + 'ignoredmessages' => array(), + 'announcedASversion' => false, + ); + + static private $loadedData; + protected $newdevice; + protected $hierarchyCache; + protected $ignoredMessageIds; + + /** + * AS Device constructor + * + * @param string $devid + * @param string $devicetype + * @param string $getuser + * @param string $useragent + * + * @access public + * @return + */ + public function ASDevice($devid, $devicetype, $getuser, $useragent) { + $this->deviceid = $devid; + $this->devicetype = $devicetype; + list ($this->deviceuser, $this->domain) = Utils::SplitDomainUser($getuser); + $this->useragent = $useragent; + $this->firstsynctime = time(); + $this->newdevice = true; + $this->ignoredMessageIds = array(); + } + + /** + * initializes the ASDevice with previousily saved data + * + * @param mixed $stateObject the StateObject containing the device data + * @param boolean $semanticUpdate indicates if data relevant for all users should be cross checked (e.g. wipe requests) + * + * @access public + * @return + */ + public function SetData($stateObject, $semanticUpdate = true) { + if (!($stateObject instanceof StateObject) || !isset($stateObject->devices) || !is_array($stateObject->devices)) return; + + // is information about this device & user available? + if (isset($stateObject->devices[$this->deviceuser]) && $stateObject->devices[$this->deviceuser] instanceof ASDevice) { + // overwrite local data with data from the saved object + $this->SetDataArray($stateObject->devices[$this->deviceuser]->GetDataArray()); + $this->newdevice = false; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ASDevice data loaded for user: '%s'", $this->deviceuser)); + } + + // check if RWStatus from another user on same device may require action + if ($semanticUpdate && count($stateObject->devices) > 1) { + foreach ($stateObject->devices as $user=>$asuserdata) { + if ($user == $this->user) continue; + + // another user has a required action on this device + if (isset($asuserdata->wipeStatus) && $asuserdata->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) { + ZLog::Write(LOGLEVEL_INFO, sprintf("User '%s' has requested a remote wipe for this device on '%s'", $asuserdata->wipeRequestBy, strftime("%Y-%m-%d %H:%M", $asuserdata->wipeRequstOn))); + + // reset status to PENDING if wipe was executed before + $this->wipeStatus = ($asuserdata->wipeStatus & SYNC_PROVISION_RWSTATUS_WIPED)?SYNC_PROVISION_RWSTATUS_PENDING:$asuserdata->wipeStatus; + $this->wipeRequestBy = $asuserdata->wipeRequestBy; + $this->wipeRequestOn = $asuserdata->wipeRequestOn; + $this->wipeActionOn = $asuserdata->wipeActionOn; + break; + } + } + } + + self::$loadedData = $stateObject; + $this->changed = false; + } + + /** + * Returns the current AS Device in it's StateObject + * If the data was not changed, it returns false (no need to update any data) + * + * @access public + * @return array/boolean + */ + public function GetData() { + if (! $this->changed) + return false; + + // device was updated + $this->lastupdatetime = time(); + unset($this->ignoredMessageIds); + + if (!isset(self::$loadedData) || !isset(self::$loadedData->devices) || !is_array(self::$loadedData->devices)) { + self::$loadedData = new StateObject(); + $devices = array(); + } + else + $devices = self::$loadedData->devices; + + $devices[$this->deviceuser] = $this; + + // check if RWStatus has to be updated so it can be updated for other users on same device + if (isset($this->wipeStatus) && $this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK) { + foreach ($devices as $user=>$asuserdata) { + if ($user == $this->deviceuser) continue; + if (isset($this->wipeStatus)) $asuserdata->wipeStatus = $this->wipeStatus; + if (isset($this->wipeRequestBy)) $asuserdata->wipeRequestBy = $this->wipeRequestBy; + if (isset($this->wipeRequestOn)) $asuserdata->wipeRequestOn = $this->wipeRequestOn; + if (isset($this->wipeActionOn)) $asuserdata->wipeActionOn = $this->wipeActionOn; + $devices[$user] = $asuserdata; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Updated remote wipe status for user '%s' on the same device", $user)); + } + } + self::$loadedData->devices = $devices; + return self::$loadedData; + } + + /** + * Removes internal data from the object, so this data can not be exposed + * + * @access public + * @return boolean + */ + public function StripData() { + unset($this->changed); + unset($this->unsetdata); + unset($this->hierarchyCache); + unset($this->forceSave); + unset($this->newdevice); + unset($this->ignoredMessageIds); + + if (isset($this->ignoredmessages) && is_array($this->ignoredmessages)) { + $imessages = $this->ignoredmessages; + $unserializedMessage = array(); + foreach ($imessages as $im) { + $im->asobject = unserialize($im->asobject); + $im->asobject->StripData(); + $unserializedMessage[] = $im; + } + $this->ignoredmessages = $unserializedMessage; + } + + return true; + } + + /** + * Indicates if the object was just created + * + * @access public + * @return boolean + */ + public function IsNewDevice() { + return (isset($this->newdevice) && $this->newdevice === true); + } + + + /**---------------------------------------------------------------------------------------------------------- + * Non-standard Getter and Setter + */ + + /** + * Returns the user agent of this device + * + * @access public + * @return string + */ + public function GetDeviceUserAgent() { + if (!isset($this->useragent) || !$this->useragent) + return "unknown"; + + return $this->useragent; + } + + /** + * Returns the user agent history of this device + * + * @access public + * @return string + */ + public function GetDeviceUserAgentHistory() { + return $this->useragentHistory; + } + + /** + * Sets the useragent of the current request + * If this value is alreay available, no update is done + * + * @param string $useragent + * + * @access public + * @return boolean + */ + public function SetUserAgent($useragent) { + if ($useragent == $this->useragent || $useragent === false || $useragent === Request::UNKNOWN) + return true; + + // save the old user agent, if available + if ($this->useragent != "") { + // [] = changedate, previous user agent + $a = $this->useragentHistory; + $a[] = array(time(), $this->useragent); + $this->useragentHistory = $a; + } + $this->useragent = $useragent; + return true; + } + + /** + * Sets the current remote wipe status + * + * @param int $status + * @param string $requestedBy + * @access public + * @return int + */ + public function SetWipeStatus($status, $requestedBy = false) { + // force saving the updated information if there was a transition between the wiping status + if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_OK && $status > SYNC_PROVISION_RWSTATUS_OK) + $this->forceSave = true; + + if ($requestedBy != false) { + $this->wipeRequestedBy = $requestedBy; + $this->wipeRequestedOn = time(); + } + else { + $this->wipeActionOn = time(); + } + + $this->wipeStatus = $status; + + if ($this->wipeStatus > SYNC_PROVISION_RWSTATUS_PENDING) + ZLog::Write(LOGLEVEL_INFO, sprintf("ASDevice id '%s' was %s remote wiped on %s. Action requested by user '%s' on %s", + $this->deviceid, ($this->wipeStatus == SYNC_PROVISION_RWSTATUS_REQUESTED ? "requested to be": "sucessfully"), + strftime("%Y-%m-%d %H:%M", $this->wipeActionOn), $this->wipeRequestedBy, strftime("%Y-%m-%d %H:%M", $this->wipeRequestedOn))); + } + + /** + * Sets the deployed policy key + * + * @param int $policykey + * + * @access public + * @return + */ + public function SetPolicyKey($policykey) { + $this->policykey = $policykey; + if ($this->GetWipeStatus() == SYNC_PROVISION_RWSTATUS_NA) + $this->wipeStatus = SYNC_PROVISION_RWSTATUS_OK; + } + + /** + * Adds a messages which was ignored to the device data + * + * @param StateObject $ignoredMessage + * + * @access public + * @return boolean + */ + public function AddIgnoredMessage($ignoredMessage) { + // we should have all previousily ignored messages in an id array + if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) { + foreach($this->ignoredMessages as $oldMessage) { + if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) + $this->ignoredMessageIds[$oldMessage->folderid] = array(); + $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id; + } + } + + // serialize the AS object - if available + if (isset($ignoredMessage->asobject)) + $ignoredMessage->asobject = serialize($ignoredMessage->asobject); + + // try not to add the same message several times + if (isset($ignoredMessage->folderid) && isset($ignoredMessage->id)) { + if (!isset($this->ignoredMessageIds[$ignoredMessage->folderid])) + $this->ignoredMessageIds[$ignoredMessage->folderid] = array(); + + if (in_array($ignoredMessage->id, $this->ignoredMessageIds[$ignoredMessage->folderid])) + $this->RemoveIgnoredMessage($ignoredMessage->folderid, $ignoredMessage->id); + + $this->ignoredMessageIds[$ignoredMessage->folderid][] = $ignoredMessage->id; + $msges = $this->ignoredMessages; + $msges[] = $ignoredMessage; + $this->ignoredMessages = $msges; + + return true; + } + else { + $msges = $this->ignoredMessages; + $msges[] = $ignoredMessage; + $this->ignoredMessages = $msges; + ZLog::Write(LOGLEVEL_WARN, "ASDevice->AddIgnoredMessage(): added message has no folder/id"); + return true; + } + } + + /** + * Removes message in the list of ignored messages + * + * @param string $folderid parent folder id of the message + * @param string $id message id + * + * @access public + * @return boolean + */ + public function RemoveIgnoredMessage($folderid, $id) { + // we should have all previousily ignored messages in an id array + if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) { + foreach($this->ignoredMessages as $oldMessage) { + if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) + $this->ignoredMessageIds[$oldMessage->folderid] = array(); + $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id; + } + } + + $foundMessage = false; + // there are ignored messages in that folder + if (isset($this->ignoredMessageIds[$folderid])) { + // resync of a folder.. we should remove all previousily ignored messages + if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) { + $ignored = $this->ignoredMessages; + $newMessages = array(); + foreach ($ignored as $im) { + if ($im->folderid = $folderid) { + if ($id === false || $im->id === $id) { + $foundMessage = true; + if (count($this->ignoredMessageIds[$folderid]) == 1) { + unset($this->ignoredMessageIds[$folderid]); + } + else { + unset($this->ignoredMessageIds[$folderid][array_search($id, $this->ignoredMessageIds[$folderid])]); + } + continue; + } + else + $newMessages[] = $im; + } + } + $this->ignoredMessages = $newMessages; + } + } + + return $foundMessage; + } + + /** + * Indicates if a message is in the list of ignored messages + * + * @param string $folderid parent folder id of the message + * @param string $id message id + * + * @access public + * @return boolean + */ + public function HasIgnoredMessage($folderid, $id) { + // we should have all previousily ignored messages in an id array + if (count($this->ignoredMessages) != count($this->ignoredMessageIds)) { + foreach($this->ignoredMessages as $oldMessage) { + if (!isset($this->ignoredMessageIds[$oldMessage->folderid])) + $this->ignoredMessageIds[$oldMessage->folderid] = array(); + $this->ignoredMessageIds[$oldMessage->folderid][] = $oldMessage->id; + } + } + + $foundMessage = false; + // there are ignored messages in that folder + if (isset($this->ignoredMessageIds[$folderid])) { + // resync of a folder.. we should remove all previousily ignored messages + if ($id === false || in_array($id, $this->ignoredMessageIds[$folderid], true)) { + $foundMessage = true; + } + } + + return $foundMessage; + } + + /**---------------------------------------------------------------------------------------------------------- + * HierarchyCache and ContentData operations + */ + + /** + * Sets the HierarchyCache + * The hierarchydata, can be: + * - false a new HierarchyCache is initialized + * - array() new HierarchyCache is initialized and data from GetHierarchy is loaded + * - string previousely serialized data is loaded + * + * @param string $hierarchydata (opt) + * + * @access public + * @return boolean + */ + public function SetHierarchyCache($hierarchydata = false) { + if ($hierarchydata !== false && $hierarchydata instanceof ChangesMemoryWrapper) { + $this->hierarchyCache = $hierarchydata; + $this->hierarchyCache->CopyOldState(); + } + else + $this->hierarchyCache = new ChangesMemoryWrapper(); + + if (is_array($hierarchydata)) + return $this->hierarchyCache->ImportFolders($hierarchydata); + return true; + } + + /** + * Returns serialized data of the HierarchyCache + * + * @access public + * @return string + */ + public function GetHierarchyCacheData() { + if (isset($this->hierarchyCache)) + return $this->hierarchyCache; + + ZLog::Write(LOGLEVEL_WARN, "ASDevice->GetHierarchyCacheData() has no data! HierarchyCache probably never initialized."); + return false; + } + + /** + * Returns the HierarchyCache Object + * + * @access public + * @return object HierarchyCache + */ + public function GetHierarchyCache() { + if (!isset($this->hierarchyCache)) + $this->SetHierarchyCache(); + + ZLog::Write(LOGLEVEL_DEBUG, "ASDevice->GetHierarchyCache(): ". $this->hierarchyCache->GetStat()); + return $this->hierarchyCache; + } + + /** + * Returns all known folderids + * + * @access public + * @return array + */ + public function GetAllFolderIds() { + if (isset($this->contentData) && is_array($this->contentData)) + return array_keys($this->contentData); + return array(); + } + + /** + * Returns a linked UUID for a folder id + * + * @param string $folderid (opt) if not set, Hierarchy UUID is returned + * + * @access public + * @return string + */ + public function GetFolderUUID($folderid = false) { + if ($folderid === false) + return (isset($this->hierarchyUuid) && $this->hierarchyUuid !== self::UNDEFINED) ? $this->hierarchyUuid : false; + else if (isset($this->contentData) && isset($this->contentData[$folderid]) && isset($this->contentData[$folderid][self::FOLDERUUID])) + return $this->contentData[$folderid][self::FOLDERUUID]; + return false; + } + + /** + * Link a UUID to a folder id + * If a boolean false UUID is sent, the relation is removed + * + * @param string $uuid + * @param string $folderid (opt) if not set Hierarchy UUID is linked + * + * @access public + * @return boolean + */ + public function SetFolderUUID($uuid, $folderid = false) { + if ($folderid === false) { + $this->hierarchyUuid = $uuid; + // when unsetting the hierarchycache, also remove saved contentdata and ignoredmessages + if ($folderid === false) { + $this->contentData = array(); + $this->ignoredMessageIds = array(); + $this->ignoredMessages = array(); + } + } + else { + + $contentData = $this->contentData; + if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid])) + $contentData[$folderid] = array(); + + // check if the foldertype is set. This has to be available at this point, as generated during the first HierarchySync + if (!isset($contentData[$folderid][self::FOLDERTYPE])) + return false; + + if ($uuid) + $contentData[$folderid][self::FOLDERUUID] = $uuid; + else + $contentData[$folderid][self::FOLDERUUID] = false; + + $this->contentData = $contentData; + } + } + + /** + * Returns a foldertype for a folder already known to the mobile + * + * @param string $folderid + * + * @access public + * @return int/boolean returns false if the type is not set + */ + public function GetFolderType($folderid) { + if (isset($this->contentData) && isset($this->contentData[$folderid]) && + isset($this->contentData[$folderid][self::FOLDERTYPE]) ) + + return $this->contentData[$folderid][self::FOLDERTYPE]; + return false; + } + + /** + * Sets the foldertype of a folder id + * + * @param string $uuid + * @param string $folderid (opt) if not set Hierarchy UUID is linked + * + * @access public + * @return boolean true if the type was set or updated + */ + public function SetFolderType($folderid, $foldertype) { + $contentData = $this->contentData; + + if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid])) + $contentData[$folderid] = array(); + if (!isset($contentData[$folderid][self::FOLDERTYPE]) || $contentData[$folderid][self::FOLDERTYPE] != $foldertype ) { + $contentData[$folderid][self::FOLDERTYPE] = $foldertype; + $this->contentData = $contentData; + return true; + } + return false; + } + + /** + * Gets the supported fields transmitted previousely by the device + * for a certain folder + * + * @param string $folderid + * + * @access public + * @return array/boolean false means no supportedFields are available + */ + public function GetSupportedFields($folderid) { + if (isset($this->contentData) && isset($this->contentData[$folderid]) && + isset($this->contentData[$folderid][self::FOLDERUUID]) && $this->contentData[$folderid][self::FOLDERUUID] !== false && + isset($this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS]) ) + + return $this->contentData[$folderid][self::FOLDERSUPPORTEDFIELDS]; + + return false; + } + + /** + * Sets the set of supported fields transmitted by the device for a certain folder + * + * @param string $folderid + * @param array $fieldlist supported fields + * + * @access public + * @return boolean + */ + public function SetSupportedFields($folderid, $fieldlist) { + $contentData = $this->contentData; + if (!isset($contentData[$folderid]) || !is_array($contentData[$folderid])) + $contentData[$folderid] = array(); + + $contentData[$folderid][self::FOLDERSUPPORTEDFIELDS] = $fieldlist; + $this->contentData = $contentData; + return true; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/bodypreference.php b/z-push/lib/core/bodypreference.php new file mode 100644 index 0000000..3238102 --- /dev/null +++ b/z-push/lib/core/bodypreference.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/core/changesmemorywrapper.php b/z-push/lib/core/changesmemorywrapper.php new file mode 100644 index 0000000..2676c64 --- /dev/null +++ b/z-push/lib/core/changesmemorywrapper.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/contentparameters.php b/z-push/lib/core/contentparameters.php new file mode 100644 index 0000000..1344de8 --- /dev/null +++ b/z-push/lib/core/contentparameters.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/devicemanager.php b/z-push/lib/core/devicemanager.php new file mode 100644 index 0000000..33b6960 --- /dev/null +++ b/z-push/lib/core/devicemanager.php @@ -0,0 +1,766 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class DeviceManager { + // stream up to 100 messages to the client by default + const DEFAULTWINDOWSIZE = 100; + + // 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 = self::DEFAULTWINDOWSIZE; + + $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 exported before. + * 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); + } + + /** + * 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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/hierarchycache.php b/z-push/lib/core/hierarchycache.php new file mode 100644 index 0000000..c2fdd2f --- /dev/null +++ b/z-push/lib/core/hierarchycache.php @@ -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"); + } + +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/interprocessdata.php b/z-push/lib/core/interprocessdata.php new file mode 100644 index 0000000..2a9845a --- /dev/null +++ b/z-push/lib/core/interprocessdata.php @@ -0,0 +1,297 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +abstract class InterProcessData { + const CLEANUPTIME = 1; + + static protected $devid; + static protected $pid; + static protected $user; + static protected $start; + protected $type; + protected $allocate; + private $mutexid; + private $memid; + + /** + * Constructor + * + * @access public + */ + public function InterProcessData() { + if (!isset($this->type) || !isset($this->allocate)) + throw new FatalNotImplementedException(sprintf("Class InterProcessData can not be initialized. Subclass %s did not initialize type and allocable memory.", get_class($this))); + + if ($this->InitSharedMem()) + ZLog::Write(LOGLEVEL_DEBUG, sprintf("%s(): Initialized mutexid %s and memid %s.", get_class($this), $this->mutexid, $this->memid)); + } + + /** + * Initializes internal parameters + * + * @access public + * @return boolean + */ + public function InitializeParams() { + if (!isset(self::$devid)) { + self::$devid = Request::GetDeviceID(); + self::$pid = @getmypid(); + self::$user = Request::GetAuthUser(); + self::$start = time(); + } + return true; + } + + /** + * Allocates shared memory + * + * @access private + * @return boolean + */ + private function InitSharedMem() { + // shared mem general "turn off switch" + if (defined("USE_SHARED_MEM") && USE_SHARED_MEM === false) { + ZLog::Write(LOGLEVEL_INFO, "InterProcessData::InitSharedMem(): the usage of shared memory for Z-Push has been disabled. Check your config for 'USE_SHARED_MEM'."); + return false; + } + + if (!function_exists('sem_get') || !function_exists('shm_attach') || !function_exists('sem_acquire')|| !function_exists('shm_get_var')) { + ZLog::Write(LOGLEVEL_INFO, "InterProcessData::InitSharedMem(): PHP libraries for the use shared memory are not available. Functionalities like z-push-top or loop detection are not available. Check your php packages."); + return false; + } + + // Create mutex + $this->mutexid = @sem_get($this->type, 1); + if ($this->mutexid === false) { + ZLog::Write(LOGLEVEL_ERROR, "InterProcessData::InitSharedMem(): could not aquire semaphore"); + return false; + } + + // Attach shared memory + $this->memid = shm_attach($this->type+10, $this->allocate); + if ($this->memid === false) { + ZLog::Write(LOGLEVEL_ERROR, "InterProcessData::InitSharedMem(): could not attach shared memory"); + @sem_remove($this->mutexid); + $this->mutexid = false; + return false; + } + + // TODO mem cleanup has to be implemented + //$this->setInitialCleanTime(); + + return true; + } + + /** + * Removes and detaches shared memory + * + * @access private + * @return boolean + */ + private function RemoveSharedMem() { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) { + @sem_acquire($this->mutexid); + $memid = $this->memid; + $this->memid = false; + @sem_release($this->mutexid); + + @sem_remove($this->mutexid); + @shm_remove($memid); + @shm_detach($memid); + + $this->mutexid = false; + + return true; + } + return false; + } + + /** + * Reinitializes shared memory by removing, detaching and re-allocating it + * + * @access public + * @return boolean + */ + public function ReInitSharedMem() { + return ($this->RemoveSharedMem() && $this->InitSharedMem()); + } + + /** + * Cleans up the shared memory block + * + * @access public + * @return boolean + */ + public function Clean() { + $stat = false; + + // exclusive block + if ($this->blockMutex()) { + $cleanuptime = ($this->hasData(1)) ? $this->getData(1) : false; + + // TODO implement Shared Memory cleanup + + $this->releaseMutex(); + } + // end exclusive block + + return $stat; + } + + /** + * Indicates if the shared memory is active + * + * @access public + * @return boolean + */ + public function IsActive() { + return ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)); + } + + /** + * Blocks the class mutex + * Method blocks until mutex is available! + * ATTENTION: make sure that you *always* release a blocked mutex! + * + * @access protected + * @return boolean + */ + protected function blockMutex() { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) + return @sem_acquire($this->mutexid); + + return false; + } + + /** + * Releases the class mutex + * After the release other processes are able to block the mutex themselfs + * + * @access protected + * @return boolean + */ + protected function releaseMutex() { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) + return @sem_release($this->mutexid); + + return false; + } + + /** + * Indicates if the requested variable is available in shared memory + * + * @param int $id int indicating the variable + * + * @access protected + * @return boolean + */ + protected function hasData($id = 2) { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) { + if (function_exists("shm_has_var")) + return @shm_has_var($this->memid, $id); + else { + $some = $this->getData($id); + return isset($some); + } + } + return false; + } + + /** + * Returns the requested variable from shared memory + * + * @param int $id int indicating the variable + * + * @access protected + * @return mixed + */ + protected function getData($id = 2) { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) + return @shm_get_var($this->memid, $id); + + return ; + } + + /** + * Writes the transmitted variable to shared memory + * Subclasses may never use an id < 2! + * + * @param mixed $data data which should be saved into shared memory + * @param int $id int indicating the variable (bigger than 2!) + * + * @access protected + * @return boolean + */ + protected function setData($data, $id = 2) { + if ((isset($this->mutexid) && $this->mutexid !== false) && (isset($this->memid) && $this->memid !== false)) + return @shm_put_var($this->memid, $id, $data); + + return false; + } + + /** + * Sets the time when the shared memory block was created + * + * @access private + * @return boolean + */ + private function setInitialCleanTime() { + $stat = false; + + // exclusive block + if ($this->blockMutex()) { + + if ($this->hasData(1) == false) + $stat = $this->setData(time(), 1); + + $this->releaseMutex(); + } + // end exclusive block + + return $stat; + } + +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/loopdetection.php b/z-push/lib/core/loopdetection.php new file mode 100644 index 0000000..b2542d2 --- /dev/null +++ b/z-push/lib/core/loopdetection.php @@ -0,0 +1,878 @@ +. +* +* 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; + } + + /** + * 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))) { + ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IsSyncStateObsolete(): yes, counter already processed"); + $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; + + // 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; + } + + // 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(); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/paddingfilter.php b/z-push/lib/core/paddingfilter.php new file mode 100644 index 0000000..ab8345f --- /dev/null +++ b/z-push/lib/core/paddingfilter.php @@ -0,0 +1,101 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +/* Define our filter class + * + * Usage: stream_filter_append($stream, 'padding.X'); + * where X is a number a stream will be padded to be + * multiple of (e.g. padding.3 will pad the stream + * to be multiple of 3 which is useful in base64 + * encoding). + * + * */ +class padding_filter extends php_user_filter { + private $padding = 4; // default padding + + /** + * This method is called whenever data is read from or written to the attached stream + * + * @see php_user_filter::filter() + * + * @param resource $in + * @param resource $out + * @param int $consumed + * @param boolean $closing + * + * @access public + * @return int + * + */ + function filter($in, $out, &$consumed, $closing) { + while ($bucket = stream_bucket_make_writeable($in)) { + if ($this->padding != 0 && $bucket->datalen < 8192) { + $bucket->data .= str_pad($bucket->data, $this->padding, 0x0); + } + $consumed += ($this->padding != 0 && $bucket->datalen < 8192) ? ($bucket->datalen + $this->padding) : $bucket->datalen; + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } + + /** + * Called when creating the filter + * + * @see php_user_filter::onCreate() + * + * @access public + * @return boolean + */ + function onCreate() { + $delim = strrpos($this->filtername, '.'); + if ($delim !== false) { + $padding = substr($this->filtername, $delim + 1); + if (is_numeric($padding)) + $this->padding = $padding; + } + return true; + } +} + +stream_filter_register("padding.*", "padding_filter"); +?> \ No newline at end of file diff --git a/z-push/lib/core/pingtracking.php b/z-push/lib/core/pingtracking.php new file mode 100644 index 0000000..1391e89 --- /dev/null +++ b/z-push/lib/core/pingtracking.php @@ -0,0 +1,156 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class PingTracking extends InterProcessData { + + /** + * Constructor + * + * @access public + */ + public function PingTracking() { + // initialize super parameters + $this->allocate = 512000; // 500 KB + $this->type = 2; + parent::__construct(); + + $this->initPing(); + } + + /** + * Destructor + * Used to remove the current ping data from shared memory + * + * @access public + */ + public function __destruct() { + // exclusive block + if ($this->blockMutex()) { + $pings = $this->getData(); + + // check if our ping is still in the list + if (isset($pings[self::$devid][self::$user][self::$pid])) { + unset($pings[self::$devid][self::$user][self::$pid]); + $stat = $this->setData($pings); + } + + $this->releaseMutex(); + } + // end exclusive block + } + + /** + * Initialized the current request + * + * @access public + * @return boolean + */ + protected function initPing() { + $stat = false; + + // initialize params + $this->InitializeParams(); + + // exclusive block + if ($this->blockMutex()) { + $pings = ($this->hasData()) ? $this->getData() : array(); + + // set the start time for the current process + $this->checkArrayStructure($pings); + $pings[self::$devid][self::$user][self::$pid] = self::$start; + $stat = $this->setData($pings); + $this->releaseMutex(); + } + // end exclusive block + + return $stat; + } + + /** + * Checks if there are newer ping requests for the same device & user so + * the current process could be terminated + * + * @access public + * @return boolean true if the current process is obsolete + */ + public function DoForcePingTimeout() { + $pings = false; + // exclusive block + if ($this->blockMutex()) { + $pings = $this->getData(); + $this->releaseMutex(); + } + // end exclusive block + + // check if there is another (and newer) active ping connection + if (is_array($pings) && isset($pings[self::$devid][self::$user]) && count($pings[self::$devid][self::$user]) > 1) { + foreach ($pings[self::$devid][self::$user] as $pid=>$starttime) + if ($starttime > self::$start) + return true; + } + + return false; + } + + /** + * Builds an array structure for the concurrent ping connection detection + * + * @param array $array reference to the ping data array + * + * @access private + * @return + */ + private function checkArrayStructure(&$array) { + if (!is_array($array)) + $array = array(); + + if (!isset($array[self::$devid])) + $array[self::$devid] = array(); + + if (!isset($array[self::$devid][self::$user])) + $array[self::$devid][self::$user] = array(); + + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/statemanager.php b/z-push/lib/core/statemanager.php new file mode 100644 index 0000000..933cfcf --- /dev/null +++ b/z-push/lib/core/statemanager.php @@ -0,0 +1,530 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class StateManager { + const FIXEDHIERARCHYCOUNTER = 99999; + + // backend storage types + const BACKENDSTORAGE_PERMANENT = 1; + const BACKENDSTORAGE_STATE = 2; + + private $statemachine; + private $device; + private $hierarchyOperation = false; + private $deleteOldStates = false; + + private $foldertype; + private $uuid; + private $oldStateCounter; + private $newStateCounter; + private $synchedFolders; + + + /** + * Constructor + * + * @access public + */ + public function StateManager() { + $this->statemachine = ZPush::GetStateMachine(); + $this->hierarchyOperation = ZPush::HierarchyCommand(Request::GetCommandCode()); + $this->deleteOldStates = (Request::GetCommandCode() === ZPush::COMMAND_SYNC || $this->hierarchyOperation); + $this->synchedFolders = array(); + } + + /** + * Sets an ASDevice for the Statemanager to work with + * + * @param ASDevice $device + * + * @access public + * @return boolean + */ + public function SetDevice(&$device) { + $this->device = $device; + return true; + } + + /** + * Returns an array will all synchronized folderids + * + * @access public + * @return array + */ + public function GetSynchedFolders() { + $synched = array(); + foreach ($this->device->GetAllFolderIds() as $folderid) { + $uuid = $this->device->GetFolderUUID($folderid); + if ($uuid) + $synched[] = $folderid; + } + return $synched; + } + + /** + * Returns a folder state (SyncParameters) for a folder id + * + * @param $folderid + * + * @access public + * @return SyncParameters + */ + public function GetSynchedFolderState($folderid) { + // new SyncParameters are cached + if (isset($this->synchedFolders[$folderid])) + return $this->synchedFolders[$folderid]; + + $uuid = $this->device->GetFolderUUID($folderid); + if ($uuid) { + try { + $data = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $uuid); + if ($data !== false) { + $this->synchedFolders[$folderid] = $data; + } + } + catch (StateNotFoundException $ex) { } + } + + if (!isset($this->synchedFolders[$folderid])) + $this->synchedFolders[$folderid] = new SyncParameters(); + + return $this->synchedFolders[$folderid]; + } + + /** + * Saves a folder state - SyncParameters object + * + * @param SyncParamerters $spa + * + * @access public + * @return boolean + */ + public function SetSynchedFolderState($spa) { + // make sure the current uuid is linked on the device for the folder. + // if not, old states will be automatically removed and the new ones linked + self::LinkState($this->device, $spa->GetUuid(), $spa->GetFolderId()); + + $spa->SetReferencePolicyKey($this->device->GetPolicyKey()); + + return $this->statemachine->SetState($spa, $this->device->GetDeviceId(), IStateMachine::FOLDERDATA, $spa->GetUuid()); + } + + /** + * Gets the new sync key for a specified sync key. The new sync state must be + * associated to this sync key when calling SetSyncState() + * + * @param string $synckey + * + * @access public + * @return string + */ + function GetNewSyncKey($synckey) { + if(!isset($synckey) || $synckey == "0" || $synckey == false) { + $this->uuid = $this->getNewUuid(); + $this->newStateCounter = 1; + } + else { + list($uuid, $counter) = self::ParseStateKey($synckey); + $this->uuid = $uuid; + $this->newStateCounter = $counter + 1; + } + + return self::BuildStateKey($this->uuid, $this->newStateCounter); + } + + /** + * Gets the state for a specified synckey (uuid + counter) + * + * @param string $synckey + * + * @access public + * @return string + * @throws StateInvalidException, StateNotFoundException + */ + public function GetSyncState($synckey) { + // No sync state for sync key '0' + if($synckey == "0") { + $this->oldStateCounter = 0; + return ""; + } + + // Check if synckey is allowed and set uuid and counter + list($this->uuid, $this->oldStateCounter) = self::ParseStateKey($synckey); + + // make sure the hierarchy cache is in place + if ($this->hierarchyOperation) + $this->loadHierarchyCache(); + + // the state machine will discard any sync states before this one, as they are no longer required + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); + } + + /** + * Writes the sync state to a new synckey + * + * @param string $synckey + * @param string $syncstate + * @param string $folderid (opt) the synckey is associated with the folder - should always be set when performing CONTENT operations + * + * @access public + * @return boolean + * @throws StateInvalidException + */ + public function SetSyncState($synckey, $syncstate, $folderid = false) { + $internalkey = self::BuildStateKey($this->uuid, $this->newStateCounter); + if ($this->oldStateCounter != 0 && $synckey != $internalkey) + throw new StateInvalidException(sprintf("Unexpected synckey value oldcounter: '%s' synckey: '%s' internal key: '%s'", $this->oldStateCounter, $synckey, $internalkey)); + + // make sure the hierarchy cache is also saved + if ($this->hierarchyOperation) + $this->saveHierarchyCache(); + + // announce this uuid to the device, while old uuid/states should be deleted + self::LinkState($this->device, $this->uuid, $folderid); + + return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::DEFTYPE, $this->uuid, $this->newStateCounter); + } + + /** + * Gets the failsave sync state for the current synckey + * + * @access public + * @return array/boolean false if not available + */ + public function GetSyncFailState() { + if (!$this->uuid) + return false; + + try { + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); + } + catch (StateNotFoundException $snfex) { + return false; + } + } + + /** + * Writes the failsave sync state for the current (old) synckey + * + * @param mixed $syncstate + * + * @access public + * @return boolean + */ + public function SetSyncFailState($syncstate) { + if ($this->oldStateCounter == 0) + return false; + + return $this->statemachine->SetState($syncstate, $this->device->GetDeviceId(), IStateMachine::FAILSAVE, $this->uuid, $this->oldStateCounter); + } + + /** + * Gets the backendstorage data + * + * @param int $type permanent or state related storage + * + * @access public + * @return mixed + * @throws StateNotYetAvailableException, StateNotFoundException + */ + public function GetBackendStorage($type = self::BACKENDSTORAGE_PERMANENT) { + if ($type == self::BACKENDSTORAGE_STATE) { + if (!$this->uuid) + throw new StateNotYetAvailableException(); + + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->oldStateCounter, $this->deleteOldStates); + } + else { + return $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime()); + } + } + + /** + * Writes the backendstorage data + * + * @param mixed $data + * @param int $type permanent or state related storage + * + * @access public + * @return int amount of bytes saved + * @throws StateNotYetAvailableException, StateNotFoundException + */ + public function SetBackendStorage($data, $type = self::BACKENDSTORAGE_PERMANENT) { + if ($type == self::BACKENDSTORAGE_STATE) { + if (!$this->uuid) + throw new StateNotYetAvailableException(); + + // TODO serialization should be done in the StateMachine + return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $this->uuid, $this->newStateCounter); + } + else { + return $this->statemachine->SetState($data, $this->device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, false, $this->device->GetFirstSyncTime()); + } + } + + /** + * Initializes the HierarchyCache for legacy syncs + * this is for AS 1.0 compatibility: + * save folder information synched with GetHierarchy() + * handled by StateManager + * + * @param string $folders Array with folder information + * + * @access public + * @return boolean + */ + public function InitializeFolderCache($folders) { + if (!is_array($folders)) + return false; + + if (!isset($this->device)) + throw new FatalException("ASDevice not initialized"); + + // redeclare this operation as hierarchyOperation + $this->hierarchyOperation = true; + + // as there is no hierarchy uuid, we have to create one + $this->uuid = $this->getNewUuid(); + $this->newStateCounter = self::FIXEDHIERARCHYCOUNTER; + + // initialize legacy HierarchCache + $this->device->SetHierarchyCache($folders); + + // force saving the hierarchy cache! + return $this->saveHierarchyCache(true); + } + + + /**---------------------------------------------------------------------------------------------------------- + * static StateManager methods + */ + + /** + * Links a folderid to the a UUID + * Old states are removed if an folderid is linked to a new UUID + * assisting the StateMachine to get rid of old data. + * + * @param ASDevice $device + * @param string $uuid the uuid to link to + * @param string $folderid (opt) if not set, hierarchy state is linked + * + * @access public + * @return boolean + */ + static public function LinkState(&$device, $newUuid, $folderid = false) { + $savedUuid = $device->GetFolderUUID($folderid); + // delete 'old' states! + if ($savedUuid != $newUuid) { + // remove states but no need to notify device + self::UnLinkState($device, $folderid, false); + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::linkState(#ASDevice, '%s','%s'): linked to uuid '%s'.", $newUuid, (($folderid === false)?'HierarchyCache':$folderid), $newUuid)); + return $device->SetFolderUUID($newUuid, $folderid); + } + return true; + } + + /** + * UnLinks all states from a folder id + * Old states are removed assisting the StateMachine to get rid of old data. + * The UUID is then removed from the device + * + * @param ASDevice $device + * @param string $folderid + * @param boolean $removeFromDevice indicates if the device should be + * notified that the state was removed + * @param boolean $retrieveUUIDFromDevice indicates if the UUID should be retrieved from + * device. If not true this parameter will be used as UUID. + * + * @access public + * @return boolean + */ + static public function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) { + if ($retrieveUUIDFromDevice === true) + $savedUuid = $device->GetFolderUUID($folderid); + else + $savedUuid = $retrieveUUIDFromDevice; + + if ($savedUuid) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::UnLinkState('%s'): saved state '%s' will be deleted.", $folderid, $savedUuid)); + ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::DEFTYPE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2); + ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FOLDERDATA, $savedUuid); // CPO + ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FAILSAVE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2); + ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2); + + // remove all messages which could not be synched before + $device->RemoveIgnoredMessage($folderid, false); + + if ($folderid === false && $savedUuid !== false) + ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::HIERARCHY, $savedUuid, self::FIXEDHIERARCHYCOUNTER *2); + } + // delete this id from the uuid cache + if ($removeFromDevice) + return $device->SetFolderUUID(false, $folderid); + else + return true; + } + + /** + * Parses a SyncKey and returns UUID and counter + * + * @param string $synckey + * + * @access public + * @return array uuid, counter + * @throws StateInvalidException + */ + static public function ParseStateKey($synckey) { + $matches = array(); + if(!preg_match('/^\{([0-9A-Za-z-]+)\}([0-9]+)$/', $synckey, $matches)) + throw new StateInvalidException(sprintf("SyncKey '%s' is invalid", $synckey)); + + return array($matches[1], (int)$matches[2]); + } + + /** + * Builds a SyncKey from a UUID and counter + * + * @param string $uuid + * @param int $counter + * + * @access public + * @return string syncKey + * @throws StateInvalidException + */ + static public function BuildStateKey($uuid, $counter) { + if(!preg_match('/^([0-9A-Za-z-]+)$/', $uuid, $matches)) + throw new StateInvalidException(sprintf("UUID '%s' is invalid", $uuid)); + + return "{" . $uuid . "}" . $counter; + } + + + /**---------------------------------------------------------------------------------------------------------- + * private StateManager methods + */ + + /** + * Loads the HierarchyCacheState and initializes the HierarchyChache + * if this is an hierarchy operation + * + * @access private + * @return boolean + * @throws StateNotFoundException + */ + private function loadHierarchyCache() { + if (!$this->hierarchyOperation) + return false; + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager->loadHierarchyCache(): '%s-%s-%s-%d'", $this->device->GetDeviceId(), $this->uuid, IStateMachine::HIERARCHY, $this->oldStateCounter)); + + // check if a full hierarchy sync might be necessary + if ($this->device->GetFolderUUID(false) === false) { + self::UnLinkState($this->device, false, false, $this->uuid); + throw new StateNotFoundException("No hierarchy UUID linked to device. Requesting folder resync."); + } + + $hierarchydata = $this->statemachine->GetState($this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid , $this->oldStateCounter, $this->deleteOldStates); + $this->device->SetHierarchyCache($hierarchydata); + return true; + } + + /** + * Saves the HierarchyCacheState of the HierarchyChache + * if this is an hierarchy operation + * + * @param boolean $forceLoad indicates if the cache should be saved also if not a hierary operation + * + * @access private + * @return boolean + * @throws StateInvalidException + */ + private function saveHierarchyCache($forceSaving = false) { + if (!$this->hierarchyOperation && !$forceSaving) + return false; + + // link the hierarchy cache again, if the UUID does not match the UUID saved in the devicedata + if (($this->uuid != $this->device->GetFolderUUID() || $forceSaving) ) + self::LinkState($this->device, $this->uuid); + + // check all folders and deleted folders to update data of ASDevice and delete old states + $hc = $this->device->getHierarchyCache(); + foreach ($hc->GetDeletedFolders() as $delfolder) + self::UnLinkState($this->device, $delfolder->serverid); + + foreach ($hc->ExportFolders() as $folder) + $this->device->SetFolderType($folder->serverid, $folder->type); + + return $this->statemachine->SetState($this->device->GetHierarchyCacheData(), $this->device->GetDeviceId(), IStateMachine::HIERARCHY, $this->uuid, $this->newStateCounter); + } + + /** + * Generates a new UUID + * + * @access private + * @return string + */ + private function getNewUuid() { + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), + mt_rand( 0, 0x0fff ) | 0x4000, + mt_rand( 0, 0x3fff ) | 0x8000, + mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) ); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/core/stateobject.php b/z-push/lib/core/stateobject.php new file mode 100644 index 0000000..f11320d --- /dev/null +++ b/z-push/lib/core/stateobject.php @@ -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"); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/streamer.php b/z-push/lib/core/streamer.php new file mode 100644 index 0000000..6b4b75d --- /dev/null +++ b/z-push/lib/core/streamer.php @@ -0,0 +1,465 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Streamer implements Serializable { + const STREAMER_VAR = 1; + const STREAMER_ARRAY = 2; + const STREAMER_TYPE = 3; + const STREAMER_PROP = 4; + const STREAMER_TYPE_DATE = 1; + const STREAMER_TYPE_HEX = 2; + const STREAMER_TYPE_DATE_DASHES = 3; + const STREAMER_TYPE_STREAM = 4; + const STREAMER_TYPE_IGNORE = 5; + const STREAMER_TYPE_SEND_EMPTY = 6; + const STREAMER_TYPE_NO_CONTAINER = 7; + const STREAMER_TYPE_COMMA_SEPARATED = 8; + const STREAMER_TYPE_SEMICOLON_SEPARATED = 9; + const STREAMER_TYPE_MULTIPART = 10; + + protected $mapping; + public $flags; + public $content; + + /** + * Constructor + * + * @param array $mapping internal mapping of variables + * @access public + */ + function Streamer($mapping) { + $this->mapping = $mapping; + $this->flags = false; + } + + /** + * Decodes the WBXML from a WBXMLdecoder until we reach the same depth level of WBXML. + * This means that if there are multiple objects at this level, then only the first is + * decoded SubOjects are auto-instantiated and decoded using the same functionality + * + * @param WBXMLDecoder $decoder + * + * @access public + */ + public function Decode(&$decoder) { + while(1) { + $entity = $decoder->getElement(); + + if($entity[EN_TYPE] == EN_TYPE_STARTTAG) { + if(! ($entity[EN_FLAGS] & EN_FLAGS_CONTENT)) { + $map = $this->mapping[$entity[EN_TAG]]; + if (isset($map[self::STREAMER_ARRAY])) { + $this->$map[self::STREAMER_VAR] = array(); + } else if(!isset($map[self::STREAMER_TYPE])) { + $this->$map[self::STREAMER_VAR] = ""; + } + else if ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES ) { + $this->$map[self::STREAMER_VAR] = ""; + } + else if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) + $this->$map[self::STREAMER_VAR] = ""; + continue; + } + // Found a start tag + if(!isset($this->mapping[$entity[EN_TAG]])) { + // This tag shouldn't be here, abort + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Tag '%s' unexpected in type XML type '%s'", $entity[EN_TAG], get_class($this))); + return false; + } + else { + $map = $this->mapping[$entity[EN_TAG]]; + + // Handle an array + if(isset($map[self::STREAMER_ARRAY])) { + while(1) { + //do not get start tag for an array without a container + if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { + if(!$decoder->getElementStartTag($map[self::STREAMER_ARRAY])) + break; + } + if(isset($map[self::STREAMER_TYPE])) { + $decoded = new $map[self::STREAMER_TYPE]; + + $decoded->Decode($decoder); + } + else { + $decoded = $decoder->getElementContent(); + } + + if(!isset($this->$map[self::STREAMER_VAR])) + $this->$map[self::STREAMER_VAR] = array($decoded); + else + array_push($this->$map[self::STREAMER_VAR], $decoded); + + if(!$decoder->getElementEndTag()) //end tag of a container element + return false; + + if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER) { + $e = $decoder->peek(); + //go back to the initial while if another block of no container elements is found + if ($e[EN_TYPE] == EN_TYPE_STARTTAG) { + continue 2; + } + //break on end tag because no container elements block end is reached + if ($e[EN_TYPE] == EN_TYPE_ENDTAG) + break; + if (empty($e)) + break; + } + } + //do not get end tag for an array without a container + if (!(isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_NO_CONTAINER)) { + if(!$decoder->getElementEndTag()) //end tag of container + return false; + } + } + else { // Handle single value + if(isset($map[self::STREAMER_TYPE])) { + // Complex type, decode recursively + if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES) { + $decoded = $this->parseDate($decoder->getElementContent()); + if(!$decoder->getElementEndTag()) + return false; + } + else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { + $decoded = hex2bin($decoder->getElementContent()); + if(!$decoder->getElementEndTag()) + return false; + } + // explode comma or semicolon strings into arrays + else if($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED) { + $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; "; + $decoded = explode($glue, $decoder->getElementContent()); + if(!$decoder->getElementEndTag()) + return false; + } + else { + $subdecoder = new $map[self::STREAMER_TYPE](); + if($subdecoder->Decode($decoder) === false) + return false; + + $decoded = $subdecoder; + + if(!$decoder->getElementEndTag()) { + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("No end tag for '%s'", $entity[EN_TAG])); + return false; + } + } + } + else { + // Simple type, just get content + $decoded = $decoder->getElementContent(); + + if($decoded === false) { + // the tag is declared to have content, but no content is available. + // set an empty content + $decoded = ""; + } + + if(!$decoder->getElementEndTag()) { + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("Unable to get end tag for '%s'", $entity[EN_TAG])); + return false; + } + } + // $decoded now contains data object (or string) + $this->$map[self::STREAMER_VAR] = $decoded; + } + } + } + else if($entity[EN_TYPE] == EN_TYPE_ENDTAG) { + $decoder->ungetElement($entity); + break; + } + else { + ZLog::Write(LOGLEVEL_WBXMLSTACK, "Unexpected content in type"); + break; + } + } + } + + /** + * Encodes this object and any subobjects - output is ordered according to mapping + * + * @param WBXMLEncoder $encoder + * + * @access public + */ + public function Encode(&$encoder) { + // A return value if anything was streamed. We need for empty tags. + $streamed = false; + foreach($this->mapping as $tag => $map) { + if(isset($this->$map[self::STREAMER_VAR])) { + // Variable is available + if(is_object($this->$map[self::STREAMER_VAR])) { + // Subobjects can do their own encoding + if ($this->$map[self::STREAMER_VAR] instanceof Streamer) { + $encoder->startTag($tag); + $res = $this->$map[self::STREAMER_VAR]->Encode($encoder); + $encoder->endTag(); + // nothing was streamed in previous encode but it should be streamed empty anyway + if (!$res && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) + $encoder->startTag($tag, false, true); + } + else + ZLog::Write(LOGLEVEL_ERROR, sprintf("Streamer->Encode(): parameter '%s' of object %s is not of type Streamer", $map[self::STREAMER_VAR], get_class($this))); + } + // Array of objects + else if(isset($map[self::STREAMER_ARRAY])) { + if (empty($this->$map[self::STREAMER_VAR]) && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) { + $encoder->startTag($tag, false, true); + } + else { + // Outputs array container (eg Attachments) + // Do not output start and end tag when type is STREAMER_TYPE_NO_CONTAINER + if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) + $encoder->startTag($tag); + + foreach ($this->$map[self::STREAMER_VAR] as $element) { + if(is_object($element)) { + $encoder->startTag($map[self::STREAMER_ARRAY]); // Outputs object container (eg Attachment) + $element->Encode($encoder); + $encoder->endTag(); + } + else { + if(strlen($element) == 0) + // Do not output empty items. Not sure if we should output an empty tag with $encoder->startTag($map[self::STREAMER_ARRAY], false, true); + ; + else { + $encoder->startTag($map[self::STREAMER_ARRAY]); + $encoder->content($element); + $encoder->endTag(); + $streamed = true; + } + } + } + + if (!isset($map[self::STREAMER_PROP]) || $map[self::STREAMER_PROP] != self::STREAMER_TYPE_NO_CONTAINER) + $encoder->endTag(); + } + } + else { + if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_IGNORE) { + continue; + } + + if ($encoder->getMultipart() && isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_MULTIPART) { + $encoder->addBodypartStream($this->$map[self::STREAMER_VAR]); + $encoder->startTag(SYNC_ITEMOPERATIONS_PART); + $encoder->content(count($encoder->getBodypartsCount())); + $encoder->endTag(); + continue; + } + + // Simple type + if(!isset($map[self::STREAMER_TYPE]) && strlen($this->$map[self::STREAMER_VAR]) == 0) { + // send empty tags + if (isset($map[self::STREAMER_PROP]) && $map[self::STREAMER_PROP] == self::STREAMER_TYPE_SEND_EMPTY) + $encoder->startTag($tag, false, true); + + // Do not output empty items. See above: $encoder->startTag($tag, false, true); + continue; + } else + $encoder->startTag($tag); + + if(isset($map[self::STREAMER_TYPE]) && ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_DATE_DASHES)) { + if($this->$map[self::STREAMER_VAR] != 0) // don't output 1-1-1970 + $encoder->content($this->formatDate($this->$map[self::STREAMER_VAR], $map[self::STREAMER_TYPE])); + } + else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_HEX) { + $encoder->content(strtoupper(bin2hex($this->$map[self::STREAMER_VAR]))); + } + else if(isset($map[self::STREAMER_TYPE]) && $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_STREAM) { + //encode stream with base64 + $stream = $this->$map[self::STREAMER_VAR]; + $stat = fstat($stream); + // the padding size muss be calculated for the entire stream, + // the base64 filter seems to process 8192 byte chunks correctly itself + $padding = (isset($stat['size']) && $stat['size'] > 8192) ? ($stat['size'] % 3) : 0; + + $paddingfilter = stream_filter_append($stream, 'padding.'.$padding); + $base64filter = stream_filter_append($stream, 'convert.base64-encode'); + $d = ""; + while (!feof($stream)) { + $d .= fgets($stream, 4096); + } + $encoder->content($d); + stream_filter_remove($base64filter); + stream_filter_remove($paddingfilter); + } + // implode comma or semicolon arrays into a string + else if(isset($map[self::STREAMER_TYPE]) && is_array($this->$map[self::STREAMER_VAR]) && + ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED || $map[self::STREAMER_TYPE] == self::STREAMER_TYPE_SEMICOLON_SEPARATED)) { + $glue = ($map[self::STREAMER_TYPE] == self::STREAMER_TYPE_COMMA_SEPARATED)?", ":"; "; + $encoder->content(implode($glue, $this->$map[self::STREAMER_VAR])); + } + else { + $encoder->content($this->$map[self::STREAMER_VAR]); + } + $encoder->endTag(); + $streamed = true; + } + } + } + // Output our own content + if(isset($this->content)) + $encoder->content($this->content); + + return $streamed; + } + + /** + * Removes not necessary data from the object + * + * @access public + * @return boolean + */ + public function StripData() { + foreach ($this->mapping as $k=>$v) { + if (isset($this->$v[self::STREAMER_VAR])) { + if (is_object($this->$v[self::STREAMER_VAR]) && method_exists($this->$v[self::STREAMER_VAR], "StripData") ) { + $this->$v[self::STREAMER_VAR]->StripData(); + } + else if (isset($v[self::STREAMER_ARRAY]) && !empty($this->$v[self::STREAMER_VAR])) { + foreach ($this->$v[self::STREAMER_VAR] as $element) { + if (is_object($element) && method_exists($element, "StripData") ) { + $element->StripData(); + } + } + } + } + } + unset($this->mapping); + + return true; + } + + /** + * Method to serialize a Streamer and respective SyncObject + * + * @access public + * @return array + */ + public function serialize() { + $values = array(); + foreach ($this->mapping as $k=>$v) { + if (isset($this->$v[self::STREAMER_VAR])) + $values[$v[self::STREAMER_VAR]] = serialize($this->$v[self::STREAMER_VAR]); + } + + return serialize($values); + } + + /** + * Method to unserialize a Streamer and respective SyncObject + * + * @access public + * @return array + */ + public function unserialize($data) { + $class = get_class($this); + $this->$class(); + $values = unserialize($data); + foreach ($values as $k=>$v) + $this->$k = unserialize($v); + + return true; + } + + /**---------------------------------------------------------------------------------------------------------- + * Private methods for conversion + */ + + /** + * Formats a timestamp + * Oh yeah, this is beautiful. Exchange outputs date fields differently in calendar items + * and emails. We could just always send one or the other, but unfortunately nokia's 'Mail for + * exchange' depends on this quirk. So we have to send a different date type depending on where + * it's used. Sigh. + * + * @param long $ts + * @param int $type + * + * @access private + * @return string + */ + private function formatDate($ts, $type) { + if($type == self::STREAMER_TYPE_DATE) + return gmstrftime("%Y%m%dT%H%M%SZ", $ts); + else if($type == self::STREAMER_TYPE_DATE_DASHES) + return gmstrftime("%Y-%m-%dT%H:%M:%S.000Z", $ts); + } + + /** + * Transforms an AS timestamp into a unix timestamp + * + * @param string $ts + * + * @access private + * @return long + */ + function parseDate($ts) { + if(preg_match("/(\d{4})[^0-9]*(\d{2})[^0-9]*(\d{2})(T(\d{2})[^0-9]*(\d{2})[^0-9]*(\d{2})(.\d+)?Z){0,1}$/", $ts, $matches)) { + if ($matches[1] >= 2038){ + $matches[1] = 2038; + $matches[2] = 1; + $matches[3] = 18; + $matches[5] = $matches[6] = $matches[7] = 0; + } + + if (!isset($matches[5])) $matches[5] = 0; + if (!isset($matches[6])) $matches[6] = 0; + if (!isset($matches[7])) $matches[7] = 0; + + return gmmktime($matches[5], $matches[6], $matches[7], $matches[2], $matches[3], $matches[1]); + } + return 0; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/streamimporter.php b/z-push/lib/core/streamimporter.php new file mode 100644 index 0000000..ee888dd --- /dev/null +++ b/z-push/lib/core/streamimporter.php @@ -0,0 +1,258 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class ImportChangesStream implements IImportChanges { + private $encoder; + private $objclass; + private $seenObjects; + private $importedMsgs; + private $checkForIgnoredMessages; + + /** + * Constructor of the StreamImporter + * + * @param WBXMLEncoder $encoder Objects are streamed to this encoder + * @param SyncObject $class SyncObject class (only these are accepted when streaming content messages) + * + * @access public + */ + public function ImportChangesStream(&$encoder, $class) { + $this->encoder = &$encoder; + $this->objclass = $class; + $this->classAsString = (is_object($class))?get_class($class):''; + $this->seenObjects = array(); + $this->importedMsgs = 0; + $this->checkForIgnoredMessages = true; + } + + /** + * Implement interface - never used + */ + public function Config($state, $flags = 0) { return true; } + public function GetState() { return false;} + public function LoadConflicts($contentparameters, $state) { return true; } + + /** + * Imports a single message + * + * @param string $id + * @param SyncObject $message + * + * @access public + * @return boolean + */ + public function ImportMessageChange($id, $message) { + // ignore other SyncObjects + if(!($message instanceof $this->classAsString)) + return false; + + // prevent sending the same object twice in one request + if (in_array($id, $this->seenObjects)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Object '%s' discarded! Object already sent in this request.", $id)); + return true; + } + + $this->importedMsgs++; + $this->seenObjects[] = $id; + + // checks if the next message may cause a loop or is broken + if (ZPush::GetDeviceManager()->DoNotStreamMessage($id, $message)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): message ignored and requested to be removed from mobile", $id)); + + // this is an internal operation & should not trigger an update in the device manager + $this->checkForIgnoredMessages = false; + $stat = $this->ImportMessageDeletion($id); + $this->checkForIgnoredMessages = true; + + return $stat; + } + + if ($message->flags === false || $message->flags === SYNC_NEWMESSAGE) + $this->encoder->startTag(SYNC_ADD); + else { + // on update of an SyncEmail we only export the flags + if($message instanceof SyncMail && isset($message->flag) && $message->flag instanceof SyncMailFlags) { + $newmessage = new SyncMail(); + $newmessage->read = $message->read; + $newmessage->flag = $message->flag; + $message = $newmessage; + unset($newmessage); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportMessageChange('%s'): SyncMail message updated. Message content is striped, only flags are streamed.", $id)); + } + + $this->encoder->startTag(SYNC_MODIFY); + } + $this->encoder->startTag(SYNC_SERVERENTRYID); + $this->encoder->content($id); + $this->encoder->endTag(); + $this->encoder->startTag(SYNC_DATA); + $message->Encode($this->encoder); + $this->encoder->endTag(); + $this->encoder->endTag(); + + return true; + } + + /** + * Imports a deletion + * + * @param string $id + * + * @access public + * @return boolean + */ + public function ImportMessageDeletion($id) { + if ($this->checkForIgnoredMessages) { + ZPush::GetDeviceManager()->RemoveBrokenMessage($id); + } + + $this->importedMsgs++; + $this->encoder->startTag(SYNC_REMOVE); + $this->encoder->startTag(SYNC_SERVERENTRYID); + $this->encoder->content($id); + $this->encoder->endTag(); + $this->encoder->endTag(); + + return true; + } + + /** + * Imports a change in 'read' flag + * Can only be applied to SyncMail (Email) requests + * + * @param string $id + * @param int $flags - read/unread + * + * @access public + * @return boolean + */ + public function ImportMessageReadFlag($id, $flags) { + if(!($this->objclass instanceof SyncMail)) + return false; + + $this->importedMsgs++; + + $this->encoder->startTag(SYNC_MODIFY); + $this->encoder->startTag(SYNC_SERVERENTRYID); + $this->encoder->content($id); + $this->encoder->endTag(); + $this->encoder->startTag(SYNC_DATA); + $this->encoder->startTag(SYNC_POOMMAIL_READ); + $this->encoder->content($flags); + $this->encoder->endTag(); + $this->encoder->endTag(); + $this->encoder->endTag(); + + return true; + } + + /** + * ImportMessageMove is not implemented, as this operation can not be streamed to a WBXMLEncoder + * + * @param string $id + * @param int $flags read/unread + * + * @access public + * @return boolean + */ + public function ImportMessageMove($id, $newfolder) { + return true; + } + + /** + * Imports a change on a folder + * + * @param object $folder SyncFolder + * + * @access public + * @return string id of the folder + */ + public function ImportFolderChange($folder) { + // checks if the next message may cause a loop or is broken + if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid)); + return true; + } + + // send a modify flag if the folder is already known on the device + if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); + else + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); + + $folder->Encode($this->encoder); + $this->encoder->endTag(); + + return true; + } + + /** + * Imports a folder deletion + * + * @param string $id + * @param string $parent id + * + * @access public + * @return boolean + */ + public function ImportFolderDeletion($id, $parent = false) { + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); + $this->encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); + $this->encoder->content($id); + $this->encoder->endTag(); + $this->encoder->endTag(); + + return true; + } + + /** + * Returns the number of messages which were changed, deleted and had changed read status + * + * @access public + * @return int + */ + public function GetImportedMessages() { + return $this->importedMsgs; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/core/synccollections.php b/z-push/lib/core/synccollections.php new file mode 100644 index 0000000..fc98207 --- /dev/null +++ b/z-push/lib/core/synccollections.php @@ -0,0 +1,705 @@ +. +* +* Consult LICENSE file for details +************************************************/ + + +class SyncCollections implements Iterator { + const ERROR_NO_COLLECTIONS = 1; + const ERROR_WRONG_HIERARCHY = 2; + + private $stateManager; + + private $collections = array(); + private $addparms = array(); + private $changes = array(); + private $saveData = true; + + private $refPolicyKey = false; + private $refLifetime = false; + + private $globalWindowSize; + private $lastSyncTime; + + private $waitingTime = 0; + + + /** + * Constructor + */ + public function SyncCollections() { + } + + /** + * Sets the StateManager for this object + * If this is not done and a method needs it, the StateManager will be + * requested from the DeviceManager + * + * @param StateManager $statemanager + * + * @access public + * @return + */ + public function SetStateManager($statemanager) { + $this->stateManager = $statemanager; + } + + /** + * Loads all collections known for the current device + * + * @param boolean $overwriteLoaded (opt) overwrites Collection with saved state if set to true + * @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default true + * @param boolean $checkPermissions (opt) if set to true each folder will pass + * through a backend->Setup() to check permissions. + * If this fails a StatusException will be thrown. + * + * @access public + * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails + * @throws StateNotFoundException if the sync state can not be found ($loadState = true) + * @return boolean + */ + public function LoadAllCollections($overwriteLoaded = false, $loadState = false, $checkPermissions = false) { + $this->loadStateManager(); + + $invalidStates = false; + foreach($this->stateManager->GetSynchedFolders() as $folderid) { + if ($overwriteLoaded === false && isset($this->collections[$folderid])) + continue; + + // Load Collection! + if (! $this->LoadCollection($folderid, $loadState, $checkPermissions)) + $invalidStates = true; + } + + if ($invalidStates) + throw new StateInvalidException("Invalid states found while loading collections. Forcing sync"); + + return true; + } + + /** + * Loads all collections known for the current device + * + * @param boolean $folderid folder id to be loaded + * @param boolean $loadState (opt) indicates if the collection sync state should be loaded, default true + * @param boolean $checkPermissions (opt) if set to true each folder will pass + * through a backend->Setup() to check permissions. + * If this fails a StatusException will be thrown. + * + * @access public + * @throws StatusException with SyncCollections::ERROR_WRONG_HIERARCHY if permission check fails + * @throws StateNotFoundException if the sync state can not be found ($loadState = true) + * @return boolean + */ + public function LoadCollection($folderid, $loadState = false, $checkPermissions = false) { + $this->loadStateManager(); + + try { + // Get SyncParameters for the folder from the state + $spa = $this->stateManager->GetSynchedFolderState($folderid); + + // TODO remove resync of folders for < Z-Push 2 beta4 users + // this forces a resync of all states previous to Z-Push 2 beta4 + if (! $spa instanceof SyncParameters) + throw new StateInvalidException("Saved state are not of type SyncParameters"); + } + catch (StateInvalidException $sive) { + // in case there is something wrong with the state, just stop here + // later when trying to retrieve the SyncParameters nothing will be found + + // we also generate a fake change, so a sync on this folder is triggered + $this->changes[$folderid] = 1; + + return false; + } + + // if this is an additional folder the backend has to be setup correctly + if ($checkPermissions === true && ! ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) + throw new StatusException(sprintf("SyncCollections->LoadCollection(): could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), self::ERROR_WRONG_HIERARCHY); + + // add collection to object + $this->AddCollection($spa); + + // load the latest known syncstate if requested + if ($loadState === true) + $this->addparms[$folderid]["state"] = $this->stateManager->GetSyncState($spa->GetLatestSyncKey()); + + return true; + } + + /** + * Saves a SyncParameters Object + * + * @param SyncParamerts $spa + * + * @access public + * @return boolean + */ + public function SaveCollection($spa) { + if (! $this->saveData) + return false; + + if ($spa->IsDataChanged()) { + $this->loadStateManager(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->SaveCollection(): Data of folder '%s' changed", $spa->GetFolderId())); + + // save new windowsize + if (isset($this->globalWindowSize)) + $spa->SetWindowSize($this->globalWindowSize); + + // update latest lifetime + if (isset($this->refLifetime)) + $spa->SetReferenceLifetime($this->refLifetime); + + return $this->stateManager->SetSynchedFolderState($spa); + } + return false; + } + + /** + * Adds a SyncParameters object to the current list of collections + * + * @param SyncParameters $spa + * + * @access public + * @return boolean + */ + public function AddCollection($spa) { + $this->collections[$spa->GetFolderId()] = $spa; + + if ($spa->HasLastSyncTime() && $spa->GetLastSyncTime() > $this->lastSyncTime) { + $this->lastSyncTime = $spa->GetLastSyncTime(); + + // use SyncParameters PolicyKey as reference if available + if ($spa->HasReferencePolicyKey()) + $this->refPolicyKey = $spa->GetReferencePolicyKey(); + + // use SyncParameters LifeTime as reference if available + if ($spa->HasReferenceLifetime()) + $this->refLifetime = $spa->GetReferenceLifetime(); + } + + return true; + } + + /** + * Returns a previousily added or loaded SyncParameters object for a folderid + * + * @param SyncParameters $spa + * + * @access public + * @return SyncParameters / boolean false if no SyncParameters object is found for folderid + */ + public function GetCollection($folderid) { + if (isset($this->collections[$folderid])) + return $this->collections[$folderid]; + else + return false; + } + + /** + * Indicates if there are any loaded CPOs + * + * @access public + * @return boolean + */ + public function HasCollections() { + return ! empty($this->collections); + } + + /** + * Add a non-permanent key/value pair for a SyncParameters object + * + * @param SyncParameters $spa target SyncParameters + * @param string $key + * @param mixed $value + * + * @access public + * @return boolean + */ + public function AddParameter($spa, $key, $value) { + if (!$spa->HasFolderId()) + return false; + + $folderid = $spa->GetFolderId(); + if (!isset($this->addparms[$folderid])) + $this->addparms[$folderid] = array(); + + $this->addparms[$folderid][$key] = $value; + return true; + } + + /** + * Returns a previousily set non-permanent value for a SyncParameters object + * + * @param SyncParameters $spa target SyncParameters + * @param string $key + * + * @access public + * @return mixed returns 'null' if nothing set + */ + public function GetParameter($spa, $key) { + if (isset($this->addparms[$spa->GetFolderId()]) && isset($this->addparms[$spa->GetFolderId()][$key])) + return $this->addparms[$spa->GetFolderId()][$key]; + else + return null; + } + + /** + * Returns the latest known PolicyKey to be used as reference + * + * @access public + * @return int/boolen returns false if nothing found in collections + */ + public function GetReferencePolicyKey() { + return $this->refPolicyKey; + } + + /** + * Sets a global window size which should be used for all collections + * in a case of a heartbeat and/or partial sync + * + * @param int $windowsize + * + * @access public + * @return boolean + */ + public function SetGlobalWindowSize($windowsize) { + $this->globalWindowSize = $windowsize; + return true; + } + + /** + * Returns the global window size which should be used for all collections + * in a case of a heartbeat and/or partial sync + * + * @access public + * @return int/boolean returns false if not set or not available + */ + public function GetGlobalWindowSize() { + if (!isset($this->globalWindowSize)) + return false; + + return $this->globalWindowSize; + } + + /** + * Sets the lifetime for heartbeat or ping connections + * + * @param int $lifetime time in seconds + * + * @access public + * @return boolean + */ + public function SetLifetime($lifetime) { + $this->refLifetime = $lifetime; + return true; + } + + /** + * Sets the lifetime for heartbeat or ping connections + * previousily set or saved in a collection + * + * @access public + * @return int returns 600 as default if nothing set or not available + */ + public function GetLifetime() { + if (!isset( $this->refLifetime) || $this->refLifetime === false) + return 600; + + return $this->refLifetime; + } + + /** + * Returns the timestamp of the last synchronization for all + * loaded collections + * + * @access public + * @return int timestamp + */ + public function GetLastSyncTime() { + return $this->lastSyncTime; + } + + /** + * Returns the timestamp of the last synchronization of a device. + * + * @param $device an ASDevice + * + * @access public + * @return int timestamp + */ + static public function GetLastSyncTimeOfDevice(&$device) { + // we need a StateManager for this operation + $stateManager = new StateManager(); + $stateManager->SetDevice($device); + + $sc = new SyncCollections(); + $sc->SetStateManager($stateManager); + + // load all collections of device without loading states or checking permissions + $sc->LoadAllCollections(true, false, false); + + return $sc->GetLastSyncTime(); + } + + /** + * Checks if the currently known collections for changes for $lifetime seconds. + * If the backend provides a ChangesSink the sink will be used. + * If not every $interval seconds an exporter will be configured for each + * folder to perform GetChangeCount(). + * + * @param int $lifetime (opt) total lifetime to wait for changes / default 600s + * @param int $interval (opt) time between blocking operations of sink or polling / default 30s + * @param boolean $onlyPingable (opt) only check for folders which have the PingableFlag + * + * @access public + * @return boolean indicating if changes were found + * @throws StatusException with code SyncCollections::ERROR_NO_COLLECTIONS if no collections available + * with code SyncCollections::ERROR_WRONG_HIERARCHY if there were errors getting changes + */ + public function CheckForChanges($lifetime = 600, $interval = 30, $onlyPingable = false) { + $classes = array(); + foreach ($this->collections as $folderid => $spa){ + if ($onlyPingable && $spa->GetPingableFlag() !== true) + continue; + + if (!isset($classes[$spa->GetContentClass()])) + $classes[$spa->GetContentClass()] = 0; + $classes[$spa->GetContentClass()] += 1; + } + if (empty($classes)) + $checkClasses = "policies only"; + else if (array_sum($classes) > 4) { + $checkClasses = ""; + foreach($classes as $class=>$count) { + if ($count == 1) + $checkClasses .= sprintf("%s ", $class); + else + $checkClasses .= sprintf("%s(%d) ", $class, $count); + } + } + else + $checkClasses = implode("/", array_keys($classes)); + + $pingTracking = new PingTracking(); + $this->changes = array(); + $changesAvailable = false; + + ZPush::GetTopCollector()->SetAsPushConnection(); + ZPush::GetTopCollector()->AnnounceInformation(sprintf("lifetime %ds", $lifetime), true); + ZLog::Write(LOGLEVEL_INFO, sprintf("SyncCollections->CheckForChanges(): Waiting for %s changes... (lifetime %d seconds)", (empty($classes))?'policy':'store', $lifetime)); + + // use changes sink where available + $changesSink = false; + $forceRealExport = 0; + // do not create changessink if there are no folders + if (!empty($classes) && ZPush::GetBackend()->HasChangesSink()) { + $changesSink = true; + + // initialize all possible folders + foreach ($this->collections as $folderid => $spa) { + if ($onlyPingable && $spa->GetPingableFlag() !== true) + continue; + + // switch user store if this is a additional folder and initialize sink + ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid)); + if (! ZPush::GetBackend()->ChangesSinkInitialize($folderid)) + throw new StatusException(sprintf("Error initializing ChangesSink for folder id '%s'", $folderid), self::ERROR_WRONG_HIERARCHY); + } + } + + // wait for changes + $started = time(); + $endat = time() + $lifetime; + while(($now = time()) < $endat) { + // how long are we waiting for changes + $this->waitingTime = $now-$started; + + $nextInterval = $interval; + // we should not block longer than the lifetime + if ($endat - $now < $nextInterval) + $nextInterval = $endat - $now; + + // Check if provisioning is necessary + // if a PolicyKey was sent use it. If not, compare with the ReferencePolicyKey + if (PROVISIONING === true && $this->GetReferencePolicyKey() !== false && ZPush::GetDeviceManager()->ProvisioningRequired($this->GetReferencePolicyKey(), true)) + // the hierarchysync forces provisioning + throw new StatusException("SyncCollections->CheckForChanges(): PolicyKey changed. Provisioning required.", self::ERROR_WRONG_HIERARCHY); + + // Check if a hierarchy sync is necessary + if (ZPush::GetDeviceManager()->IsHierarchySyncRequired()) + throw new StatusException("SyncCollections->CheckForChanges(): HierarchySync required.", self::ERROR_WRONG_HIERARCHY); + + // Check if there are newer requests + // If so, this process should be terminated if more than 60 secs to go + if ($pingTracking->DoForcePingTimeout()) { + // do not update CPOs because another process has already read them! + $this->saveData = false; + + // more than 60 secs to go? + if (($now + 60) < $endat) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Timeout forced after %ss from %ss due to other process", ($now-$started), $lifetime)); + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Forced timeout after %ds", ($now-$started)), true); + return false; + } + } + + // Use changes sink if available + if ($changesSink) { + // in some occasions we do realize a full export to see if there are pending changes + // every 5 minutes this is also done to see if there were "missed" notifications + if (SINK_FORCERECHECK !== false && $forceRealExport+SINK_FORCERECHECK <= $now) { + if ($this->CountChanges($onlyPingable)) { + ZLog::Write(LOGLEVEL_DEBUG, "SyncCollections->CheckForChanges(): Using ChangesSink but found relevant changes on regular export"); + return true; + } + $forceRealExport = $now; + } + + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Sink %d/%ds on %s", ($now-$started), $lifetime, $checkClasses)); + $notifications = ZPush::GetBackend()->ChangesSink($nextInterval); + + $validNotifications = false; + foreach ($notifications as $folderid) { + // check if the notification on the folder is within our filter + if ($this->CountChange($folderid)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s'", $folderid)); + $validNotifications = true; + } + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Notification received on folder '%s', but it is not relevant", $folderid)); + } + } + if ($validNotifications) + return true; + } + // use polling mechanism + else { + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Polling %d/%ds on %s", ($now-$started), $lifetime, $checkClasses)); + if ($this->CountChanges($onlyPingable)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): Found changes polling")); + return true; + } + else { + sleep($nextInterval); + } + } // end polling + } // end wait for changes + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->CheckForChanges(): no changes found after %ds", time() - $started)); + + return false; + } + + /** + * Checks if the currently known collections for + * changes performing Exporter->GetChangeCount() + * + * @param boolean $onlyPingable (opt) only check for folders which have the PingableFlag + * + * @access public + * @return boolean indicating if changes were found or not + */ + public function CountChanges($onlyPingable = false) { + $changesAvailable = false; + foreach ($this->collections as $folderid => $spa) { + if ($onlyPingable && $spa->GetPingableFlag() !== true) + continue; + + if (isset($this->addparms[$spa->GetFolderId()]["status"]) && $this->addparms[$spa->GetFolderId()]["status"] != SYNC_STATUS_SUCCESS) + continue; + + if ($this->CountChange($folderid)) + $changesAvailable = true; + } + + return $changesAvailable; + } + + /** + * Checks a folder for changes performing Exporter->GetChangeCount() + * + * @param string $folderid counts changes for a folder + * + * @access private + * @return boolean indicating if changes were found or not + */ + private function CountChange($folderid) { + $spa = $this->GetCollection($folderid); + + // switch user store if this is a additional folder (additional true -> do not debug) + ZPush::GetBackend()->Setup(ZPush::GetAdditionalSyncFolderStore($folderid, true)); + $changecount = false; + + try { + $exporter = ZPush::GetBackend()->GetExporter($folderid); + if ($exporter !== false && isset($this->addparms[$folderid]["state"])) { + $importer = false; + + $exporter->Config($this->addparms[$folderid]["state"], BACKEND_DISCARD_DATA); + $exporter->ConfigContentParameters($spa->GetCPO()); + $ret = $exporter->InitializeExporter($importer); + + if ($ret !== false) + $changecount = $exporter->GetChangeCount(); + } + } + catch (StatusException $ste) { + throw new StatusException("SyncCollections->CountChange(): exporter can not be re-configured.", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); + } + + // start over if exporter can not be configured atm + if ($changecount === false ) + ZLog::Write(LOGLEVEL_WARN, "SyncCollections->CountChange(): no changes received from Exporter."); + + $this->changes[$folderid] = $changecount; + + if(isset($this->addparms[$folderid]['savestate'])) { + try { + // Discard any data + while(is_array($exporter->Synchronize())); + $this->addparms[$folderid]['savestate'] = $exporter->GetState(); + } + catch (StatusException $ste) { + throw new StatusException("SyncCollections->CountChange(): could not get new state from exporter", self::ERROR_WRONG_HIERARCHY, null, LOGLEVEL_WARN); + } + } + + return ($changecount > 0); + } + + /** + * Returns an array with all folderid and the amount of changes found + * + * @access public + * @return array + */ + public function GetChangedFolderIds() { + return $this->changes; + } + + /** + * Indicates if the process did wait in a sink, polling or before running a + * regular export to find changes + * + * @access public + * @return array + */ + public function WaitedForChanges() { + return ($this->waitingTime > 1); + } + + /** + * Simple Iterator Interface implementation to traverse through collections + */ + + /** + * Rewind the Iterator to the first element + * + * @access public + * @return + */ + public function rewind() { + return reset($this->collections); + } + + /** + * Returns the current element + * + * @access public + * @return mixed + */ + public function current() { + return current($this->collections); + } + + /** + * Return the key of the current element + * + * @access public + * @return scalar on success, or NULL on failure. + */ + public function key() { + return key($this->collections); + } + + /** + * Move forward to next element + * + * @access public + * @return + */ + public function next() { + return next($this->collections); + } + + /** + * Checks if current position is valid + * + * @access public + * @return boolean + */ + public function valid() { + return (key($this->collections) !== null); + } + + /** + * Gets the StateManager from the DeviceManager + * if it's not available + * + * @access private + * @return + */ + private function loadStateManager() { + if (!isset($this->stateManager)) + $this->stateManager = ZPush::GetDeviceManager()->GetStateManager(); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/syncparameters.php b/z-push/lib/core/syncparameters.php new file mode 100644 index 0000000..85182ab --- /dev/null +++ b/z-push/lib/core/syncparameters.php @@ -0,0 +1,337 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class SyncParameters extends StateObject { + const DEFAULTOPTIONS = "DEFAULT"; + 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) { + if ($options !== self::DEFAULTOPTIONS && $options !== self::SMSOPTIONS) + throw new FatalNotImplementedException(sprintf("SyncParameters->GetCPO('%s') ContentParameters is invalid. Such type is not available.", $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) { + if ($options !== self::DEFAULTOPTIONS && $options !== self::SMSOPTIONS) + throw new FatalNotImplementedException(sprintf("SyncParameters->UseCPO('%s') ContentParameters is invalid. Such type is not available.", $options)); + + 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) { + if (!isset($this->contentParameters[$options])) { + $a = $this->contentParameters; + $a[$options] = new ContentParameters(); + $this->contentParameters = $a; + } + + return true; + } + + /** + * 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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/topcollector.php b/z-push/lib/core/topcollector.php new file mode 100644 index 0000000..7255327 --- /dev/null +++ b/z-push/lib/core/topcollector.php @@ -0,0 +1,297 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class TopCollector extends InterProcessData { + const ENABLEDAT = 2; + const TOPDATA = 3; + + protected $preserved; + protected $latest; + + /** + * Constructor + * + * @access public + */ + public function TopCollector() { + // initialize super parameters + $this->allocate = 2097152; // 2 MB + $this->type = 20; + parent::__construct(); + + // initialize params + $this->InitializeParams(); + + $this->preserved = array(); + // static vars come from the parent class + $this->latest = array( "pid" => self::$pid, + "ip" => Request::GetRemoteAddr(), + "user" => self::$user, + "start" => self::$start, + "devtype" => Request::GetDeviceType(), + "devid" => self::$devid, + "devagent" => Request::GetUserAgent(), + "command" => Request::GetCommandCode(), + "ended" => 0, + "push" => false, + ); + + $this->AnnounceInformation("initializing"); + } + + /** + * Destructor + * indicates that the process is shutting down + * + * @access public + */ + public function __destruct() { + $this->AnnounceInformation("OK", false, true); + } + + /** + * Advices all other processes that they should start/stop + * collecting data. The data saved is a timestamp. It has to be + * reactivated every couple of seconds + * + * @param boolean $stop (opt) default false (do collect) + * + * @access public + * @return boolean indicating if it was set to collect before + */ + public function CollectData($stop = false) { + $wasEnabled = false; + + // exclusive block + if ($this->blockMutex()) { + $wasEnabled = ($this->hasData(self::ENABLEDAT)) ? $this->getData(self::ENABLEDAT) : false; + + $time = time(); + if ($stop === true) $time = 0; + + if (! $this->setData($time, self::ENABLEDAT)) + return false; + $this->releaseMutex(); + } + // end exclusive block + + return $wasEnabled; + } + + /** + * Announces a string to the TopCollector + * + * @param string $info + * @param boolean $preserve info should be displayed when process terminates + * @param boolean $terminating indicates if the process is terminating + * + * @access public + * @return boolean + */ + public function AnnounceInformation($addinfo, $preserve = false, $terminating = false) { + $this->latest["addinfo"] = $addinfo; + $this->latest["update"] = time(); + + if ($terminating) { + $this->latest["ended"] = time(); + foreach ($this->preserved as $p) + $this->latest["addinfo"] .= " : ".$p; + } + + if ($preserve) + $this->preserved[] = $addinfo; + + // exclusive block + if ($this->blockMutex()) { + + if ($this->isEnabled()) { + $topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA): array(); + + $this->checkArrayStructure($topdata); + + // update + $topdata[self::$devid][self::$user][self::$pid] = $this->latest; + $ok = $this->setData($topdata, self::TOPDATA); + } + $this->releaseMutex(); + } + // end exclusive block + + if ($this->isEnabled() === true && !$ok) { + ZLog::Write(LOGLEVEL_WARN, "TopCollector::AnnounceInformation(): could not write to shared memory. Z-Push top will not display this data."); + return false; + } + + return true; + } + + /** + * Returns all available top data + * + * @access public + * @return array + */ + public function ReadLatest() { + $topdata = array(); + + // exclusive block + if ($this->blockMutex()) { + $topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA) : array(); + $this->releaseMutex(); + } + // end exclusive block + + return $topdata; + } + + /** + * Cleans up data collected so far + * + * @param boolean $all (optional) if set all data independently from the age is removed + * + * @access public + * @return boolean status + */ + public function ClearLatest($all = false) { + // it's ok when doing this every 10 sec + if ($all == false && time() % 10 != 0 ) + return true; + + $stat = false; + + // exclusive block + if ($this->blockMutex()) { + if ($all == true) { + $topdata = array(); + } + else { + $topdata = ($this->hasData(self::TOPDATA)) ? $this->getData(self::TOPDATA) : array(); + + $toClear = array(); + foreach ($topdata as $devid=>$users) { + foreach ($users as $user=>$pids) { + foreach ($pids as $pid=>$line) { + // remove everything which terminated for 20 secs or is not updated for more than 120 secs + if (($line["ended"] != 0 && time() - $line["ended"] > 20) || + time() - $line["update"] > 120) { + $toClear[] = array($devid, $user, $pid); + } + } + } + } + foreach ($toClear as $tc) + unset($topdata[$tc[0]][$tc[1]][$tc[2]]); + } + + $stat = $this->setData($topdata, self::TOPDATA); + $this->releaseMutex(); + } + // end exclusive block + + return $stat; + } + + /** + * Sets a different UserAgent for this connection + * + * @param string $agent + * + * @access public + * @return boolean + */ + public function SetUserAgent($agent) { + $this->latest["devagent"] = $agent; + } + + /** + * Marks this process as push connection + * + * @param string $agent + * + * @access public + * @return boolean + */ + public function SetAsPushConnection() { + $this->latest["push"] = true; + } + + /** + * Indicates if top data should be saved or not + * Returns true for 10 seconds after the latest CollectData() + * SHOULD only be called with locked mutex! + * + * @access private + * @return boolean + */ + private function isEnabled() { + $isEnabled = ($this->hasData(self::ENABLEDAT)) ? $this->getData(self::ENABLEDAT) : false; + return ($isEnabled !== false && ($isEnabled +300) > time()); + } + + /** + * Builds an array structure for the top data + * + * @param array $topdata reference to the topdata array + * + * @access private + * @return + */ + private function checkArrayStructure(&$topdata) { + if (!isset($topdata) || !is_array($topdata)) + $topdata = array(); + + if (!isset($topdata[self::$devid])) + $topdata[self::$devid] = array(); + + if (!isset($topdata[self::$devid][self::$user])) + $topdata[self::$devid][self::$user] = array(); + + if (!isset($topdata[self::$devid][self::$user][self::$pid])) + $topdata[self::$devid][self::$user][self::$pid] = array(); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/core/zlog.php b/z-push/lib/core/zlog.php new file mode 100644 index 0000000..5177f12 --- /dev/null +++ b/z-push/lib/core/zlog.php @@ -0,0 +1,257 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class ZLog { + static private $devid = ''; + static private $user = ''; + static private $authUser = false; + static private $pidstr; + static private $wbxmlDebug = ''; + static private $lastLogs = array(); + + /** + * Initializes the logging + * + * @access public + * @return boolean + */ + static public function Initialize() { + global $specialLogUsers; + + // define some constants for the logging + if (!defined('LOGUSERLEVEL')) + define('LOGUSERLEVEL', LOGLEVEL_OFF); + + if (!defined('LOGLEVEL')) + define('LOGLEVEL', LOGLEVEL_OFF); + + list($user,) = Utils::SplitDomainUser(Request::GetGETUser()); + if (!defined('WBXML_DEBUG') && $user) { + // define the WBXML_DEBUG mode on user basis depending on the configurations + if (LOGLEVEL >= LOGLEVEL_WBXML || (LOGUSERLEVEL >= LOGLEVEL_WBXML && in_array($user, $specialLogUsers))) + define('WBXML_DEBUG', true); + else + define('WBXML_DEBUG', false); + } + + if ($user) + self::$user = '['. $user .'] '; + else + self::$user = ''; + + // log the device id if the global loglevel is set to log devid or the user is in and has the right log level + if (Request::GetDeviceID() != "" && (LOGLEVEL >= LOGLEVEL_DEVICEID || (LOGUSERLEVEL >= LOGLEVEL_DEVICEID && in_array($user, $specialLogUsers)))) + self::$devid = '['. Request::GetDeviceID() .'] '; + else + self::$devid = ''; + + return true; + } + + /** + * Writes a log line + * + * @param int $loglevel one of the defined LOGLEVELS + * @param string $message + * + * @access public + * @return + */ + static public function Write($loglevel, $message) { + self::$lastLogs[$loglevel] = $message; + $data = self::buildLogString($loglevel) . $message . "\n"; + + if ($loglevel <= LOGLEVEL) { + @file_put_contents(LOGFILE, $data, FILE_APPEND); + } + + if ($loglevel <= LOGUSERLEVEL && self::logToUserFile()) { + // padd level for better reading + $data = str_replace(self::getLogLevelString($loglevel), self::getLogLevelString($loglevel,true), $data); + // only use plain old a-z characters for the generic log file + @file_put_contents(LOGFILEDIR . self::logToUserFile() . ".log", $data, FILE_APPEND); + } + + if (($loglevel & LOGLEVEL_FATAL) || ($loglevel & LOGLEVEL_ERROR)) { + @file_put_contents(LOGERRORFILE, $data, FILE_APPEND); + } + + if ($loglevel & LOGLEVEL_WBXMLSTACK) { + self::$wbxmlDebug .= $message. "\n"; + } + } + + /** + * Returns logged information about the WBXML stack + * + * @access public + * @return string + */ + static public function GetWBXMLDebugInfo() { + return trim(self::$wbxmlDebug); + } + + /** + * Returns the last message logged for a log level + * + * @param int $loglevel one of the defined LOGLEVELS + * + * @access public + * @return string/false returns false if there was no message logged in that level + */ + static public function GetLastMessage($loglevel) { + return (isset(self::$lastLogs[$loglevel]))?self::$lastLogs[$loglevel]:false; + } + + /**---------------------------------------------------------------------------------------------------------- + * private log stuff + */ + + /** + * Returns the filename logs for a WBXML debug log user should be saved to + * + * @access private + * @return string + */ + static private function logToUserFile() { + global $specialLogUsers; + + if (self::$authUser === false) { + if (RequestProcessor::isUserAuthenticated()) { + $authuser = Request::GetAuthUser(); + if ($authuser && in_array($authuser, $specialLogUsers)) + self::$authUser = preg_replace('/[^a-z0-9]/', '_', strtolower($authuser)); + } + } + return self::$authUser; + } + + /** + * Returns the string to be logged + * + * @access private + * @return string + */ + static private function buildLogString($loglevel) { + if (!isset(self::$pidstr)) + self::$pidstr = '[' . str_pad(@getmypid(),5," ",STR_PAD_LEFT) . '] '; + + if (!isset(self::$user)) + self::$user = ''; + + if (!isset(self::$devid)) + self::$devid = ''; + + return Utils::GetFormattedTime() ." ". self::$pidstr . self::getLogLevelString($loglevel, (LOGLEVEL > LOGLEVEL_INFO)) ." ". self::$user . self::$devid; + } + + /** + * Returns the string representation of the LOGLEVEL. + * String can be padded + * + * @param int $loglevel one of the LOGLEVELs + * @param boolean $pad + * + * @access private + * @return string + */ + static private function getLogLevelString($loglevel, $pad = false) { + if ($pad) $s = " "; + else $s = ""; + switch($loglevel) { + case LOGLEVEL_OFF: return ""; break; + case LOGLEVEL_FATAL: return "[FATAL]"; break; + case LOGLEVEL_ERROR: return "[ERROR]"; break; + case LOGLEVEL_WARN: return "[".$s."WARN]"; break; + case LOGLEVEL_INFO: return "[".$s."INFO]"; break; + case LOGLEVEL_DEBUG: return "[DEBUG]"; break; + case LOGLEVEL_WBXML: return "[WBXML]"; break; + case LOGLEVEL_DEVICEID: return "[DEVICEID]"; break; + case LOGLEVEL_WBXMLSTACK: return "[WBXMLSTACK]"; break; + } + } +} + +/**---------------------------------------------------------------------------------------------------------- + * Legacy debug stuff + */ + +// deprecated +// backwards compatible +function debugLog($message) { + ZLog::Write(LOGLEVEL_DEBUG, $message); +} + +// TODO review error handler +function zarafa_error_handler($errno, $errstr, $errfile, $errline, $errcontext) { + $bt = debug_backtrace(); + switch ($errno) { + case 8192: // E_DEPRECATED since PHP 5.3.0 + // do not handle this message + break; + + case E_NOTICE: + case E_WARNING: + // TODO check if there is a better way to avoid these messages + if (stripos($errfile,'interprocessdata') !== false && stripos($errstr,'shm_get_var()') !== false) + break; + ZLog::Write(LOGLEVEL_WARN, "$errfile:$errline $errstr ($errno)"); + break; + + default: + ZLog::Write(LOGLEVEL_ERROR, "trace error: $errfile:$errline $errstr ($errno) - backtrace: ". (count($bt)-1) . " steps"); + for($i = 1, $bt_length = count($bt); $i < $bt_length; $i++) { + $file = $line = "unknown"; + if (isset($bt[$i]['file'])) $file = $bt[$i]['file']; + if (isset($bt[$i]['line'])) $line = $bt[$i]['line']; + ZLog::Write(LOGLEVEL_ERROR, "trace: $i:". $file . ":" . $line. " - " . ((isset($bt[$i]['class']))? $bt[$i]['class'] . $bt[$i]['type']:""). $bt[$i]['function']. "()"); + } + //throw new Exception("An error occured."); + break; + } +} + +error_reporting(E_ALL); +set_error_handler("zarafa_error_handler"); + +?> \ No newline at end of file diff --git a/z-push/lib/core/zpush.php b/z-push/lib/core/zpush.php new file mode 100644 index 0000000..c4a62d6 --- /dev/null +++ b/z-push/lib/core/zpush.php @@ -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 LOGFILE 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 ActiveSync +
    + + +

    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.
    +
    + + +END; + } + + /** + * Indicates the latest AS version supported by Z-Push + * + * @access public + * @return string + */ + static public function GetLatestSupportedASVersion() { + return end(self::$supportedASVersions); + } + + /** + * Indicates which is the highest AS version supported by the backend + * + * @access public + * @return string + * @throws FatalNotImplementedException if the backend returns an invalid version + */ + static public function GetSupportedASVersion() { + $version = self::GetBackend()->GetSupportedASVersion(); + if (!in_array($version, self::$supportedASVersions)) + throw new FatalNotImplementedException(sprintf("AS version '%s' reported by the backend is not supported", $version)); + + return $version; + } + + /** + * Returns AS server header + * + * @access public + * @return string + */ + static public function GetServerHeader() { + if (self::GetSupportedASVersion() == self::ASV_25) + return "MS-Server-ActiveSync: 6.5.7638.1"; + else + return "MS-Server-ActiveSync: ". self::GetSupportedASVersion(); + } + + /** + * Returns AS protocol versions which are supported + * + * @param boolean $valueOnly (opt) default: false (also returns the header name) + * + * @access public + * @return string + */ + static public function GetSupportedProtocolVersions($valueOnly = false) { + $versions = implode(',', array_slice(self::$supportedASVersions, 0, (array_search(self::GetSupportedASVersion(), self::$supportedASVersions)+1))); + ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedProtocolVersions(): " . $versions); + + if ($valueOnly === true) + return $versions; + + return "MS-ASProtocolVersions: " . $versions; + } + + /** + * Returns AS commands which are supported + * + * @access public + * @return string + */ + static public function GetSupportedCommands() { + $asCommands = array(); + // filter all non-activesync commands + foreach (self::$supportedCommands as $c=>$v) + if (!self::checkCommandOptions($c, self::NOACTIVESYNCCOMMAND) && + self::checkCommandOptions($c, self::GetSupportedASVersion())) + $asCommands[] = Utils::GetCommandFromCode($c); + + $commands = implode(',', $asCommands); + ZLog::Write(LOGLEVEL_DEBUG, "ZPush::GetSupportedCommands(): " . $commands); + return "MS-ASProtocolCommands: " . $commands; + } + + /** + * Loads and instantiates a request processor for a command + * + * @param int $commandCode + * + * @access public + * @return RequestProcessor sub-class + */ + static public function GetRequestHandlerForCommand($commandCode) { + if (!array_key_exists($commandCode, self::$supportedCommands) || + !array_key_exists(self::REQUESTHANDLER, self::$supportedCommands[$commandCode]) ) + throw new FatalNotImplementedException(sprintf("Command '%s' has no request handler or class", Utils::GetCommandFromCode($commandCode))); + + $class = self::$supportedCommands[$commandCode][self::REQUESTHANDLER]; + if ($class == "Webservice") + $handlerclass = REAL_BASE_PATH . "lib/webservice/webservice.php"; + else + $handlerclass = REAL_BASE_PATH . "lib/request/" . strtolower($class) . ".php"; + + if (is_file($handlerclass)) + include($handlerclass); + + if (class_exists($class)) + return new $class(); + else + throw new FatalNotImplementedException(sprintf("Request handler '%s' can not be loaded", $class)); + } + + /** + * Indicates if a commands requires authentication or not + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + static public function CommandNeedsAuthentication($commandCode) { + $stat = ! self::checkCommandOptions($commandCode, self::UNAUTHENTICATED); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsAuthentication(%d): %s", $commandCode, Utils::PrintAsString($stat))); + return $stat; + } + + /** + * Indicates if the Provisioning check has to be forced on these commands + * + * @param string $commandCode + + * @access public + * @return boolean + */ + static public function CommandNeedsProvisioning($commandCode) { + $stat = ! self::checkCommandOptions($commandCode, self::UNPROVISIONED); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsProvisioning(%s): %s", $commandCode, Utils::PrintAsString($stat))); + return $stat; + } + + /** + * Indicates if these commands expect plain text input instead of wbxml + * + * @param string $commandCode + * + * @access public + * @return boolean + */ + static public function CommandNeedsPlainInput($commandCode) { + $stat = self::checkCommandOptions($commandCode, self::PLAININPUT); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::CommandNeedsPlainInput(%d): %s", $commandCode, Utils::PrintAsString($stat))); + return $stat; + } + + /** + * Indicates if the comand to be executed operates on the hierarchy + * + * @param int $commandCode + + * @access public + * @return boolean + */ + static public function HierarchyCommand($commandCode) { + $stat = self::checkCommandOptions($commandCode, self::HIERARCHYCOMMAND); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZPush::HierarchyCommand(%d): %s", $commandCode, Utils::PrintAsString($stat))); + return $stat; + } + + /** + * Checks access types of a command + * + * @param string $commandCode a commandCode + * @param string $option e.g. self::UNAUTHENTICATED + + * @access private + * @throws FatalNotImplementedException + * @return object StateMachine + */ + static private function checkCommandOptions($commandCode, $option) { + if ($commandCode === false) return false; + + if (!array_key_exists($commandCode, self::$supportedCommands)) + throw new FatalNotImplementedException(sprintf("Command '%s' is not supported", Utils::GetCommandFromCode($commandCode))); + + $capa = self::$supportedCommands[$commandCode]; + $defcapa = in_array($option, $capa, true); + + // if not looking for a default capability, check if the command is supported since a previous AS version + if (!$defcapa) { + $verkey = array_search($option, self::$supportedASVersions, true); + if ($verkey !== false && ($verkey >= array_search($capa[0], self::$supportedASVersions))) { + $defcapa = true; + } + } + + return $defcapa; + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/core/zpushdefs.php b/z-push/lib/core/zpushdefs.php new file mode 100644 index 0000000..b85ae29 --- /dev/null +++ b/z-push/lib/core/zpushdefs.php @@ -0,0 +1,1050 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +define("SYNC_SYNCHRONIZE","Synchronize"); +define("SYNC_REPLIES","Replies"); +define("SYNC_ADD","Add"); +define("SYNC_MODIFY","Modify"); +define("SYNC_REMOVE","Remove"); +define("SYNC_FETCH","Fetch"); +define("SYNC_SYNCKEY","SyncKey"); +define("SYNC_CLIENTENTRYID","ClientEntryId"); +define("SYNC_SERVERENTRYID","ServerEntryId"); +define("SYNC_STATUS","Status"); +define("SYNC_FOLDER","Folder"); +define("SYNC_FOLDERTYPE","FolderType"); +define("SYNC_VERSION","Version"); +define("SYNC_FOLDERID","FolderId"); +define("SYNC_GETCHANGES","GetChanges"); +define("SYNC_MOREAVAILABLE","MoreAvailable"); +define("SYNC_MAXITEMS","MaxItems"); +define("SYNC_WINDOWSIZE","WindowSize"); //MaxItems before z-push 2 +define("SYNC_PERFORM","Perform"); +define("SYNC_OPTIONS","Options"); +define("SYNC_FILTERTYPE","FilterType"); +define("SYNC_TRUNCATION","Truncation"); +define("SYNC_RTFTRUNCATION","RtfTruncation"); +define("SYNC_CONFLICT","Conflict"); +define("SYNC_FOLDERS","Folders"); +define("SYNC_DATA","Data"); +define("SYNC_DELETESASMOVES","DeletesAsMoves"); +define("SYNC_NOTIFYGUID","NotifyGUID"); +define("SYNC_SUPPORTED","Supported"); +define("SYNC_SOFTDELETE","SoftDelete"); +define("SYNC_MIMESUPPORT","MIMESupport"); +define("SYNC_MIMETRUNCATION","MIMETruncation"); +define("SYNC_NEWMESSAGE","NewMessage"); +define("SYNC_WAIT","Wait"); //12.1 and 14.0 +define("SYNC_LIMIT","Limit"); //12.1 and 14.0 +define("SYNC_PARTIAL","Partial"); //12.1 and 14.0 +define("SYNC_CONVERSATIONMODE","ConversationMode"); //14.0 +define("SYNC_HEARTBEATINTERVAL","HeartbeatInterval"); //14.0 + +// POOMCONTACTS +define("SYNC_POOMCONTACTS_ANNIVERSARY","POOMCONTACTS:Anniversary"); +define("SYNC_POOMCONTACTS_ASSISTANTNAME","POOMCONTACTS:AssistantName"); +define("SYNC_POOMCONTACTS_ASSISTNAMEPHONENUMBER","POOMCONTACTS:AssistnamePhoneNumber"); +define("SYNC_POOMCONTACTS_BIRTHDAY","POOMCONTACTS:Birthday"); +define("SYNC_POOMCONTACTS_BODY","POOMCONTACTS:Body"); +define("SYNC_POOMCONTACTS_BODYSIZE","POOMCONTACTS:BodySize"); +define("SYNC_POOMCONTACTS_BODYTRUNCATED","POOMCONTACTS:BodyTruncated"); +define("SYNC_POOMCONTACTS_BUSINESS2PHONENUMBER","POOMCONTACTS:Business2PhoneNumber"); +define("SYNC_POOMCONTACTS_BUSINESSCITY","POOMCONTACTS:BusinessCity"); +define("SYNC_POOMCONTACTS_BUSINESSCOUNTRY","POOMCONTACTS:BusinessCountry"); +define("SYNC_POOMCONTACTS_BUSINESSPOSTALCODE","POOMCONTACTS:BusinessPostalCode"); +define("SYNC_POOMCONTACTS_BUSINESSSTATE","POOMCONTACTS:BusinessState"); +define("SYNC_POOMCONTACTS_BUSINESSSTREET","POOMCONTACTS:BusinessStreet"); +define("SYNC_POOMCONTACTS_BUSINESSFAXNUMBER","POOMCONTACTS:BusinessFaxNumber"); +define("SYNC_POOMCONTACTS_BUSINESSPHONENUMBER","POOMCONTACTS:BusinessPhoneNumber"); +define("SYNC_POOMCONTACTS_CARPHONENUMBER","POOMCONTACTS:CarPhoneNumber"); +define("SYNC_POOMCONTACTS_CATEGORIES","POOMCONTACTS:Categories"); +define("SYNC_POOMCONTACTS_CATEGORY","POOMCONTACTS:Category"); +define("SYNC_POOMCONTACTS_CHILDREN","POOMCONTACTS:Children"); +define("SYNC_POOMCONTACTS_CHILD","POOMCONTACTS:Child"); +define("SYNC_POOMCONTACTS_COMPANYNAME","POOMCONTACTS:CompanyName"); +define("SYNC_POOMCONTACTS_DEPARTMENT","POOMCONTACTS:Department"); +define("SYNC_POOMCONTACTS_EMAIL1ADDRESS","POOMCONTACTS:Email1Address"); +define("SYNC_POOMCONTACTS_EMAIL2ADDRESS","POOMCONTACTS:Email2Address"); +define("SYNC_POOMCONTACTS_EMAIL3ADDRESS","POOMCONTACTS:Email3Address"); +define("SYNC_POOMCONTACTS_FILEAS","POOMCONTACTS:FileAs"); +define("SYNC_POOMCONTACTS_FIRSTNAME","POOMCONTACTS:FirstName"); +define("SYNC_POOMCONTACTS_HOME2PHONENUMBER","POOMCONTACTS:Home2PhoneNumber"); +define("SYNC_POOMCONTACTS_HOMECITY","POOMCONTACTS:HomeCity"); +define("SYNC_POOMCONTACTS_HOMECOUNTRY","POOMCONTACTS:HomeCountry"); +define("SYNC_POOMCONTACTS_HOMEPOSTALCODE","POOMCONTACTS:HomePostalCode"); +define("SYNC_POOMCONTACTS_HOMESTATE","POOMCONTACTS:HomeState"); +define("SYNC_POOMCONTACTS_HOMESTREET","POOMCONTACTS:HomeStreet"); +define("SYNC_POOMCONTACTS_HOMEFAXNUMBER","POOMCONTACTS:HomeFaxNumber"); +define("SYNC_POOMCONTACTS_HOMEPHONENUMBER","POOMCONTACTS:HomePhoneNumber"); +define("SYNC_POOMCONTACTS_JOBTITLE","POOMCONTACTS:JobTitle"); +define("SYNC_POOMCONTACTS_LASTNAME","POOMCONTACTS:LastName"); +define("SYNC_POOMCONTACTS_MIDDLENAME","POOMCONTACTS:MiddleName"); +define("SYNC_POOMCONTACTS_MOBILEPHONENUMBER","POOMCONTACTS:MobilePhoneNumber"); +define("SYNC_POOMCONTACTS_OFFICELOCATION","POOMCONTACTS:OfficeLocation"); +define("SYNC_POOMCONTACTS_OTHERCITY","POOMCONTACTS:OtherCity"); +define("SYNC_POOMCONTACTS_OTHERCOUNTRY","POOMCONTACTS:OtherCountry"); +define("SYNC_POOMCONTACTS_OTHERPOSTALCODE","POOMCONTACTS:OtherPostalCode"); +define("SYNC_POOMCONTACTS_OTHERSTATE","POOMCONTACTS:OtherState"); +define("SYNC_POOMCONTACTS_OTHERSTREET","POOMCONTACTS:OtherStreet"); +define("SYNC_POOMCONTACTS_PAGERNUMBER","POOMCONTACTS:PagerNumber"); +define("SYNC_POOMCONTACTS_RADIOPHONENUMBER","POOMCONTACTS:RadioPhoneNumber"); +define("SYNC_POOMCONTACTS_SPOUSE","POOMCONTACTS:Spouse"); +define("SYNC_POOMCONTACTS_SUFFIX","POOMCONTACTS:Suffix"); +define("SYNC_POOMCONTACTS_TITLE","POOMCONTACTS:Title"); +define("SYNC_POOMCONTACTS_WEBPAGE","POOMCONTACTS:WebPage"); +define("SYNC_POOMCONTACTS_YOMICOMPANYNAME","POOMCONTACTS:YomiCompanyName"); +define("SYNC_POOMCONTACTS_YOMIFIRSTNAME","POOMCONTACTS:YomiFirstName"); +define("SYNC_POOMCONTACTS_YOMILASTNAME","POOMCONTACTS:YomiLastName"); +define("SYNC_POOMCONTACTS_RTF","POOMCONTACTS:Rtf"); +define("SYNC_POOMCONTACTS_PICTURE","POOMCONTACTS:Picture"); +define("SYNC_POOMCONTACTS_ALIAS","POOMCONTACTS:Alias"); //14.0 +define("SYNC_POOMCONTACTS_WEIGHEDRANK","POOMCONTACTS:WeightedRank"); //14.0 + +// POOMMAIL +define("SYNC_POOMMAIL_ATTACHMENT","POOMMAIL:Attachment"); +define("SYNC_POOMMAIL_ATTACHMENTS","POOMMAIL:Attachments"); +define("SYNC_POOMMAIL_ATTNAME","POOMMAIL:AttName"); +define("SYNC_POOMMAIL_ATTSIZE","POOMMAIL:AttSize"); +define("SYNC_POOMMAIL_ATTOID","POOMMAIL:AttOid"); +define("SYNC_POOMMAIL_ATTMETHOD","POOMMAIL:AttMethod"); +define("SYNC_POOMMAIL_ATTREMOVED","POOMMAIL:AttRemoved"); +define("SYNC_POOMMAIL_BODY","POOMMAIL:Body"); +define("SYNC_POOMMAIL_BODYSIZE","POOMMAIL:BodySize"); +define("SYNC_POOMMAIL_BODYTRUNCATED","POOMMAIL:BodyTruncated"); +define("SYNC_POOMMAIL_DATERECEIVED","POOMMAIL:DateReceived"); +define("SYNC_POOMMAIL_DISPLAYNAME","POOMMAIL:DisplayName"); +define("SYNC_POOMMAIL_DISPLAYTO","POOMMAIL:DisplayTo"); +define("SYNC_POOMMAIL_IMPORTANCE","POOMMAIL:Importance"); +define("SYNC_POOMMAIL_MESSAGECLASS","POOMMAIL:MessageClass"); +define("SYNC_POOMMAIL_SUBJECT","POOMMAIL:Subject"); +define("SYNC_POOMMAIL_READ","POOMMAIL:Read"); +define("SYNC_POOMMAIL_TO","POOMMAIL:To"); +define("SYNC_POOMMAIL_CC","POOMMAIL:Cc"); +define("SYNC_POOMMAIL_FROM","POOMMAIL:From"); +define("SYNC_POOMMAIL_REPLY_TO","POOMMAIL:Reply-To"); +define("SYNC_POOMMAIL_ALLDAYEVENT","POOMMAIL:AllDayEvent"); +define("SYNC_POOMMAIL_CATEGORIES","POOMMAIL:Categories"); //not supported in 12.1 +define("SYNC_POOMMAIL_CATEGORY","POOMMAIL:Category"); //not supported in 12.1 +define("SYNC_POOMMAIL_DTSTAMP","POOMMAIL:DtStamp"); +define("SYNC_POOMMAIL_ENDTIME","POOMMAIL:EndTime"); +define("SYNC_POOMMAIL_INSTANCETYPE","POOMMAIL:InstanceType"); +define("SYNC_POOMMAIL_BUSYSTATUS","POOMMAIL:BusyStatus"); +define("SYNC_POOMMAIL_LOCATION","POOMMAIL:Location"); +define("SYNC_POOMMAIL_MEETINGREQUEST","POOMMAIL:MeetingRequest"); +define("SYNC_POOMMAIL_ORGANIZER","POOMMAIL:Organizer"); +define("SYNC_POOMMAIL_RECURRENCEID","POOMMAIL:RecurrenceId"); +define("SYNC_POOMMAIL_REMINDER","POOMMAIL:Reminder"); +define("SYNC_POOMMAIL_RESPONSEREQUESTED","POOMMAIL:ResponseRequested"); +define("SYNC_POOMMAIL_RECURRENCES","POOMMAIL:Recurrences"); +define("SYNC_POOMMAIL_RECURRENCE","POOMMAIL:Recurrence"); +define("SYNC_POOMMAIL_TYPE","POOMMAIL:Type"); +define("SYNC_POOMMAIL_UNTIL","POOMMAIL:Until"); +define("SYNC_POOMMAIL_OCCURRENCES","POOMMAIL:Occurrences"); +define("SYNC_POOMMAIL_INTERVAL","POOMMAIL:Interval"); +define("SYNC_POOMMAIL_DAYOFWEEK","POOMMAIL:DayOfWeek"); +define("SYNC_POOMMAIL_DAYOFMONTH","POOMMAIL:DayOfMonth"); +define("SYNC_POOMMAIL_WEEKOFMONTH","POOMMAIL:WeekOfMonth"); +define("SYNC_POOMMAIL_MONTHOFYEAR","POOMMAIL:MonthOfYear"); +define("SYNC_POOMMAIL_STARTTIME","POOMMAIL:StartTime"); +define("SYNC_POOMMAIL_SENSITIVITY","POOMMAIL:Sensitivity"); +define("SYNC_POOMMAIL_TIMEZONE","POOMMAIL:TimeZone"); +define("SYNC_POOMMAIL_GLOBALOBJID","POOMMAIL:GlobalObjId"); +define("SYNC_POOMMAIL_THREADTOPIC","POOMMAIL:ThreadTopic"); +define("SYNC_POOMMAIL_MIMEDATA","POOMMAIL:MIMEData"); +define("SYNC_POOMMAIL_MIMETRUNCATED","POOMMAIL:MIMETruncated"); +define("SYNC_POOMMAIL_MIMESIZE","POOMMAIL:MIMESize"); +define("SYNC_POOMMAIL_INTERNETCPID","POOMMAIL:InternetCPID"); +define("SYNC_POOMMAIL_FLAG", "POOMMAIL:Flag"); //12.0, 12.1 and 14.0 +define("SYNC_POOMMAIL_FLAGSTATUS", "POOMMAIL:FlagStatus"); //12.0, 12.1 and 14.0 +define("SYNC_POOMMAIL_CONTENTCLASS", "POOMMAIL:ContentClass"); //12.0, 12.1 and 14.0 +define("SYNC_POOMMAIL_FLAGTYPE", "POOMMAIL:FlagType"); //12.0, 12.1 and 14.0 +define("SYNC_POOMMAIL_COMPLETETIME", "POOMMAIL:CompleteTime"); //14.0 +define("SYNC_POOMMAIL_DISALLOWNEWTIMEPROPOSAL", "POOMMAIL:DisallowNewTimeProposal"); //14.0 + +// AIRNOTIFY +define("SYNC_AIRNOTIFY_NOTIFY","AirNotify:Notify"); +define("SYNC_AIRNOTIFY_NOTIFICATION","AirNotify:Notification"); +define("SYNC_AIRNOTIFY_VERSION","AirNotify:Version"); +define("SYNC_AIRNOTIFY_LIFETIME","AirNotify:Lifetime"); +define("SYNC_AIRNOTIFY_DEVICEINFO","AirNotify:DeviceInfo"); +define("SYNC_AIRNOTIFY_ENABLE","AirNotify:Enable"); +define("SYNC_AIRNOTIFY_FOLDER","AirNotify:Folder"); +define("SYNC_AIRNOTIFY_SERVERENTRYID","AirNotify:ServerEntryId"); +define("SYNC_AIRNOTIFY_DEVICEADDRESS","AirNotify:DeviceAddress"); +define("SYNC_AIRNOTIFY_VALIDCARRIERPROFILES","AirNotify:ValidCarrierProfiles"); +define("SYNC_AIRNOTIFY_CARRIERPROFILE","AirNotify:CarrierProfile"); +define("SYNC_AIRNOTIFY_STATUS","AirNotify:Status"); +define("SYNC_AIRNOTIFY_REPLIES","AirNotify:Replies"); +define("SYNC_AIRNOTIFY_VERSION='1.1'","AirNotify:Version='1.1'"); +define("SYNC_AIRNOTIFY_DEVICES","AirNotify:Devices"); +define("SYNC_AIRNOTIFY_DEVICE","AirNotify:Device"); +define("SYNC_AIRNOTIFY_ID","AirNotify:Id"); +define("SYNC_AIRNOTIFY_EXPIRY","AirNotify:Expiry"); +define("SYNC_AIRNOTIFY_NOTIFYGUID","AirNotify:NotifyGUID"); + +// POOMCAL +define("SYNC_POOMCAL_TIMEZONE","POOMCAL:Timezone"); +define("SYNC_POOMCAL_ALLDAYEVENT","POOMCAL:AllDayEvent"); +define("SYNC_POOMCAL_ATTENDEES","POOMCAL:Attendees"); +define("SYNC_POOMCAL_ATTENDEE","POOMCAL:Attendee"); +define("SYNC_POOMCAL_EMAIL","POOMCAL:Email"); +define("SYNC_POOMCAL_NAME","POOMCAL:Name"); +define("SYNC_POOMCAL_BODY","POOMCAL:Body"); +define("SYNC_POOMCAL_BODYTRUNCATED","POOMCAL:BodyTruncated"); +define("SYNC_POOMCAL_BUSYSTATUS","POOMCAL:BusyStatus"); +define("SYNC_POOMCAL_CATEGORIES","POOMCAL:Categories"); +define("SYNC_POOMCAL_CATEGORY","POOMCAL:Category"); +define("SYNC_POOMCAL_RTF","POOMCAL:Rtf"); +define("SYNC_POOMCAL_DTSTAMP","POOMCAL:DtStamp"); +define("SYNC_POOMCAL_ENDTIME","POOMCAL:EndTime"); +define("SYNC_POOMCAL_EXCEPTION","POOMCAL:Exception"); +define("SYNC_POOMCAL_EXCEPTIONS","POOMCAL:Exceptions"); +define("SYNC_POOMCAL_DELETED","POOMCAL:Deleted"); +define("SYNC_POOMCAL_EXCEPTIONSTARTTIME","POOMCAL:ExceptionStartTime"); +define("SYNC_POOMCAL_LOCATION","POOMCAL:Location"); +define("SYNC_POOMCAL_MEETINGSTATUS","POOMCAL:MeetingStatus"); +define("SYNC_POOMCAL_ORGANIZEREMAIL","POOMCAL:OrganizerEmail"); +define("SYNC_POOMCAL_ORGANIZERNAME","POOMCAL:OrganizerName"); +define("SYNC_POOMCAL_RECURRENCE","POOMCAL:Recurrence"); +define("SYNC_POOMCAL_TYPE","POOMCAL:Type"); +define("SYNC_POOMCAL_UNTIL","POOMCAL:Until"); +define("SYNC_POOMCAL_OCCURRENCES","POOMCAL:Occurrences"); +define("SYNC_POOMCAL_INTERVAL","POOMCAL:Interval"); +define("SYNC_POOMCAL_DAYOFWEEK","POOMCAL:DayOfWeek"); +define("SYNC_POOMCAL_DAYOFMONTH","POOMCAL:DayOfMonth"); +define("SYNC_POOMCAL_WEEKOFMONTH","POOMCAL:WeekOfMonth"); +define("SYNC_POOMCAL_MONTHOFYEAR","POOMCAL:MonthOfYear"); +define("SYNC_POOMCAL_REMINDER","POOMCAL:Reminder"); +define("SYNC_POOMCAL_SENSITIVITY","POOMCAL:Sensitivity"); +define("SYNC_POOMCAL_SUBJECT","POOMCAL:Subject"); +define("SYNC_POOMCAL_STARTTIME","POOMCAL:StartTime"); +define("SYNC_POOMCAL_UID","POOMCAL:UID"); +define("SYNC_POOMCAL_ATTENDEESTATUS","POOMCAL:Attendee_Status"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTENDEETYPE","POOMCAL:Attendee_Type"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTACHMENT","POOMCAL:Attachment"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTACHMENTS","POOMCAL:Attachments"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTNAME","POOMCAL:AttName"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTSIZE","POOMCAL:AttSize"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTOID","POOMCAL:AttOid"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTMETHOD","POOMCAL:AttMethod"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_ATTREMOVED","POOMCAL:AttRemoved"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_DISPLAYNAME","POOMCAL:DisplayName"); //12.0, 12.1 and 14.0 +define("SYNC_POOMCAL_DISALLOWNEWTIMEPROPOSAL","POOMCAL:DisallowNewTimeProposal"); //14.0 +define("SYNC_POOMCAL_RESPONSEREQUESTED","POOMCAL:ResponseRequested"); //14.0 +define("SYNC_POOMCAL_APPOINTMENTREPLYTIME","POOMCAL:AppointmentReplyTime"); //14.0 +define("SYNC_POOMCAL_RESPONSETYPE","POOMCAL:ResponseType"); //14.0 +define("SYNC_POOMCAL_CALENDARTYPE","POOMCAL:CalendarType"); //14.0 +define("SYNC_POOMCAL_ISLEAPMONTH","POOMCAL:IsLeapMonth"); //14.0 +define("SYNC_POOMCAL_FIRSTDAYOFWEEK","POOMCAL:FirstDayOfWeek"); //post 14.0 +define("SYNC_POOMCAL_ONLINEMEETINGINTERNALLINK","POOMCAL:OnlineMeetingInternalLink"); //post 14.0 +define("SYNC_POOMCAL_ONLINEMEETINGEXTERNALLINK","POOMCAL:OnlineMeetingExternalLink"); //post 14.0 + +// Move +define("SYNC_MOVE_MOVES","Move:Moves"); +define("SYNC_MOVE_MOVE","Move:Move"); +define("SYNC_MOVE_SRCMSGID","Move:SrcMsgId"); +define("SYNC_MOVE_SRCFLDID","Move:SrcFldId"); +define("SYNC_MOVE_DSTFLDID","Move:DstFldId"); +define("SYNC_MOVE_RESPONSE","Move:Response"); +define("SYNC_MOVE_STATUS","Move:Status"); +define("SYNC_MOVE_DSTMSGID","Move:DstMsgId"); + +// GetItemEstimate +define("SYNC_GETITEMESTIMATE_GETITEMESTIMATE","GetItemEstimate:GetItemEstimate"); +define("SYNC_GETITEMESTIMATE_VERSION","GetItemEstimate:Version"); +define("SYNC_GETITEMESTIMATE_FOLDERS","GetItemEstimate:Folders"); +define("SYNC_GETITEMESTIMATE_FOLDER","GetItemEstimate:Folder"); +define("SYNC_GETITEMESTIMATE_FOLDERTYPE","GetItemEstimate:FolderType"); +define("SYNC_GETITEMESTIMATE_FOLDERID","GetItemEstimate:FolderId"); +define("SYNC_GETITEMESTIMATE_DATETIME","GetItemEstimate:DateTime"); +define("SYNC_GETITEMESTIMATE_ESTIMATE","GetItemEstimate:Estimate"); +define("SYNC_GETITEMESTIMATE_RESPONSE","GetItemEstimate:Response"); +define("SYNC_GETITEMESTIMATE_STATUS","GetItemEstimate:Status"); + +// FolderHierarchy +define("SYNC_FOLDERHIERARCHY_FOLDERS","FolderHierarchy:Folders"); +define("SYNC_FOLDERHIERARCHY_FOLDER","FolderHierarchy:Folder"); +define("SYNC_FOLDERHIERARCHY_DISPLAYNAME","FolderHierarchy:DisplayName"); +define("SYNC_FOLDERHIERARCHY_SERVERENTRYID","FolderHierarchy:ServerEntryId"); +define("SYNC_FOLDERHIERARCHY_PARENTID","FolderHierarchy:ParentId"); +define("SYNC_FOLDERHIERARCHY_TYPE","FolderHierarchy:Type"); +define("SYNC_FOLDERHIERARCHY_RESPONSE","FolderHierarchy:Response"); +define("SYNC_FOLDERHIERARCHY_STATUS","FolderHierarchy:Status"); +define("SYNC_FOLDERHIERARCHY_CONTENTCLASS","FolderHierarchy:ContentClass"); +define("SYNC_FOLDERHIERARCHY_CHANGES","FolderHierarchy:Changes"); +define("SYNC_FOLDERHIERARCHY_ADD","FolderHierarchy:Add"); +define("SYNC_FOLDERHIERARCHY_REMOVE","FolderHierarchy:Remove"); +define("SYNC_FOLDERHIERARCHY_UPDATE","FolderHierarchy:Update"); +define("SYNC_FOLDERHIERARCHY_SYNCKEY","FolderHierarchy:SyncKey"); +define("SYNC_FOLDERHIERARCHY_FOLDERCREATE","FolderHierarchy:FolderCreate"); +define("SYNC_FOLDERHIERARCHY_FOLDERDELETE","FolderHierarchy:FolderDelete"); +define("SYNC_FOLDERHIERARCHY_FOLDERUPDATE","FolderHierarchy:FolderUpdate"); +define("SYNC_FOLDERHIERARCHY_FOLDERSYNC","FolderHierarchy:FolderSync"); +define("SYNC_FOLDERHIERARCHY_COUNT","FolderHierarchy:Count"); +define("SYNC_FOLDERHIERARCHY_VERSION","FolderHierarchy:Version"); +// only for internal use - never to be streamed to the mobile +define("SYNC_FOLDERHIERARCHY_IGNORE_STORE","FolderHierarchy:IgnoreStore"); + +// MeetingResponse +define("SYNC_MEETINGRESPONSE_CALENDARID","MeetingResponse:CalendarId"); +define("SYNC_MEETINGRESPONSE_FOLDERID","MeetingResponse:FolderId"); +define("SYNC_MEETINGRESPONSE_MEETINGRESPONSE","MeetingResponse:MeetingResponse"); +define("SYNC_MEETINGRESPONSE_REQUESTID","MeetingResponse:RequestId"); +define("SYNC_MEETINGRESPONSE_REQUEST","MeetingResponse:Request"); +define("SYNC_MEETINGRESPONSE_RESULT","MeetingResponse:Result"); +define("SYNC_MEETINGRESPONSE_STATUS","MeetingResponse:Status"); +define("SYNC_MEETINGRESPONSE_USERRESPONSE","MeetingResponse:UserResponse"); +define("SYNC_MEETINGRESPONSE_VERSION","MeetingResponse:Version"); +define("SYNC_MEETINGRESPONSE_INSTANCEID","MeetingResponse:InstanceId"); + +// POOMTASKS +define("SYNC_POOMTASKS_BODY","POOMTASKS:Body"); +define("SYNC_POOMTASKS_BODYSIZE","POOMTASKS:BodySize"); +define("SYNC_POOMTASKS_BODYTRUNCATED","POOMTASKS:BodyTruncated"); +define("SYNC_POOMTASKS_CATEGORIES","POOMTASKS:Categories"); +define("SYNC_POOMTASKS_CATEGORY","POOMTASKS:Category"); +define("SYNC_POOMTASKS_COMPLETE","POOMTASKS:Complete"); +define("SYNC_POOMTASKS_DATECOMPLETED","POOMTASKS:DateCompleted"); +define("SYNC_POOMTASKS_DUEDATE","POOMTASKS:DueDate"); +define("SYNC_POOMTASKS_UTCDUEDATE","POOMTASKS:UtcDueDate"); +define("SYNC_POOMTASKS_IMPORTANCE","POOMTASKS:Importance"); +define("SYNC_POOMTASKS_RECURRENCE","POOMTASKS:Recurrence"); +define("SYNC_POOMTASKS_TYPE","POOMTASKS:Type"); +define("SYNC_POOMTASKS_START","POOMTASKS:Start"); +define("SYNC_POOMTASKS_UNTIL","POOMTASKS:Until"); +define("SYNC_POOMTASKS_OCCURRENCES","POOMTASKS:Occurrences"); +define("SYNC_POOMTASKS_INTERVAL","POOMTASKS:Interval"); +define("SYNC_POOMTASKS_DAYOFWEEK","POOMTASKS:DayOfWeek"); +define("SYNC_POOMTASKS_DAYOFMONTH","POOMTASKS:DayOfMonth"); +define("SYNC_POOMTASKS_WEEKOFMONTH","POOMTASKS:WeekOfMonth"); +define("SYNC_POOMTASKS_MONTHOFYEAR","POOMTASKS:MonthOfYear"); +define("SYNC_POOMTASKS_REGENERATE","POOMTASKS:Regenerate"); +define("SYNC_POOMTASKS_DEADOCCUR","POOMTASKS:DeadOccur"); +define("SYNC_POOMTASKS_REMINDERSET","POOMTASKS:ReminderSet"); +define("SYNC_POOMTASKS_REMINDERTIME","POOMTASKS:ReminderTime"); +define("SYNC_POOMTASKS_SENSITIVITY","POOMTASKS:Sensitivity"); +define("SYNC_POOMTASKS_STARTDATE","POOMTASKS:StartDate"); +define("SYNC_POOMTASKS_UTCSTARTDATE","POOMTASKS:UtcStartDate"); +define("SYNC_POOMTASKS_SUBJECT","POOMTASKS:Subject"); +define("SYNC_POOMTASKS_RTF","POOMTASKS:Rtf"); +define("SYNC_POOMTASKS_ORDINALDATE","POOMTASKS:OrdinalDate"); //12.0, 12.1 and 14.0 +define("SYNC_POOMTASKS_SUBORDINALDATE","POOMTASKS:SubOrdinalDate"); //12.0, 12.1 and 14.0 +define("SYNC_POOMTASKS_CALENDARTYPE","POOMTASKS:CalendarType"); //14.0 +define("SYNC_POOMTASKS_ISLEAPMONTH","POOMTASKS:IsLeapMonth"); //14.0 +define("SYNC_POOMTASKS_FIRSTDAYOFWEEK","POOMTASKS:FirstDayOfWeek"); // post 14.0 + +// ResolveRecipients +define("SYNC_RESOLVERECIPIENTS_RESOLVERECIPIENTS","ResolveRecipients:ResolveRecipients"); +define("SYNC_RESOLVERECIPIENTS_RESPONSE","ResolveRecipients:Response"); +define("SYNC_RESOLVERECIPIENTS_STATUS","ResolveRecipients:Status"); +define("SYNC_RESOLVERECIPIENTS_TYPE","ResolveRecipients:Type"); +define("SYNC_RESOLVERECIPIENTS_RECIPIENT","ResolveRecipients:Recipient"); +define("SYNC_RESOLVERECIPIENTS_DISPLAYNAME","ResolveRecipients:DisplayName"); +define("SYNC_RESOLVERECIPIENTS_EMAILADDRESS","ResolveRecipients:EmailAddress"); +define("SYNC_RESOLVERECIPIENTS_CERTIFICATES","ResolveRecipients:Certificates"); +define("SYNC_RESOLVERECIPIENTS_CERTIFICATE","ResolveRecipients:Certificate"); +define("SYNC_RESOLVERECIPIENTS_MINICERTIFICATE","ResolveRecipients:MiniCertificate"); +define("SYNC_RESOLVERECIPIENTS_OPTIONS","ResolveRecipients:Options"); +define("SYNC_RESOLVERECIPIENTS_TO","ResolveRecipients:To"); +define("SYNC_RESOLVERECIPIENTS_CERTIFICATERETRIEVAL","ResolveRecipients:CertificateRetrieval"); +define("SYNC_RESOLVERECIPIENTS_RECIPIENTCOUNT","ResolveRecipients:RecipientCount"); +define("SYNC_RESOLVERECIPIENTS_MAXCERTIFICATES","ResolveRecipients:MaxCertificates"); +define("SYNC_RESOLVERECIPIENTS_MAXAMBIGUOUSRECIPIENTS","ResolveRecipients:MaxAmbiguousRecipients"); +define("SYNC_RESOLVERECIPIENTS_CERTIFICATECOUNT","ResolveRecipients:CertificateCount"); +define("SYNC_RESOLVERECIPIENTS_AVAILABILITY","ResolveRecipients:Availability"); //14.0 +define("SYNC_RESOLVERECIPIENTS_STARTTIME","ResolveRecipients:StartTime"); //14.0 +define("SYNC_RESOLVERECIPIENTS_ENDTIME","ResolveRecipients:EndTime"); //14.0 +define("SYNC_RESOLVERECIPIENTS_MERGEDFREEBUSY","ResolveRecipients:MergedFreeBusy"); //14.0 +define("SYNC_RESOLVERECIPIENTS_PICTURE","ResolveRecipients:Picture"); //post 14.0 +define("SYNC_RESOLVERECIPIENTS_MAXSIZE","ResolveRecipients:MaxSize"); //post 14.0 +define("SYNC_RESOLVERECIPIENTS_DATA","ResolveRecipients:Data"); //post 14.0 +define("SYNC_RESOLVERECIPIENTS_MAXPICTURES","ResolveRecipients:MaxPictures"); //post 14.0 + +// ValidateCert +define("SYNC_VALIDATECERT_VALIDATECERT","ValidateCert:ValidateCert"); +define("SYNC_VALIDATECERT_CERTIFICATES","ValidateCert:Certificates"); +define("SYNC_VALIDATECERT_CERTIFICATE","ValidateCert:Certificate"); +define("SYNC_VALIDATECERT_CERTIFICATECHAIN","ValidateCert:CertificateChain"); +define("SYNC_VALIDATECERT_CHECKCRL","ValidateCert:CheckCRL"); +define("SYNC_VALIDATECERT_STATUS","ValidateCert:Status"); + +// POOMCONTACTS2 +define("SYNC_POOMCONTACTS2_CUSTOMERID","POOMCONTACTS2:CustomerId"); +define("SYNC_POOMCONTACTS2_GOVERNMENTID","POOMCONTACTS2:GovernmentId"); +define("SYNC_POOMCONTACTS2_IMADDRESS","POOMCONTACTS2:IMAddress"); +define("SYNC_POOMCONTACTS2_IMADDRESS2","POOMCONTACTS2:IMAddress2"); +define("SYNC_POOMCONTACTS2_IMADDRESS3","POOMCONTACTS2:IMAddress3"); +define("SYNC_POOMCONTACTS2_MANAGERNAME","POOMCONTACTS2:ManagerName"); +define("SYNC_POOMCONTACTS2_COMPANYMAINPHONE","POOMCONTACTS2:CompanyMainPhone"); +define("SYNC_POOMCONTACTS2_ACCOUNTNAME","POOMCONTACTS2:AccountName"); +define("SYNC_POOMCONTACTS2_NICKNAME","POOMCONTACTS2:NickName"); +define("SYNC_POOMCONTACTS2_MMS","POOMCONTACTS2:MMS"); + +// Ping +define("SYNC_PING_PING","Ping:Ping"); +define("SYNC_PING_STATUS","Ping:Status"); +define("SYNC_PING_LIFETIME", "Ping:LifeTime"); +define("SYNC_PING_FOLDERS", "Ping:Folders"); +define("SYNC_PING_FOLDER", "Ping:Folder"); +define("SYNC_PING_SERVERENTRYID", "Ping:ServerEntryId"); +define("SYNC_PING_FOLDERTYPE", "Ping:FolderType"); +define("SYNC_PING_MAXFOLDERS", "Ping:MaxFolders"); //missing in < z-push 2 +define("SYNC_PING_VERSION", "Ping:Version"); //missing in < z-push 2 + +//Provision +define("SYNC_PROVISION_PROVISION", "Provision:Provision"); +define("SYNC_PROVISION_POLICIES", "Provision:Policies"); +define("SYNC_PROVISION_POLICY", "Provision:Policy"); +define("SYNC_PROVISION_POLICYTYPE", "Provision:PolicyType"); +define("SYNC_PROVISION_POLICYKEY", "Provision:PolicyKey"); +define("SYNC_PROVISION_DATA", "Provision:Data"); +define("SYNC_PROVISION_STATUS", "Provision:Status"); +define("SYNC_PROVISION_REMOTEWIPE", "Provision:RemoteWipe"); +define("SYNC_PROVISION_EASPROVISIONDOC", "Provision:EASProvisionDoc"); +define("SYNC_PROVISION_DEVPWENABLED", "Provision:DevicePasswordEnabled"); +define("SYNC_PROVISION_ALPHANUMPWREQ", "Provision:AlphanumericDevicePasswordRequired"); +define("SYNC_PROVISION_DEVENCENABLED", "Provision:DeviceEncryptionEnabled"); +define("SYNC_PROVISION_REQSTORAGECARDENC", "Provision:RequireStorageCardEncryption"); +define("SYNC_PROVISION_PWRECOVERYENABLED", "Provision:PasswordRecoveryEnabled"); +define("SYNC_PROVISION_DOCBROWSEENABLED", "Provision:DocumentBrowseEnabled"); +define("SYNC_PROVISION_ATTENABLED", "Provision:AttachmentsEnabled"); +define("SYNC_PROVISION_MINDEVPWLENGTH", "Provision:MinDevicePasswordLength"); +define("SYNC_PROVISION_MAXINACTTIMEDEVLOCK", "Provision:MaxInactivityTimeDeviceLock"); +define("SYNC_PROVISION_MAXDEVPWFAILEDATTEMPTS", "Provision:MaxDevicePasswordFailedAttempts"); +define("SYNC_PROVISION_MAXATTSIZE", "Provision:MaxAttachmentSize"); +define("SYNC_PROVISION_ALLOWSIMPLEDEVPW", "Provision:AllowSimpleDevicePassword"); +define("SYNC_PROVISION_DEVPWEXPIRATION", "Provision:DevicePasswordExpiration"); +define("SYNC_PROVISION_DEVPWHISTORY", "Provision:DevicePasswordHistory"); +define("SYNC_PROVISION_ALLOWSTORAGECARD", "Provision:AllowStorageCard"); +define("SYNC_PROVISION_ALLOWCAM", "Provision:AllowCamera"); +define("SYNC_PROVISION_REQDEVENC", "Provision:RequireDeviceEncryption"); +define("SYNC_PROVISION_ALLOWUNSIGNEDAPPS", "Provision:AllowUnsignedApplications"); +define("SYNC_PROVISION_ALLOWUNSIGNEDINSTALLATIONPACKAGES", "Provision:AllowUnsignedInstallationPackages"); +define("SYNC_PROVISION_MINDEVPWCOMPLEXCHARS", "Provision:MinDevicePasswordComplexCharacters"); +define("SYNC_PROVISION_ALLOWWIFI", "Provision:AllowWiFi"); +define("SYNC_PROVISION_ALLOWTEXTMESSAGING", "Provision:AllowTextMessaging"); +define("SYNC_PROVISION_ALLOWPOPIMAPEMAIL", "Provision:AllowPOPIMAPEmail"); +define("SYNC_PROVISION_ALLOWBLUETOOTH", "Provision:AllowBluetooth"); +define("SYNC_PROVISION_ALLOWIRDA", "Provision:AllowIrDA"); +define("SYNC_PROVISION_REQMANUALSYNCWHENROAM", "Provision:RequireManualSyncWhenRoaming"); +define("SYNC_PROVISION_ALLOWDESKTOPSYNC", "Provision:AllowDesktopSync"); +define("SYNC_PROVISION_MAXCALAGEFILTER", "Provision:MaxCalendarAgeFilter"); +define("SYNC_PROVISION_ALLOWHTMLEMAIL", "Provision:AllowHTMLEmail"); +define("SYNC_PROVISION_MAXEMAILAGEFILTER", "Provision:MaxEmailAgeFilter"); +define("SYNC_PROVISION_MAXEMAILBODYTRUNCSIZE", "Provision:MaxEmailBodyTruncationSize"); +define("SYNC_PROVISION_MAXEMAILHTMLBODYTRUNCSIZE", "Provision:MaxEmailHTMLBodyTruncationSize"); +define("SYNC_PROVISION_REQSIGNEDSMIMEMESSAGES", "Provision:RequireSignedSMIMEMessages"); +define("SYNC_PROVISION_REQENCSMIMEMESSAGES", "Provision:RequireEncryptedSMIMEMessages"); +define("SYNC_PROVISION_REQSIGNEDSMIMEALGORITHM", "Provision:RequireSignedSMIMEAlgorithm"); +define("SYNC_PROVISION_REQENCSMIMEALGORITHM", "Provision:RequireEncryptionSMIMEAlgorithm"); +define("SYNC_PROVISION_ALLOWSMIMEENCALGORITHNEG", "Provision:AllowSMIMEEncryptionAlgorithmNegotiation"); +define("SYNC_PROVISION_ALLOWSMIMESOFTCERTS", "Provision:AllowSMIMESoftCerts"); +define("SYNC_PROVISION_ALLOWBROWSER", "Provision:AllowBrowser"); +define("SYNC_PROVISION_ALLOWCONSUMEREMAIL", "Provision:AllowConsumerEmail"); +define("SYNC_PROVISION_ALLOWREMOTEDESKTOP", "Provision:AllowRemoteDesktop"); +define("SYNC_PROVISION_ALLOWINTERNETSHARING", "Provision:AllowInternetSharing"); +define("SYNC_PROVISION_UNAPPROVEDINROMAPPLIST", "Provision:UnapprovedInROMApplicationList"); +define("SYNC_PROVISION_APPNAME", "Provision:ApplicationName"); +define("SYNC_PROVISION_APPROVEDAPPLIST", "Provision:ApprovedApplicationList"); +define("SYNC_PROVISION_HASH", "Provision:Hash"); + + +//Search +define("SYNC_SEARCH_SEARCH", "Search:Search"); +define("SYNC_SEARCH_STORE", "Search:Store"); +define("SYNC_SEARCH_NAME", "Search:Name"); +define("SYNC_SEARCH_QUERY", "Search:Query"); +define("SYNC_SEARCH_OPTIONS", "Search:Options"); +define("SYNC_SEARCH_RANGE", "Search:Range"); +define("SYNC_SEARCH_STATUS", "Search:Status"); +define("SYNC_SEARCH_RESPONSE", "Search:Response"); +define("SYNC_SEARCH_RESULT", "Search:Result"); +define("SYNC_SEARCH_PROPERTIES", "Search:Properties"); +define("SYNC_SEARCH_TOTAL", "Search:Total"); +define("SYNC_SEARCH_EQUALTO", "Search:EqualTo"); +define("SYNC_SEARCH_VALUE", "Search:Value"); +define("SYNC_SEARCH_AND", "Search:And"); +define("SYNC_SEARCH_OR", "Search:Or"); +define("SYNC_SEARCH_FREETEXT", "Search:FreeText"); +define("SYNC_SEARCH_DEEPTRAVERSAL", "Search:DeepTraversal"); +define("SYNC_SEARCH_LONGID", "Search:LongId"); +define("SYNC_SEARCH_REBUILDRESULTS", "Search:RebuildResults"); +define("SYNC_SEARCH_LESSTHAN", "Search:LessThan"); +define("SYNC_SEARCH_GREATERTHAN", "Search:GreaterThan"); +define("SYNC_SEARCH_SCHEMA", "Search:Schema"); +define("SYNC_SEARCH_SUPPORTED", "Search:Supported"); +define("SYNC_SEARCH_USERNAME", "Search:UserName"); //12.1 and 14.0 +define("SYNC_SEARCH_PASSWORD", "Search:Password"); //12.1 and 14.0 +define("SYNC_SEARCH_CONVERSATIONID", "Search:ConversationId"); //14.0 +define("SYNC_SEARCH_PICTURE","Search:Picture"); //post 14.0 +define("SYNC_SEARCH_MAXSIZE","Search:MaxSize"); //post 14.0 +define("SYNC_SEARCH_MAXPICTURES","Search:MaxPictures"); //post 14.0 + +//GAL +define("SYNC_GAL_DISPLAYNAME", "GAL:DisplayName"); +define("SYNC_GAL_PHONE", "GAL:Phone"); +define("SYNC_GAL_OFFICE", "GAL:Office"); +define("SYNC_GAL_TITLE", "GAL:Title"); +define("SYNC_GAL_COMPANY", "GAL:Company"); +define("SYNC_GAL_ALIAS", "GAL:Alias"); +define("SYNC_GAL_FIRSTNAME", "GAL:FirstName"); +define("SYNC_GAL_LASTNAME", "GAL:LastName"); +define("SYNC_GAL_HOMEPHONE", "GAL:HomePhone"); +define("SYNC_GAL_MOBILEPHONE", "GAL:MobilePhone"); +define("SYNC_GAL_EMAILADDRESS", "GAL:EmailAddress"); +define("SYNC_GAL_PICTURE","GAL:Picture"); //post 14.0 +define("SYNC_GAL_MAXSIZE","GAL:Status"); //post 14.0 +define("SYNC_GAL_DATA","GAL:Data"); //post 14.0 + +//AirSyncBase //12.0, 12.1 and 14.0 +define("SYNC_AIRSYNCBASE_BODYPREFERENCE", "AirSyncBase:BodyPreference"); +define("SYNC_AIRSYNCBASE_TYPE", "AirSyncBase:Type"); +define("SYNC_AIRSYNCBASE_TRUNCATIONSIZE", "AirSyncBase:TruncationSize"); +define("SYNC_AIRSYNCBASE_ALLORNONE", "AirSyncBase:AllOrNone"); +define("SYNC_AIRSYNCBASE_BODY", "AirSyncBase:Body"); +define("SYNC_AIRSYNCBASE_DATA", "AirSyncBase:Data"); +define("SYNC_AIRSYNCBASE_ESTIMATEDDATASIZE", "AirSyncBase:EstimatedDataSize"); +define("SYNC_AIRSYNCBASE_TRUNCATED", "AirSyncBase:Truncated"); +define("SYNC_AIRSYNCBASE_ATTACHMENTS", "AirSyncBase:Attachments"); +define("SYNC_AIRSYNCBASE_ATTACHMENT", "AirSyncBase:Attachment"); +define("SYNC_AIRSYNCBASE_DISPLAYNAME", "AirSyncBase:DisplayName"); +define("SYNC_AIRSYNCBASE_FILEREFERENCE", "AirSyncBase:FileReference"); +define("SYNC_AIRSYNCBASE_METHOD", "AirSyncBase:Method"); +define("SYNC_AIRSYNCBASE_CONTENTID", "AirSyncBase:ContentId"); +define("SYNC_AIRSYNCBASE_CONTENTLOCATION", "AirSyncBase:ContentLocation"); //not used +define("SYNC_AIRSYNCBASE_ISINLINE", "AirSyncBase:IsInline"); +define("SYNC_AIRSYNCBASE_NATIVEBODYTYPE", "AirSyncBase:NativeBodyType"); +define("SYNC_AIRSYNCBASE_CONTENTTYPE", "AirSyncBase:ContentType"); +define("SYNC_AIRSYNCBASE_PREVIEW", "AirSyncBase:Preview"); //14.0 +define("SYNC_AIRSYNCBASE_BODYPARTPREFERENCE", "AirSyncBase:BodyPartPreference"); //post 14.0 +define("SYNC_AIRSYNCBASE_BODYPART", "AirSyncBase:BodyPart"); //post 14.0 +define("SYNC_AIRSYNCBASE_STATUS", "AirSyncBase:Status"); //post 14.0 + +//Settings //12.0, 12.1 and 14.0 +define("SYNC_SETTINGS_SETTINGS", "Settings:Settings"); +define("SYNC_SETTINGS_STATUS", "Settings:Status"); +define("SYNC_SETTINGS_GET", "Settings:Get"); +define("SYNC_SETTINGS_SET", "Settings:Set"); +define("SYNC_SETTINGS_OOF", "Settings:Oof"); +define("SYNC_SETTINGS_OOFSTATE", "Settings:OofState"); +define("SYNC_SETTINGS_STARTTIME", "Settings:StartTime"); +define("SYNC_SETTINGS_ENDTIME", "Settings:EndTime"); +define("SYNC_SETTINGS_OOFMESSAGE", "Settings:OofMessage"); +define("SYNC_SETTINGS_APPLIESTOINTERVAL", "Settings:AppliesToInternal"); +define("SYNC_SETTINGS_APPLIESTOEXTERNALKNOWN", "Settings:AppliesToExternalKnown"); +define("SYNC_SETTINGS_APPLIESTOEXTERNALUNKNOWN", "Settings:AppliesToExternalUnknown"); +define("SYNC_SETTINGS_ENABLED", "Settings:Enabled"); +define("SYNC_SETTINGS_REPLYMESSAGE", "Settings:ReplyMessage"); +define("SYNC_SETTINGS_BODYTYPE", "Settings:BodyType"); +define("SYNC_SETTINGS_DEVICEPW", "Settings:DevicePassword"); +define("SYNC_SETTINGS_PW", "Settings:Password"); +define("SYNC_SETTINGS_DEVICEINFORMATION", "Settings:DeviceInformaton"); +define("SYNC_SETTINGS_MODEL", "Settings:Model"); +define("SYNC_SETTINGS_IMEI", "Settings:IMEI"); +define("SYNC_SETTINGS_FRIENDLYNAME", "Settings:FriendlyName"); +define("SYNC_SETTINGS_OS", "Settings:OS"); +define("SYNC_SETTINGS_OSLANGUAGE", "Settings:OSLanguage"); +define("SYNC_SETTINGS_PHONENUMBER", "Settings:PhoneNumber"); +define("SYNC_SETTINGS_USERINFORMATION", "Settings:UserInformation"); +define("SYNC_SETTINGS_EMAILADDRESSES", "Settings:EmailAddresses"); +define("SYNC_SETTINGS_SMPTADDRESS", "Settings:SmtpAddress"); +define("SYNC_SETTINGS_USERAGENT", "Settings:UserAgent"); //12.1 and 14.0 +define("SYNC_SETTINGS_ENABLEOUTBOUNDSMS", "Settings:EnableOutboundSMS"); //14.0 +define("SYNC_SETTINGS_MOBILEOPERATOR", "Settings:MobileOperator"); //14.0 +define("SYNC_SETTINGS_PRIMARYSMTPADDRESS", "Settings:PrimarySmtpAddress"); +define("SYNC_SETTINGS_ACCOUNTS", "Settings:Accounts"); +define("SYNC_SETTINGS_ACCOUNT", "Settings:Account"); +define("SYNC_SETTINGS_ACCOUNTID", "Settings:AccountId"); +define("SYNC_SETTINGS_ACCOUNTNAME", "Settings:AccountName"); +define("SYNC_SETTINGS_USERDISPLAYNAME", "Settings:UserDisplayName"); //12.1 and 14.0 +define("SYNC_SETTINGS_SENDDISABLED", "Settings:SendDisabled"); //14.0 +define("SYNC_SETTINGS_IHSMANAGEMENTINFORMATION", "Settings:ihsManagementInformation"); //14.0 +// only for internal use - never to be streamed to the mobile +define("SYNC_SETTINGS_PROP_STATUS", "Settings:PropertyStatus"); + +//DocumentLibrary //12.0, 12.1 and 14.0 +define("SYNC_DOCUMENTLIBRARY_LINKID", "DocumentLibrary:LinkId"); +define("SYNC_DOCUMENTLIBRARY_DISPLAYNAME", "DocumentLibrary:DisplayName"); +define("SYNC_DOCUMENTLIBRARY_ISFOLDER", "DocumentLibrary:IsFolder"); +define("SYNC_DOCUMENTLIBRARY_CREATIONDATE", "DocumentLibrary:CreationDate"); +define("SYNC_DOCUMENTLIBRARY_LASTMODIFIEDDATE", "DocumentLibrary:LastModifiedDate"); +define("SYNC_DOCUMENTLIBRARY_ISHIDDEN", "DocumentLibrary:IsHidden"); +define("SYNC_DOCUMENTLIBRARY_CONTENTLENGTH", "DocumentLibrary:ContentLength"); +define("SYNC_DOCUMENTLIBRARY_CONTENTTYPE", "DocumentLibrary:ContentType"); + +//ItemOperations //12.0, 12.1 and 14.0 +define("SYNC_ITEMOPERATIONS_ITEMOPERATIONS", "ItemOperations:ItemOperations"); +define("SYNC_ITEMOPERATIONS_FETCH", "ItemOperations:Fetch"); +define("SYNC_ITEMOPERATIONS_STORE", "ItemOperations:Store"); +define("SYNC_ITEMOPERATIONS_OPTIONS", "ItemOperations:Options"); +define("SYNC_ITEMOPERATIONS_RANGE", "ItemOperations:Range"); +define("SYNC_ITEMOPERATIONS_TOTAL", "ItemOperations:Total"); +define("SYNC_ITEMOPERATIONS_PROPERTIES", "ItemOperations:Properties"); +define("SYNC_ITEMOPERATIONS_DATA", "ItemOperations:Data"); +define("SYNC_ITEMOPERATIONS_STATUS", "ItemOperations:Status"); +define("SYNC_ITEMOPERATIONS_RESPONSE", "ItemOperations:Response"); +define("SYNC_ITEMOPERATIONS_VERSIONS", "ItemOperations:Version"); +define("SYNC_ITEMOPERATIONS_SCHEMA", "ItemOperations:Schema"); +define("SYNC_ITEMOPERATIONS_PART", "ItemOperations:Part"); +define("SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS", "ItemOperations:EmptyFolderContents"); +define("SYNC_ITEMOPERATIONS_DELETESUBFOLDERS", "ItemOperations:DeleteSubFolders"); +define("SYNC_ITEMOPERATIONS_USERNAME", "ItemOperations:UserName"); //12.1 and 14.0 +define("SYNC_ITEMOPERATIONS_PASSWORD", "ItemOperations:Password"); //12.1 and 14.0 +define("SYNC_ITEMOPERATIONS_MOVE", "ItemOperations:Move"); //14.0 +define("SYNC_ITEMOPERATIONS_DSTFLDID", "ItemOperations:DstFldId"); //14.0 +define("SYNC_ITEMOPERATIONS_CONVERSATIONID", "ItemOperations:ConversationId"); //14.0 +define("SYNC_ITEMOPERATIONS_MOVEALWAYS", "ItemOperations:MoveAlways"); //14.0 + +//ComposeMail //14.0 +define("SYNC_COMPOSEMAIL_SENDMAIL", "ComposeMail:SendMail"); +define("SYNC_COMPOSEMAIL_SMARTFORWARD", "ComposeMail:SmartForward"); +define("SYNC_COMPOSEMAIL_SMARTREPLY", "ComposeMail:SmartReply"); +define("SYNC_COMPOSEMAIL_SAVEINSENTITEMS", "ComposeMail:SaveInSentItems"); +define("SYNC_COMPOSEMAIL_REPLACEMIME", "ComposeMail:ReplaceMime"); +define("SYNC_COMPOSEMAIL_TYPE", "ComposeMail:Type"); +define("SYNC_COMPOSEMAIL_SOURCE", "ComposeMail:Source"); +define("SYNC_COMPOSEMAIL_FOLDERID", "ComposeMail:FolderId"); +define("SYNC_COMPOSEMAIL_ITEMID", "ComposeMail:ItemId"); +define("SYNC_COMPOSEMAIL_LONGID", "ComposeMail:LongId"); +define("SYNC_COMPOSEMAIL_INSTANCEID", "ComposeMail:InstanceId"); +define("SYNC_COMPOSEMAIL_MIME", "ComposeMail:MIME"); +define("SYNC_COMPOSEMAIL_CLIENTID", "ComposeMail:ClientId"); +define("SYNC_COMPOSEMAIL_STATUS", "ComposeMail:Status"); +define("SYNC_COMPOSEMAIL_ACCOUNTID", "ComposeMail:AccountId"); +// only for internal use - never to be streamed to the mobile +define("SYNC_COMPOSEMAIL_REPLYFLAG","ComposeMail:ReplyFlag"); +define("SYNC_COMPOSEMAIL_FORWARDFLAG","ComposeMail:ForwardFlag"); + +//POOMMAIL2 //14.0 +define("SYNC_POOMMAIL2_UMCALLERID", "POOMMAIL2:UmCallerId"); +define("SYNC_POOMMAIL2_UMUSERNOTES", "POOMMAIL2:UmUserNotes"); +define("SYNC_POOMMAIL2_UMATTDURATION", "POOMMAIL2:UmAttDuration"); +define("SYNC_POOMMAIL2_UMATTORDER", "POOMMAIL2:UmAttOrder"); +define("SYNC_POOMMAIL2_CONVERSATIONID", "POOMMAIL2:ConversationId"); +define("SYNC_POOMMAIL2_CONVERSATIONINDEX", "POOMMAIL2:ConversationIndex"); +define("SYNC_POOMMAIL2_LASTVERBEXECUTED", "POOMMAIL2:LastVerbExecuted"); +define("SYNC_POOMMAIL2_LASTVERBEXECUTIONTIME", "POOMMAIL2:LastVerbExecutionTime"); +define("SYNC_POOMMAIL2_RECEIVEDASBCC", "POOMMAIL2:ReceivedAsBcc"); +define("SYNC_POOMMAIL2_SENDER", "POOMMAIL2:Sender"); +define("SYNC_POOMMAIL2_CALENDARTYPE", "POOMMAIL2:CalendarType"); +define("SYNC_POOMMAIL2_ISLEAPMONTH", "POOMMAIL2:IsLeapMonth"); +define("SYNC_POOMMAIL2_ACCOUNTID", "POOMMAIL2:AccountId"); +define("SYNC_POOMMAIL2_FIRSTDAYOFWEEK", "POOMMAIL2:FirstDayOfWeek"); +define("SYNC_POOMMAIL2_MEETINGMESSAGETYPE", "POOMMAIL2:MeetingMessageType"); + +//Notes //14.0 +define("SYNC_NOTES_SUBJECT", "Notes:Subject"); +define("SYNC_NOTES_MESSAGECLASS", "Notes:MessageClass"); +define("SYNC_NOTES_LASTMODIFIEDDATE", "Notes:LastModifiedDate"); +define("SYNC_NOTES_CATEGORIES", "Notes:Categories"); +define("SYNC_NOTES_CATEGORY", "Notes:Category"); + +//RightsManagement //post 14.0 +define("SYNC_RIGHTSMANAGEMENT_SUPPORT", "RightsManagement:RightsManagementSupport"); +define("SYNC_RIGHTSMANAGEMENT_TEMPLATES", "RightsManagement:RightsManagementTemplates"); +define("SYNC_RIGHTSMANAGEMENT_TEMPLATE", "RightsManagement:RightsManagementTemplate"); +define("SYNC_RIGHTSMANAGEMENT_LICENSE", "RightsManagement:RightsManagementLicense"); +define("SYNC_RIGHTSMANAGEMENT_EDITALLOWED", "RightsManagement:EditAllowed"); +define("SYNC_RIGHTSMANAGEMENT_REPLYALLOWED", "RightsManagement:ReplyAllowed"); +define("SYNC_RIGHTSMANAGEMENT_REPLYALLALLOWED", "RightsManagement:ReplyAllAllowed"); +define("SYNC_RIGHTSMANAGEMENT_FORWARDALLOWED", "RightsManagement:ForwardAllowed"); +define("SYNC_RIGHTSMANAGEMENT_MODIFYRECIPIENTSALLOWED", "RightsManagement:ModifyRecipientsAllowed"); +define("SYNC_RIGHTSMANAGEMENT_EXTRACTALLOWED", "RightsManagement:ExtractAllowed"); +define("SYNC_RIGHTSMANAGEMENT_PRINTALLOWED", "RightsManagement:PrintAllowed"); +define("SYNC_RIGHTSMANAGEMENT_EXPORTALLOWED", "RightsManagement:ExportAllowed"); +define("SYNC_RIGHTSMANAGEMENT_PROGRAMMATICACCESSALLOWED", "RightsManagement:ProgrammaticAccessAllowed"); +define("SYNC_RIGHTSMANAGEMENT_RMOWNER", "RightsManagement:RMOwner"); +define("SYNC_RIGHTSMANAGEMENT_CONTENTEXPIRYDATE", "RightsManagement:ContentExpiryDate"); +define("SYNC_RIGHTSMANAGEMENT_TEMPLATEID", "RightsManagement:TemplateID"); +define("SYNC_RIGHTSMANAGEMENT_TEMPLATENAME", "RightsManagement:TemplateName"); +define("SYNC_RIGHTSMANAGEMENT_TEMPLATEDESCRIPTION", "RightsManagement:TemplateDescription"); +define("SYNC_RIGHTSMANAGEMENT_CONTENTOWNER", "RightsManagement:ContentOwner"); +define("SYNC_RIGHTSMANAGEMENT_REMOVERIGHTSMGNTDIST", "RightsManagement:RemoveRightsManagementDistribution"); + +// Other constants +define("SYNC_FOLDER_TYPE_OTHER", 1); +define("SYNC_FOLDER_TYPE_INBOX", 2); +define("SYNC_FOLDER_TYPE_DRAFTS", 3); +define("SYNC_FOLDER_TYPE_WASTEBASKET", 4); +define("SYNC_FOLDER_TYPE_SENTMAIL", 5); +define("SYNC_FOLDER_TYPE_OUTBOX", 6); +define("SYNC_FOLDER_TYPE_TASK", 7); +define("SYNC_FOLDER_TYPE_APPOINTMENT", 8); +define("SYNC_FOLDER_TYPE_CONTACT", 9); +define("SYNC_FOLDER_TYPE_NOTE", 10); +define("SYNC_FOLDER_TYPE_JOURNAL", 11); +define("SYNC_FOLDER_TYPE_USER_MAIL", 12); +define("SYNC_FOLDER_TYPE_USER_APPOINTMENT", 13); +define("SYNC_FOLDER_TYPE_USER_CONTACT", 14); +define("SYNC_FOLDER_TYPE_USER_TASK", 15); +define("SYNC_FOLDER_TYPE_USER_JOURNAL", 16); +define("SYNC_FOLDER_TYPE_USER_NOTE", 17); +define("SYNC_FOLDER_TYPE_UNKNOWN", 18); +define("SYNC_FOLDER_TYPE_RECIPIENT_CACHE", 19); +define("SYNC_FOLDER_TYPE_DUMMY", 999999); + +define("SYNC_CONFLICT_OVERWRITE_SERVER", 0); +define("SYNC_CONFLICT_OVERWRITE_PIM", 1); + +define("SYNC_FILTERTYPE_ALL", 0); +define("SYNC_FILTERTYPE_1DAY", 1); +define("SYNC_FILTERTYPE_3DAYS", 2); +define("SYNC_FILTERTYPE_1WEEK", 3); +define("SYNC_FILTERTYPE_2WEEKS", 4); +define("SYNC_FILTERTYPE_1MONTH", 5); +define("SYNC_FILTERTYPE_3MONTHS", 6); +define("SYNC_FILTERTYPE_6MONTHS", 7); +define("SYNC_FILTERTYPE_INCOMPLETETASKS", 8); + +define("SYNC_TRUNCATION_HEADERS", 0); +define("SYNC_TRUNCATION_512B", 1); +define("SYNC_TRUNCATION_1K", 2); +define("SYNC_TRUNCATION_2K", 3); +define("SYNC_TRUNCATION_5K", 4); +define("SYNC_TRUNCATION_10K", 5); +define("SYNC_TRUNCATION_20K", 6); +define("SYNC_TRUNCATION_50K", 7); +define("SYNC_TRUNCATION_100K", 8); +define("SYNC_TRUNCATION_ALL", 9); + +define("SYNC_PROVISION_STATUS_SUCCESS", 1); +define("SYNC_PROVISION_STATUS_PROTERROR", 2); +define("SYNC_PROVISION_STATUS_SERVERERROR", 3); +define("SYNC_PROVISION_STATUS_DEVEXTMANAGED", 4); + +define("SYNC_PROVISION_POLICYSTATUS_SUCCESS", 1); +define("SYNC_PROVISION_POLICYSTATUS_NOPOLICY", 2); +define("SYNC_PROVISION_POLICYSTATUS_UNKNOWNVALUE", 3); +define("SYNC_PROVISION_POLICYSTATUS_CORRUPTED", 4); +define("SYNC_PROVISION_POLICYSTATUS_POLKEYMISM", 5); + +define("SYNC_PROVISION_RWSTATUS_NA", 0); +define("SYNC_PROVISION_RWSTATUS_OK", 1); +define("SYNC_PROVISION_RWSTATUS_PENDING", 2); +define("SYNC_PROVISION_RWSTATUS_REQUESTED", 4); +define("SYNC_PROVISION_RWSTATUS_WIPED", 8); + +define("SYNC_STATUS_SUCCESS", 1); +define("SYNC_STATUS_INVALIDSYNCKEY", 3); +define("SYNC_STATUS_PROTOCOLLERROR", 4); +define("SYNC_STATUS_SERVERERROR", 5); +define("SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR", 6); +define("SYNC_STATUS_CONFLICTCLIENTSERVEROBJECT", 7); +define("SYNC_STATUS_OBJECTNOTFOUND", 8); +define("SYNC_STATUS_SYNCCANNOTBECOMPLETED", 9); +define("SYNC_STATUS_FOLDERHIERARCHYCHANGED", 12); +define("SYNC_STATUS_SYNCREQUESTINCOMPLETE", 13); +define("SYNC_STATUS_INVALIDWAITORHBVALUE", 14); +define("SYNC_STATUS_SYNCREQUESTINVALID", 15); +define("SYNC_STATUS_RETRY", 16); + +define("SYNC_FSSTATUS_SUCCESS", 1); +define("SYNC_FSSTATUS_FOLDEREXISTS", 2); +define("SYNC_FSSTATUS_SYSTEMFOLDER", 3); +define("SYNC_FSSTATUS_FOLDERDOESNOTEXIST", 4); +define("SYNC_FSSTATUS_PARENTNOTFOUND", 5); +define("SYNC_FSSTATUS_SERVERERROR", 6); +define("SYNC_FSSTATUS_REQUESTTIMEOUT", 8); +define("SYNC_FSSTATUS_SYNCKEYERROR", 9); +define("SYNC_FSSTATUS_MAILFORMEDREQ", 10); +define("SYNC_FSSTATUS_UNKNOWNERROR", 11); +define("SYNC_FSSTATUS_CODEUNKNOWN", 12); + +define("SYNC_GETITEMESTSTATUS_SUCCESS", 1); +define("SYNC_GETITEMESTSTATUS_COLLECTIONINVALID", 2); +define("SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED", 3); +define("SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID", 4); + +define("SYNC_ITEMOPERATIONSSTATUS_SUCCESS", 1); +define("SYNC_ITEMOPERATIONSSTATUS_PROTERROR", 2); +define("SYNC_ITEMOPERATIONSSTATUS_SERVERERROR", 3); +define("SYNC_ITEMOPERATIONSSTATUS_DL_BADURI", 4); +define("SYNC_ITEMOPERATIONSSTATUS_DL_ACCESSDENIED", 5); +define("SYNC_ITEMOPERATIONSSTATUS_DL_NOTFOUND", 6); +define("SYNC_ITEMOPERATIONSSTATUS_DL_CONNFAILED", 7); +define("SYNC_ITEMOPERATIONSSTATUS_DL_BYTERANGEINVALID", 8); +define("SYNC_ITEMOPERATIONSSTATUS_DL_STOREUNKNOWN", 9); +define("SYNC_ITEMOPERATIONSSTATUS_DL_EMPTYFILE", 10); +define("SYNC_ITEMOPERATIONSSTATUS_DL_TOOLARGE", 11); +define("SYNC_ITEMOPERATIONSSTATUS_DL_IOFAILURE", 12); +define("SYNC_ITEMOPERATIONSSTATUS_CONVERSIONFAILED", 14); +define("SYNC_ITEMOPERATIONSSTATUS_INVALIDATT", 15); +define("SYNC_ITEMOPERATIONSSTATUS_BLOCKED", 16); +define("SYNC_ITEMOPERATIONSSTATUS_EMPTYFOLDER", 17); +define("SYNC_ITEMOPERATIONSSTATUS_CREDSREQUIRED", 18); +define("SYNC_ITEMOPERATIONSSTATUS_PROTOCOLERROR", 155); +define("SYNC_ITEMOPERATIONSSTATUS_UNSUPPORTEDACTION", 156); + +define("SYNC_MEETRESPSTATUS_SUCCESS", 1); +define("SYNC_MEETRESPSTATUS_INVALIDMEETREQ", 2); +define("SYNC_MEETRESPSTATUS_MAILBOXERROR", 3); +define("SYNC_MEETRESPSTATUS_SERVERERROR", 4); + +define("SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID", 1); +define("SYNC_MOVEITEMSSTATUS_INVALIDDESTID", 2); +define("SYNC_MOVEITEMSSTATUS_SUCCESS", 3); +define("SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST", 4); +define("SYNC_MOVEITEMSSTATUS_CANNOTMOVE", 5); +define("SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED", 7); + +define("SYNC_PINGSTATUS_HBEXPIRED", 1); +define("SYNC_PINGSTATUS_CHANGES", 2); +define("SYNC_PINGSTATUS_FAILINGPARAMS", 3); +define("SYNC_PINGSTATUS_SYNTAXERROR", 4); +define("SYNC_PINGSTATUS_HBOUTOFRANGE", 5); +define("SYNC_PINGSTATUS_TOOMUCHFOLDERS", 6); +define("SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED", 7); +define("SYNC_PINGSTATUS_SERVERERROR", 8); + +define("SYNC_RESOLVERECIPSSTATUS_SUCCESS", 1); +define("SYNC_RESOLVERECIPSSTATUS_PROTOCOLERROR", 5); +define("SYNC_RESOLVERECIPSSTATUS_SERVERERROR", 6); +define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_SUCCESS", 1); +define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIP", 2); +define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_AMBRECIPPARTIAL", 3); +define("SYNC_RESOLVERECIPSSTATUS_RESPONSE_UNRESOLCEDRECIP", 4); +define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_SUCCESS", 1); +define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_NOVALIDCERT", 7); +define("SYNC_RESOLVERECIPSSTATUS_CERTIFICATES_CERTLIMIT", 8); +define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_SUCCESS", 1); +define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN100", 160); +define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_MORETHAN20", 161); +define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_REISSUE", 162); +define("SYNC_RESOLVERECIPSSTATUS_AVAILABILITY_FAILED", 163); +define("SYNC_RESOLVERECIPSSTATUS_PICTURE_SUCCESS", 1); +define("SYNC_RESOLVERECIPSSTATUS_PICTURE_NOFOTO", 173); +define("SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXSIZEEXCEEDED", 174); +define("SYNC_RESOLVERECIPSSTATUS_PICTURE_MAXPICTURESEXCEEDED", 175); + +define("SYNC_SEARCHSTATUS_SUCCESS", 1); +define("SYNC_SEARCHSTATUS_SERVERERROR", 3); +define("SYNC_SEARCHSTATUS_STORE_SUCCESS", 1); +define("SYNC_SEARCHSTATUS_STORE_REQINVALID", 2); +define("SYNC_SEARCHSTATUS_STORE_SERVERERROR", 3); +define("SYNC_SEARCHSTATUS_STORE_BADLINK", 4); +define("SYNC_SEARCHSTATUS_STORE_ACCESSDENIED", 5); +define("SYNC_SEARCHSTATUS_STORE_NOTFOUND", 6); +define("SYNC_SEARCHSTATUS_STORE_CONNECTIONFAILED", 7); +define("SYNC_SEARCHSTATUS_STORE_TOOCOMPLEX", 8); +define("SYNC_SEARCHSTATUS_STORE_TIMEDOUT", 10); +define("SYNC_SEARCHSTATUS_STORE_FOLDERSYNCREQ", 11); +define("SYNC_SEARCHSTATUS_STORE_ENDOFRETRANGE", 12); +define("SYNC_SEARCHSTATUS_STORE_ACCESSBLOCKED", 13); +define("SYNC_SEARCHSTATUS_STORE_CREDENTIALSREQ", 14); +define("SYNC_SEARCHSTATUS_PICTURE_SUCCESS", 1); +define("SYNC_SEARCHSTATUS_PICTURE_NOFOTO", 173); +define("SYNC_SEARCHSTATUS_PICTURE_MAXSIZEEXCEEDED", 174); +define("SYNC_SEARCHSTATUS_PICTURE_MAXPICTURESEXCEEDED", 175); + +define("SYNC_SETTINGSSTATUS_SUCCESS", 1); +define("SYNC_SETTINGSSTATUS_PROTOCOLLERROR", 2); +define("SYNC_SETTINGSSTATUS_DEVINFO_SUCCESS", 1); +define("SYNC_SETTINGSSTATUS_DEVINFO_PROTOCOLLERROR", 2); +define("SYNC_SETTINGSSTATUS_DEVIPASS_SUCCESS", 1); +define("SYNC_SETTINGSSTATUS_DEVIPASS_PROTOCOLLERROR", 2); +define("SYNC_SETTINGSSTATUS_DEVIPASS_INVALIDARGS", 3); +define("SYNC_SETTINGSSTATUS_DEVIPASS_DENIED", 7); +define("SYNC_SETTINGSSTATUS_USERINFO_SUCCESS", 1); +define("SYNC_SETTINGSSTATUS_USERINFO_PROTOCOLLERROR", 2); + +define("SYNC_SETTINGSOOF_DISABLED", 0); +define("SYNC_SETTINGSOOF_GLOBAL", 1); +define("SYNC_SETTINGSOOF_TIMEBASED", 2); + +define("SYNC_MIMETRUNCATION_ALL", 0); +define("SYNC_MIMETRUNCATION_4096", 1); +define("SYNC_MIMETRUNCATION_5120", 2); +define("SYNC_MIMETRUNCATION_7168", 3); +define("SYNC_MIMETRUNCATION_10240", 4); +define("SYNC_MIMETRUNCATION_20480", 5); +define("SYNC_MIMETRUNCATION_51200", 6); +define("SYNC_MIMETRUNCATION_102400", 7); +define("SYNC_MIMETRUNCATION_COMPLETE", 8); + +define("SYNC_MIMESUPPORT_NEVER", 0); +define("SYNC_MIMESUPPORT_SMIME", 1); +define("SYNC_MIMESUPPORT_ALWAYS", 2); + +define("SYNC_VALIDATECERTSTATUS_SUCCESS", 1); +define("SYNC_VALIDATECERTSTATUS_PROTOCOLLERROR", 2); +define("SYNC_VALIDATECERTSTATUS_CANTVALIDATESIG", 3); +define("SYNC_VALIDATECERTSTATUS_DIGIDUNTRUSTED", 4); +define("SYNC_VALIDATECERTSTATUS_CERTCHAINNOTCORRECT", 5); +define("SYNC_VALIDATECERTSTATUS_DIGIDNOTVALIDFORSIGN", 6); +define("SYNC_VALIDATECERTSTATUS_DIGIDNOTVALID", 7); +define("SYNC_VALIDATECERTSTATUS_INVALIDCHAINCERTSTIME", 8); +define("SYNC_VALIDATECERTSTATUS_DIGIDUSEDINCORRECTLY", 9); +define("SYNC_VALIDATECERTSTATUS_INCORRECTDIGIDINFO", 10); +define("SYNC_VALIDATECERTSTATUS_INCORRECTUSEOFDIGIDINCHAIN", 11); +define("SYNC_VALIDATECERTSTATUS_DIGIDDOESNOTMATCHEMAIL", 12); +define("SYNC_VALIDATECERTSTATUS_DIGIDREVOKED", 13); +define("SYNC_VALIDATECERTSTATUS_DIGIDSERVERUNAVAILABLE", 14); +define("SYNC_VALIDATECERTSTATUS_DIGIDINCHAINREVOKED", 15); +define("SYNC_VALIDATECERTSTATUS_DIGIDREVSTATUSUNVALIDATED", 16); +define("SYNC_VALIDATECERTSTATUS_SERVERERROR", 17); + +define("SYNC_COMMONSTATUS_SUCCESS", 1); +define("SYNC_COMMONSTATUS_INVALIDCONTENT", 101); +define("SYNC_COMMONSTATUS_INVALIDWBXML", 102); +define("SYNC_COMMONSTATUS_INVALIDXML", 103); +define("SYNC_COMMONSTATUS_INVALIDDATETIME", 104); +define("SYNC_COMMONSTATUS_INVALIDCOMBINATIONOFIDS", 105); +define("SYNC_COMMONSTATUS_INVALIDIDS", 106); +define("SYNC_COMMONSTATUS_INVALIDMIME", 107); +define("SYNC_COMMONSTATUS_DEVIDMISSINGORINVALID", 108); +define("SYNC_COMMONSTATUS_DEVTYPEMISSINGORINVALID", 109); +define("SYNC_COMMONSTATUS_SERVERERROR", 110); +define("SYNC_COMMONSTATUS_SERVERERRORRETRYLATER", 111); +define("SYNC_COMMONSTATUS_ADACCESSDENIED", 112); +define("SYNC_COMMONSTATUS_MAILBOXQUOTAEXCEEDED", 113); +define("SYNC_COMMONSTATUS_MAILBOXSERVEROFFLINE", 114); +define("SYNC_COMMONSTATUS_SENDQUOTAEXCEEDED", 115); +define("SYNC_COMMONSTATUS_MESSRECIPUNRESOLVED", 116); +define("SYNC_COMMONSTATUS_MESSREPLYNOTALLOWED", 117); +define("SYNC_COMMONSTATUS_MESSPREVSENT", 118); +define("SYNC_COMMONSTATUS_MESSHASNORECIP", 119); +define("SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED", 120); +define("SYNC_COMMONSTATUS_MESSREPLYFAILED", 121); +define("SYNC_COMMONSTATUS_ATTTOOLARGE", 122); +define("SYNC_COMMONSTATUS_USERHASNOMAILBOX", 123); +define("SYNC_COMMONSTATUS_USERCANTBEANONYMOUS", 124); +define("SYNC_COMMONSTATUS_USERPRINCIPALNOTFOUND", 125); +define("SYNC_COMMONSTATUS_USERDISABLEDFORSYNC", 126); +define("SYNC_COMMONSTATUS_USERONNEWMAILBOXCANTSYNC", 127); +define("SYNC_COMMONSTATUS_USERONLEGACYMAILBOXCANTSYNC", 128); +define("SYNC_COMMONSTATUS_DEVICEBLOCKEDFORUSER", 129); +define("SYNC_COMMONSTATUS_ACCESSDENIED", 130); +define("SYNC_COMMONSTATUS_ACCOUNTDISABLED", 131); +define("SYNC_COMMONSTATUS_SYNCSTATENOTFOUND", 132); +define("SYNC_COMMONSTATUS_SYNCSTATELOCKED", 133); +define("SYNC_COMMONSTATUS_SYNCSTATECORRUPT", 134); +define("SYNC_COMMONSTATUS_SYNCSTATEEXISTS", 135); +define("SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID", 136); +define("SYNC_COMMONSTATUS_COMMANDONOTSUPPORTED", 137); +define("SYNC_COMMONSTATUS_VERSIONNOTSUPPORTED", 138); +define("SYNC_COMMONSTATUS_DEVNOTFULLYPROVISIONABLE", 139); +define("SYNC_COMMONSTATUS_REMWIPEREQUESTED", 140); +define("SYNC_COMMONSTATUS_LEGACYDEVONSTRICTPOLICY", 141); +define("SYNC_COMMONSTATUS_DEVICENOTPROVISIONED", 142); +define("SYNC_COMMONSTATUS_POLICYREFRESH", 143); +define("SYNC_COMMONSTATUS_INVALIDPOLICYKEY", 144); +define("SYNC_COMMONSTATUS_EXTMANDEVICESNOTALLOWED", 145); +define("SYNC_COMMONSTATUS_NORECURRINCAL", 146); +define("SYNC_COMMONSTATUS_UNEXPECTEDITEMCLASS", 147); +define("SYNC_COMMONSTATUS_REMSERVERHASNOSSL", 148); +define("SYNC_COMMONSTATUS_INVALIDSTOREDREQ", 149); +define("SYNC_COMMONSTATUS_ITEMNOTFOUND", 150); +define("SYNC_COMMONSTATUS_TOOMANYFOLDERS", 151); +define("SYNC_COMMONSTATUS_NOFOLDERSFOUND", 152); +define("SYNC_COMMONSTATUS_ITEMLOSTAFTERMOVE", 153); +define("SYNC_COMMONSTATUS_FAILUREINMOVE", 154); +define("SYNC_COMMONSTATUS_NONPERSISTANTMOVEDISALLOWED", 155); +define("SYNC_COMMONSTATUS_MOVEINVALIDDESTFOLDER", 156); +define("SYNC_COMMONSTATUS_INVALIDACCOUNTID", 166); +define("SYNC_COMMONSTATUS_ACCOUNTSENDDISABLED", 167); +define("SYNC_COMMONSTATUS_IRMFEATUREDISABLED", 168); +define("SYNC_COMMONSTATUS_IRMTRANSIENTERROR", 169); +define("SYNC_COMMONSTATUS_IRMPERMANENTERROR", 170); +define("SYNC_COMMONSTATUS_IRMINVALIDTEMPLATEID", 171); +define("SYNC_COMMONSTATUS_IRMOPERATIONNOTPERMITTED", 172); +define("SYNC_COMMONSTATUS_NOPICTURE", 173); +define("SYNC_COMMONSTATUS_PICTURETOOLARGE", 174); +define("SYNC_COMMONSTATUS_PICTURELIMITREACHED", 175); +define("SYNC_COMMONSTATUS_BODYPARTCONVERSATIONTOOLARGE", 176); +define("SYNC_COMMONSTATUS_MAXDEVICESREACHED", 177); + +define("HTTP_CODE_200", 200); +define("HTTP_CODE_401", 401); +define("HTTP_CODE_449", 449); +define("HTTP_CODE_500", 500); + + +//logging defs +define("LOGLEVEL_OFF", 0); +define("LOGLEVEL_FATAL", 1); +define("LOGLEVEL_ERROR", 2); +define("LOGLEVEL_WARN", 4); +define("LOGLEVEL_INFO", 8); +define("LOGLEVEL_DEBUG", 16); +define("LOGLEVEL_WBXML", 32); +define("LOGLEVEL_DEVICEID", 64); +define("LOGLEVEL_WBXMLSTACK", 128); + +define("LOGLEVEL_ALL", LOGLEVEL_FATAL | LOGLEVEL_ERROR | LOGLEVEL_WARN | LOGLEVEL_INFO | LOGLEVEL_DEBUG | LOGLEVEL_WBXML); + +define("BACKEND_DISCARD_DATA", 1); + +define("SYNC_BODYPREFERENCE_UNDEFINED", 0); +define("SYNC_BODYPREFERENCE_PLAIN", 1); +define("SYNC_BODYPREFERENCE_HTML", 2); +define("SYNC_BODYPREFERENCE_RTF", 3); +define("SYNC_BODYPREFERENCE_MIME", 4); + +define("SYNC_FLAGSTATUS_CLEAR", 0); +define("SYNC_FLAGSTATUS_COMPLETE", 1); +define("SYNC_FLAGSTATUS_ACTIVE", 2); + +define("DEFAULT_EMAIL_CONTENTCLASS", "urn:content-classes:message"); + +define("SYNC_MAIL_LASTVERB_UNKNOWN", 0); +define("SYNC_MAIL_LASTVERB_REPLYSENDER", 1); +define("SYNC_MAIL_LASTVERB_REPLYALL", 2); +define("SYNC_MAIL_LASTVERB_FORWARD", 3); + +define("INTERNET_CPID_WINDOWS1252", 1252); +define("INTERNET_CPID_UTF8", 65001); + +define("MAPI_E_NOT_ENOUGH_MEMORY_32BIT", -2147024882); +define("MAPI_E_NOT_ENOUGH_MEMORY_64BIT", 2147942414); + +define("SYNC_SETTINGSOOF_BODYTYPE_HTML", "HTML"); +define("SYNC_SETTINGSOOF_BODYTYPE_TEXT", "TEXT"); + +define("SYNC_FILEAS_FIRSTLAST", 1); +define("SYNC_FILEAS_LASTFIRST", 2); +define("SYNC_FILEAS_COMPANYONLY", 3); +define("SYNC_FILEAS_COMPANYLAST", 4); +define("SYNC_FILEAS_COMPANYFIRST", 5); +define("SYNC_FILEAS_LASTCOMPANY", 6); +define("SYNC_FILEAS_FIRSTCOMPANY", 7); +?> \ No newline at end of file diff --git a/z-push/lib/default/backend.php b/z-push/lib/default/backend.php new file mode 100644 index 0000000..3c31bdc --- /dev/null +++ b/z-push/lib/default/backend.php @@ -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) { } + } + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/default/diffbackend/diffbackend.php b/z-push/lib/default/diffbackend/diffbackend.php new file mode 100644 index 0000000..6e77ae9 --- /dev/null +++ b/z-push/lib/default/diffbackend/diffbackend.php @@ -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); + +} +?> \ No newline at end of file diff --git a/z-push/lib/default/diffbackend/diffstate.php b/z-push/lib/default/diffbackend/diffstate.php new file mode 100644 index 0000000..6ac7639 --- /dev/null +++ b/z-push/lib/default/diffbackend/diffstate.php @@ -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) + } + } + +} + +?> \ No newline at end of file diff --git a/z-push/lib/default/diffbackend/exportchangesdiff.php b/z-push/lib/default/diffbackend/exportchangesdiff.php new file mode 100644 index 0000000..f5015c7 --- /dev/null +++ b/z-push/lib/default/diffbackend/exportchangesdiff.php @@ -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; + } + } + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/default/diffbackend/importchangesdiff.php b/z-push/lib/default/diffbackend/importchangesdiff.php new file mode 100644 index 0000000..d3a8f1f --- /dev/null +++ b/z-push/lib/default/diffbackend/importchangesdiff.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/default/filestatemachine.php b/z-push/lib/default/filestatemachine.php new file mode 100644 index 0000000..85383a4 --- /dev/null +++ b/z-push/lib/default/filestatemachine.php @@ -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; + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/default/searchprovider.php b/z-push/lib/default/searchprovider.php new file mode 100644 index 0000000..6ce98b0 --- /dev/null +++ b/z-push/lib/default/searchprovider.php @@ -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; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/default/simplemutex.php b/z-push/lib/default/simplemutex.php new file mode 100644 index 0000000..f5c19fe --- /dev/null +++ b/z-push/lib/default/simplemutex.php @@ -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; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/authenticationrequiredexception.php b/z-push/lib/exceptions/authenticationrequiredexception.php new file mode 100644 index 0000000..dbc5b43 --- /dev/null +++ b/z-push/lib/exceptions/authenticationrequiredexception.php @@ -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; +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/exceptions.php b/z-push/lib/exceptions/exceptions.php new file mode 100644 index 0000000..7306dda --- /dev/null +++ b/z-push/lib/exceptions/exceptions.php @@ -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'); + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/fatalexception.php b/z-push/lib/exceptions/fatalexception.php new file mode 100644 index 0000000..8eb4c6b --- /dev/null +++ b/z-push/lib/exceptions/fatalexception.php @@ -0,0 +1,47 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class FatalException extends ZPushException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/fatalmisconfigurationexception.php b/z-push/lib/exceptions/fatalmisconfigurationexception.php new file mode 100644 index 0000000..ee6022a --- /dev/null +++ b/z-push/lib/exceptions/fatalmisconfigurationexception.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class FatalMisconfigurationException extends FatalException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/fatalnotimplementedexception.php b/z-push/lib/exceptions/fatalnotimplementedexception.php new file mode 100644 index 0000000..a5fb8a3 --- /dev/null +++ b/z-push/lib/exceptions/fatalnotimplementedexception.php @@ -0,0 +1,47 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class FatalNotImplementedException extends FatalException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/httpreturncodeexception.php b/z-push/lib/exceptions/httpreturncodeexception.php new file mode 100644 index 0000000..8704a12 --- /dev/null +++ b/z-push/lib/exceptions/httpreturncodeexception.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/nohierarchycacheavailableexception.php b/z-push/lib/exceptions/nohierarchycacheavailableexception.php new file mode 100644 index 0000000..e708768 --- /dev/null +++ b/z-push/lib/exceptions/nohierarchycacheavailableexception.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class NoHierarchyCacheAvailableException extends StateNotFoundException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/nopostrequestexception.php b/z-push/lib/exceptions/nopostrequestexception.php new file mode 100644 index 0000000..a97d7a9 --- /dev/null +++ b/z-push/lib/exceptions/nopostrequestexception.php @@ -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; +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/notimplementedexception.php b/z-push/lib/exceptions/notimplementedexception.php new file mode 100644 index 0000000..81690a7 --- /dev/null +++ b/z-push/lib/exceptions/notimplementedexception.php @@ -0,0 +1,49 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class NotImplementedException extends ZPushException { + protected $defaultLogLevel = LOGLEVEL_ERROR; +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/provisioningrequiredexception.php b/z-push/lib/exceptions/provisioningrequiredexception.php new file mode 100644 index 0000000..12ea057 --- /dev/null +++ b/z-push/lib/exceptions/provisioningrequiredexception.php @@ -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"; +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/stateinvalidexception.php b/z-push/lib/exceptions/stateinvalidexception.php new file mode 100644 index 0000000..e610363 --- /dev/null +++ b/z-push/lib/exceptions/stateinvalidexception.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class StateInvalidException extends StatusException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/statenotfoundexception.php b/z-push/lib/exceptions/statenotfoundexception.php new file mode 100644 index 0000000..095c2d8 --- /dev/null +++ b/z-push/lib/exceptions/statenotfoundexception.php @@ -0,0 +1,47 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class StateNotFoundException extends StatusException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/statenotyetavailableexception.php b/z-push/lib/exceptions/statenotyetavailableexception.php new file mode 100644 index 0000000..2677fda --- /dev/null +++ b/z-push/lib/exceptions/statenotyetavailableexception.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class StateNotYetAvailableException extends StatusException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/statusexception.php b/z-push/lib/exceptions/statusexception.php new file mode 100644 index 0000000..1369d4e --- /dev/null +++ b/z-push/lib/exceptions/statusexception.php @@ -0,0 +1,48 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class StatusException extends ZPushException { + protected $defaultLogLevel = LOGLEVEL_INFO; +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/syncobjectbrokenexception.php b/z-push/lib/exceptions/syncobjectbrokenexception.php new file mode 100644 index 0000000..0a8c95b --- /dev/null +++ b/z-push/lib/exceptions/syncobjectbrokenexception.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/wbxmlexception.php b/z-push/lib/exceptions/wbxmlexception.php new file mode 100644 index 0000000..04e5f1e --- /dev/null +++ b/z-push/lib/exceptions/wbxmlexception.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class WBXMLException extends FatalNotImplementedException {} + +?> \ No newline at end of file diff --git a/z-push/lib/exceptions/zpushexception.php b/z-push/lib/exceptions/zpushexception.php new file mode 100644 index 0000000..3377eb4 --- /dev/null +++ b/z-push/lib/exceptions/zpushexception.php @@ -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; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/interface/ibackend.php b/z-push/lib/interface/ibackend.php new file mode 100644 index 0000000..b3df537 --- /dev/null +++ b/z-push/lib/interface/ibackend.php @@ -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); +} + +?> \ No newline at end of file diff --git a/z-push/lib/interface/ichanges.php b/z-push/lib/interface/ichanges.php new file mode 100644 index 0000000..0b3d76b --- /dev/null +++ b/z-push/lib/interface/ichanges.php @@ -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(); +} + +?> \ No newline at end of file diff --git a/z-push/lib/interface/iexportchanges.php b/z-push/lib/interface/iexportchanges.php new file mode 100644 index 0000000..79e935b --- /dev/null +++ b/z-push/lib/interface/iexportchanges.php @@ -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(); +} + +?> \ No newline at end of file diff --git a/z-push/lib/interface/iimportchanges.php b/z-push/lib/interface/iimportchanges.php new file mode 100644 index 0000000..d5bbe20 --- /dev/null +++ b/z-push/lib/interface/iimportchanges.php @@ -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); + +} + +?> \ No newline at end of file diff --git a/z-push/lib/interface/isearchprovider.php b/z-push/lib/interface/isearchprovider.php new file mode 100644 index 0000000..db3e5f4 --- /dev/null +++ b/z-push/lib/interface/isearchprovider.php @@ -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(); +} + +?> \ No newline at end of file diff --git a/z-push/lib/interface/istatemachine.php b/z-push/lib/interface/istatemachine.php new file mode 100644 index 0000000..e8ce6c2 --- /dev/null +++ b/z-push/lib/interface/istatemachine.php @@ -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); +} + +?> \ No newline at end of file diff --git a/z-push/lib/request/folderchange.php b/z-push/lib/request/folderchange.php new file mode 100644 index 0000000..cbcfcac --- /dev/null +++ b/z-push/lib/request/folderchange.php @@ -0,0 +1,249 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class FolderChange extends RequestProcessor { + + /** + * Handles creates, updates or deletes of a folder + * issued by the commands FolderCreate, FolderUpdate and FolderDelete + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle ($commandCode) { + $el = self::$decoder->getElement(); + + if($el[EN_TYPE] != EN_TYPE_STARTTAG) + return false; + + $create = $update = $delete = false; + if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERCREATE) + $create = true; + else if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERUPDATE) + $update = true; + else if($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERDELETE) + $delete = true; + + if(!$create && !$update && !$delete) + return false; + + // SyncKey + if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) + return false; + $synckey = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + + // ServerID + $serverid = false; + if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID)) { + $serverid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + // Parent + $parentid = false; + + // when creating or updating more information is necessary + if (!$delete) { + if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_PARENTID)) { + $parentid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + // Displayname + if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_DISPLAYNAME)) + return false; + $displayname = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + + // Type + $type = false; + if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_TYPE)) { + $type = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + } + + if(!self::$decoder->getElementEndTag()) + return false; + + $status = SYNC_FSSTATUS_SUCCESS; + // Get state of hierarchy + try { + $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); + $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); + + // Over the ChangesWrapper the HierarchyCache is notified about all changes + $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); + + // the hierarchyCache should now fully be initialized - check for changes in the additional folders + $changesMem->Config(ZPush::GetAdditionalSyncFolders()); + + // there are unprocessed changes in the hierarchy, trigger resync + if ($changesMem->GetChangeCount() > 0) + throw new StatusException("HandleFolderChange() can not proceed as there are unprocessed hierarchy changes", SYNC_FSSTATUS_SERVERERROR); + + // any additional folders can not be modified! + if ($serverid !== false && ZPush::GetAdditionalSyncFolderStore($serverid)) + throw new StatusException("HandleFolderChange() can not change additional folders which are configured", SYNC_FSSTATUS_SYSTEMFOLDER); + + // switch user store if this this happens inside an additional folder + // if this is an additional folder the backend has to be setup correctly + if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore((($parentid != false)?$parentid:$serverid)))) + throw new StatusException(sprintf("HandleFolderChange() could not Setup() the backend for folder id '%s'", (($parentid != false)?$parentid:$serverid)), SYNC_FSSTATUS_SERVERERROR); + } + catch (StateNotFoundException $snfex) { + $status = SYNC_FSSTATUS_SYNCKEYERROR; + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + + // set $newsynckey in case of an error + if (!isset($newsynckey)) + $newsynckey = $synckey; + + if ($status == SYNC_FSSTATUS_SUCCESS) { + try { + // Configure importer with last state + $importer = self::$backend->GetImporter(); + $importer->Config($syncstate); + + // the messages from the PIM will be forwarded to the real importer + $changesMem->SetDestinationImporter($importer); + + // process incoming change + if (!$delete) { + // Send change + $folder = new SyncFolder(); + $folder->serverid = $serverid; + $folder->parentid = $parentid; + $folder->displayname = $displayname; + $folder->type = $type; + + $serverid = $changesMem->ImportFolderChange($folder); + } + else { + // delete folder + $changesMem->ImportFolderDeletion($serverid, 0); + } + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + } + + self::$encoder->startWBXML(); + if ($create) { + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERCREATE); + { + { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); + self::$encoder->content($newsynckey); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); + self::$encoder->content($serverid); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + } + self::$encoder->endTag(); + } + + elseif ($update) { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERUPDATE); + { + { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); + self::$encoder->content($newsynckey); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + } + } + + elseif ($delete) { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERDELETE); + { + { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); + self::$encoder->content($newsynckey); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + } + } + + self::$encoder->endTag(); + + self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true); + + // Save the sync state for the next time + if (isset($importer)) + self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $importer->GetState()); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/foldersync.php b/z-push/lib/request/foldersync.php new file mode 100644 index 0000000..22b7b2f --- /dev/null +++ b/z-push/lib/request/foldersync.php @@ -0,0 +1,232 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class FolderSync extends RequestProcessor { + + /** + * Handles the FolderSync command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle ($commandCode) { + // Maps serverid -> clientid for items that are received from the PIM + $map = array(); + + // Parse input + if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) + return false; + + $synckey = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + + $status = SYNC_FSSTATUS_SUCCESS; + $newsynckey = $synckey; + try { + $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); + + // We will be saving the sync state under 'newsynckey' + $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); + } + catch (StateNotFoundException $snfex) { + $status = SYNC_FSSTATUS_SYNCKEYERROR; + } + catch (StateInvalidException $sive) { + $status = SYNC_FSSTATUS_SYNCKEYERROR; + } + + // The ChangesWrapper caches all imports in-memory, so we can send a change count + // before sending the actual data. + // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend + $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); + + // the hierarchyCache should now fully be initialized - check for changes in the additional folders + $changesMem->Config(ZPush::GetAdditionalSyncFolders()); + + // process incoming changes + if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { + // Ignore if present + if(self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { + self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + // Process the changes (either , , or ) + $element = self::$decoder->getElement(); + + if($element[EN_TYPE] != EN_TYPE_STARTTAG) + return false; + + $importer = false; + while(1) { + $folder = new SyncFolder(); + if(!$folder->Decode(self::$decoder)) + break; + + try { + if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) { + // Configure the backends importer with last state + $importer = self::$backend->GetImporter(); + $importer->Config($syncstate); + // the messages from the PIM will be forwarded to the backend + $changesMem->forwardImporter($importer); + } + + if ($status == SYNC_FSSTATUS_SUCCESS) { + switch($element[EN_TAG]) { + case SYNC_ADD: + case SYNC_MODIFY: + $serverid = $changesMem->ImportFolderChange($folder); + break; + case SYNC_REMOVE: + $serverid = $changesMem->ImportFolderDeletion($folder); + break; + } + + // TODO what does $map?? + if($serverid) + $map[$serverid] = $folder->clientid; + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname)); + self::$topCollector->AnnounceInformation("Incoming change ignored", true); + } + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + } + + if(!self::$decoder->getElementEndTag()) + return false; + } + // no incoming changes + else { + // check for a potential process loop like described in Issue ZP-5 + if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) + $status = SYNC_FSSTATUS_SYNCKEYERROR; + self::$deviceManager->AnnounceProcessStatus(false, $status); + } + + if(!self::$decoder->getElementEndTag()) + return false; + + // We have processed incoming foldersync requests, now send the PIM + // our changes + + // Output our WBXML reply now + self::$encoder->StartWBXML(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); + { + if ($status == SYNC_FSSTATUS_SUCCESS) { + try { + // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends) + if (!Request::IsValidDeviceID()) + throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR); + + // Changes from backend are sent to the MemImporter and processed for the HierarchyCache. + // The state which is saved is from the backend, as the MemImporter is only a proxy. + $exporter = self::$backend->GetExporter(); + + $exporter->Config($syncstate); + $exporter->InitializeExporter($changesMem); + + // Stream all changes to the ImportExportChangesMem + while(is_array($exporter->Synchronize())); + + // get the new state from the backend + $newsyncstate = (isset($exporter))?$exporter->GetState():""; + } + catch (StatusException $stex) { + if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) + $status = SYNC_FSSTATUS_SYNCKEYERROR; + else + $status = $stex->getCode(); + } + } + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + if ($status == SYNC_FSSTATUS_SUCCESS) { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); + $synckey = ($changesMem->IsStateChanged()) ? $newsynckey : $synckey; + self::$encoder->content($synckey); + self::$encoder->endTag(); + + // Stream folders directly to the PDA + $streamimporter = new ImportChangesStream(self::$encoder, false); + $changesMem->InitializeExporter($streamimporter); + $changeCount = $changesMem->GetChangeCount(); + + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); + { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); + self::$encoder->content($changeCount); + self::$encoder->endTag(); + while($changesMem->Synchronize()); + } + self::$encoder->endTag(); + self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders",$changeCount), true); + + // everything fine, save the sync state for the next time + if ($synckey == $newsynckey) + self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate); + } + } + self::$encoder->endTag(); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/getattachment.php b/z-push/lib/request/getattachment.php new file mode 100644 index 0000000..3472719 --- /dev/null +++ b/z-push/lib/request/getattachment.php @@ -0,0 +1,88 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class GetAttachment extends RequestProcessor { + + /** + * Handles the GetAttachment command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $attname = Request::GetGETAttachmentName(); + if(!$attname) + return false; + + try { + $attachment = self::$backend->GetAttachmentData($attname); + $stream = $attachment->data; + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment stream from backend: %s", $stream)); + + header("Content-Type: application/octet-stream"); + $l = 0; + while (!feof($stream)) { + $d = fgets($stream, 4096); + $l += strlen($d); + echo $d; + + // announce an update every 100K + if (($l/1024) % 100 == 0) + self::$topCollector->AnnounceInformation(sprintf("Streaming attachment: %d KB sent", round($l/1024))); + } + fclose($stream); + self::$topCollector->AnnounceInformation(sprintf("Streamed %d KB attachment", $l/1024), true); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetAttachment(): attachment with %d KB sent to mobile", $l/1024)); + + } + catch (StatusException $s) { + // StatusException already logged so we just need to pass it upwards to send a HTTP error + throw new HTTPReturnCodeException($s->getMessage(), HTTP_CODE_500, null, LOGLEVEL_DEBUG); + } + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/gethierarchy.php b/z-push/lib/request/gethierarchy.php new file mode 100644 index 0000000..1df744f --- /dev/null +++ b/z-push/lib/request/gethierarchy.php @@ -0,0 +1,81 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class GetHierarchy extends RequestProcessor { + + /** + * Handles the GetHierarchy command + * simply returns current hierarchy of all folders + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + try { + $folders = self::$backend->GetHierarchy(); + if (!$folders || empty($folders)) + throw new StatusException("GetHierarchy() did not return any data."); + + // TODO execute $data->Check() to see if SyncObject is valid + + } + catch (StatusException $ex) { + return false; + } + + self::$encoder->StartWBXML(); + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERS); + foreach ($folders as $folder) { + self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDER); + $folder->Encode(self::$encoder); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + + // save hierarchy for upcoming syncing + return self::$deviceManager->InitializeFolderCache($folders); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/getitemestimate.php b/z-push/lib/request/getitemestimate.php new file mode 100644 index 0000000..27a9eaa --- /dev/null +++ b/z-push/lib/request/getitemestimate.php @@ -0,0 +1,283 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class GetItemEstimate extends RequestProcessor { + + /** + * Handles the GetItemEstimate command + * Returns an estimation of how many items will be synchronized at the next sync + * This is mostly used to show something in the progress bar + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $sc = new SyncCollections(); + + if(!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) + return false; + + while(self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) { + $spa = new SyncParameters(); + $spastatus = false; + + if (Request::GetProtocolVersion() >= 14.0) { + if(self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { + try { + $spa->SetSyncKey(self::$decoder->getElementContent()); + } + catch (StateInvalidException $siex) { + $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED; + } + + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) { + $spa->SetFolderId( self::$decoder->getElementContent()); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + // conversation mode requested + if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { + $spa->SetConversationMode(true); + if(($conversationmode = self::$decoder->getElementContent()) !== false) { + $spa->SetConversationMode((boolean)$conversationmode); + if(!self::$decoder->getElementEndTag()) + return false; + } + } + + if(self::$decoder->getElementStartTag(SYNC_OPTIONS)) { + while(1) { + if(self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { + $spa->SetFilterType(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { + $spa->SetContentClass(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_MAXITEMS)) { + $spa->SetWindowSize($maxitems = self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); + break; + } + } + } + } + else { + //get items estimate does not necessarily send the folder type + if(self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) { + $spa->SetContentClass(self::$decoder->getElementContent()); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) { + $spa->SetFolderId(self::$decoder->getElementContent()); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) + return false; + + $spa->SetFilterType(self::$decoder->getElementContent()); + + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_SYNCKEY)) + return false; + + try { + $spa->SetSyncKey(self::$decoder->getElementContent()); + } + catch (StateInvalidException $siex) { + $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED; + } + + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; //SYNC_GETITEMESTIMATE_FOLDER + + // Process folder data + + //In AS 14 request only collectionid is sent, without class + if (! $spa->HasContentClass() && $spa->HasFolderId()) { + try { + $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); + } + catch (NoHierarchyCacheAvailableException $nhca) { + $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID; + } + } + + // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() + if (! $spa->HasFolderId() && $spa->HasContentClass()) { + $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass())); + } + + // Add collection to SC and load state + $sc->AddCollection($spa); + if ($spastatus) { + // the CPO has a folder id now, so we can set the status + $sc->AddParameter($spa, "status", $spastatus); + } + else { + try { + $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey())); + + // if this is an additional folder the backend has to be setup correctly + if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) + throw new StatusException(sprintf("HandleGetItemEstimate() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); + } + catch (StateNotFoundException $snfex) { + // ok, the key is invalid. Question is, if the hierarchycache is still ok + //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync + try { + self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()); + // we got here, so the HierarchyCache is ok + $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID); + } + catch (NoHierarchyCacheAvailableException $nhca) { + $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); + } + + self::$topCollector->AnnounceInformation("StateNotFoundException ". $sc->GetParameter($spa, "status"), true); + } + catch (StatusException $stex) { + if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) + $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); + else + $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED); + self::$topCollector->AnnounceInformation("StatusException ". $sc->GetParameter($spa, "status"), true); + } + } + } + if(!self::$decoder->getElementEndTag()) + return false; //SYNC_GETITEMESTIMATE_FOLDERS + + if(!self::$decoder->getElementEndTag()) + return false; //SYNC_GETITEMESTIMATE_GETITEMESTIMATE + + self::$encoder->startWBXML(); + self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE); + { + $status = SYNC_GETITEMESTSTATUS_SUCCESS; + // look for changes in all collections + + try { + $sc->CountChanges(); + } + catch (StatusException $ste) { + $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID; + } + $changes = $sc->GetChangedFolderIds(); + + foreach($sc as $folderid => $spa) { + self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE); + { + if ($sc->GetParameter($spa, "status")) + $status = $sc->GetParameter($spa, "status"); + + self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER); + { + self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE); + self::$encoder->content($spa->GetContentClass()); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID); + self::$encoder->content($spa->GetFolderId()); + self::$encoder->endTag(); + + if (isset($changes[$folderid]) && $changes[$folderid] !== false) { + self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE); + self::$encoder->content($changes[$folderid]); + self::$encoder->endTag(); + + if ($changes[$folderid] > 0) + self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true); + } + } + self::$encoder->endTag(); + } + self::$encoder->endTag(); + } + if (array_sum($changes) == 0) + self::$topCollector->AnnounceInformation("No changes found", true); + } + self::$encoder->endTag(); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/itemoperations.php b/z-push/lib/request/itemoperations.php new file mode 100644 index 0000000..fb7c511 --- /dev/null +++ b/z-push/lib/request/itemoperations.php @@ -0,0 +1,324 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class ItemOperations extends RequestProcessor { + + /** + * Handles the ItemOperations command + * Provides batched online handling for Fetch, EmptyFolderContents and Move + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + // Parse input + if(!self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_ITEMOPERATIONS)) + return false; + + //TODO check if multiple item operations are possible in one request + $el = self::$decoder->getElement(); + + if($el[EN_TYPE] != EN_TYPE_STARTTAG) + return false; + //ItemOperations can either be Fetch, EmptyFolderContents or Move + $fetch = $efc = $move = false; + if($el[EN_TAG] == SYNC_ITEMOPERATIONS_FETCH) { + $fetch = true; + self::$topCollector->AnnounceInformation("Fetch", true); + } + else if($el[EN_TAG] == SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS) { + $efc = true; + self::$topCollector->AnnounceInformation("Empty Folder", true); + } + else if($el[EN_TAG] == SYNC_ITEMOPERATIONS_MOVE) { + $move = true; + self::$topCollector->AnnounceInformation("Move", true); + } + + if(!$fetch && !$efc && !$move) { + ZLog::Write(LOGLEVEL_DEBUG, "Unknown item operation:".print_r($el, 1)); + self::$topCollector->AnnounceInformation("Unknown operation", true); + return false; + } + + if ($fetch) { + if(!self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_STORE)) + return false; + $store = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_ITEMOPERATIONS_STORE + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_LONGID)) { + $longid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_SEARCH_LONGID + } + + if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) { + $folderid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_FOLDERID + } + + if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { + $serverid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_SERVERENTRYID + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_FILEREFERENCE)) { + $filereference = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_AIRSYNCBASE_FILEREFERENCE + } + + if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) { + //TODO other options + //schema + //range + //username + //password + //bodypartpreference + //rm:RightsManagementSupport + + // Save all OPTIONS into a ContentParameters object + $collection["cpo"] = new ContentParameters(); + while(1) { + while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { + $bptype = self::$decoder->getElementContent(); + $collection["cpo"]->BodyPreference($bptype); + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { + $collection["cpo"]->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { + $collection["cpo"]->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { + $collection["cpo"]->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_AIRSYNCBASE_BODYPREFERENCE + } + + if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { + $collection["cpo"]->SetMimeSupport(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + //break if it reached the endtag + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); + break; + } + } + } + } + + if ($efc) { + if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) { + $folderid = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_FOLDERID + } + if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_OPTIONS)) { + if(self::$decoder->getElementStartTag(SYNC_ITEMOPERATIONS_DELETESUBFOLDERS)) { + $deletesubfolders = true; + if (($dsf = self::$decoder->getElementContent()) !== false) { + $deletesubfolders = (boolean)$dsf; + if(!self::$decoder->getElementEndTag()) + return false; + } + } + self::$decoder->getElementEndTag(); + } + } + + //TODO EmptyFolderContents + //TODO move + + if(!self::$decoder->getElementEndTag()) + return false; //SYNC_ITEMOPERATIONS_FETCH or SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS or SYNC_ITEMOPERATIONS_MOVE + + if(!self::$decoder->getElementEndTag()) + return false;//SYNC_ITEMOPERATIONS_ITEMOPERATIONS + + $status = SYNC_ITEMOPERATIONSSTATUS_SUCCESS; + //TODO status handling + + self::$encoder->startWBXML(); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_ITEMOPERATIONS); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_STATUS); + self::$encoder->content($status); + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_STATUS + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_RESPONSE); + + // fetch response + if ($fetch) { + self::$encoder->startTag(SYNC_ITEMOPERATIONS_FETCH); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_STATUS); + self::$encoder->content($status); + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_STATUS + + if (isset($folderid) && isset($serverid)) { + self::$encoder->startTag(SYNC_FOLDERID); + self::$encoder->content($folderid); + self::$encoder->endTag(); // end SYNC_FOLDERID + + self::$encoder->startTag(SYNC_SERVERENTRYID); + self::$encoder->content($serverid); + self::$encoder->endTag(); // end SYNC_SERVERENTRYID + + self::$encoder->startTag(SYNC_FOLDERTYPE); + self::$encoder->content("Email"); + self::$encoder->endTag(); + + self::$topCollector->AnnounceInformation("Fetching data from backend with item and folder id"); + + $data = self::$backend->Fetch($folderid, $serverid, $collection["cpo"]); + } + + if (isset($longid)) { + self::$encoder->startTag(SYNC_SEARCH_LONGID); + self::$encoder->content($longid); + self::$encoder->endTag(); // end SYNC_FOLDERID + + self::$encoder->startTag(SYNC_FOLDERTYPE); + self::$encoder->content("Email"); + self::$encoder->endTag(); + + $tmp = explode(":", $longid); + + self::$topCollector->AnnounceInformation("Fetching data from backend with long id"); + + $data = self::$backend->Fetch($tmp[0], $tmp[1], $collection["cpo"]); + } + + if (isset($filereference)) { + self::$encoder->startTag(SYNC_AIRSYNCBASE_FILEREFERENCE); + self::$encoder->content($filereference); + self::$encoder->endTag(); // end SYNC_AIRSYNCBASE_FILEREFERENCE + + self::$topCollector->AnnounceInformation("Get attachment data from backend with file reference"); + + $data = self::$backend->GetAttachmentData($filereference); + } + + //TODO put it in try catch block + + if (isset($data)) { + self::$topCollector->AnnounceInformation("Streaming data"); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_PROPERTIES); + $data->Encode(self::$encoder); + self::$encoder->endTag(); //SYNC_ITEMOPERATIONS_PROPERTIES + } + + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_FETCH + } + // empty folder contents operation + else if ($efc) { + try { + self::$topCollector->AnnounceInformation("Emptying folder"); + + // send request to backend + self::$backend->EmptyFolder($folderid, $deletesubfolders); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_STATUS); + self::$encoder->content($status); + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_STATUS + + if (isset($folderid)) { + self::$encoder->startTag(SYNC_FOLDERID); + self::$encoder->content($folderid); + self::$encoder->endTag(); // end SYNC_FOLDERID + } + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_EMPTYFOLDERCONTENTS + } + // TODO implement ItemOperations Move + // move operation + else { + self::$topCollector->AnnounceInformation("not implemented", true); + + self::$encoder->startTag(SYNC_ITEMOPERATIONS_MOVE); + self::$encoder->startTag(SYNC_ITEMOPERATIONS_STATUS); + self::$encoder->content($status); + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_STATUS + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_MOVE + } + + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_RESPONSE + self::$encoder->endTag();//SYNC_ITEMOPERATIONS_ITEMOPERATIONS + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/meetingresponse.php b/z-push/lib/request/meetingresponse.php new file mode 100644 index 0000000..65de967 --- /dev/null +++ b/z-push/lib/request/meetingresponse.php @@ -0,0 +1,129 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class MeetingResponse extends RequestProcessor { + + /** + * Handles the MeetingResponse command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $requests = Array(); + + if(!self::$decoder->getElementStartTag(SYNC_MEETINGRESPONSE_MEETINGRESPONSE)) + return false; + + while(self::$decoder->getElementStartTag(SYNC_MEETINGRESPONSE_REQUEST)) { + $req = Array(); + + if(self::$decoder->getElementStartTag(SYNC_MEETINGRESPONSE_USERRESPONSE)) { + $req["response"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_MEETINGRESPONSE_FOLDERID)) { + $req["folderid"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_MEETINGRESPONSE_REQUESTID)) { + $req["requestid"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; + + array_push($requests, $req); + } + + if(!self::$decoder->getElementEndTag()) + return false; + + // output the error code, plus the ID of the calendar item that was generated by the + // accept of the meeting response + self::$encoder->StartWBXML(); + self::$encoder->startTag(SYNC_MEETINGRESPONSE_MEETINGRESPONSE); + + foreach($requests as $req) { + $status = SYNC_MEETRESPSTATUS_SUCCESS; + + try { + $calendarid = self::$backend->MeetingResponse($req["requestid"], $req["folderid"], $req["response"]); + if ($calendarid === false) + throw new StatusException("HandleMeetingResponse() not possible", SYNC_MEETRESPSTATUS_SERVERERROR); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + + self::$encoder->startTag(SYNC_MEETINGRESPONSE_RESULT); + self::$encoder->startTag(SYNC_MEETINGRESPONSE_REQUESTID); + self::$encoder->content($req["requestid"]); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_MEETINGRESPONSE_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + if($status == SYNC_MEETRESPSTATUS_SUCCESS && !empty($calendarid)) { + self::$encoder->startTag(SYNC_MEETINGRESPONSE_CALENDARID); + self::$encoder->content($calendarid); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true); + } + self::$encoder->endTag(); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/moveitems.php b/z-push/lib/request/moveitems.php new file mode 100644 index 0000000..7d46508 --- /dev/null +++ b/z-push/lib/request/moveitems.php @@ -0,0 +1,132 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class MoveItems extends RequestProcessor { + + /** + * Handles the MoveItems command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + if(!self::$decoder->getElementStartTag(SYNC_MOVE_MOVES)) + return false; + + $moves = array(); + while(self::$decoder->getElementStartTag(SYNC_MOVE_MOVE)) { + $move = array(); + if(self::$decoder->getElementStartTag(SYNC_MOVE_SRCMSGID)) { + $move["srcmsgid"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + break; + } + if(self::$decoder->getElementStartTag(SYNC_MOVE_SRCFLDID)) { + $move["srcfldid"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + break; + } + if(self::$decoder->getElementStartTag(SYNC_MOVE_DSTFLDID)) { + $move["dstfldid"] = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) + break; + } + array_push($moves, $move); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; + + self::$encoder->StartWBXML(); + + self::$encoder->startTag(SYNC_MOVE_MOVES); + + foreach($moves as $move) { + self::$encoder->startTag(SYNC_MOVE_RESPONSE); + self::$encoder->startTag(SYNC_MOVE_SRCMSGID); + self::$encoder->content($move["srcmsgid"]); + self::$encoder->endTag(); + + $status = SYNC_MOVEITEMSSTATUS_SUCCESS; + $result = false; + try { + // if the source folder is an additional folder the backend has to be setup correctly + if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($move["srcfldid"]))) + throw new StatusException(sprintf("HandleMoveItems() could not Setup() the backend for folder id '%s'", $move["srcfldid"]), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); + + $importer = self::$backend->GetImporter($move["srcfldid"]); + if ($importer === false) + throw new StatusException(sprintf("HandleMoveItems() could not get an importer for folder id '%s'", $move["srcfldid"]), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); + + $result = $importer->ImportMessageMove($move["srcmsgid"], $move["dstfldid"]); + // We discard the importer state for now. + } + catch (StatusException $stex) { + if ($stex->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) // same as SYNC_FSSTATUS_CODEUNKNOWN + $status = SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID; + else + $status = $stex->getCode(); + } + + self::$topCollector->AnnounceInformation(sprintf("Operation status: %s", $status), true); + + self::$encoder->startTag(SYNC_MOVE_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_MOVE_DSTMSGID); + self::$encoder->content( (($result !== false ) ? $result : $move["srcmsgid"])); + self::$encoder->endTag(); + self::$encoder->endTag(); + } + + self::$encoder->endTag(); + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/notify.php b/z-push/lib/request/notify.php new file mode 100644 index 0000000..e7e5b8f --- /dev/null +++ b/z-push/lib/request/notify.php @@ -0,0 +1,83 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Notify extends RequestProcessor { + + /** + * Handles the Notify command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + if(!self::$decoder->getElementStartTag(SYNC_AIRNOTIFY_NOTIFY)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_AIRNOTIFY_DEVICEINFO)) + return false; + + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementEndTag()) + return false; + + self::$encoder->StartWBXML(); + + self::$encoder->startTag(SYNC_AIRNOTIFY_NOTIFY); + { + self::$encoder->startTag(SYNC_AIRNOTIFY_STATUS); + self::$encoder->content(1); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_AIRNOTIFY_VALIDCARRIERPROFILES); + self::$encoder->endTag(); + } + self::$encoder->endTag(); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/ping.php b/z-push/lib/request/ping.php new file mode 100644 index 0000000..cc38660 --- /dev/null +++ b/z-push/lib/request/ping.php @@ -0,0 +1,205 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Ping extends RequestProcessor { + + /** + * Handles the Ping command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30; + $pingstatus = false; + $fakechanges = array(); + $foundchanges = false; + + // Contains all requested folders (containers) + $sc = new SyncCollections(); + + // Load all collections - do load states and check permissions + try { + $sc->LoadAllCollections(true, true, true); + } + catch (StateNotFoundException $snfex) { + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + self::$topCollector->AnnounceInformation("StateNotFoundException: require HierarchySync", true); + } + catch (StateInvalidException $snfex) { + // we do not have a ping status for this, but SyncCollections should have generated fake changes for the folders which are broken + $fakechanges = $sc->GetChangedFolderIds(); + $foundchanges = true; + + self::$topCollector->AnnounceInformation("StateInvalidException: force sync", true); + } + catch (StatusException $stex) { + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + self::$topCollector->AnnounceInformation("StatusException: require HierarchySync", true); + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): reference PolicyKey for PING: %s", $sc->GetReferencePolicyKey())); + + // receive PING initialization data + if(self::$decoder->getElementStartTag(SYNC_PING_PING)) { + self::$topCollector->AnnounceInformation("Processing PING data"); + ZLog::Write(LOGLEVEL_DEBUG, "HandlePing(): initialization data received"); + + if(self::$decoder->getElementStartTag(SYNC_PING_LIFETIME)) { + $sc->SetLifetime(self::$decoder->getElementContent()); + self::$decoder->getElementEndTag(); + } + + if(($el = self::$decoder->getElementStartTag(SYNC_PING_FOLDERS)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) { + // remove PingableFlag from all collections + foreach ($sc as $folderid => $spa) + $spa->DelPingableFlag(); + + while(self::$decoder->getElementStartTag(SYNC_PING_FOLDER)) { + while(1) { + if(self::$decoder->getElementStartTag(SYNC_PING_SERVERENTRYID)) { + $folderid = self::$decoder->getElementContent(); + self::$decoder->getElementEndTag(); + } + if(self::$decoder->getElementStartTag(SYNC_PING_FOLDERTYPE)) { + $class = self::$decoder->getElementContent(); + self::$decoder->getElementEndTag(); + } + + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); + break; + } + } + + $spa = $sc->GetCollection($folderid); + if (! $spa) { + // The requested collection is not synchronized. + // check if the HierarchyCache is available, if not, trigger a HierarchySync + try { + self::$deviceManager->GetFolderClassFromCacheByID($folderid); + } + catch (NoHierarchyCacheAvailableException $nhca) { + ZLog::Write(LOGLEVEL_INFO, sprintf("HandlePing(): unknown collection '%s', triggering HierarchySync", $folderid)); + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + } + + // Trigger a Sync request because then the device will be forced to resync this folder. + $fakechanges[$folderid] = 1; + $foundchanges = true; + } + else if ($class == $spa->GetContentClass()) { + $spa->SetPingableFlag(true); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandlePing(): using saved sync state for '%s' id '%s'", $spa->GetContentClass(), $folderid)); + } + + } + if(!self::$decoder->getElementEndTag()) + return false; + } + if(!self::$decoder->getElementEndTag()) + return false; + + // save changed data + foreach ($sc as $folderid => $spa) + $sc->SaveCollection($spa); + } // END SYNC_PING_PING + + // Check for changes on the default LifeTime, set interval and ONLY on pingable collections + try { + if (empty($fakechanges)) { + $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval, true); + } + } + catch (StatusException $ste) { + switch($ste->getCode()) { + case SyncCollections::ERROR_NO_COLLECTIONS: + $pingstatus = SYNC_PINGSTATUS_FAILINGPARAMS; + break; + case SyncCollections::ERROR_WRONG_HIERARCHY: + $pingstatus = SYNC_PINGSTATUS_FOLDERHIERSYNCREQUIRED; + self::$deviceManager->AnnounceProcessStatus(false, $pingstatus); + break; + + } + } + + self::$encoder->StartWBXML(); + self::$encoder->startTag(SYNC_PING_PING); + { + self::$encoder->startTag(SYNC_PING_STATUS); + if (isset($pingstatus) && $pingstatus) + self::$encoder->content($pingstatus); + else + self::$encoder->content($foundchanges ? SYNC_PINGSTATUS_CHANGES : SYNC_PINGSTATUS_HBEXPIRED); + self::$encoder->endTag(); + + if (! $pingstatus) { + self::$encoder->startTag(SYNC_PING_FOLDERS); + + if (empty($fakechanges)) + $changes = $sc->GetChangedFolderIds(); + else + $changes = $fakechanges; + + foreach ($changes as $folderid => $changecount) { + if ($changecount > 0) { + self::$encoder->startTag(SYNC_PING_FOLDER); + self::$encoder->content($folderid); + self::$encoder->endTag(); + if (empty($fakechanges)) + self::$topCollector->AnnounceInformation(sprintf("Found change in %s", $sc->GetCollection($folderid)->GetContentClass()), true); + } + } + self::$encoder->endTag(); + } + } + self::$encoder->endTag(); + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/provisioning.php b/z-push/lib/request/provisioning.php new file mode 100644 index 0000000..0a0d216 --- /dev/null +++ b/z-push/lib/request/provisioning.php @@ -0,0 +1,230 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Provisioning extends RequestProcessor { + + /** + * Handles the Provisioning command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $status = SYNC_PROVISION_STATUS_SUCCESS; + $policystatus = SYNC_PROVISION_POLICYSTATUS_SUCCESS; + + $rwstatus = self::$deviceManager->GetProvisioningWipeStatus(); + $rwstatusWiped = false; + + // if this is a regular provisioning require that an authenticated remote user + if ($rwstatus < SYNC_PROVISION_RWSTATUS_PENDING) { + ZLog::Write(LOGLEVEL_DEBUG, "RequestProcessor::HandleProvision(): Forcing delayed Authentication"); + self::Authenticate(); + } + + $phase2 = true; + + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_PROVISION)) + return false; + + //handle android remote wipe. + if (self::$decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) + return false; + + $instatus = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementEndTag()) + return false; + + $phase2 = false; + $rwstatusWiped = true; + } + else { + + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICIES)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICY)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYTYPE)) + return false; + + $policytype = self::$decoder->getElementContent(); + if ($policytype != 'MS-WAP-Provisioning-XML' && $policytype != 'MS-EAS-Provisioning-WBXML') { + $status = SYNC_PROVISION_STATUS_SERVERERROR; + } + if(!self::$decoder->getElementEndTag()) //policytype + return false; + + if (self::$decoder->getElementStartTag(SYNC_PROVISION_POLICYKEY)) { + $devpolicykey = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) + return false; + + $instatus = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + + $phase2 = false; + } + + if(!self::$decoder->getElementEndTag()) //policy + return false; + + if(!self::$decoder->getElementEndTag()) //policies + return false; + + if (self::$decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { + if(!self::$decoder->getElementStartTag(SYNC_PROVISION_STATUS)) + return false; + + $status = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementEndTag()) + return false; + + $rwstatusWiped = true; + } + } + if(!self::$decoder->getElementEndTag()) //provision + return false; + + if (PROVISIONING !== true) { + ZLog::Write(LOGLEVEL_INFO, "No policies deployed to device"); + $policystatus = SYNC_PROVISION_POLICYSTATUS_NOPOLICY; + } + + self::$encoder->StartWBXML(); + + //set the new final policy key in the device manager + // START ADDED dw2412 Android provisioning fix + if (!$phase2) { + $policykey = self::$deviceManager->GenerateProvisioningPolicyKey(); + self::$deviceManager->SetProvisioningPolicyKey($policykey); + self::$topCollector->AnnounceInformation("Policies deployed", true); + } + else { + // just create a temporary key (i.e. iPhone OS4 Beta does not like policykey 0 in response) + $policykey = self::$deviceManager->GenerateProvisioningPolicyKey(); + } + // END ADDED dw2412 Android provisioning fix + + self::$encoder->startTag(SYNC_PROVISION_PROVISION); + { + self::$encoder->startTag(SYNC_PROVISION_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_PROVISION_POLICIES); + self::$encoder->startTag(SYNC_PROVISION_POLICY); + + if(isset($policytype)) { + self::$encoder->startTag(SYNC_PROVISION_POLICYTYPE); + self::$encoder->content($policytype); + self::$encoder->endTag(); + } + + self::$encoder->startTag(SYNC_PROVISION_STATUS); + self::$encoder->content($policystatus); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_PROVISION_POLICYKEY); + self::$encoder->content($policykey); + self::$encoder->endTag(); + + if ($phase2 && $policystatus === SYNC_PROVISION_POLICYSTATUS_SUCCESS) { + self::$encoder->startTag(SYNC_PROVISION_DATA); + if ($policytype == 'MS-WAP-Provisioning-XML') { + self::$encoder->content(''); + } + elseif ($policytype == 'MS-EAS-Provisioning-WBXML') { + self::$encoder->startTag(SYNC_PROVISION_EASPROVISIONDOC); + + $prov = self::$deviceManager->GetProvisioningObject(); + if (!$prov->Check()) + throw new FatalException("Invalid policies!"); + + $prov->Encode(self::$encoder); + self::$encoder->endTag(); + } + else { + ZLog::Write(LOGLEVEL_WARN, "Wrong policy type"); + self::$topCollector->AnnounceInformation("Policytype not supported", true); + return false; + } + self::$topCollector->AnnounceInformation("Updated provisiong", true); + + self::$encoder->endTag();//data + } + self::$encoder->endTag();//policy + self::$encoder->endTag(); //policies + } + + //wipe data if a higher RWSTATUS is requested + if ($rwstatus > SYNC_PROVISION_RWSTATUS_OK && $policystatus === SYNC_PROVISION_POLICYSTATUS_SUCCESS) { + self::$encoder->startTag(SYNC_PROVISION_REMOTEWIPE, false, true); + self::$deviceManager->SetProvisioningWipeStatus(($rwstatusWiped)?SYNC_PROVISION_RWSTATUS_WIPED:SYNC_PROVISION_RWSTATUS_REQUESTED); + self::$topCollector->AnnounceInformation(sprintf("Remote wipe %s", ($rwstatusWiped)?"executed":"requested"), true); + } + + self::$encoder->endTag();//provision + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/request.php b/z-push/lib/request/request.php new file mode 100644 index 0000000..aa014f7 --- /dev/null +++ b/z-push/lib/request/request.php @@ -0,0 +1,588 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Request { + const UNKNOWN = "unknown"; + + /** + * self::filterEvilInput() options + */ + const LETTERS_ONLY = 1; + const HEX_ONLY = 2; + const WORDCHAR_ONLY = 3; + const NUMBERS_ONLY = 4; + const NUMBERSDOT_ONLY = 5; + const HEX_EXTENDED = 6; + + /** + * Command parameters for base64 encoded requests (AS >= 12.1) + */ + const COMMANDPARAM_ATTACHMENTNAME = 0; + const COMMANDPARAM_COLLECTIONID = 1; //deprecated + const COMMANDPARAM_COLLECTIONNAME = 2; //deprecated + const COMMANDPARAM_ITEMID = 3; + const COMMANDPARAM_LONGID = 4; + const COMMANDPARAM_PARENTID = 5; //deprecated + const COMMANDPARAM_OCCURRENCE = 6; + const COMMANDPARAM_OPTIONS = 7; //used by SmartReply, SmartForward, SendMail, ItemOperations + const COMMANDPARAM_USER = 8; //used by any command + //possible bitflags for COMMANDPARAM_OPTIONS + const COMMANDPARAM_OPTIONS_SAVEINSENT = 0x01; + const COMMANDPARAM_OPTIONS_ACCEPTMULTIPART = 0x02; + + static private $input; + static private $output; + static private $headers; + static private $getparameters; + static private $command; + static private $device; + static private $method; + static private $remoteAddr; + static private $getUser; + static private $devid; + static private $devtype; + static private $authUser; + static private $authDomain; + static private $authPassword; + static private $asProtocolVersion; + static private $policykey; + static private $useragent; + static private $attachmentName; + static private $collectionId; + static private $itemId; + static private $longId; //TODO + static private $occurence; //TODO + static private $saveInSent; + static private $acceptMultipart; + + + /** + * Initializes request data + * + * @access public + * @return + */ + static public function Initialize() { + // try to open stdin & stdout + self::$input = fopen("php://input", "r"); + self::$output = fopen("php://output", "w+"); + + // Parse the standard GET parameters + if(isset($_GET["Cmd"])) + self::$command = self::filterEvilInput($_GET["Cmd"], self::LETTERS_ONLY); + + // getUser is unfiltered, as everything is allowed.. even "/", "\" or ".." + if(isset($_GET["User"])) + self::$getUser = strtolower($_GET["User"]); + if(isset($_GET["DeviceId"])) + self::$devid = self::filterEvilInput($_GET["DeviceId"], self::WORDCHAR_ONLY); + if(isset($_GET["DeviceType"])) + self::$devtype = self::filterEvilInput($_GET["DeviceType"], self::LETTERS_ONLY); + if (isset($_GET["AttachmentName"])) + self::$attachmentName = self::filterEvilInput($_GET["AttachmentName"], self::HEX_EXTENDED); + if (isset($_GET["CollectionId"])) + self::$collectionId = self::filterEvilInput($_GET["CollectionId"], self::HEX_ONLY); + if (isset($_GET["ItemId"])) + self::$itemId = self::filterEvilInput($_GET["ItemId"], self::HEX_ONLY); + if (isset($_GET["SaveInSent"]) && $_GET["SaveInSent"] == "T") + self::$saveInSent = true; + + if(isset($_SERVER["REQUEST_METHOD"])) + self::$method = self::filterEvilInput($_SERVER["REQUEST_METHOD"], self::LETTERS_ONLY); + // TODO check IPv6 addresses + if(isset($_SERVER["REMOTE_ADDR"])) + self::$remoteAddr = self::filterEvilInput($_SERVER["REMOTE_ADDR"], self::NUMBERSDOT_ONLY); + + // in protocol version > 14 mobile send these inputs as encoded query string + if (!isset(self::$command) && !empty($_SERVER['QUERY_STRING']) && Utils::IsBase64String($_SERVER['QUERY_STRING'])) { + $query = Utils::DecodeBase64URI($_SERVER['QUERY_STRING']); + if (!isset(self::$command) && isset($query['Command'])) + self::$command = Utils::GetCommandFromCode($query['Command']); + + if (!isset(self::$getUser) && isset($query[self::COMMANDPARAM_USER])) + self::$getUser = strtolower($query[self::COMMANDPARAM_USER]); + + if (!isset(self::$devid) && isset($query['DevID'])) + self::$devid = self::filterEvilInput($query['DevID'], self::WORDCHAR_ONLY); + + if (!isset(self::$devtype) && isset($query['DevType'])) + self::$devtype = self::filterEvilInput($query['DevType'], self::LETTERS_ONLY); + + if (isset($query['PolKey'])) + self::$policykey = (int) self::filterEvilInput($query['PolKey'], self::NUMBERS_ONLY); + + if (isset($query['ProtVer'])) + self::$asProtocolVersion = self::filterEvilInput($query['ProtVer'], self::NUMBERS_ONLY) / 10; + + if (isset($query[self::COMMANDPARAM_ATTACHMENTNAME])) + self::$attachmentName = self::filterEvilInput($query[self::COMMANDPARAM_ATTACHMENTNAME], self::HEX_EXTENDED); + + if (isset($query[self::COMMANDPARAM_COLLECTIONID])) + self::$collectionId = self::filterEvilInput($query[self::COMMANDPARAM_COLLECTIONID], self::HEX_ONLY); + + if (isset($query[self::COMMANDPARAM_ITEMID])) + self::$itemId = self::filterEvilInput($query[self::COMMANDPARAM_ITEMID], self::HEX_ONLY); + + if (isset($query[self::COMMANDPARAM_OPTIONS]) && (ord($query[self::COMMANDPARAM_OPTIONS]) & self::COMMANDPARAM_OPTIONS_SAVEINSENT)) + self::$saveInSent = true; + + if (isset($query[self::COMMANDPARAM_OPTIONS]) && (ord($query[self::COMMANDPARAM_OPTIONS]) & self::COMMANDPARAM_OPTIONS_ACCEPTMULTIPART)) + self::$acceptMultipart = true; + } + + // in base64 encoded query string user is not necessarily set + if (!isset(self::$getUser) && isset($_SERVER['PHP_AUTH_USER'])) + list(self::$getUser,) = Utils::SplitDomainUser(strtolower($_SERVER['PHP_AUTH_USER'])); + } + + /** + * Reads and processes the request headers + * + * @access public + * @return + */ + static public function ProcessHeaders() { + self::$headers = array_change_key_case(apache_request_headers(), CASE_LOWER); + self::$useragent = (isset(self::$headers["user-agent"]))? self::$headers["user-agent"] : self::UNKNOWN; + if (!isset(self::$asProtocolVersion)) + self::$asProtocolVersion = (isset(self::$headers["ms-asprotocolversion"]))? self::filterEvilInput(self::$headers["ms-asprotocolversion"], self::NUMBERSDOT_ONLY) : ZPush::GetLatestSupportedASVersion(); + + //if policykey is not yet set, try to set it from the header + //the policy key might be set in Request::Initialize from the base64 encoded query + if (!isset(self::$policykey)) { + if (isset(self::$headers["x-ms-policykey"])) + self::$policykey = (int) self::filterEvilInput(self::$headers["x-ms-policykey"], self::NUMBERS_ONLY); + else + self::$policykey = 0; + } + + if (!empty($_SERVER['QUERY_STRING']) && Utils::IsBase64String($_SERVER['QUERY_STRING'])) { + ZLog::Write(LOGLEVEL_DEBUG, "Using data from base64 encoded query string"); + if (isset(self::$policykey)) + self::$headers["x-ms-policykey"] = self::$policykey; + + if (isset(self::$asProtocolVersion)) + self::$headers["ms-asprotocolversion"] = self::$asProtocolVersion; + } + + if (!isset(self::$acceptMultipart) && isset(self::$headers["ms-asacceptmultipart"]) && strtoupper(self::$headers["ms-asacceptmultipart"]) == "T") { + self::$acceptMultipart = true; + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request::ProcessHeaders() ASVersion: %s", self::$asProtocolVersion)); + } + + /** + * Reads and parses the HTTP-Basic-Auth data + * + * @access public + * @return boolean data sent or not + */ + static public function AuthenticationInfo() { + // split username & domain if received as one + if (isset($_SERVER['PHP_AUTH_USER'])) { + list(self::$authUser, self::$authDomain) = Utils::SplitDomainUser($_SERVER['PHP_AUTH_USER']); + self::$authPassword = (isset($_SERVER['PHP_AUTH_PW']))?$_SERVER['PHP_AUTH_PW'] : ""; + } + // authUser & authPassword are unfiltered! + return (self::$authUser != "" && self::$authPassword != ""); + } + + + /**---------------------------------------------------------------------------------------------------------- + * Getter & Checker + */ + + /** + * Returns the input stream + * + * @access public + * @return handle/boolean false if not available + */ + static public function GetInputStream() { + if (isset(self::$input)) + return self::$input; + else + return false; + } + + /** + * Returns the output stream + * + * @access public + * @return handle/boolean false if not available + */ + static public function GetOutputStream() { + if (isset(self::$output)) + return self::$output; + else + return false; + } + + /** + * Returns the request method + * + * @access public + * @return string + */ + static public function GetMethod() { + if (isset(self::$method)) + return self::$method; + else + return self::UNKNOWN; + } + + /** + * Returns the value of the user parameter of the querystring + * + * @access public + * @return string/boolean false if not available + */ + static public function GetGETUser() { + if (isset(self::$getUser)) + return self::$getUser; + else + return self::UNKNOWN; + } + + /** + * Returns the value of the ItemId parameter of the querystring + * + * @access public + * @return string/boolean false if not available + */ + static public function GetGETItemId() { + if (isset(self::$itemId)) + return self::$itemId; + else + return false; + } + + /** + * Returns the value of the CollectionId parameter of the querystring + * + * @access public + * @return string/boolean false if not available + */ + static public function GetGETCollectionId() { + if (isset(self::$collectionId)) + return self::$collectionId; + else + return false; + } + + /** + * Returns if the SaveInSent parameter of the querystring is set + * + * @access public + * @return boolean + */ + static public function GetGETSaveInSent() { + if (isset(self::$saveInSent)) + return self::$saveInSent; + else + return true; + } + + /** + * Returns if the AcceptMultipart parameter of the querystring is set + * + * @access public + * @return boolean + */ + static public function GetGETAcceptMultipart() { + if (isset(self::$acceptMultipart)) + return self::$acceptMultipart; + else + return false; + } + + /** + * Returns the value of the AttachmentName parameter of the querystring + * + * @access public + * @return string/boolean false if not available + */ + static public function GetGETAttachmentName() { + if (isset(self::$attachmentName)) + return self::$attachmentName; + else + return false; + } + + /** + * Returns the authenticated user + * + * @access public + * @return string/boolean false if not available + */ + static public function GetAuthUser() { + if (isset(self::$authUser)) + return self::$authUser; + else + return false; + } + + /** + * Returns the authenticated domain for the user + * + * @access public + * @return string/boolean false if not available + */ + static public function GetAuthDomain() { + if (isset(self::$authDomain)) + return self::$authDomain; + else + return false; + } + + /** + * Returns the transmitted password + * + * @access public + * @return string/boolean false if not available + */ + static public function GetAuthPassword() { + if (isset(self::$authPassword)) + return self::$authPassword; + else + return false; + } + + /** + * Returns the RemoteAddress + * + * @access public + * @return string + */ + static public function GetRemoteAddr() { + if (isset(self::$remoteAddr)) + return self::$remoteAddr; + else + return "UNKNOWN"; + } + + /** + * Returns the command to be executed + * + * @access public + * @return string/boolean false if not available + */ + static public function GetCommand() { + if (isset(self::$command)) + return self::$command; + else + return false; + } + + /** + * Returns the command code which is being executed + * + * @access public + * @return string/boolean false if not available + */ + static public function GetCommandCode() { + if (isset(self::$command)) + return Utils::GetCodeFromCommand(self::$command); + else + return false; + } + + /** + * Returns the device id transmitted + * + * @access public + * @return string/boolean false if not available + */ + static public function GetDeviceID() { + if (isset(self::$devid)) + return self::$devid; + else + return false; + } + + /** + * Returns the device type if transmitted + * + * @access public + * @return string/boolean false if not available + */ + static public function GetDeviceType() { + if (isset(self::$devtype)) + return self::$devtype; + else + return false; + } + + /** + * Returns the value of supported AS protocol from the headers + * + * @access public + * @return string/boolean false if not available + */ + static public function GetProtocolVersion() { + if (isset(self::$asProtocolVersion)) + return self::$asProtocolVersion; + else + return false; + } + + /** + * Returns the user agent sent in the headers + * + * @access public + * @return string/boolean false if not available + */ + static public function GetUserAgent() { + if (isset(self::$useragent)) + return self::$useragent; + else + return self::UNKNOWN; + } + + /** + * Returns policy key sent by the device + * + * @access public + * @return int/boolean false if not available + */ + static public function GetPolicyKey() { + if (isset(self::$policykey)) + return self::$policykey; + else + return false; + } + + /** + * Indicates if a policy key was sent by the device + * + * @access public + * @return boolean + */ + static public function WasPolicyKeySent() { + return isset(self::$headers["x-ms-policykey"]); + } + + /** + * Indicates if Z-Push was called with a POST request + * + * @access public + * @return boolean + */ + static public function IsMethodPOST() { + return (self::$method == "POST"); + } + + /** + * Indicates if Z-Push was called with a GET request + * + * @access public + * @return boolean + */ + static public function IsMethodGET() { + return (self::$method == "GET"); + } + + /** + * Indicates if Z-Push was called with a OPTIONS request + * + * @access public + * @return boolean + */ + static public function IsMethodOPTIONS() { + return (self::$method == "OPTIONS"); + } + + /** + * Sometimes strange device ids are sumbitted + * No device information should be saved when this happens + * + * @access public + * @return boolean false if invalid + */ + static public function IsValidDeviceID() { + if (self::GetDeviceID() === "validate" || self::GetDeviceID() === "webservice") + return false; + else + return true; + } + + /** + * Returns the amount of data sent in this request (from the headers) + * + * @access public + * @return int + */ + static public function GetContentLength() { + return (isset(self::$headers["content-length"]))? (int) self::$headers["content-length"] : 0; + } + + + /**---------------------------------------------------------------------------------------------------------- + * Private stuff + */ + + /** + * Replaces all not allowed characters in a string + * + * @param string $input the input string + * @param int $filter one of the predefined filters: LETTERS_ONLY, HEX_ONLY, WORDCHAR_ONLY, NUMBERS_ONLY, NUMBERSDOT_ONLY + * @param char $replacevalue (opt) a character the filtered characters should be replaced with + * + * @access public + * @return string + */ + static private function filterEvilInput($input, $filter, $replacevalue = '') { + $re = false; + if ($filter == self::LETTERS_ONLY) $re = "/[^A-Za-z]/"; + else if ($filter == self::HEX_ONLY) $re = "/[^A-Fa-f0-9]/"; + else if ($filter == self::WORDCHAR_ONLY) $re = "/[^A-Za-z0-9]/"; + else if ($filter == self::NUMBERS_ONLY) $re = "/[^0-9]/"; + else if ($filter == self::NUMBERSDOT_ONLY) $re = "/[^0-9\.]/"; + else if ($filter == self::HEX_EXTENDED) $re = "/[^A-Fa-f0-9\:]/"; + + return ($re) ? preg_replace($re, $replacevalue, $input) : ''; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/requestprocessor.php b/z-push/lib/request/requestprocessor.php new file mode 100644 index 0000000..607fdb4 --- /dev/null +++ b/z-push/lib/request/requestprocessor.php @@ -0,0 +1,139 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +abstract class RequestProcessor { + static protected $backend; + static protected $deviceManager; + static protected $topCollector; + static protected $decoder; + static protected $encoder; + static protected $userIsAuthenticated; + + /** + * Authenticates the remote user + * The sent HTTP authentication information is used to on Backend->Logon(). + * As second step the GET-User verified by Backend->Setup() for permission check + * Request::GetGETUser() is usually the same as the Request::GetAuthUser(). + * If the GETUser is different from the AuthUser, the AuthUser MUST HAVE admin + * permissions on GETUsers data store. Only then the Setup() will be sucessfull. + * This allows the user 'john' to do operations as user 'joe' if he has sufficient privileges. + * + * @access public + * @return + * @throws AuthenticationRequiredException + */ + static public function Authenticate() { + self::$userIsAuthenticated = false; + + $backend = ZPush::GetBackend(); + if($backend->Logon(Request::GetAuthUser(), Request::GetAuthDomain(), Request::GetAuthPassword()) == false) + throw new AuthenticationRequiredException("Access denied. Username or password incorrect"); + + // mark this request as "authenticated" + self::$userIsAuthenticated = true; + + // check Auth-User's permissions on GETUser's store + if($backend->Setup(Request::GetGETUser(), true) == false) + throw new AuthenticationRequiredException(sprintf("Not enough privileges of '%s' to setup for user '%s': Permission denied", Request::GetAuthUser(), Request::GetGETUser())); + } + + /** + * Indicates if the user was "authenticated" + * + * @access public + * @return boolean + */ + static public function isUserAuthenticated() { + if (!isset(self::$userIsAuthenticated)) + return false; + return self::$userIsAuthenticated; + } + + /** + * Initialize the RequestProcessor + * + * @access public + * @return + */ + static public function Initialize() { + self::$backend = ZPush::GetBackend(); + self::$deviceManager = ZPush::GetDeviceManager(); + self::$topCollector = ZPush::GetTopCollector(); + + if (!ZPush::CommandNeedsPlainInput(Request::GetCommandCode())) + self::$decoder = new WBXMLDecoder(Request::GetInputStream()); + + self::$encoder = new WBXMLEncoder(Request::GetOutputStream(), Request::GetGETAcceptMultipart()); + } + + /** + * Loads the command handler and processes a command sent from the mobile + * + * @access public + * @return boolean + */ + static public function HandleRequest() { + $handler = ZPush::GetRequestHandlerForCommand(Request::GetCommandCode()); + + // TODO handle WBXML exceptions here and print stack + return $handler->Handle(Request::GetCommandCode()); + } + + /** + * Handles a command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + abstract public function Handle($commandCode); +} +?> \ No newline at end of file diff --git a/z-push/lib/request/search.php b/z-push/lib/request/search.php new file mode 100644 index 0000000..3e70c8f --- /dev/null +++ b/z-push/lib/request/search.php @@ -0,0 +1,425 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Search extends RequestProcessor { + + /** + * Handles the Search command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $searchrange = '0'; + $cpo = new ContentParameters(); + + if(!self::$decoder->getElementStartTag(SYNC_SEARCH_SEARCH)) + return false; + + // TODO check: possible to search in other stores? + if(!self::$decoder->getElementStartTag(SYNC_SEARCH_STORE)) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_SEARCH_NAME)) + return false; + $searchname = strtoupper(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + + if(!self::$decoder->getElementStartTag(SYNC_SEARCH_QUERY)) + return false; + + // check if it is a content of an element (= GAL search) + // or a starttag (= mailbox or documentlibrary search) + $searchquery = self::$decoder->getElementContent(); + if($searchquery && !self::$decoder->getElementEndTag()) + return false; + + if ($searchquery === false) { + $cpo->SetSearchName($searchname); + if (self::$decoder->getElementStartTag(SYNC_SEARCH_AND)) { + if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { + $searchfolderid = self::$decoder->getElementContent(); + $cpo->SetSearchFolderid($searchfolderid); + if(!self::$decoder->getElementEndTag()) // SYNC_FOLDERTYPE + return false; + } + + + if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { + $searchclass = self::$decoder->getElementContent(); + $cpo->SetSearchClass($searchclass); + if(!self::$decoder->getElementEndTag()) // SYNC_FOLDERTYPE + return false; + } + + if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { + $searchfolderid = self::$decoder->getElementContent(); + $cpo->SetSearchFolderid($searchfolderid); + if(!self::$decoder->getElementEndTag()) // SYNC_FOLDERTYPE + return false; + } + + if (self::$decoder->getElementStartTag(SYNC_SEARCH_FREETEXT)) { + $searchfreetext = self::$decoder->getElementContent(); + $cpo->SetSearchFreeText($searchfreetext); + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_FREETEXT + return false; + } + + //TODO - review + if (self::$decoder->getElementStartTag(SYNC_SEARCH_GREATERTHAN)) { + if(self::$decoder->getElementStartTag(SYNC_POOMMAIL_DATERECEIVED)) { + $datereceivedgreater = true; + if (($dam = self::$decoder->getElementContent()) !== false) { + $datereceivedgreater = true; + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + $cpo->SetSearchDateReceivedGreater($datereceivedgreater); + } + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_VALUE)) { + $searchvalue = self::$decoder->getElementContent(); + $cpo->SetSearchValueGreater($searchvalue); + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_VALUE + return false; + } + + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_GREATERTHAN + return false; + } + + if (self::$decoder->getElementStartTag(SYNC_SEARCH_LESSTHAN)) { + if(self::$decoder->getElementStartTag(SYNC_POOMMAIL_DATERECEIVED)) { + $datereceivedless = true; + if (($dam = self::$decoder->getElementContent()) !== false) { + $datereceivedless = true; + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + $cpo->SetSearchDateReceivedLess($datereceivedless); + } + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_VALUE)) { + $searchvalue = self::$decoder->getElementContent(); + $cpo->SetSearchValueLess($searchvalue); + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_VALUE + return false; + } + + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_LESSTHAN + return false; + } + + if (self::$decoder->getElementStartTag(SYNC_SEARCH_FREETEXT)) { + $searchfreetext = self::$decoder->getElementContent(); + $cpo->SetSearchFreeText($searchfreetext); + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_FREETEXT + return false; + } + + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_AND + return false; + } + + if(!self::$decoder->getElementEndTag()) // SYNC_SEARCH_QUERY + return false; + + } + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_OPTIONS)) { + while(1) { + if(self::$decoder->getElementStartTag(SYNC_SEARCH_RANGE)) { + $searchrange = self::$decoder->getElementContent(); + $cpo->SetSearchRange($searchrange); + if(!self::$decoder->getElementEndTag()) + return false; + } + + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_REBUILDRESULTS)) { + $rebuildresults = true; + if (($dam = self::$decoder->getElementContent()) !== false) { + $rebuildresults = true; + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + $cpo->SetSearchRebuildResults($rebuildresults); + } + + if(self::$decoder->getElementStartTag(SYNC_SEARCH_DEEPTRAVERSAL)) { + $deeptraversal = true; + if (($dam = self::$decoder->getElementContent()) !== false) { + $deeptraversal = true; + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + $cpo->SetSearchDeepTraversal($deeptraversal); + } + + if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { + $cpo->SetMimeSupport(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + //TODO body preferences + while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { + $bptype = self::$decoder->getElementContent(); + $cpo->BodyPreference($bptype); + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { + $cpo->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { + $cpo->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { + $cpo->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; + } + + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); + break; + } + } + } + if(!self::$decoder->getElementEndTag()) //store + return false; + + if(!self::$decoder->getElementEndTag()) //search + return false; + + // get SearchProvider + $searchprovider = ZPush::GetSearchProvider(); + $status = SYNC_SEARCHSTATUS_SUCCESS; + $rows = array(); + + // TODO support other searches + if ($searchprovider->SupportsType($searchname)) { + $storestatus = SYNC_SEARCHSTATUS_STORE_SUCCESS; + try { + if ($searchname == ISearchProvider::SEARCH_GAL) { + //get search results from the searchprovider + $rows = $searchprovider->GetGALSearchResults($searchquery, $searchrange); + } + elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) { + $rows = $searchprovider->GetMailboxSearchResults($cpo); + } + } + catch (StatusException $stex) { + $storestatus = $stex->getCode(); + } + } + else { + $rows = array('searchtotal' => 0); + $status = SYNC_SEARCHSTATUS_SERVERERROR; + ZLog::Write(LOGLEVEL_WARN, sprintf("Searchtype '%s' is not supported.", $searchname)); + self::$topCollector->AnnounceInformation(sprintf("Unsupported type '%s''", $searchname), true); + } + $searchprovider->Disconnect(); + + self::$topCollector->AnnounceInformation(sprintf("'%s' search found %d results", $searchname, $rows['searchtotal']), true); + + self::$encoder->startWBXML(); + self::$encoder->startTag(SYNC_SEARCH_SEARCH); + + self::$encoder->startTag(SYNC_SEARCH_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + if ($status == SYNC_SEARCHSTATUS_SUCCESS) { + self::$encoder->startTag(SYNC_SEARCH_RESPONSE); + self::$encoder->startTag(SYNC_SEARCH_STORE); + + self::$encoder->startTag(SYNC_SEARCH_STATUS); + self::$encoder->content($storestatus); + self::$encoder->endTag(); + + if (isset($rows['range'])) { + $searchrange = $rows['range']; + unset($rows['range']); + } + if (isset($rows['searchtotal'])) { + $searchtotal = $rows['searchtotal']; + unset($rows['searchtotal']); + } + if ($searchname == ISearchProvider::SEARCH_GAL) { + if (is_array($rows) && !empty($rows)) { + foreach ($rows as $u) { + self::$encoder->startTag(SYNC_SEARCH_RESULT); + self::$encoder->startTag(SYNC_SEARCH_PROPERTIES); + + self::$encoder->startTag(SYNC_GAL_DISPLAYNAME); + self::$encoder->content((isset($u[SYNC_GAL_DISPLAYNAME]))?$u[SYNC_GAL_DISPLAYNAME]:"No name"); + self::$encoder->endTag(); + + if (isset($u[SYNC_GAL_PHONE])) { + self::$encoder->startTag(SYNC_GAL_PHONE); + self::$encoder->content($u[SYNC_GAL_PHONE]); + self::$encoder->endTag(); + } + + if (isset($u[SYNC_GAL_OFFICE])) { + self::$encoder->startTag(SYNC_GAL_OFFICE); + self::$encoder->content($u[SYNC_GAL_OFFICE]); + self::$encoder->endTag(); + } + + if (isset($u[SYNC_GAL_TITLE])) { + self::$encoder->startTag(SYNC_GAL_TITLE); + self::$encoder->content($u[SYNC_GAL_TITLE]); + self::$encoder->endTag(); + } + + if (isset($u[SYNC_GAL_COMPANY])) { + self::$encoder->startTag(SYNC_GAL_COMPANY); + self::$encoder->content($u[SYNC_GAL_COMPANY]); + self::$encoder->endTag(); + } + + if (isset($u[SYNC_GAL_ALIAS])) { + self::$encoder->startTag(SYNC_GAL_ALIAS); + self::$encoder->content($u[SYNC_GAL_ALIAS]); + self::$encoder->endTag(); + } + + // Always send the firstname, even empty. Nokia needs this to display the entry + self::$encoder->startTag(SYNC_GAL_FIRSTNAME); + self::$encoder->content((isset($u[SYNC_GAL_FIRSTNAME]))?$u[SYNC_GAL_FIRSTNAME]:""); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_GAL_LASTNAME); + self::$encoder->content((isset($u[SYNC_GAL_LASTNAME]))?$u[SYNC_GAL_LASTNAME]:"No name"); + self::$encoder->endTag(); + + if (isset($u[SYNC_GAL_HOMEPHONE])) { + self::$encoder->startTag(SYNC_GAL_HOMEPHONE); + self::$encoder->content($u[SYNC_GAL_HOMEPHONE]); + self::$encoder->endTag(); + } + + if (isset($u[SYNC_GAL_MOBILEPHONE])) { + self::$encoder->startTag(SYNC_GAL_MOBILEPHONE); + self::$encoder->content($u[SYNC_GAL_MOBILEPHONE]); + self::$encoder->endTag(); + } + + self::$encoder->startTag(SYNC_GAL_EMAILADDRESS); + self::$encoder->content((isset($u[SYNC_GAL_EMAILADDRESS]))?$u[SYNC_GAL_EMAILADDRESS]:""); + self::$encoder->endTag(); + + self::$encoder->endTag();//result + self::$encoder->endTag();//properties + } + } + } + elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) { + foreach ($rows as $u) { + self::$encoder->startTag(SYNC_SEARCH_RESULT); + self::$encoder->startTag(SYNC_FOLDERTYPE); + self::$encoder->content($u['class']); + self::$encoder->endTag(); + self::$encoder->startTag(SYNC_SEARCH_LONGID); + self::$encoder->content($u['longid']); + self::$encoder->endTag(); + self::$encoder->startTag(SYNC_FOLDERID); + self::$encoder->content($u['folderid']); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_SEARCH_PROPERTIES); + $tmp = explode(":", $u['longid']); + $message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo); + $message->Encode(self::$encoder); + + self::$encoder->endTag();//result + self::$encoder->endTag();//properties + } + } + // it seems that android 4 requires range and searchtotal + // or it won't display the search results + if (isset($searchrange)) { + self::$encoder->startTag(SYNC_SEARCH_RANGE); + self::$encoder->content($searchrange); + self::$encoder->endTag(); + } + if (isset($searchtotal) && $searchtotal > 0) { + self::$encoder->startTag(SYNC_SEARCH_TOTAL); + self::$encoder->content($searchtotal); + self::$encoder->endTag(); + } + + self::$encoder->endTag();//store + self::$encoder->endTag();//response + } + self::$encoder->endTag();//search + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/sendmail.php b/z-push/lib/request/sendmail.php new file mode 100644 index 0000000..735d701 --- /dev/null +++ b/z-push/lib/request/sendmail.php @@ -0,0 +1,139 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class SendMail extends RequestProcessor { + + /** + * Handles the SendMail, SmartReply and SmartForward command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + $status = SYNC_COMMONSTATUS_SUCCESS; + $sm = new SyncSendMail(); + + $reply = $forward = $parent = $sendmail = $smartreply = $smartforward = false; + if (Request::GetGETCollectionId()) + $parent = Request::GetGETCollectionId(); + if ($commandCode == ZPush::COMMAND_SMARTFORWARD) + $forward = Request::GetGETItemId(); + else if ($commandCode == ZPush::COMMAND_SMARTREPLY) + $reply = Request::GetGETItemId(); + + if (self::$decoder->IsWBXML()) { + $el = self::$decoder->getElement(); + + if($el[EN_TYPE] != EN_TYPE_STARTTAG) + return false; + + + if($el[EN_TAG] == SYNC_COMPOSEMAIL_SENDMAIL) + $sendmail = true; + else if($el[EN_TAG] == SYNC_COMPOSEMAIL_SMARTREPLY) + $smartreply = true; + else if($el[EN_TAG] == SYNC_COMPOSEMAIL_SMARTFORWARD) + $smartforward = true; + + if(!$sendmail && !$smartreply && !$smartforward) + return false; + + $sm->Decode(self::$decoder); + } + else { + $sm->mime = self::$decoder->GetPlainInputStream(); + // no wbxml output is provided, only a http OK + $sm->saveinsent = Request::GetGETSaveInSent(); + } + // Check if it is a reply or forward. Two cases are possible: + // 1. Either $smartreply or $smartforward are set after reading WBXML + // 2. Either $reply or $forward are set after geting the request parameters + if ($reply || $smartreply || $forward || $smartforward) { + // If the mobile sends an email in WBXML data the variables below + // should be set. If it is a RFC822 message, get the reply/forward message id + // from the request as they are always available there + if (!isset($sm->source)) $sm->source = new SyncSendMailSource(); + if (!isset($sm->source->itemid)) $sm->source->itemid = Request::GetGETItemId(); + if (!isset($sm->source->folderid)) $sm->source->folderid = Request::GetGETCollectionId(); + + // replyflag and forward flags are actually only for the correct icon. + // Even if they are a part of SyncSendMail object, they won't be streamed. + if ($smartreply || $reply) + $sm->replyflag = true; + else + $sm->forwardflag = true; + + if (!isset($sm->source->folderid)) + ZLog::Write(LOGLEVEL_ERROR, sprintf("No parent folder id while replying or forwarding message:'%s'", (($reply) ? $reply : $forward))); + } + + self::$topCollector->AnnounceInformation(sprintf("Sending email with %d bytes", strlen($sm->mime)), true); + + try { + $status = self::$backend->SendMail($sm); + } + catch (StatusException $se) { + $status = $se->getCode(); + $statusMessage = $se->getMessage(); + } + + if ($status != SYNC_COMMONSTATUS_SUCCESS) { + if (self::$decoder->IsWBXML()) { + // TODO check no WBXML on SmartReply and SmartForward + self::$encoder->StartWBXML(); + self::$encoder->startTag(SYNC_COMPOSEMAIL_SENDMAIL); + self::$encoder->startTag(SYNC_COMPOSEMAIL_STATUS); + self::$encoder->content($status); //TODO return the correct status + self::$encoder->endTag(); + self::$encoder->endTag(); + } + else + throw new HTTPReturnCodeException($statusMessage, HTTP_CODE_500, null, LOGLEVEL_WARN); + } + + return $status; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/settings.php b/z-push/lib/request/settings.php new file mode 100644 index 0000000..55346bf --- /dev/null +++ b/z-push/lib/request/settings.php @@ -0,0 +1,233 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Settings extends RequestProcessor { + + /** + * Handles the Settings command + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + if (!self::$decoder->getElementStartTag(SYNC_SETTINGS_SETTINGS)) + return false; + + //save the request parameters + $request = array(); + + // Loop through properties. Possible are: + // - Out of office + // - DevicePassword + // - DeviceInformation + // - UserInformation + // Each of them should only be once per request. Each property must be processed in order. + while (1) { + $propertyName = ""; + if (self::$decoder->getElementStartTag(SYNC_SETTINGS_OOF)) { + $propertyName = SYNC_SETTINGS_OOF; + } + if (self::$decoder->getElementStartTag(SYNC_SETTINGS_DEVICEPW)) { + $propertyName = SYNC_SETTINGS_DEVICEPW; + } + if (self::$decoder->getElementStartTag(SYNC_SETTINGS_DEVICEINFORMATION)) { + $propertyName = SYNC_SETTINGS_DEVICEINFORMATION; + } + if (self::$decoder->getElementStartTag(SYNC_SETTINGS_USERINFORMATION)) { + $propertyName = SYNC_SETTINGS_USERINFORMATION; + } + //TODO - check if it is necessary + //no property name available - break + if (!$propertyName) + break; + + //the property name is followed by either get or set + if (self::$decoder->getElementStartTag(SYNC_SETTINGS_GET)) { + //get is only available for OOF and user information + switch ($propertyName) { + case SYNC_SETTINGS_OOF: + $oofGet = new SyncOOF(); + $oofGet->Decode(self::$decoder); + if(!self::$decoder->getElementEndTag()) + return false; // SYNC_SETTINGS_GET + break; + + case SYNC_SETTINGS_USERINFORMATION: + $userInformation = new SyncUserInformation(); + break; + + default: + //TODO: a special status code needed? + ZLog::Write(LOGLEVEL_WARN, sprintf ("This property ('%s') is not allowed to use get in request", $propertyName)); + } + } + elseif (self::$decoder->getElementStartTag(SYNC_SETTINGS_SET)) { + //set is available for OOF, device password and device information + switch ($propertyName) { + case SYNC_SETTINGS_OOF: + $oofSet = new SyncOOF(); + $oofSet->Decode(self::$decoder); + //TODO check - do it after while(1) finished? + break; + + case SYNC_SETTINGS_DEVICEPW: + //TODO device password + $devicepassword = new SyncDevicePassword(); + $devicepassword->Decode(self::$decoder); + break; + + case SYNC_SETTINGS_DEVICEINFORMATION: + $deviceinformation = new SyncDeviceInformation(); + $deviceinformation->Decode(self::$decoder); + self::$deviceManager->SaveDeviceInformation($deviceinformation); + break; + + default: + //TODO: a special status code needed? + ZLog::Write(LOGLEVEL_WARN, sprintf ("This property ('%s') is not allowed to use set in request", $propertyName)); + } + + if(!self::$decoder->getElementEndTag()) + return false; // SYNC_SETTINGS_SET + } + else { + ZLog::Write(LOGLEVEL_WARN, sprintf("Neither get nor set found for property '%s'", $propertyName)); + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; // SYNC_SETTINGS_OOF or SYNC_SETTINGS_DEVICEPW or SYNC_SETTINGS_DEVICEINFORMATION or SYNC_SETTINGS_USERINFORMATION + + //break if it reached the endtag + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); //SYNC_SETTINGS_SETTINGS + break; + } + } + + $status = SYNC_SETTINGSSTATUS_SUCCESS; + + //TODO put it in try catch block + //TODO implement Settings in the backend + //TODO save device information in device manager + //TODO status handling +// $data = self::$backend->Settings($request); + + self::$encoder->startWBXML(); + self::$encoder->startTag(SYNC_SETTINGS_SETTINGS); + + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + + //get oof settings + if (isset($oofGet)) { + $oofGet = self::$backend->Settings($oofGet); + self::$encoder->startTag(SYNC_SETTINGS_OOF); + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($oofGet->Status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + + self::$encoder->startTag(SYNC_SETTINGS_GET); + $oofGet->Encode(self::$encoder); + self::$encoder->endTag(); //SYNC_SETTINGS_GET + self::$encoder->endTag(); //SYNC_SETTINGS_OOF + } + + //get user information + //TODO none email address found + if (isset($userInformation)) { + self::$backend->Settings($userInformation); + self::$encoder->startTag(SYNC_SETTINGS_USERINFORMATION); + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($userInformation->Status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + + self::$encoder->startTag(SYNC_SETTINGS_GET); + $userInformation->Encode(self::$encoder); + self::$encoder->endTag(); //SYNC_SETTINGS_GET + self::$encoder->endTag(); //SYNC_SETTINGS_USERINFORMATION + } + + //set out of office + if (isset($oofSet)) { + $oofSet = self::$backend->Settings($oofSet); + self::$encoder->startTag(SYNC_SETTINGS_OOF); + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($oofSet->Status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + self::$encoder->endTag(); //SYNC_SETTINGS_OOF + } + + //set device passwort + if (isset($devicepassword)) { + self::$encoder->startTag(SYNC_SETTINGS_DEVICEPW); + self::$encoder->startTag(SYNC_SETTINGS_SET); + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($devicepassword->Status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + self::$encoder->endTag(); //SYNC_SETTINGS_SET + self::$encoder->endTag(); //SYNC_SETTINGS_DEVICEPW + } + + //set device information + if (isset($deviceinformation)) { + self::$encoder->startTag(SYNC_SETTINGS_DEVICEINFORMATION); + self::$encoder->startTag(SYNC_SETTINGS_SET); + self::$encoder->startTag(SYNC_SETTINGS_STATUS); + self::$encoder->content($deviceinformation->Status); + self::$encoder->endTag(); //SYNC_SETTINGS_STATUS + self::$encoder->endTag(); //SYNC_SETTINGS_SET + self::$encoder->endTag(); //SYNC_SETTINGS_DEVICEINFORMATION + } + + + self::$encoder->endTag(); //SYNC_SETTINGS_SETTINGS + + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/request/sync.php b/z-push/lib/request/sync.php new file mode 100644 index 0000000..2f879f3 --- /dev/null +++ b/z-push/lib/request/sync.php @@ -0,0 +1,1170 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Sync extends RequestProcessor { + // Ignored SMS identifier + const ZPUSHIGNORESMS = "ZPISMS"; + private $importer; + + /** + * Handles the Sync command + * Performs the synchronization of messages + * + * @param int $commandCode + * + * @access public + * @return boolean + */ + public function Handle($commandCode) { + // Contains all requested folders (containers) + $sc = new SyncCollections(); + $status = SYNC_STATUS_SUCCESS; + $wbxmlproblem = false; + $emptysync = false; + + // Start Synchronize + if(self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { + + // AS 1.0 sends version information in WBXML + if(self::$decoder->getElementStartTag(SYNC_VERSION)) { + $sync_version = self::$decoder->getElementContent(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version)); + if(!self::$decoder->getElementEndTag()) + return false; + } + + // Synching specified folders + // Android still sends heartbeat sync even if all syncfolders are disabled. + // Check if Folders tag is empty () and only sync if there are + // some folders in the request. See ZP-172 + $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS); + if(isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) { + while(self::$decoder->getElementStartTag(SYNC_FOLDER)) { + $actiondata = array(); + $actiondata["requested"] = true; + $actiondata["clientids"] = array(); + $actiondata["modifyids"] = array(); + $actiondata["removeids"] = array(); + $actiondata["fetchids"] = array(); + $actiondata["statusids"] = array(); + + // read class, synckey and folderid without SyncParameters Object for now + $class = $synckey = $folderid = false; + + //for AS versions < 2.5 + if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { + $class = self::$decoder->getElementContent(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class)); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + // SyncKey + if(self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { + $synckey = "0"; + if (($synckey = self::$decoder->getElementContent()) !== false) { + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + } + else + return false; + + // FolderId + if(self::$decoder->getElementStartTag(SYNC_FOLDERID)) { + $folderid = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() + if (! $folderid && $class) { + $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class); + } + + // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update + try { + $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid); + + // TODO remove resync of folders for < Z-Push 2 beta4 users + // this forces a resync of all states previous to Z-Push 2 beta4 + if (! $spa instanceof SyncParameters) + throw new StateInvalidException("Saved state are not of type SyncParameters"); + + // new/resync requested + if ($synckey == "0") + $spa->RemoveSyncKey(); + else if ($synckey !== false) + $spa->SetSyncKey($synckey); + } + catch (StateInvalidException $stie) { + $spa = new SyncParameters(); + $status = SYNC_STATUS_INVALIDSYNCKEY; + self::$topCollector->AnnounceInformation("State invalid - Resync folder", true); + self::$deviceManager->ForceFolderResync($folderid); + } + + // update folderid.. this might be a new object + $spa->SetFolderId($folderid); + + if ($class !== false) + $spa->SetContentClass($class); + + // Get class for as versions >= 12.0 + if (! $spa->HasContentClass()) { + try { + $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId())); + } + catch (NoHierarchyCacheAvailableException $nhca) { + $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; + self::$deviceManager->ForceFullResync(); + } + } + + // done basic SPA initialization/loading -> add to SyncCollection + $sc->AddCollection($spa); + $sc->AddParameter($spa, "requested", true); + + if ($spa->HasContentClass()) + self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), true); + else + ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache."); + + // SUPPORTED properties + if(self::$decoder->getElementStartTag(SYNC_SUPPORTED)) { + $supfields = array(); + while(1) { + $el = self::$decoder->getElement(); + + if($el[EN_TYPE] == EN_TYPE_ENDTAG) + break; + else + $supfields[] = $el[EN_TAG]; + } + self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields); + } + + // Deletes as moves can be an empty tag as well as have value + if(self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) { + $spa->SetDeletesAsMoves(true); + if (($dam = self::$decoder->getElementContent()) !== false) { + $spa->SetDeletesAsMoves((boolean)$dam); + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + } + + // Get changes can be an empty tag as well as have value + // code block partly contributed by dw2412 + if(self::$decoder->getElementStartTag(SYNC_GETCHANGES)) { + $sc->AddParameter($spa, "getchanges", true); + if (($gc = self::$decoder->getElementContent()) !== false) { + $sc->AddParameter($spa, "getchanges", $gc); + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + } + + if(self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { + $spa->SetWindowSize(self::$decoder->getElementContent()); + + // also announce the currently requested window size to the DeviceManager + self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize()); + + if(!self::$decoder->getElementEndTag()) + return false; + } + + // conversation mode requested + if(self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { + $spa->SetConversationMode(true); + if(($conversationmode = self::$decoder->getElementContent()) !== false) { + $spa->SetConversationMode((boolean)$conversationmode); + if(!self::$decoder->getElementEndTag()) + return false; + } + } + + // Do not truncate by default + $spa->SetTruncation(SYNC_TRUNCATION_ALL); + + while(self::$decoder->getElementStartTag(SYNC_OPTIONS)) { + $firstOption = true; + while(1) { + // foldertype definition + if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { + $foldertype = self::$decoder->getElementContent(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype)); + + // switch the foldertype for the next options + $spa->UseCPO($foldertype); + + // set to synchronize all changes. The mobile could overwrite this value + $spa->SetFilterType(SYNC_FILTERTYPE_ALL); + + if(!self::$decoder->getElementEndTag()) + return false; + } + // if no foldertype is defined, use default cpo + else if ($firstOption){ + $spa->UseCPO(); + // set to synchronize all changes. The mobile could overwrite this value + $spa->SetFilterType(SYNC_FILTERTYPE_ALL); + } + $firstOption = false; + + if(self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { + $spa->SetFilterType(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + if(self::$decoder->getElementStartTag(SYNC_TRUNCATION)) { + $spa->SetTruncation(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + if(self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { + $spa->SetRTFTruncation(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { + $spa->SetMimeSupport(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { + $spa->SetMimeTruncation(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_CONFLICT)) { + $spa->SetConflict(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { + $bptype = self::$decoder->getElementContent(); + $spa->BodyPreference($bptype); + if(!self::$decoder->getElementEndTag()) { + return false; + } + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { + $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { + $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { + $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) + return false; + } + + if(!self::$decoder->getElementEndTag()) + return false; + } + + $e = self::$decoder->peek(); + if($e[EN_TYPE] == EN_TYPE_ENDTAG) { + self::$decoder->getElementEndTag(); + break; + } + } + } + + // limit items to be synchronized to the mobiles if configured + if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && + (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX)); + $spa->SetFilterType(SYNC_FILTERTIME_MAX); + } + + // set default conflict behavior from config if the device doesn't send a conflict resolution parameter + if (! $spa->HasConflict()) { + $spa->SetConflict(SYNC_CONFLICT_DEFAULT); + } + + // Check if the hierarchycache is available. If not, trigger a HierarchySync + if (self::$deviceManager->IsHierarchySyncRequired()) { + $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; + ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device"); + } + + if(self::$decoder->getElementStartTag(SYNC_PERFORM)) { + // We can not proceed here as the content class is unknown + if ($status != SYNC_STATUS_SUCCESS) { + ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem."); + $wbxmlproblem = true; + break; + } + + $performaction = true; + + // unset the importer + $this->importer = false; + + $nchanges = 0; + while(1) { + // ADD, MODIFY, REMOVE or FETCH + $element = self::$decoder->getElement(); + + if($element[EN_TYPE] != EN_TYPE_STARTTAG) { + self::$decoder->ungetElement($element); + break; + } + + if ($status == SYNC_STATUS_SUCCESS) + $nchanges++; + + // Foldertype sent when synching SMS + if(self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { + $foldertype = self::$decoder->getElementContent(); + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype)); + + if(!self::$decoder->getElementEndTag()) + return false; + } + else + $foldertype = false; + + if(self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) { + $serverid = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) // end serverid + return false; + } + else + $serverid = false; + + if(self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { + $clientid = self::$decoder->getElementContent(); + + if(!self::$decoder->getElementEndTag()) // end clientid + return false; + } + else + $clientid = false; + + // Get the SyncMessage if sent + if(self::$decoder->getElementStartTag(SYNC_DATA)) { + $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()); + $message->Decode(self::$decoder); + + // set Ghosted fields + $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId())); + if(!self::$decoder->getElementEndTag()) // end applicationdata + return false; + } + else + $message = false; + + switch($element[EN_TAG]) { + case SYNC_FETCH: + array_push($actiondata["fetchids"], $serverid); + break; + default: + // get the importer + if ($this->importer == false) + $status = $this->getImporter($sc, $spa, $actiondata); + + if ($status == SYNC_STATUS_SUCCESS) + $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges); + else + ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem."); + + break; + } + + if ($actiondata["fetchids"]) + self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges),true); + else + self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges),($nchanges>0)?true:false); + + if(!self::$decoder->getElementEndTag()) // end add/change/delete/move + return false; + } + + if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) { + ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges)); + try { + // Save the updated state, which is used for the exporter later + $sc->AddParameter($spa, "state", $this->importer->GetState()); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + } + + if(!self::$decoder->getElementEndTag()) // end PERFORM + return false; + } + + // save the failsave state + if (!empty($actiondata["statusids"])) { + unset($actiondata["failstate"]); + $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state"); + self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata); + } + + // save actiondata + $sc->AddParameter($spa, "actiondata", $actiondata); + + if(!self::$decoder->getElementEndTag()) // end collection + return false; + + // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes + if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) + $sc->AddParameter($spa, "getchanges", true); + } // END FOLDER + + if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end collections + return false; + } // end FOLDERS + + if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) { + $hbinterval = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) // SYNC_HEARTBEATINTERVAL + return false; + } + + if (self::$decoder->getElementStartTag(SYNC_WAIT)) { + $wait = self::$decoder->getElementContent(); + if(!self::$decoder->getElementEndTag()) // SYNC_WAIT + return false; + + // internally the heartbeat interval and the wait time are the same + // heartbeat is in seconds, wait in minutes + $hbinterval = $wait * 60; + } + + if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) { + $sc->SetGlobalWindowSize(self::$decoder->getElementContent()); + if(!self::$decoder->getElementEndTag()) // SYNC_WINDOWSIZE + return false; + } + + if(self::$decoder->getElementStartTag(SYNC_PARTIAL)) + $partial = true; + else + $partial = false; + + if(!$wbxmlproblem && !self::$decoder->getElementEndTag()) // end sync + return false; + } + // we did not receive a SYNCHRONIZE block - assume empty sync + else { + $emptysync = true; + } + // END SYNCHRONIZE + + // check heartbeat/wait time + if (isset($hbinterval)) { + if ($hbinterval < 60 || $hbinterval > 3540) { + $status = SYNC_STATUS_INVALIDWAITORHBVALUE; + ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval)); + } + } + + // Partial & Empty Syncs need saved data to proceed with synchronization + if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true) ) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders.")); + + // Load all collections - do not overwrite existing (received!), laod states and check permissions + try { + $sc->LoadAllCollections(false, true, true); + } + catch (StateNotFoundException $snfex) { + $status = SYNC_STATUS_INVALIDSYNCKEY; + self::$topCollector->AnnounceInformation("StateNotFoundException", true); + } + catch (StatusException $stex) { + $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; + self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); + } + + // update a few values + foreach($sc as $folderid => $spa) { + // manually set getchanges parameter for this collection + $sc->AddParameter($spa, "getchanges", true); + + // set new global windowsize without marking the SPA as changed + if ($sc->GetGlobalWindowSize()) + $spa->SetWindowSize($sc->GetGlobalWindowSize(), false); + + // announce WindowSize to DeviceManager + self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize()); + } + if (!$sc->HasCollections()) + $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE; + } + + // HEARTBEAT & Empty sync + if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) { + $interval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 30; + + if (isset($hbinterval)) + $sc->SetLifetime($hbinterval); + + // states are lazy loaded - we have to make sure that they are there! + foreach($sc as $folderid => $spa) { + $fad = array(); + // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS + // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY + $loadstatus = $this->loadStates($sc, $spa, $fad); + } + + if ($loadstatus = SYNC_STATUS_SUCCESS) { + $foundchanges = false; + + // wait for changes + try { + // if doing an empty sync, check only once for changes + if ($emptysync) { + $foundchanges = $sc->CountChanges(); + } + // wait for changes + else { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode")); + $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval); + } + } + catch (StatusException $stex) { + $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED; + self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); + } + + // in case of an empty sync with no changes, we can reply with an empty response + if ($emptysync && !$foundchanges){ + ZLog::Write(LOGLEVEL_DEBUG, "No changes found for empty sync. Replying with empty response"); + return true; + } + + if ($foundchanges) { + foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) { + // check if there were other sync requests for a folder during the heartbeat + $spa = $sc->GetCollection($folderid); + if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid)); + $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID; + } + else + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid)); + } + } + } + } + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output")); + + // Start the output + self::$encoder->startWBXML(); + self::$encoder->startTag(SYNC_SYNCHRONIZE); + { + // global status + // SYNC_COMMONSTATUS_* start with values from 101 + if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) { + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + } + else { + self::$encoder->startTag(SYNC_FOLDERS); + { + foreach($sc as $folderid => $spa) { + // get actiondata + $actiondata = $sc->GetParameter($spa, "actiondata"); + + if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection.")); + continue; + } + + if (! $sc->GetParameter($spa, "requested")) + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId())); + + // initialize exporter to get changecount + $changecount = 0; + if (isset($exporter)) + unset($exporter); + + // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again + if($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || ! $spa->HasSyncKey())) { + + //make sure the states are loaded + $status = $this->loadStates($sc, $spa, $actiondata); + + if($status == SYNC_STATUS_SUCCESS) { + try { + // Use the state from the importer, as changes may have already happened + $exporter = self::$backend->GetExporter($spa->GetFolderId()); + + if ($exporter === false) + throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + try { + // Stream the messages directly to the PDA + $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass())); + + if ($exporter !== false) { + $exporter->Config($sc->GetParameter($spa, "state")); + $exporter->ConfigContentParameters($spa->GetCPO()); + $exporter->InitializeExporter($streamimporter); + + $changecount = $exporter->GetChangeCount(); + } + } + catch (StatusException $stex) { + if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) + $status = SYNC_STATUS_INVALIDSYNCKEY; + else + $status = $stex->getCode(); + } + + if (! $spa->HasSyncKey()) + self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), true); + else if ($status != SYNC_STATUS_SUCCESS) + self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); + } + } + + if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) { + ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output."); + continue; + } + + // Get a new sync key to output to the client if any changes have been send or will are available + if (!empty($actiondata["modifyids"]) || + !empty($actiondata["clientids"]) || + !empty($actiondata["removeids"]) || + $changecount > 0 || (! $spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS)) + $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey())); + + self::$encoder->startTag(SYNC_FOLDER); + + if($spa->HasContentClass()) { + self::$encoder->startTag(SYNC_FOLDERTYPE); + self::$encoder->content($spa->GetContentClass()); + self::$encoder->endTag(); + } + + self::$encoder->startTag(SYNC_SYNCKEY); + if($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) + self::$encoder->content($spa->GetNewSyncKey()); + else + self::$encoder->content($spa->GetSyncKey()); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_FOLDERID); + self::$encoder->content($spa->GetFolderId()); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content($status); + self::$encoder->endTag(); + + // announce failing status to the process loop detection + if ($status !== SYNC_STATUS_SUCCESS) + self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status); + + // Output IDs and status for incoming items & requests + if($status == SYNC_STATUS_SUCCESS && ( + !empty($actiondata["clientids"]) || + !empty($actiondata["modifyids"]) || + !empty($actiondata["removeids"]) || + !empty($actiondata["fetchids"]) )) { + + self::$encoder->startTag(SYNC_REPLIES); + // output result of all new incoming items + foreach($actiondata["clientids"] as $clientid => $serverid) { + self::$encoder->startTag(SYNC_ADD); + self::$encoder->startTag(SYNC_CLIENTENTRYID); + self::$encoder->content($clientid); + self::$encoder->endTag(); + if ($serverid) { + self::$encoder->startTag(SYNC_SERVERENTRYID); + self::$encoder->content($serverid); + self::$encoder->endTag(); + } + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content((isset($actiondata["statusids"][$clientid])?$actiondata["statusids"][$clientid]:SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR)); + self::$encoder->endTag(); + self::$encoder->endTag(); + } + + // loop through modify operations which were not a success, send status + foreach($actiondata["modifyids"] as $serverid) { + if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { + self::$encoder->startTag(SYNC_MODIFY); + self::$encoder->startTag(SYNC_SERVERENTRYID); + self::$encoder->content($serverid); + self::$encoder->endTag(); + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content($actiondata["statusids"][$serverid]); + self::$encoder->endTag(); + self::$encoder->endTag(); + } + } + + // loop through remove operations which were not a success, send status + foreach($actiondata["removeids"] as $serverid) { + if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) { + self::$encoder->startTag(SYNC_REMOVE); + self::$encoder->startTag(SYNC_SERVERENTRYID); + self::$encoder->content($serverid); + self::$encoder->endTag(); + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content($actiondata["statusids"][$serverid]); + self::$encoder->endTag(); + self::$encoder->endTag(); + } + } + + if (!empty($actiondata["fetchids"])) + self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true); + + foreach($actiondata["fetchids"] as $id) { + $data = false; + try { + $fetchstatus = SYNC_STATUS_SUCCESS; + + // if this is an additional folder the backend has to be setup correctly + if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) + throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND); + + $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO()); + + // check if the message is broken + if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager.", $id)); + $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; + } + } + catch (StatusException $stex) { + $fetchstatus = $stex->getCode(); + } + + self::$encoder->startTag(SYNC_FETCH); + self::$encoder->startTag(SYNC_SERVERENTRYID); + self::$encoder->content($id); + self::$encoder->endTag(); + + self::$encoder->startTag(SYNC_STATUS); + self::$encoder->content($fetchstatus); + self::$encoder->endTag(); + + if($data !== false && $status == SYNC_STATUS_SUCCESS) { + self::$encoder->startTag(SYNC_DATA); + $data->Encode(self::$encoder); + self::$encoder->endTag(); + } + else + ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id)); + self::$encoder->endTag(); + + } + self::$encoder->endTag(); + } + + if($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) { + $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount); + + if($changecount > $windowSize) { + self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true); + } + } + + // Stream outgoing changes + if($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) { + self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", (($changecount > $windowSize)?$windowSize:$changecount))); + + // Output message changes per folder + self::$encoder->startTag(SYNC_PERFORM); + + $n = 0; + while(1) { + try { + $progress = $exporter->Synchronize(); + if(!is_array($progress)) + break; + $n++; + } + catch (SyncObjectBrokenException $mbe) { + $brokenSO = $mbe->GetSyncObject(); + if (!$brokenSO) { + ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend.")); + } + else { + if (!isset($brokenSO->id)) { + $brokenSO->id = "Unknown ID"; + ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend.")); + } + self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO); + } + } + + if($n >= $windowSize) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount)); + break; + } + + } + + // $progress is not an array when exporting the last message + // so we get the number to display from the streamimporter + if (isset($streamimporter)) { + $n = $streamimporter->GetImportedMessages(); + } + + self::$encoder->endTag(); + self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, ($n >= $windowSize)?" of ".$changecount:""), true); + } + + self::$encoder->endTag(); + + // Save the sync state for the next time + if($spa->HasNewSyncKey()) { + self::$topCollector->AnnounceInformation("Saving state"); + + try { + if (isset($exporter) && $exporter) + $state = $exporter->GetState(); + + // nothing exported, but possibly imported - get the importer state + else if ($sc->GetParameter($spa, "state") !== null) + $state = $sc->GetParameter($spa, "state"); + + // if a new request without state information (hierarchy) save an empty state + else if (! $spa->HasSyncKey()) + $state = ""; + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + + + if (isset($state) && $status == SYNC_STATUS_SUCCESS) + self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId()); + else + ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey())); + } + + // save SyncParameters + if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) + $sc->SaveCollection($spa); + + } // END foreach collection + } + self::$encoder->endTag(); //SYNC_FOLDERS + } + } + self::$encoder->endTag(); //SYNC_SYNCHRONIZE + + return true; + } + + /** + * Loads the states and writes them into the SyncCollection Object and the actiondata failstate + * + * @param SyncCollection $sc SyncCollection object + * @param SyncParameters $spa SyncParameters object + * @param array $actiondata Actiondata array + * @param boolean $loadFailsave (opt) default false - indicates if the failsave states should be loaded + * + * @access private + * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS + */ + private function loadStates($sc, $spa, &$actiondata, $loadFailsave = false) { + $status = SYNC_STATUS_SUCCESS; + + if ($sc->GetParameter($spa, "state") == null) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'",$spa->GetFolderId())); + + try { + $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey())); + + if ($loadFailsave) { + // if this request was made before, there will be a failstate available + $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState(); + } + + // if this is an additional folder the backend has to be setup correctly + if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) + throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); + } + catch (StateNotFoundException $snfex) { + $status = SYNC_STATUS_INVALIDSYNCKEY; + self::$topCollector->AnnounceInformation("StateNotFoundException", true); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); + } + } + + return $status; + } + + /** + * Initializes the importer for the SyncParameters folder, loads necessary + * states (incl. failsave states) and initializes the conflict detection + * + * @param SyncCollection $sc SyncCollection object + * @param SyncParameters $spa SyncParameters object + * @param array $actiondata Actiondata array + * + * @access private + * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS + */ + private function getImporter($sc, $spa, &$actiondata) { + ZLog::Write(LOGLEVEL_DEBUG, "Sync->getImporter(): initialize importer"); + $status = SYNC_STATUS_SUCCESS; + + // load the states with failsave data + $status = $this->loadStates($sc, $spa, $actiondata, true); + + try { + // Configure importer with last state + $this->importer = self::$backend->GetImporter($spa->GetFolderId()); + + // if something goes wrong, ask the mobile to resync the hierarchy + if ($this->importer === false) + throw new StatusException(sprintf("Sync->getImporter(): no importer for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); + + // if there is a valid state obtained after importing changes in a previous loop, we use that state + if (isset($actiondata["failstate"]) && isset($actiondata["failstate"]["failedsyncstate"])) { + $this->importer->Config($actiondata["failstate"]["failedsyncstate"], $spa->GetConflict()); + } + else + $this->importer->Config($sc->GetParameter($spa, "state"), $spa->GetConflict()); + } + catch (StatusException $stex) { + $status = $stex->getCode(); + } + + $this->importer->LoadConflicts($spa->GetCPO(), $sc->GetParameter($spa, "state")); + + return $status; + } + + /** + * Imports a message + * + * @param SyncParameters $spa SyncParameters object + * @param array $actiondata Actiondata array + * @param integer $todo WBXML flag indicating how message should be imported. + * Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE + * @param SyncObject $message SyncObject message to be imported + * @param string $clientid Client message identifier + * @param string $serverid Server message identifier + * @param string $foldertype On sms sync, this says "SMS", else false + * @param integer $messageCount Counter of already imported messages + * + * @access private + * @throws StatusException in case the importer is not available + * @return - Message related status are returned in the actiondata. + */ + private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount) { + // the importer needs to be available! + if ($this->importer == false) + throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR)); + + // Detect incoming loop + // messages which were created/removed before will not have the same action executed again + // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime + $ignoreMessage = false; + if ($actiondata["failstate"]) { + // message was ADDED before, do NOT add it again + if ($todo == SYNC_ADD && $actiondata["failstate"]["clientids"][$clientid]) { + $ignoreMessage = true; + + // make sure no messages are sent back + self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0); + + $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid]; + $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid]; + + ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid])); + } + + // message was REMOVED before, do NOT attemp to remove it again + if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) { + $ignoreMessage = true; + + // make sure no messages are sent back + self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0); + + $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid]; + $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid]; + + ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid])); + } + } + + if (!$ignoreMessage) { + switch($todo) { + case SYNC_MODIFY: + self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount)); + try { + $actiondata["modifyids"][] = $serverid; + + // ignore sms messages + if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) { + ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); + // TODO we should update the SMS + $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; + } + // check incoming message without logging WARN messages about errors + else if (!($message instanceof SyncObject) || !$message->Check(true)) { + $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; + } + else { + if(isset($message->read)) { + // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag. + $this->importer->ImportMessageReadFlag($serverid, $message->read); + } + elseif (!isset($message->flag)) { + $this->importer->ImportMessageChange($serverid, $message); + } + + // email todoflags - some devices send todos flags together with read flags, + // so they have to be handled separately + if (isset($message->flag)){ + $this->importer->ImportMessageChange($serverid, $message); + } + + $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; + } + } + catch (StatusException $stex) { + $actiondata["statusids"][$serverid] = $stex->getCode(); + } + + break; + case SYNC_ADD: + self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount)); + try { + // ignore sms messages + if ($foldertype == "SMS") { + ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); + // TODO we should create the SMS + // return a fake serverid which we can identify later + $actiondata["clientids"][$clientid] = self::ZPUSHIGNORESMS . $clientid; + $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS; + } + // check incoming message without logging WARN messages about errors + else if (!($message instanceof SyncObject) || !$message->Check(true)) { + $actiondata["clientids"][$clientid] = false; + $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR; + } + else { + $actiondata["clientids"][$clientid] = false; + $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message); + $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS; + } + } + catch (StatusException $stex) { + $actiondata["statusids"][$clientid] = $stex->getCode(); + } + break; + case SYNC_REMOVE: + self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount)); + try { + $actiondata["removeids"][] = $serverid; + // ignore sms messages + if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) { + ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message."); + // TODO we should delete the SMS + $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; + } + else { + // if message deletions are to be moved, move them + if($spa->GetDeletesAsMoves()) { + $folderid = self::$backend->GetWasteBasket(); + + if($folderid) { + $this->importer->ImportMessageMove($serverid, $folderid); + $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; + break; + } + else + ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!"); + } + + $this->importer->ImportMessageDeletion($serverid); + $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS; + } + } + catch (StatusException $stex) { + $actiondata["statusids"][$serverid] = $stex->getCode(); + } + break; + } + ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported"); + } + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncappointment.php b/z-push/lib/syncobjects/syncappointment.php new file mode 100644 index 0000000..94b84cf --- /dev/null +++ b/z-push/lib/syncobjects/syncappointment.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncappointmentexception.php b/z-push/lib/syncobjects/syncappointmentexception.php new file mode 100644 index 0000000..1dc9368 --- /dev/null +++ b/z-push/lib/syncobjects/syncappointmentexception.php @@ -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); + + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncattachment.php b/z-push/lib/syncobjects/syncattachment.php new file mode 100644 index 0000000..59f5ffa --- /dev/null +++ b/z-push/lib/syncobjects/syncattachment.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncattendee.php b/z-push/lib/syncobjects/syncattendee.php new file mode 100644 index 0000000..e237a16 --- /dev/null +++ b/z-push/lib/syncobjects/syncattendee.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncbaseattachment.php b/z-push/lib/syncobjects/syncbaseattachment.php new file mode 100644 index 0000000..8bf9c64 --- /dev/null +++ b/z-push/lib/syncobjects/syncbaseattachment.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncbasebody.php b/z-push/lib/syncobjects/syncbasebody.php new file mode 100644 index 0000000..f9210c3 --- /dev/null +++ b/z-push/lib/syncobjects/syncbasebody.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/synccontact.php b/z-push/lib/syncobjects/synccontact.php new file mode 100644 index 0000000..4a51eb1 --- /dev/null +++ b/z-push/lib/syncobjects/synccontact.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncdeviceinformation.php b/z-push/lib/syncobjects/syncdeviceinformation.php new file mode 100644 index 0000000..2c9ed76 --- /dev/null +++ b/z-push/lib/syncobjects/syncdeviceinformation.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncdevicepassword.php b/z-push/lib/syncobjects/syncdevicepassword.php new file mode 100644 index 0000000..40f0bb2 --- /dev/null +++ b/z-push/lib/syncobjects/syncdevicepassword.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncfolder.php b/z-push/lib/syncobjects/syncfolder.php new file mode 100644 index 0000000..0e58f8c --- /dev/null +++ b/z-push/lib/syncobjects/syncfolder.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncitemoperationsattachment.php b/z-push/lib/syncobjects/syncitemoperationsattachment.php new file mode 100644 index 0000000..2593d7e --- /dev/null +++ b/z-push/lib/syncobjects/syncitemoperationsattachment.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncmail.php b/z-push/lib/syncobjects/syncmail.php new file mode 100644 index 0000000..33d0be0 --- /dev/null +++ b/z-push/lib/syncobjects/syncmail.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncmailflags.php b/z-push/lib/syncobjects/syncmailflags.php new file mode 100644 index 0000000..e0eecb5 --- /dev/null +++ b/z-push/lib/syncobjects/syncmailflags.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncmeetingrequest.php b/z-push/lib/syncobjects/syncmeetingrequest.php new file mode 100644 index 0000000..67d8480 --- /dev/null +++ b/z-push/lib/syncobjects/syncmeetingrequest.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncmeetingrequestrecurrence.php b/z-push/lib/syncobjects/syncmeetingrequestrecurrence.php new file mode 100644 index 0000000..8b9c880 --- /dev/null +++ b/z-push/lib/syncobjects/syncmeetingrequestrecurrence.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncnote.php b/z-push/lib/syncobjects/syncnote.php new file mode 100644 index 0000000..129305c --- /dev/null +++ b/z-push/lib/syncobjects/syncnote.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncobject.php b/z-push/lib/syncobjects/syncobject.php new file mode 100644 index 0000000..68012b6 --- /dev/null +++ b/z-push/lib/syncobjects/syncobject.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncoof.php b/z-push/lib/syncobjects/syncoof.php new file mode 100644 index 0000000..178c41c --- /dev/null +++ b/z-push/lib/syncobjects/syncoof.php @@ -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); + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncoofmessage.php b/z-push/lib/syncobjects/syncoofmessage.php new file mode 100644 index 0000000..2b683fe --- /dev/null +++ b/z-push/lib/syncobjects/syncoofmessage.php @@ -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); + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncprovisioning.php b/z-push/lib/syncobjects/syncprovisioning.php new file mode 100644 index 0000000..ca66402 --- /dev/null +++ b/z-push/lib/syncobjects/syncprovisioning.php @@ -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(); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncrecurrence.php b/z-push/lib/syncobjects/syncrecurrence.php new file mode 100644 index 0000000..f83ce65 --- /dev/null +++ b/z-push/lib/syncobjects/syncrecurrence.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncsendmail.php b/z-push/lib/syncobjects/syncsendmail.php new file mode 100644 index 0000000..872d28e --- /dev/null +++ b/z-push/lib/syncobjects/syncsendmail.php @@ -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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncsendmailsource.php b/z-push/lib/syncobjects/syncsendmailsource.php new file mode 100644 index 0000000..646cef4 --- /dev/null +++ b/z-push/lib/syncobjects/syncsendmailsource.php @@ -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); + } + +} +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/synctask.php b/z-push/lib/syncobjects/synctask.php new file mode 100644 index 0000000..6db4bf4 --- /dev/null +++ b/z-push/lib/syncobjects/synctask.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/synctaskrecurrence.php b/z-push/lib/syncobjects/synctaskrecurrence.php new file mode 100644 index 0000000..6927399 --- /dev/null +++ b/z-push/lib/syncobjects/synctaskrecurrence.php @@ -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; + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/syncobjects/syncuserinformation.php b/z-push/lib/syncobjects/syncuserinformation.php new file mode 100644 index 0000000..8d276b5 --- /dev/null +++ b/z-push/lib/syncobjects/syncuserinformation.php @@ -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); + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/utils/compat.php b/z-push/lib/utils/compat.php new file mode 100644 index 0000000..b04caf0 --- /dev/null +++ b/z-push/lib/utils/compat.php @@ -0,0 +1,135 @@ +. +* +* 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("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() { + $header = array(); + + if(isset($_SERVER['HTTP_MS_ASPROTOCOLVERSION'])) { + $header['Ms-Asprotocolversion'] = $_SERVER['HTTP_MS_ASPROTOCOLVERSION']; + } + if(isset($_SERVER['REDIRECT_HTTP_MS_ASPROTOCOLVERSION'])) { + $header['Ms-Asprotocolversion'] = $_SERVER['REDIRECT_HTTP_MS_ASPROTOCOLVERSION']; + } + + if(isset($_SERVER['HTTP_X_MS_POLICYKEY'])) { + $header['X-Ms-Policykey'] = $_SERVER['HTTP_X_MS_POLICYKEY']; + } + if(isset($_SERVER['REDIRECT_HTTP_X_MS_POLICYKEY'])) { + $header['X-Ms-Policykey'] = $_SERVER['REDIRECT_HTTP_X_MS_POLICYKEY']; + } + + $header['User-Agent'] = $_SERVER['HTTP_USER_AGENT']; + + return $header; + } + + /* + $header = apache_request_headers(); + if(isset($header['Authorization'])) { + list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($header['Authorization'], 6))); + } + /*/ + if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { + list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 6))); + } + if(isset($_SERVER['HTTP_AUTHORIZATION'])) { + list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); + } + //*/ +} + + +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); + } +} +?> \ No newline at end of file diff --git a/z-push/lib/utils/timezoneutil.php b/z-push/lib/utils/timezoneutil.php new file mode 100644 index 0000000..e34f584 --- /dev/null +++ b/z-push/lib/utils/timezoneutil.php @@ -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"; + } + } + +} + +?> \ No newline at end of file diff --git a/z-push/lib/utils/utils.php b/z-push/lib/utils/utils.php new file mode 100644 index 0000000..ff47bc8 --- /dev/null +++ b/z-push/lib/utils/utils.php @@ -0,0 +1,872 @@ +. +* +* 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 = $firstlast; + 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; + } +} + + + +// 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"); } + + +?> \ No newline at end of file diff --git a/z-push/lib/utils/zpushadmin.php b/z-push/lib/utils/zpushadmin.php new file mode 100644 index 0000000..7c7658c --- /dev/null +++ b/z-push/lib/utils/zpushadmin.php @@ -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); + } + + +} + +?> \ No newline at end of file diff --git a/z-push/lib/wbxml/wbxmldecoder.php b/z-push/lib/wbxml/wbxmldecoder.php new file mode 100644 index 0000000..65a847f --- /dev/null +++ b/z-push/lib/wbxml/wbxmldecoder.php @@ -0,0 +1,668 @@ +. +* +* Consult LICENSE file for details +************************************************/ + + +class WBXMLDecoder extends WBXMLDefs { + private $in; + + private $version; + private $publicid; + private $publicstringid; + private $charsetid; + private $stringtable; + + private $tagcp = 0; + private $attrcp = 0; + + private $ungetbuffer; + + private $logStack = array(); + + private $inputBuffer = ""; + private $isWBXML = true; + + const VERSION = 0x03; + + /** + * WBXML Decode Constructor + * + * @param stream $input the incoming data stream + * + * @access public + */ + public function WBXMLDecoder($input) { + // make sure WBXML_DEBUG is defined. It should be at this point + if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false); + + $this->in = $input; + + $this->readVersion(); + if (isset($this->version) && $this->version != self::VERSION) { + $this->isWBXML = false; + return; + } + + $this->publicid = $this->getMBUInt(); + if($this->publicid == 0) { + $this->publicstringid = $this->getMBUInt(); + } + + $this->charsetid = $this->getMBUInt(); + $this->stringtable = $this->getStringTable(); + } + + /** + * Returns either start, content or end, and auto-concatenates successive content + * + * @access public + * @return element/value + */ + public function getElement() { + $element = $this->getToken(); + + switch($element[EN_TYPE]) { + case EN_TYPE_STARTTAG: + return $element; + case EN_TYPE_ENDTAG: + return $element; + case EN_TYPE_CONTENT: + while(1) { + $next = $this->getToken(); + if($next == false) + return false; + else if($next[EN_TYPE] == EN_CONTENT) { + $element[EN_CONTENT] .= $next[EN_CONTENT]; + } else { + $this->ungetElement($next); + break; + } + } + return $element; + } + + return false; + } + + /** + * Get a peek at the next element + * + * @access public + * @return element + */ + public function peek() { + $element = $this->getElement(); + $this->ungetElement($element); + return $element; + } + + /** + * Get the element of a StartTag + * + * @param $tag + * + * @access public + * @return element/boolean returns false if not available + */ + public function getElementStartTag($tag) { + $element = $this->getToken(); + + if($element[EN_TYPE] == EN_TYPE_STARTTAG && $element[EN_TAG] == $tag) + return $element; + else { + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementStartTag(): unmatched WBXML tag: '%s' matching '%s' type '%s' flags '%s'", $tag, ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:""))); + $this->ungetElement($element); + } + + return false; + } + + /** + * Get the element of a EndTag + * + * @access public + * @return element/boolean returns false if not available + */ + public function getElementEndTag() { + $element = $this->getToken(); + + if($element[EN_TYPE] == EN_TYPE_ENDTAG) + return $element; + else { + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementEndTag(): unmatched WBXML tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:""))); + + $bt = debug_backtrace(); + ZLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the Z-Push dev team.", $bt[0]["file"] . ":" . $bt[0]["line"])); + + // log the remaining wbxml content + $this->ungetElement($element); + while($el = $this->getElement()); + } + + return false; + } + + /** + * Get the content of an element + * + * @access public + * @return string/boolean returns false if not available + */ + public function getElementContent() { + $element = $this->getToken(); + + if($element[EN_TYPE] == EN_TYPE_CONTENT) { + return $element[EN_CONTENT]; + } + else { + ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementContent(): unmatched WBXML content: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:""))); + $this->ungetElement($element); + } + + return false; + } + + /** + * 'Ungets' an element writing it into a buffer to be 'get' again + * + * @param element $element the element to get ungetten + * + * @access public + * @return + */ + public function ungetElement($element) { + if($this->ungetbuffer) + ZLog::Write(LOGLEVEL_ERROR,sprintf("WBXMLDecoder->ungetElement(): WBXML double unget on tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:""))); + + $this->ungetbuffer = $element; + } + + /** + * Returns the plain input stream + * + * @access public + * @return string + */ + public function GetPlainInputStream() { + $plain = $this->inputBuffer; + while($data = fread($this->in, 4096)) + $plain .= $data; + + return $plain; + } + + /** + * Returns if the input is WBXML + * + * @access public + * @return boolean + */ + public function IsWBXML() { + return $this->isWBXML; + } + + + + /**---------------------------------------------------------------------------------------------------------- + * Private WBXMLDecoder stuff + */ + + /** + * Returns the next token + * + * @access private + * @return token + */ + private function getToken() { + // See if there's something in the ungetBuffer + if($this->ungetbuffer) { + $element = $this->ungetbuffer; + $this->ungetbuffer = false; + return $element; + } + + $el = $this->_getToken(); + $this->logToken($el); + + return $el; + } + + /** + * Log the a token to ZLog + * + * @param string $el token + * + * @access private + * @return + */ + private function logToken($el) { + if(!WBXML_DEBUG) + return; + + $spaces = str_repeat(" ", count($this->logStack)); + + switch($el[EN_TYPE]) { + case EN_TYPE_STARTTAG: + if($el[EN_FLAGS] & EN_FLAGS_CONTENT) { + ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " <". $el[EN_TAG] . ">"); + array_push($this->logStack, $el[EN_TAG]); + } else + ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " <" . $el[EN_TAG] . "/>"); + + break; + case EN_TYPE_ENDTAG: + $tag = array_pop($this->logStack); + ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . ""); + break; + case EN_TYPE_CONTENT: + ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " " . $el[EN_CONTENT]); + break; + } + } + + /** + * Returns either a start tag, content or end tag + * + * @access private + * @return + */ + private function _getToken() { + // Get the data from the input stream + $element = array(); + + while(1) { + $byte = $this->getByte(); + + if(!isset($byte)) + break; + + switch($byte) { + case WBXML_SWITCH_PAGE: + $this->tagcp = $this->getByte(); + continue; + + case WBXML_END: + $element[EN_TYPE] = EN_TYPE_ENDTAG; + return $element; + + case WBXML_ENTITY: + $entity = $this->getMBUInt(); + $element[EN_TYPE] = EN_TYPE_CONTENT; + $element[EN_CONTENT] = $this->entityToCharset($entity); + return $element; + + case WBXML_STR_I: + $element[EN_TYPE] = EN_TYPE_CONTENT; + $element[EN_CONTENT] = $this->getTermStr(); + return $element; + + case WBXML_LITERAL: + $element[EN_TYPE] = EN_TYPE_STARTTAG; + $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); + $element[EN_FLAGS] = 0; + return $element; + + case WBXML_EXT_I_0: + case WBXML_EXT_I_1: + case WBXML_EXT_I_2: + $this->getTermStr(); + // Ignore extensions + continue; + + case WBXML_PI: + // Ignore PI + $this->getAttributes(); + continue; + + case WBXML_LITERAL_C: + $element[EN_TYPE] = EN_TYPE_STARTTAG; + $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); + $element[EN_FLAGS] = EN_FLAGS_CONTENT; + return $element; + + case WBXML_EXT_T_0: + case WBXML_EXT_T_1: + case WBXML_EXT_T_2: + $this->getMBUInt(); + // Ingore extensions; + continue; + + case WBXML_STR_T: + $element[EN_TYPE] = EN_TYPE_CONTENT; + $element[EN_CONTENT] = $this->getStringTableEntry($this->getMBUInt()); + return $element; + + case WBXML_LITERAL_A: + $element[EN_TYPE] = EN_TYPE_STARTTAG; + $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); + $element[EN_ATTRIBUTES] = $this->getAttributes(); + $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES; + return $element; + case WBXML_EXT_0: + case WBXML_EXT_1: + case WBXML_EXT_2: + continue; + + case WBXML_OPAQUE: + $length = $this->getMBUInt(); + $element[EN_TYPE] = EN_TYPE_CONTENT; + $element[EN_CONTENT] = $this->getOpaque($length); + return $element; + + case WBXML_LITERAL_AC: + $element[EN_TYPE] = EN_TYPE_STARTTAG; + $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt()); + $element[EN_ATTRIBUTES] = $this->getAttributes(); + $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES | EN_FLAGS_CONTENT; + return $element; + + default: + $element[EN_TYPE] = EN_TYPE_STARTTAG; + $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f); + $element[EN_FLAGS] = ($byte & 0x80 ? EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? EN_FLAGS_CONTENT : 0); + if($byte & 0x80) + $element[EN_ATTRIBUTES] = $this->getAttributes(); + return $element; + } + } + } + + /** + * Gets attributes + * + * @access private + * @return + */ + private function getAttributes() { + $attributes = array(); + $attr = ""; + + while(1) { + $byte = $this->getByte(); + + if(count($byte) == 0) + break; + + switch($byte) { + case WBXML_SWITCH_PAGE: + $this->attrcp = $this->getByte(); + break; + + case WBXML_END: + if($attr != "") + $attributes += $this->splitAttribute($attr); + + return $attributes; + + case WBXML_ENTITY: + $entity = $this->getMBUInt(); + $attr .= $this->entityToCharset($entity); + return $element; + + case WBXML_STR_I: + $attr .= $this->getTermStr(); + return $element; + + case WBXML_LITERAL: + if($attr != "") + $attributes += $this->splitAttribute($attr); + + $attr = $this->getStringTableEntry($this->getMBUInt()); + return $element; + + case WBXML_EXT_I_0: + case WBXML_EXT_I_1: + case WBXML_EXT_I_2: + $this->getTermStr(); + continue; + + case WBXML_PI: + case WBXML_LITERAL_C: + // Invalid + return false; + + case WBXML_EXT_T_0: + case WBXML_EXT_T_1: + case WBXML_EXT_T_2: + $this->getMBUInt(); + continue; + + case WBXML_STR_T: + $attr .= $this->getStringTableEntry($this->getMBUInt()); + return $element; + + case WBXML_LITERAL_A: + return false; + + case WBXML_EXT_0: + case WBXML_EXT_1: + case WBXML_EXT_2: + continue; + + case WBXML_OPAQUE: + $length = $this->getMBUInt(); + $attr .= $this->getOpaque($length); + return $element; + + case WBXML_LITERAL_AC: + return false; + + default: + if($byte < 128) { + if($attr != "") { + $attributes += $this->splitAttribute($attr); + $attr = ""; + } + } + $attr .= $this->getMapping($this->attrcp, $byte); + break; + } + } + } + + /** + * Splits an attribute + * + * @param string $attr attribute to be splitted + * + * @access private + * @return array + */ + private function splitAttribute($attr) { + $attributes = array(); + + $pos = strpos($attr,chr(61)); // equals sign + + if($pos) + $attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1); + else + $attributes[$attr] = null; + + return $attributes; + } + + /** + * Reads from the stream until getting a string terminator + * + * @access private + * @return string + */ + private function getTermStr() { + $str = ""; + while(1) { + $in = $this->getByte(); + + if($in == 0) + break; + else + $str .= chr($in); + } + + return $str; + } + + /** + * Reads $len from the input stream + * + * @param int $len + * + * @access private + * @return string + */ + private function getOpaque($len) { + // TODO check if it's possible to do it other way + // fread stops reading because the following condition is true (from php.net): + // if the stream is read buffered and it does not represent a plain file, + // at most one read of up to a number of bytes equal to the chunk size + // (usually 8192) is made; depending on the previously buffered data, + // the size of the returned data may be larger than the chunk size. + + // using only return fread it will return only a part of stream if chunk is smaller + // than $len. Read from stream in a loop until the $len is reached. + $d = ""; + $l = 0; + while (1) { + $l = (($len - strlen($d)) > 8192) ? 8192 : ($len - strlen($d)); + if ($l > 0) { + $data = fread($this->in, $l); + + // Stream ends prematurely on instable connections and big mails + if ($data === false || feof($this->in)) + throw new HTTPReturnCodeException(sprintf("WBXMLDecoder->getOpaque() connection unavailable while trying to read %d bytes from stream. Aborting after %d bytes read.", $len, strlen($d)), HTTP_CODE_500, null, LOGLEVEL_WARN); + else + $d .= $data; + } + if (strlen($d) >= $len) break; + } + return $d; + } + + /** + * Reads one byte from the input stream + * + * @access private + * @return int + */ + private function getByte() { + $ch = fread($this->in, 1); + if(strlen($ch) > 0) + return ord($ch); + else + return; + } + + /** + * Reads string length from the input stream + * + * @access private + * @return + */ + private function getMBUInt() { + $uint = 0; + + while(1) { + $byte = $this->getByte(); + + $uint |= $byte & 0x7f; + + if($byte & 0x80) + $uint = $uint << 7; + else + break; + } + + return $uint; + } + + /** + * Reads string table from the input stream + * + * @access private + * @return int + */ + private function getStringTable() { + $stringtable = ""; + + $length = $this->getMBUInt(); + if($length > 0) + $stringtable = fread($this->in, $length); + + return $stringtable; + } + + /** + * Returns the mapping for a specified codepage and id + * + * @param $cp codepage + * @param $id + * + * @access public + * @return string + */ + private function getMapping($cp, $id) { + if(!isset($this->dtd["codes"][$cp]) || !isset($this->dtd["codes"][$cp][$id])) + return false; + else { + if(isset($this->dtd["namespaces"][$cp])) { + return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id]; + } else + return $this->dtd["codes"][$cp][$id]; + } + } + + /** + * Reads one byte from the input stream + * + * @access private + * @return void + */ + private function readVersion() { + $ch = $this->getByte(); + + if($ch != NULL) { + $this->inputBuffer .= chr($ch); + $this->version = $ch; + } + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/wbxml/wbxmldefs.php b/z-push/lib/wbxml/wbxmldefs.php new file mode 100644 index 0000000..d6723d5 --- /dev/null +++ b/z-push/lib/wbxml/wbxmldefs.php @@ -0,0 +1,769 @@ +. +* +* Consult LICENSE file for details +************************************************/ + + +define('WBXML_SWITCH_PAGE', 0x00); +define('WBXML_END', 0x01); +define('WBXML_ENTITY', 0x02); +define('WBXML_STR_I', 0x03); +define('WBXML_LITERAL', 0x04); +define('WBXML_EXT_I_0', 0x40); +define('WBXML_EXT_I_1', 0x41); +define('WBXML_EXT_I_2', 0x42); +define('WBXML_PI', 0x43); +define('WBXML_LITERAL_C', 0x44); +define('WBXML_EXT_T_0', 0x80); +define('WBXML_EXT_T_1', 0x81); +define('WBXML_EXT_T_2', 0x82); +define('WBXML_STR_T', 0x83); +define('WBXML_LITERAL_A', 0x84); +define('WBXML_EXT_0', 0xC0); +define('WBXML_EXT_1', 0xC1); +define('WBXML_EXT_2', 0xC2); +define('WBXML_OPAQUE', 0xC3); +define('WBXML_LITERAL_AC', 0xC4); + +define('EN_TYPE', 1); +define('EN_TAG', 2); +define('EN_CONTENT', 3); +define('EN_FLAGS', 4); +define('EN_ATTRIBUTES', 5); + +define('EN_TYPE_STARTTAG', 1); +define('EN_TYPE_ENDTAG', 2); +define('EN_TYPE_CONTENT', 3); + +define('EN_FLAGS_CONTENT', 1); +define('EN_FLAGS_ATTRIBUTES', 2); + +class WBXMLDefs { + /** + * The WBXML DTDs + */ + protected $dtd = array( + "codes" => array ( + 0 => array ( + 0x05 => "Synchronize", + 0x06 => "Replies", //Responses + 0x07 => "Add", + 0x08 => "Modify", //Change + 0x09 => "Remove", //Delete + 0x0a => "Fetch", + 0x0b => "SyncKey", + 0x0c => "ClientEntryId", //ClientId + 0x0d => "ServerEntryId", //ServerId + 0x0e => "Status", + 0x0f => "Folder", //collection + 0x10 => "FolderType", //class + 0x11 => "Version", + 0x12 => "FolderId", //CollectionId + 0x13 => "GetChanges", + 0x14 => "MoreAvailable", + 0x15 => "WindowSize", //WindowSize - MaxItems before z-push 2 + 0x16 => "Perform", //Commands + 0x17 => "Options", + 0x18 => "FilterType", + 0x19 => "Truncation", //2.0 and 2.5 + 0x1a => "RtfTruncation", //2.0 and 2.5 + 0x1b => "Conflict", + 0x1c => "Folders", //Collections + 0x1d => "Data", + 0x1e => "DeletesAsMoves", + 0x1f => "NotifyGUID", //2.0 and 2.5 + 0x20 => "Supported", + 0x21 => "SoftDelete", + 0x22 => "MIMESupport", + 0x23 => "MIMETruncation", + 0x24 => "Wait", //12.1 and 14.0 + 0x25 => "Limit", //12.1 and 14.0 + 0x26 => "Partial", //12.1 and 14.0 + 0x27 => "ConversationMode", //14.0 + 0x28 => "MaxItems", //14.0 + 0x29 => "HeartbeatInterval", //14.0 Either this tag or the Wait tag can be present, but not both. + ), + 1 => array ( + 0x05 => "Anniversary", + 0x06 => "AssistantName", + 0x07 => "AssistnamePhoneNumber", //AssistantTelephoneNumber + 0x08 => "Birthday", + 0x09 => "Body", // 2.5, but is in code page 17 in ActiveSync versions 12.0, 12.1, and 14.0. + 0x0a => "BodySize", //2.0 and 2.5 + 0x0b => "BodyTruncated", //2.0 and 2.5 + 0x0c => "Business2PhoneNumber", + 0x0d => "BusinessCity", + 0x0e => "BusinessCountry", + 0x0f => "BusinessPostalCode", + 0x10 => "BusinessState", + 0x11 => "BusinessStreet", + 0x12 => "BusinessFaxNumber", + 0x13 => "BusinessPhoneNumber", + 0x14 => "CarPhoneNumber", + 0x15 => "Categories", + 0x16 => "Category", + 0x17 => "Children", + 0x18 => "Child", + 0x19 => "CompanyName", + 0x1a => "Department", + 0x1b => "Email1Address", + 0x1c => "Email2Address", + 0x1d => "Email3Address", + 0x1e => "FileAs", + 0x1f => "FirstName", + 0x20 => "Home2PhoneNumber", + 0x21 => "HomeCity", + 0x22 => "HomeCountry", + 0x23 => "HomePostalCode", + 0x24 => "HomeState", + 0x25 => "HomeStreet", + 0x26 => "HomeFaxNumber", + 0x27 => "HomePhoneNumber", + 0x28 => "JobTitle", + 0x29 => "LastName", + 0x2a => "MiddleName", + 0x2b => "MobilePhoneNumber", + 0x2c => "OfficeLocation", + 0x2d => "OtherCity", + 0x2e => "OtherCountry", + 0x2f => "OtherPostalCode", + 0x30 => "OtherState", + 0x31 => "OtherStreet", + 0x32 => "PagerNumber", + 0x33 => "RadioPhoneNumber", + 0x34 => "Spouse", + 0x35 => "Suffix", + 0x36 => "Title", + 0x37 => "WebPage", + 0x38 => "YomiCompanyName", + 0x39 => "YomiFirstName", + 0x3a => "YomiLastName", + 0x3b => "Rtf", //CompressedRTF - 2.5 + 0x3c => "Picture", + 0x3d => "Alias", //14.0 + 0x3e => "WeightedRank" //14.0 + ), + 2 => array ( + 0x05 => "Attachment", //2.5, 12.0, 12.1 and 14.0 + 0x06 => "Attachments", //2.5, 12.0, 12.1 and 14.0 + 0x07 => "AttName", //2.5, 12.0, 12.1 and 14.0 + 0x08 => "AttSize", //2.5, 12.0, 12.1 and 14.0 + 0x09 => "AttOid", //2.5, 12.0, 12.1 and 14.0 + 0x0a => "AttMethod", //2.5, 12.0, 12.1 and 14.0 + 0x0b => "AttRemoved", //2.5, 12.0, 12.1 and 14.0 + 0x0c => "Body", // 2.5, but is in code page 17 in ActiveSync versions 12.0, 12.1, and 14.0. + 0x0d => "BodySize", //2.5, 12.0, 12.1 and 14.0 + 0x0e => "BodyTruncated", //2.5, 12.0, 12.1 and 14.0 + 0x0f => "DateReceived", //2.5, 12.0, 12.1 and 14.0 + 0x10 => "DisplayName", //2.5, 12.0, 12.1 and 14.0 + 0x11 => "DisplayTo", //2.5, 12.0, 12.1 and 14.0 + 0x12 => "Importance", //2.5, 12.0, 12.1 and 14.0 + 0x13 => "MessageClass", //2.5, 12.0, 12.1 and 14.0 + 0x14 => "Subject", //2.5, 12.0, 12.1 and 14.0 + 0x15 => "Read", //2.5, 12.0, 12.1 and 14.0 + 0x16 => "To", //2.5, 12.0, 12.1 and 14.0 + 0x17 => "Cc", //2.5, 12.0, 12.1 and 14.0 + 0x18 => "From", //2.5, 12.0, 12.1 and 14.0 + 0x19 => "Reply-To", //ReplyTo 2.5, 12.0, 12.1 and 14.0 + 0x1a => "AllDayEvent", //2.5, 12.0, 12.1 and 14.0 + 0x1b => "Categories", //2.5, 12.0, 12.1 and 14.0 + 0x1c => "Category", //2.5, 12.0, 12.1 and 14.0 + 0x1d => "DtStamp", //2.5, 12.0, 12.1 and 14.0 + 0x1e => "EndTime", //2.5, 12.0, 12.1 and 14.0 + 0x1f => "InstanceType", //2.5, 12.0, 12.1 and 14.0 + 0x20 => "BusyStatus", //2.5, 12.0, 12.1 and 14.0 + 0x21 => "Location", //2.5, 12.0, 12.1 and 14.0 + 0x22 => "MeetingRequest", //2.5, 12.0, 12.1 and 14.0 + 0x23 => "Organizer", //2.5, 12.0, 12.1 and 14.0 + 0x24 => "RecurrenceId", //2.5, 12.0, 12.1 and 14.0 + 0x25 => "Reminder", //2.5, 12.0, 12.1 and 14.0 + 0x26 => "ResponseRequested", //2.5, 12.0, 12.1 and 14.0 + 0x27 => "Recurrences", //2.5, 12.0, 12.1 and 14.0 + 0x28 => "Recurrence", //2.5, 12.0, 12.1 and 14.0 + 0x29 => "Type", //Recurrence_Type //2.5, 12.0, 12.1 and 14.0 + 0x2a => "Until", //Recurrence_Until //2.5, 12.0, 12.1 and 14.0 + 0x2b => "Occurrences", //Recurrence_Occurrences //2.5, 12.0, 12.1 and 14.0 + 0x2c => "Interval", //Recurrence_Interval //2.5, 12.0, 12.1 and 14.0 + 0x2d => "DayOfWeek", //Recurrence_DayOfWeek //2.5, 12.0, 12.1 and 14.0 + 0x2e => "DayOfMonth", //Recurrence_DayOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x2f => "WeekOfMonth", //Recurrence_WeekOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x30 => "MonthOfYear", //Recurrence_MonthOfYear //2.5, 12.0, 12.1 and 14.0 + 0x31 => "StartTime", //2.5, 12.0, 12.1 and 14.0 + 0x32 => "Sensitivity", //2.5, 12.0, 12.1 and 14.0 + 0x33 => "TimeZone", //2.5, 12.0, 12.1 and 14.0 + 0x34 => "GlobalObjId", //2.5, 12.0, 12.1 and 14.0 + 0x35 => "ThreadTopic", //2.5, 12.0, 12.1 and 14.0 + 0x36 => "MIMEData", //2.5 + 0x37 => "MIMETruncated", //2.5 + 0x38 => "MIMESize", //2.5 + 0x39 => "InternetCPID", //2.5, 12.0, 12.1 and 14.0 + 0x3a => "Flag", //12.0, 12.1 and 14.0 + 0x3b => "FlagStatus", //12.0, 12.1 and 14.0 + 0x3c => "ContentClass", //12.0, 12.1 and 14.0 + 0x3d => "FlagType", //12.0, 12.1 and 14.0 + 0x3e => "CompleteTime", //14.0 + 0x3f => "DisallowNewTimeProposal", //14.0 + ), + 3 => array ( //Code page 3 is no longer in use, however, tokens 05 through 17 have been defined. 20100501 + 0x05 => "Notify", + 0x06 => "Notification", + 0x07 => "Version", + 0x08 => "Lifetime", + 0x09 => "DeviceInfo", + 0x0a => "Enable", + 0x0b => "Folder", + 0x0c => "ServerEntryId", + 0x0d => "DeviceAddress", + 0x0e => "ValidCarrierProfiles", + 0x0f => "CarrierProfile", + 0x10 => "Status", + 0x11 => "Replies", +// 0x05 => "Version='1.1'", + 0x12 => "Devices", + 0x13 => "Device", + 0x14 => "Id", + 0x15 => "Expiry", + 0x16 => "NotifyGUID", + ), + 4 => array ( + 0x05 => "Timezone", //2.5, 12.0, 12.1 and 14.0 + 0x06 => "AllDayEvent", //2.5, 12.0, 12.1 and 14.0 + 0x07 => "Attendees", //2.5, 12.0, 12.1 and 14.0 + 0x08 => "Attendee", //2.5, 12.0, 12.1 and 14.0 + 0x09 => "Email", //Attendee_Email //2.5, 12.0, 12.1 and 14.0 + 0x0a => "Name", //Attendee_Name //2.5, 12.0, 12.1 and 14.0 + 0x0b => "Body", //2.5, but is in code page 17 in ActiveSync versions 12.0, 12.1, and 14.0 + 0x0c => "BodyTruncated", //2.5, 12.0, 12.1 and 14.0 + 0x0d => "BusyStatus", //2.5, 12.0, 12.1 and 14.0 + 0x0e => "Categories", //2.5, 12.0, 12.1 and 14.0 + 0x0f => "Category", //2.5, 12.0, 12.1 and 14.0 + 0x10 => "Rtf", //2.5 + 0x11 => "DtStamp", //2.5, 12.0, 12.1 and 14.0 + 0x12 => "EndTime", //2.5, 12.0, 12.1 and 14.0 + 0x13 => "Exception", //2.5, 12.0, 12.1 and 14.0 + 0x14 => "Exceptions", //2.5, 12.0, 12.1 and 14.0 + 0x15 => "Deleted", //Exception_Deleted //2.5, 12.0, 12.1 and 14.0 + 0x16 => "ExceptionStartTime", //Exception_StartTime //2.5, 12.0, 12.1 and 14.0 + 0x17 => "Location", //2.5, 12.0, 12.1 and 14.0 + 0x18 => "MeetingStatus", //2.5, 12.0, 12.1 and 14.0 + 0x19 => "OrganizerEmail", //Organizer_Email //2.5, 12.0, 12.1 and 14.0 + 0x1a => "OrganizerName", //Organizer_Name //2.5, 12.0, 12.1 and 14.0 + 0x1b => "Recurrence", //2.5, 12.0, 12.1 and 14.0 + 0x1c => "Type", //Recurrence_Type //2.5, 12.0, 12.1 and 14.0 + 0x1d => "Until", //Recurrence_Until //2.5, 12.0, 12.1 and 14.0 + 0x1e => "Occurrences", //Recurrence_Occurrences //2.5, 12.0, 12.1 and 14.0 + 0x1f => "Interval", //Recurrence_Interval //2.5, 12.0, 12.1 and 14.0 + 0x20 => "DayOfWeek", //Recurrence_DayOfWeek //2.5, 12.0, 12.1 and 14.0 + 0x21 => "DayOfMonth", //Recurrence_DayOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x22 => "WeekOfMonth", //Recurrence_WeekOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x23 => "MonthOfYear", //Recurrence_MonthOfYear //2.5, 12.0, 12.1 and 14.0 + 0x24 => "Reminder", //Reminder_MinsBefore //2.5, 12.0, 12.1 and 14.0 + 0x25 => "Sensitivity", //2.5, 12.0, 12.1 and 14.0 + 0x26 => "Subject", //2.5, 12.0, 12.1 and 14.0 + 0x27 => "StartTime", //2.5, 12.0, 12.1 and 14.0 + 0x28 => "UID", //2.5, 12.0, 12.1 and 14.0 + 0x29 => "Attendee_Status", //12.0, 12.1 and 14.0 + 0x2a => "Attendee_Type", //12.0, 12.1 and 14.0 + 0x2b => "Attachment", //12.0, 12.1 and 14.0 + 0x2c => "Attachments", //12.0, 12.1 and 14.0 + 0x2d => "AttName", //12.0, 12.1 and 14.0 + 0x2e => "AttSize", //12.0, 12.1 and 14.0 + 0x2f => "AttOid", //12.0, 12.1 and 14.0 + 0x30 => "AttMethod", //12.0, 12.1 and 14.0 + 0x31 => "AttRemoved", //12.0, 12.1 and 14.0 + 0x32 => "DisplayName", //12.0, 12.1 and 14.0 + 0x33 => "DisallowNewTimeProposal", //14.0 + 0x34 => "ResponseRequested", //14.0 + 0x35 => "AppointmentReplyTime", //14.0 + 0x36 => "ResponseType", //14.0 + 0x37 => "CalendarType", //14.0 + 0x38 => "IsLeapMonth", //14.0 + 0x39 => "FirstDayOfWeek", //post 14.0 20100501 + 0x3a => "OnlineMeetingInternalLink", //post 14.0 20100501 + 0x3b => "OnlineMeetingExternalLink", //post 14.0 20120630 + ), + 5 => array ( + 0x05 => "Moves", + 0x06 => "Move", + 0x07 => "SrcMsgId", + 0x08 => "SrcFldId", + 0x09 => "DstFldId", + 0x0a => "Response", + 0x0b => "Status", + 0x0c => "DstMsgId", + ), + 6 => array ( + 0x05 => "GetItemEstimate", + 0x06 => "Version", //only 12.1 20100501 + 0x07 => "Folders", //Collections + 0x08 => "Folder", //Collection + 0x09 => "FolderType", //Class //only 12.1 //The tag defined in code page 0 should be used in all other instances. 20100501 + 0x0a => "FolderId", //CollectionId + 0x0b => "DateTime", //not supported by 14. only supported 12.1. 20100501 + 0x0c => "Estimate", + 0x0d => "Response", + 0x0e => "Status", + ), + 7 => array ( + 0x05 => "Folders", //2.0 + 0x06 => "Folder", //2.0 + 0x07 => "DisplayName", + 0x08 => "ServerEntryId", //ServerId + 0x09 => "ParentId", + 0x0a => "Type", + 0x0b => "Response", //2.0 + 0x0c => "Status", + 0x0d => "ContentClass", //2.0 + 0x0e => "Changes", + 0x0f => "Add", + 0x10 => "Remove", + 0x11 => "Update", + 0x12 => "SyncKey", + 0x13 => "FolderCreate", + 0x14 => "FolderDelete", + 0x15 => "FolderUpdate", + 0x16 => "FolderSync", + 0x17 => "Count", + 0x18 => "Version", //2.0 - not defined in 20100501 + ), + 8 => array ( + 0x05 => "CalendarId", + 0x06 => "FolderId", //CollectionId + 0x07 => "MeetingResponse", + 0x08 => "RequestId", + 0x09 => "Request", + 0x0a => "Result", + 0x0b => "Status", + 0x0c => "UserResponse", + 0x0d => "Version", //2.0 - not defined in 20100501 + 0x0e => "InstanceId" // first in 20100501 + ), + 9 => array ( + 0x05 => "Body", //2.5, but is in code page 17 in ActiveSync versions 12.0, 12.1, and 14.0 + 0x06 => "BodySize", //2.5, but is in code page 17 as the EstimatedDataSize tag in ActiveSync versions 12.0, 12.1 and 14.0 + 0x07 => "BodyTruncated", //2.5, but is in code page 17 as the Truncated tag in ActiveSync versions 12.0, 12.1, and 14.0 + 0x08 => "Categories", //2.5, 12.0, 12.1 and 14.0 + 0x09 => "Category", //2.5, 12.0, 12.1 and 14.0 + 0x0a => "Complete", //2.5, 12.0, 12.1 and 14.0 + 0x0b => "DateCompleted", //2.5, 12.0, 12.1 and 14.0 + 0x0c => "DueDate", //2.5, 12.0, 12.1 and 14.0 + 0x0d => "UtcDueDate", //2.5, 12.0, 12.1 and 14.0 + 0x0e => "Importance", //2.5, 12.0, 12.1 and 14.0 + 0x0f => "Recurrence", //2.5, 12.0, 12.1 and 14.0 + 0x10 => "Type", //Recurrence_Type //2.5, 12.0, 12.1 and 14.0 + 0x11 => "Start", //Recurrence_Start //2.5, 12.0, 12.1 and 14.0 + 0x12 => "Until", //Recurrence_Until //2.5, 12.0, 12.1 and 14.0 + 0x13 => "Occurrences", //Recurrence_Occurrences //2.5, 12.0, 12.1 and 14.0 + 0x14 => "Interval", //Recurrence_Interval //2.5, 12.0, 12.1 and 14.0 + 0x16 => "DayOfWeek", //Recurrence_DayOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x15 => "DayOfMonth", //Recurrence_DayOfWeek //2.5, 12.0, 12.1 and 14.0 + 0x17 => "WeekOfMonth", //Recurrence_WeekOfMonth //2.5, 12.0, 12.1 and 14.0 + 0x18 => "MonthOfYear", //Recurrence_MonthOfYear //2.5, 12.0, 12.1 and 14.0 + 0x19 => "Regenerate", //Recurrence_Regenerate //2.5, 12.0, 12.1 and 14.0 + 0x1a => "DeadOccur", //Recurrence_DeadOccur //2.5, 12.0, 12.1 and 14.0 + 0x1b => "ReminderSet", //2.5, 12.0, 12.1 and 14.0 + 0x1c => "ReminderTime", //2.5, 12.0, 12.1 and 14.0 + 0x1d => "Sensitivity", //2.5, 12.0, 12.1 and 14.0 + 0x1e => "StartDate", //2.5, 12.0, 12.1 and 14.0 + 0x1f => "UtcStartDate", //2.5, 12.0, 12.1 and 14.0 + 0x20 => "Subject", //2.5, 12.0, 12.1 and 14.0 + 0x21 => "Rtf", //CompressedRTF //2.5, but is in code page 17 as the Type tag in Active Sync versions 12.0, 12.1, and 14.0 + 0x22 => "OrdinalDate", //12.0, 12.1 and 14.0 + 0x23 => "SubOrdinalDate", //12.0, 12.1 and 14.0 + 0x24 => "CalendarType", //14.0 + 0x25 => "IsLeapMonth", //14.0 + 0x26 => "FirstDayOfWeek", // first in 20100501 post 14.0 + ), + 0xa => array ( + 0x05 => "ResolveRecipients", + 0x06 => "Response", + 0x07 => "Status", + 0x08 => "Type", + 0x09 => "Recipient", + 0x0a => "DisplayName", + 0x0b => "EmailAddress", + 0x0c => "Certificates", + 0x0d => "Certificate", + 0x0e => "MiniCertificate", + 0x0f => "Options", + 0x10 => "To", + 0x11 => "CertificateRetrieval", + 0x12 => "RecipientCount", + 0x13 => "MaxCertificates", + 0x14 => "MaxAmbiguousRecipients", + 0x15 => "CertificateCount", + 0x16 => "Availability", //14.0 + 0x17 => "StartTime", //14.0 + 0x18 => "EndTime", //14.0 + 0x19 => "MergedFreeBusy", //14.0 + 0x1A => "Picture", // first in 20100501 post 14.0 + 0x1B => "MaxSize", // first in 20100501 post 14.0 + 0x1C => "Data", // first in 20100501 post 14.0 + 0x1D => "MaxPictures", // first in 20100501 post 14.0 + ), + 0xb => array ( + 0x05 => "ValidateCert", + 0x06 => "Certificates", + 0x07 => "Certificate", + 0x08 => "CertificateChain", + 0x09 => "CheckCRL", + 0x0a => "Status", + ), + 0xc => array ( + 0x05 => "CustomerId", + 0x06 => "GovernmentId", + 0x07 => "IMAddress", + 0x08 => "IMAddress2", + 0x09 => "IMAddress3", + 0x0a => "ManagerName", + 0x0b => "CompanyMainPhone", + 0x0c => "AccountName", + 0x0d => "NickName", + 0x0e => "MMS", + ), + 0xd => array ( + 0x05 => "Ping", + 0x06 => "AutdState", //(Not used by protocol) + 0x07 => "Status", + 0x08 => "LifeTime", //HeartbeatInterval + 0x09 => "Folders", + 0x0a => "Folder", + 0x0b => "ServerEntryId", //Id + 0x0c => "FolderType", //Class + 0x0d => "MaxFolders", + 0x0e => "Version" //not defined in 20100501 + ), + 0xe => array ( + 0x05 => "Provision", //2.5, 12.0, 12.1 and 14.0 + 0x06 => "Policies", //2.5, 12.0, 12.1 and 14.0 + 0x07 => "Policy", //2.5, 12.0, 12.1 and 14.0 + 0x08 => "PolicyType", //2.5, 12.0, 12.1 and 14.0 + 0x09 => "PolicyKey", //2.5, 12.0, 12.1 and 14.0 + 0x0A => "Data", //2.5, 12.0, 12.1 and 14.0 + 0x0B => "Status", //2.5, 12.0, 12.1 and 14.0 + 0x0C => "RemoteWipe", //2.5, 12.0, 12.1 and 14.0 + 0x0D => "EASProvisionDoc", //12.0, 12.1 and 14.0 + 0x0E => "DevicePasswordEnabled", //12.0, 12.1 and 14.0 + 0x0F => "AlphanumericDevicePasswordRequired", //12.0, 12.1 and 14.0 + 0x10 => "DeviceEncryptionEnabled", //12.0, 12.1 and 14.0 + //0x10 => "RequireStorageCardEncryption", //12.1 and 14.0 + 0x11 => "PasswordRecoveryEnabled", //12.0, 12.1 and 14.0 + 0x12 => "DocumentBrowseEnabled", //2.0 and 2.5. + 0x13 => "AttachmentsEnabled", //12.0, 12.1 and 14.0 + 0x14 => "MinDevicePasswordLength", //12.0, 12.1 and 14.0 + 0x15 => "MaxInactivityTimeDeviceLock", //12.0, 12.1 and 14.0 + 0x16 => "MaxDevicePasswordFailedAttempts", //12.0, 12.1 and 14.0 + 0x17 => "MaxAttachmentSize", //12.0, 12.1 and 14.0 + 0x18 => "AllowSimpleDevicePassword", //12.0, 12.1 and 14.0 + 0x19 => "DevicePasswordExpiration", //12.0, 12.1 and 14.0 + 0x1A => "DevicePasswordHistory", //12.0, 12.1 and 14.0 + 0x1B => "AllowStorageCard", //12.1 and 14.0 + 0x1C => "AllowCamera", //12.1 and 14.0 + 0x1D => "RequireDeviceEncryption", //12.1 and 14.0 + 0x1E => "AllowUnsignedApplications", //12.1 and 14.0 + 0x1F => "AllowUnsignedInstallationPackages", //12.1 and 14.0 + 0x20 => "MinDevicePasswordComplexCharacters", //12.1 and 14.0 + 0x21 => "AllowWiFi", //12.1 and 14.0 + 0x22 => "AllowTextMessaging", //12.1 and 14.0 + 0x23 => "AllowPOPIMAPEmail", //12.1 and 14.0 + 0x24 => "AllowBluetooth", //12.1 and 14.0 + 0x25 => "AllowIrDA", //12.1 and 14.0 + 0x26 => "RequireManualSyncWhenRoaming", //12.1 and 14.0 + 0x27 => "AllowDesktopSync", //12.1 and 14.0 + 0x28 => "MaxCalendarAgeFilter", //12.1 and 14.0 + 0x29 => "AllowHTMLEmail", //12.1 and 14.0 + 0x2A => "MaxEmailAgeFilter", //12.1 and 14.0 + 0x2B => "MaxEmailBodyTruncationSize", //12.1 and 14.0 + 0x2C => "MaxEmailHTMLBodyTruncationSize", //12.1 and 14.0 + 0x2D => "RequireSignedSMIMEMessages", //12.1 and 14.0 + 0x2E => "RequireEncryptedSMIMEMessages", //12.1 and 14.0 + 0x2F => "RequireSignedSMIMEAlgorithm", //12.1 and 14.0 + 0x30 => "RequireEncryptionSMIMEAlgorithm", //12.1 and 14.0 + 0x31 => "AllowSMIMEEncryptionAlgorithmNegotiation", //12.1 and 14.0 + 0x32 => "AllowSMIMESoftCerts", //12.1 and 14.0 + 0x33 => "AllowBrowser", //12.1 and 14.0 + 0x34 => "AllowConsumerEmail", //12.1 and 14.0 + 0x35 => "AllowRemoteDesktop", //12.1 and 14.0 + 0x36 => "AllowInternetSharing", //12.1 and 14.0 + 0x37 => "UnapprovedInROMApplicationList", //12.1 and 14.0 + 0x38 => "ApplicationName", //12.1 and 14.0 + 0x39 => "ApprovedApplicationList", //12.1 and 14.0 + 0x3A => "Hash", //12.1 and 14.0 + ), + 0xf => array( + 0x05 => "Search", //12.0, 12.1 and 14.0 + 0x07 => "Store", //12.0, 12.1 and 14.0 + 0x08 => "Name", //12.0, 12.1 and 14.0 + 0x09 => "Query", //12.0, 12.1 and 14.0 + 0x0A => "Options", //12.0, 12.1 and 14.0 + 0x0B => "Range", //12.0, 12.1 and 14.0 + 0x0C => "Status", //12.0, 12.1 and 14.0 + 0x0D => "Response", //12.0, 12.1 and 14.0 + 0x0E => "Result", //12.0, 12.1 and 14.0 + 0x0F => "Properties", //12.0, 12.1 and 14.0 + 0x10 => "Total", //12.0, 12.1 and 14.0 + 0x11 => "EqualTo", //12.0, 12.1 and 14.0 + 0x12 => "Value", //12.0, 12.1 and 14.0 + 0x13 => "And", //12.0, 12.1 and 14.0 + 0x14 => "Or", //14.0 + 0x15 => "FreeText", //12.0, 12.1 and 14.0 + 0x17 => "DeepTraversal", //12.0, 12.1 and 14.0 + 0x18 => "LongId", //12.0, 12.1 and 14.0 + 0x19 => "RebuildResults", //12.0, 12.1 and 14.0 + 0x1A => "LessThan", //12.0, 12.1 and 14.0 + 0x1B => "GreaterThan", //12.0, 12.1 and 14.0 + 0x1C => "Schema", //12.0, 12.1 and 14.0 + 0x1D => "Supported", //12.0, 12.1 and 14.0 + 0x1E => "UserName", //12.1 and 14.0 + 0x1F => "Password", //12.1 and 14.0 + 0x20 => "ConversationId", //14.0 + 0x21 => "Picture", // first in 20100501 post 14.0 + 0x22 => "MaxSize", // first in 20100501 post 14.0 + 0x23 => "MaxPictures", // first in 20100501 post 14.0 + ), + 0x10 => array( + 0x05 => "DisplayName", + 0x06 => "Phone", + 0x07 => "Office", + 0x08 => "Title", + 0x09 => "Company", + 0x0A => "Alias", + 0x0B => "FirstName", + 0x0C => "LastName", + 0x0D => "HomePhone", + 0x0E => "MobilePhone", + 0x0F => "EmailAddress", + 0x10 => "Picture", // first in 20100501 post 14.0 + 0x11 => "Status", // first in 20100501 post 14.0 + 0x12 => "Data", // first in 20100501 post 14.0 + ), + 0x11 => array( //12.0, 12.1 and 14.0 + 0x05 => "BodyPreference", + 0x06 => "Type", + 0x07 => "TruncationSize", + 0x08 => "AllOrNone", + 0x0A => "Body", + 0x0B => "Data", + 0x0C => "EstimatedDataSize", + 0x0D => "Truncated", + 0x0E => "Attachments", + 0x0F => "Attachment", + 0x10 => "DisplayName", + 0x11 => "FileReference", + 0x12 => "Method", + 0x13 => "ContentId", + 0x14 => "ContentLocation", //not used + 0x15 => "IsInline", + 0x16 => "NativeBodyType", + 0x17 => "ContentType", + 0x18 => "Preview", //14.0 + 0x19 => "BodyPartPreference", // first in 20100501 post 14.0 + 0x1A => "BodyPart", // first in 20100501 post 14.0 + 0x1B => "Status", // first in 20100501 post 14.0 + ), + 0x12 => array( //12.0, 12.1 and 14.0 + 0x05 => "Settings", //12.0, 12.1 and 14.0 + 0x06 => "Status", //12.0, 12.1 and 14.0 + 0x07 => "Get", //12.0, 12.1 and 14.0 + 0x08 => "Set", //12.0, 12.1 and 14.0 + 0x09 => "Oof", //12.0, 12.1 and 14.0 + 0x0A => "OofState", //12.0, 12.1 and 14.0 + 0x0B => "StartTime", //12.0, 12.1 and 14.0 + 0x0C => "EndTime", //12.0, 12.1 and 14.0 + 0x0D => "OofMessage", //12.0, 12.1 and 14.0 + 0x0E => "AppliesToInternal", //12.0, 12.1 and 14.0 + 0x0F => "AppliesToExternalKnown", //12.0, 12.1 and 14.0 + 0x10 => "AppliesToExternalUnknown", //12.0, 12.1 and 14.0 + 0x11 => "Enabled", //12.0, 12.1 and 14.0 + 0x12 => "ReplyMessage", //12.0, 12.1 and 14.0 + 0x13 => "BodyType", //12.0, 12.1 and 14.0 + 0x14 => "DevicePassword", //12.0, 12.1 and 14.0 + 0x15 => "Password", //12.0, 12.1 and 14.0 + 0x16 => "DeviceInformaton", //12.0, 12.1 and 14.0 + 0x17 => "Model", //12.0, 12.1 and 14.0 + 0x18 => "IMEI", //12.0, 12.1 and 14.0 + 0x19 => "FriendlyName", //12.0, 12.1 and 14.0 + 0x1A => "OS", //12.0, 12.1 and 14.0 + 0x1B => "OSLanguage", //12.0, 12.1 and 14.0 + 0x1C => "PhoneNumber", //12.0, 12.1 and 14.0 + 0x1D => "UserInformation", //12.0, 12.1 and 14.0 + 0x1E => "EmailAddresses", //12.0, 12.1 and 14.0 + 0x1F => "SmtpAddress", //12.0, 12.1 and 14.0 + 0x20 => "UserAgent", //12.1 and 14.0 + 0x21 => "EnableOutboundSMS", //14.0 + 0x22 => "MobileOperator", //14.0 + 0x23 => "PrimarySmtpAddress", // first in 20100501 post 14.0 + 0x24 => "Accounts", // first in 20100501 post 14.0 + 0x25 => "Account", // first in 20100501 post 14.0 + 0x26 => "AccountId", // first in 20100501 post 14.0 + 0x27 => "AccountName", // first in 20100501 post 14.0 + 0x28 => "UserDisplayName", // first in 20100501 post 14.0 + 0x29 => "SendDisabled", // first in 20100501 post 14.0 + 0x2B => "ihsManagementInformation", // first in 20100501 post 14.0 + ), + 0x13 => array( //12.0, 12.1 and 14.0 + 0x05 => "LinkId", + 0x06 => "DisplayName", + 0x07 => "IsFolder", + 0x08 => "CreationDate", + 0x09 => "LastModifiedDate", + 0x0A => "IsHidden", + 0x0B => "ContentLength", + 0x0C => "ContentType", + ), + 0x14 => array( //12.0, 12.1 and 14.0 + 0x05 => "ItemOperations", + 0x06 => "Fetch", + 0x07 => "Store", + 0x08 => "Options", + 0x09 => "Range", + 0x0A => "Total", + 0x0B => "Properties", + 0x0C => "Data", + 0x0D => "Status", + 0x0E => "Response", + 0x0F => "Version", + 0x10 => "Schema", + 0x11 => "Part", + 0x12 => "EmptyFolderContents", + 0x13 => "DeleteSubFolders", + 0x14 => "UserName", //12.1 and 14.0 + 0x15 => "Password", //12.1 and 14.0 + 0x16 => "Move", //14.0 + 0x17 => "DstFldId", //14.0 + 0x18 => "ConversationId", //14.0 + 0x19 => "MoveAlways", //14.0 + ), + 0x15 => array( //14.0 + 0x05 => "SendMail", + 0x06 => "SmartForward", + 0x07 => "SmartReply", + 0x08 => "SaveInSentItems", + 0x09 => "ReplaceMime", + 0x0A => "Type", + 0x0B => "Source", + 0x0C => "FolderId", + 0x0D => "ItemId", + 0x0E => "LongId", + 0x0F => "InstanceId", + 0x10 => "MIME", + 0x11 => "ClientId", + 0x12 => "Status", + 0x13 => "AccountId", // first in 20100501 post 14.0 + ), + 0x16 => array( // 14.0 + 0x05 => "UmCallerId", + 0x06 => "UmUserNotes", + 0x07 => "UmAttDuration", + 0x08 => "UmAttOrder", + 0x09 => "ConversationId", + 0x0A => "ConversationIndex", + 0x0B => "LastVerbExecuted", + 0x0C => "LastVerbExecutionTime", + 0x0D => "ReceivedAsBcc", + 0x0E => "Sender", + 0x0F => "CalendarType", + 0x10 => "IsLeapMonth", + 0x11 => "AccountId", // first in 20100501 post 14.0 + 0x12 => "FirstDayOfWeek", // first in 20100501 post 14.0 + 0x13 => "MeetingMessageType", // first in 20100501 post 14.0 + ), + 0x17 => array( //14.0 + 0x05 => "Subject", + 0x06 => "MessageClass", + 0x07 => "LastModifiedDate", + 0x08 => "Categories", + 0x09 => "Category", + ), + 0x18 => array( // post 14.0 + 0x05 => "RightsManagementSupport", + 0x06 => "RightsManagementTemplates", + 0x07 => "RightsManagementTemplate", + 0x08 => "RightsManagementLicense", + 0x09 => "EditAllowed", + 0x0A => "ReplyAllowed", + 0x0B => "ReplyAllAllowed", + 0x0C => "ForwardAllowed", + 0x0D => "ModifyRecipientsAllowed", + 0x0E => "ExtractAllowed", + 0x0F => "PrintAllowed", + 0x10 => "ExportAllowed", + 0x11 => "ProgrammaticAccessAllowed", + 0x12 => "RMOwner", + 0x13 => "ContentExpiryDate", + 0x14 => "TemplateID", + 0x15 => "TemplateName", + 0x16 => "TemplateDescription", + 0x17 => "ContentOwner", + 0x18 => "RemoveRightsManagementDistribution", + ), + ), + "namespaces" => array( + //0 => "AirSync", // + 1 => "POOMCONTACTS", + 2 => "POOMMAIL", + 3 => "AirNotify", //no longer used + 4 => "POOMCAL", + 5 => "Move", + 6 => "GetItemEstimate", + 7 => "FolderHierarchy", + 8 => "MeetingResponse", + 9 => "POOMTASKS", + 0xA => "ResolveRecipients", + 0xB => "ValidateCerts", + 0xC => "POOMCONTACTS2", + 0xD => "Ping", + 0xE => "Provision",// + 0xF => "Search",// + 0x10 => "GAL", + 0x11 => "AirSyncBase", //12.0, 12.1 and 14.0 + 0x12 => "Settings", //12.0, 12.1 and 14.0. + 0x13 => "DocumentLibrary", //12.0, 12.1 and 14.0 + 0x14 => "ItemOperations", //12.0, 12.1 and 14.0 + 0x15 => "ComposeMail", //14.0 + 0x16 => "POOMMAIL2", //14.0 + 0x17 => "Notes", //14.0 + 0x18 => "RightsManagement", + ) + ); +} + +?> \ No newline at end of file diff --git a/z-push/lib/wbxml/wbxmlencoder.php b/z-push/lib/wbxml/wbxmlencoder.php new file mode 100644 index 0000000..16652b1 --- /dev/null +++ b/z-push/lib/wbxml/wbxmlencoder.php @@ -0,0 +1,507 @@ +. +* +* Consult LICENSE file for details +************************************************/ + + +class WBXMLEncoder extends WBXMLDefs { + private $_dtd; + private $_out; + + private $_tagcp; + private $_attrcp; + + private $logStack = array(); + + // We use a delayed output mechanism in which we only output a tag when it actually has something + // in it. This can cause entire XML trees to disappear if they don't have output data in them; Ie + // calling 'startTag' 10 times, and then 'endTag' will cause 0 bytes of output apart from the header. + + // Only when content() is called do we output the current stack of tags + + private $_stack; + + private $multipart; // the content is multipart + private $bodyparts; + + public function WBXMLEncoder($output, $multipart = false) { + // make sure WBXML_DEBUG is defined. It should be at this point + if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false); + + $this->_out = $output; + + $this->_tagcp = 0; + $this->_attrcp = 0; + + // reverse-map the DTD + foreach($this->dtd["namespaces"] as $nsid => $nsname) { + $this->_dtd["namespaces"][$nsname] = $nsid; + } + + foreach($this->dtd["codes"] as $cp => $value) { + $this->_dtd["codes"][$cp] = array(); + foreach($this->dtd["codes"][$cp] as $tagid => $tagname) { + $this->_dtd["codes"][$cp][$tagname] = $tagid; + } + } + $this->_stack = array(); + $this->multipart = $multipart; + $this->bodyparts = array(); + } + + /** + * Puts the WBXML header on the stream + * + * @access public + * @return + */ + public function startWBXML() { + if ($this->multipart) { + header("Content-Type: application/vnd.ms-sync.multipart"); + ZLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.multipart"); + } + else { + header("Content-Type: application/vnd.ms-sync.wbxml"); + ZLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->startWBXML() type: vnd.ms-sync.wbxml"); + } + + $this->outByte(0x03); // WBXML 1.3 + $this->outMBUInt(0x01); // Public ID 1 + $this->outMBUInt(106); // UTF-8 + $this->outMBUInt(0x00); // string table length (0) + } + + /** + * Puts a StartTag on the output stack + * + * @param $tag + * @param $attributes + * @param $nocontent + * + * @access public + * @return + */ + public function startTag($tag, $attributes = false, $nocontent = false) { + $stackelem = array(); + + if(!$nocontent) { + $stackelem['tag'] = $tag; + $stackelem['attributes'] = $attributes; + $stackelem['nocontent'] = $nocontent; + $stackelem['sent'] = false; + + array_push($this->_stack, $stackelem); + + // If 'nocontent' is specified, then apparently the user wants to force + // output of an empty tag, and we therefore output the stack here + } else { + $this->_outputStack(); + $this->_startTag($tag, $attributes, $nocontent); + } + } + + /** + * Puts an EndTag on the stack + * + * @access public + * @return + */ + public function endTag() { + $stackelem = array_pop($this->_stack); + + // Only output end tags for items that have had a start tag sent + if($stackelem['sent']) { + $this->_endTag(); + + if(count($this->_stack) == 0) + ZLog::Write(LOGLEVEL_DEBUG, "WBXMLEncoder->endTag() WBXML output completed"); + + if(count($this->_stack) == 0 && $this->multipart == true) { + $this->processMultipart(); + } + } + } + + /** + * Puts content on the output stack + * + * @param $content + * + * @access public + * @return string + */ + public function content($content) { + // We need to filter out any \0 chars because it's the string terminator in WBXML. We currently + // cannot send \0 characters within the XML content anywhere. + $content = str_replace("\0","",$content); + + if("x" . $content == "x") + return; + $this->_outputStack(); + $this->_content($content); + } + + /** + * Gets the value of multipart + * + * @access public + * @return boolean + */ + public function getMultipart() { + return $this->multipart; + } + + /** + * Adds a bodypart + * + * @param Stream $bp + * + * @access public + * @return void + */ + public function addBodypartStream($bp) { + if ($this->multipart) + $this->bodyparts[] = $bp; + } + + /** + * Gets the number of bodyparts + * + * @access public + * @return int + */ + public function getBodypartsCount() { + return count($this->bodyparts); + } + + /**---------------------------------------------------------------------------------------------------------- + * Private WBXMLEncoder stuff + */ + + /** + * Output any tags on the stack that haven't been output yet + * + * @access private + * @return + */ + private function _outputStack() { + for($i=0;$i_stack);$i++) { + if(!$this->_stack[$i]['sent']) { + $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']); + $this->_stack[$i]['sent'] = true; + } + } + } + + /** + * Outputs an actual start tag + * + * @access private + * @return + */ + private function _startTag($tag, $attributes = false, $nocontent = false) { + $this->logStartTag($tag, $attributes, $nocontent); + + $mapping = $this->getMapping($tag); + + if(!$mapping) + return false; + + if($this->_tagcp != $mapping["cp"]) { + $this->outSwitchPage($mapping["cp"]); + $this->_tagcp = $mapping["cp"]; + } + + $code = $mapping["code"]; + if(isset($attributes) && is_array($attributes) && count($attributes) > 0) { + $code |= 0x80; + } + + if(!isset($nocontent) || !$nocontent) + $code |= 0x40; + + $this->outByte($code); + + if($code & 0x80) + $this->outAttributes($attributes); + } + + /** + * Outputs actual data + * + * @access private + * @return + */ + private function _content($content) { + $this->logContent($content); + $this->outByte(WBXML_STR_I); + $this->outTermStr($content); + } + + /** + * Outputs an actual end tag + * + * @access private + * @return + */ + private function _endTag() { + $this->logEndTag(); + $this->outByte(WBXML_END); + } + + /** + * Outputs a byte + * + * @param $byte + * + * @access private + * @return + */ + private function outByte($byte) { + fwrite($this->_out, chr($byte)); + } + + /** + * Outputs a string table + * + * @param $uint + * + * @access private + * @return + */ + private function outMBUInt($uint) { + while(1) { + $byte = $uint & 0x7f; + $uint = $uint >> 7; + if($uint == 0) { + $this->outByte($byte); + break; + } else { + $this->outByte($byte | 0x80); + } + } + } + + /** + * Outputs content with string terminator + * + * @param $content + * + * @access private + * @return + */ + private function outTermStr($content) { + fwrite($this->_out, $content); + fwrite($this->_out, chr(0)); + } + + /** + * Output attributes + * We don't actually support this, because to do so, we would have + * to build a string table before sending the data (but we can't + * because we're streaming), so we'll just send an END, which just + * terminates the attribute list with 0 attributes. + * + * @access private + * @return + */ + private function outAttributes() { + $this->outByte(WBXML_END); + } + + /** + * Switches the codepage + * + * @param $page + * + * @access private + * @return + */ + private function outSwitchPage($page) { + $this->outByte(WBXML_SWITCH_PAGE); + $this->outByte($page); + } + + /** + * Get the mapping for a tag + * + * @param $tag + * + * @access private + * @return array + */ + private function getMapping($tag) { + $mapping = array(); + + $split = $this->splitTag($tag); + + if(isset($split["ns"])) { + $cp = $this->_dtd["namespaces"][$split["ns"]]; + } + else { + $cp = 0; + } + + $code = $this->_dtd["codes"][$cp][$split["tag"]]; + + $mapping["cp"] = $cp; + $mapping["code"] = $code; + + return $mapping; + } + + /** + * Split a tag from a the fulltag (namespace + tag) + * + * @param $fulltag + * + * @access private + * @return array keys: 'ns' (namespace), 'tag' (tag) + */ + private function splitTag($fulltag) { + $ns = false; + $pos = strpos($fulltag, chr(58)); // chr(58) == ':' + + if($pos) { + $ns = substr($fulltag, 0, $pos); + $tag = substr($fulltag, $pos+1); + } + else { + $tag = $fulltag; + } + + $ret = array(); + if($ns) + $ret["ns"] = $ns; + $ret["tag"] = $tag; + + return $ret; + } + + /** + * Logs a StartTag to ZLog + * + * @param $tag + * @param $attr + * @param $nocontent + * + * @access private + * @return + */ + private function logStartTag($tag, $attr, $nocontent) { + if(!WBXML_DEBUG) + return; + + $spaces = str_repeat(" ", count($this->logStack)); + if($nocontent) + ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag/>"); + else { + array_push($this->logStack, $tag); + ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . " <$tag>"); + } + } + + /** + * Logs a EndTag to ZLog + * + * @access private + * @return + */ + private function logEndTag() { + if(!WBXML_DEBUG) + return; + + $spaces = str_repeat(" ", count($this->logStack)); + $tag = array_pop($this->logStack); + ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . ""); + } + + /** + * Logs content to ZLog + * + * @param $content + * + * @access private + * @return + */ + private function logContent($content) { + if(!WBXML_DEBUG) + return; + + $spaces = str_repeat(" ", count($this->logStack)); + ZLog::Write(LOGLEVEL_WBXML,"O " . $spaces . $content); + } + + /** + * Processes the multipart response + * + * @access private + * @return void + */ + private function processMultipart() { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXMLEncoder->processMultipart() with %d parts to be processed", $this->getBodypartsCount())); + $len = ob_get_length(); + $buffer = ob_get_clean(); + $nrBodyparts = $this->getBodypartsCount(); + $blockstart = (($nrBodyparts + 1) * 2) * 4 + 4; + + $data = pack("iii", ($nrBodyparts + 1), $blockstart, $len); + + ob_start(null, 1048576); + + foreach ($this->bodyparts as $bp) { + $blockstart = $blockstart + $len; + $len = fstat($bp); + $len = (isset($len['size'])) ? $len['size'] : 0; + $data .= pack("ii", $blockstart, $len); + } + + fwrite($this->_out, $data); + fwrite($this->_out, $buffer); + foreach($this->bodyparts as $bp) { + while (!feof($bp)) { + fwrite($this->_out, fread($bp, 4096)); + } + } + } +} + +?> \ No newline at end of file diff --git a/z-push/lib/webservice/webservice.php b/z-push/lib/webservice/webservice.php new file mode 100644 index 0000000..a366173 --- /dev/null +++ b/z-push/lib/webservice/webservice.php @@ -0,0 +1,80 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +class Webservice { + private $server; + + /** + * Handles a webservice command + * + * @param int $commandCode + * + * @access public + * @return boolean + * @throws SoapFault + */ + public function Handle($commandCode) { + if (Request::GetDeviceType() !== "webservice" || Request::GetDeviceID() !== "webservice") + throw new FatalException("Invalid device id and type for webservice execution"); + + if (Request::GetGETUser() != Request::GetAuthUser()) + ZLog::Write(LOGLEVEL_INFO, sprintf("Webservice::HandleWebservice('%s'): user '%s' executing action for user '%s'", $commandCode, Request::GetAuthUser(), Request::GetGETUser())); + + // initialize non-wsdl soap server + $this->server = new SoapServer(null, array('uri' => "http://z-push.sf.net/webservice")); + + // the webservice command is handled by its class + if ($commandCode == ZPush::COMMAND_WEBSERVICE_DEVICE) { + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceDevice service", $commandCode)); + + include_once('webservicedevice.php'); + $this->server->setClass("WebserviceDevice"); + } + $this->server->handle(); + + ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): sucessfully sent %d bytes", $commandCode, ob_get_length())); + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/lib/webservice/webservicedevice.php b/z-push/lib/webservice/webservicedevice.php new file mode 100644 index 0000000..723db60 --- /dev/null +++ b/z-push/lib/webservice/webservicedevice.php @@ -0,0 +1,135 @@ +. +* +* Consult LICENSE file for details +************************************************/ +include ('lib/utils/zpushadmin.php'); + +class WebserviceDevice { + + /** + * Returns a list of all known devices of the Request::GetGETUser() + * + * @access public + * @return array + */ + public function ListDevicesDetails() { + $user = Request::GetGETUser(); + $devices = ZPushAdmin::ListDevices($user); + $output = array(); + + ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::ListDevicesDetails(): found %d devices of user '%s'", count($devices), $user)); + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d devices", count($devices)), true); + + foreach ($devices as $devid) + $output[] = ZPushAdmin::GetDeviceDetails($devid, $user); + + return $output; + } + + /** + * Remove all state data for a device of the Request::GetGETUser() + * + * @param string $deviceId the device id + * + * @access public + * @return boolean + * @throws SoapFault + */ + public function RemoveDevice($deviceId) { + $deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId); + ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::RemoveDevice('%s'): remove device state data of user '%s'", $deviceId, Request::GetGETUser())); + + if (! ZPushAdmin::RemoveDevice(Request::GetGETUser(), $deviceId)) { + ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true); + throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR)); + } + + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Removed device id '%s'", $deviceId), true); + return true; + } + + /** + * Marks a device of the Request::GetGETUser() to be remotely wiped + * + * @param string $deviceId the device id + * + * @access public + * @return boolean + * @throws SoapFault + */ + public function WipeDevice($deviceId) { + $deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId); + ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::WipeDevice('%s'): mark device of user '%s' for remote wipe", $deviceId, Request::GetGETUser())); + + if (! ZPushAdmin::WipeDevice(Request::GetAuthUser(), Request::GetGETUser(), $deviceId)) { + ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true); + throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR)); + } + + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Wipe requested - device id '%s'", $deviceId), true); + return true; + } + + /** + * Marks a a device of the Request::GetGETUser() for resynchronization + * + * @param string $deviceId the device id + * + * @access public + * @return boolean + * @throws SoapFault + */ + public function ResyncDevice($deviceId) { + $deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId); + ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::ResyncDevice('%s'): mark device of user '%s' for resynchronization", $deviceId, Request::GetGETUser())); + + if (! ZPushAdmin::ResyncDevice(Request::GetGETUser(), $deviceId)) { + ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true); + throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR)); + } + + ZPush::GetTopCollector()->AnnounceInformation(sprintf("Resync requested - device id '%s'", $deviceId), true); + return true; + } +} +?> \ No newline at end of file diff --git a/z-push/logs/empty.txt b/z-push/logs/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/z-push/states/empty.txt b/z-push/states/empty.txt new file mode 100644 index 0000000..e69de29 diff --git a/z-push/version.php b/z-push/version.php new file mode 100644 index 0000000..f7ba586 --- /dev/null +++ b/z-push/version.php @@ -0,0 +1,46 @@ +. +* +* Consult LICENSE file for details +************************************************/ + +define("ZPUSH_VERSION", "2.0.4-1497"); + +?> \ No newline at end of file diff --git a/z-push/z-push-admin.php b/z-push/z-push-admin.php new file mode 100644 index 0000000..45185c7 --- /dev/null +++ b/z-push/z-push-admin.php @@ -0,0 +1,604 @@ +#!/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; + + 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" . + "\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; + + // 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_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 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"; + } + } + + } +} + + +?> \ No newline at end of file diff --git a/z-push/z-push-top.php b/z-push/z-push-top.php new file mode 100644 index 0000000..a3c305e --- /dev/null +++ b/z-push/z-push-top.php @@ -0,0 +1,768 @@ +#!/usr/bin/php +. +* +* Consult LICENSE file for details +************************************************/ + +include('lib/exceptions/exceptions.php'); +include('lib/core/zpushdefs.php'); +include('lib/core/zpush.php'); +include('lib/core/zlog.php'); +include('lib/core/interprocessdata.php'); +include('lib/core/topcollector.php'); +include('lib/utils/utils.php'); +include('lib/request/request.php'); +include('lib/request/requestprocessor.php'); +include('config.php'); +include('version.php'); + +/************************************************ + * MAIN + */ + declare(ticks = 1); + define('BASE_PATH_CLI', dirname(__FILE__) ."/"); + + try { + ZPush::CheckConfig(); + if (!function_exists("pcntl_signal")) + throw new FatalException("Function pcntl_signal() is not available. Please install package 'php5-pcntl' (or similar) on your system."); + + $zpt = new ZPushTop(); + + // check if help was requested from CLI + if (in_array('-h', $argv) || in_array('--help', $argv)) { + echo $zpt->UsageInstructions(); + exit(1); + } + + if ($zpt->IsAvailable()) { + pcntl_signal(SIGINT, array($zpt, "SignalHandler")); + $zpt->run(); + $zpt->scrClear(); + } + else + echo "Z-Push shared memory interprocess communication is not available.\n"; + } + catch (ZPushException $zpe) { + die(get_class($zpe) . ": ". $zpe->getMessage() . "\n"); + } + + echo "terminated\n"; + + +/************************************************ + * Z-Push-Top + */ +class ZPushTop { + // show options + const SHOW_DEFAULT = 0; + const SHOW_ACTIVE_ONLY = 1; + const SHOW_UNKNOWN_ONLY = 2; + const SHOW_TERM_DEFAULT_TIME = 5; // 5 secs + + private $topCollector; + private $starttime; + private $action; + private $filter; + private $status; + private $statusexpire; + private $wide; + private $wasEnabled; + private $terminate; + private $scrSize; + private $pingInterval; + private $showPush; + private $showTermSec; + + private $linesActive = array(); + private $linesOpen = array(); + private $linesUnknown = array(); + private $linesTerm = array(); + private $pushConn = 0; + private $activeConn = array(); + private $activeHosts = array(); + private $activeUsers = array(); + private $activeDevices = array(); + + /** + * Constructor + * + * @access public + */ + public function ZPushTop() { + $this->starttime = time(); + $this->currenttime = time(); + $this->action = ""; + $this->filter = false; + $this->status = false; + $this->statusexpire = 0; + $this->helpexpire = 0; + $this->doingTail = false; + $this->wide = false; + $this->terminate = false; + $this->showPush = true; + $this->showOption = self::SHOW_DEFAULT; + $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME; + $this->scrSize = array('width' => 80, 'height' => 24); + $this->pingInterval = (defined('PING_INTERVAL') && PING_INTERVAL > 0) ? PING_INTERVAL : 12; + + // get a TopCollector + $this->topCollector = new TopCollector(); + } + + /** + * Requests data from the running Z-Push processes + * + * @access private + * @return + */ + private function initialize() { + // request feedback from active processes + $this->wasEnabled = $this->topCollector->CollectData(); + + // remove obsolete data + $this->topCollector->ClearLatest(true); + + // start with default colours + $this->scrDefaultColors(); + } + + /** + * Main loop of Z-Push-top + * Runs until termination is requested + * + * @access public + * @return + */ + public function run() { + $this->initialize(); + + do { + $this->currenttime = time(); + + // see if shared memory is active + if (!$this->IsAvailable()) + $this->terminate = true; + + // active processes should continue sending data + $this->topCollector->CollectData(); + + // get and process data from processes + $this->topCollector->ClearLatest(); + $topdata = $this->topCollector->ReadLatest(); + $this->processData($topdata); + + // clear screen + $this->scrClear(); + + // check if screen size changed + $s = $this->scrGetSize(); + if ($this->scrSize['width'] != $s['width']) { + if ($s['width'] > 180) + $this->wide = true; + else + $this->wide = false; + } + $this->scrSize = $s; + + // print overview + $this->scrOverview(); + + // wait for user input + $this->readLineProcess(); + } + while($this->terminate != true); + } + + /** + * Indicates if TopCollector is available collecting data + * + * @access public + * @return boolean + */ + public function IsAvailable() { + return $this->topCollector->IsActive(); + } + + /** + * Processes data written by the running processes + * + * @param array $data + * + * @access private + * @return + */ + private function processData($data) { + $this->linesActive = array(); + $this->linesOpen = array(); + $this->linesUnknown = array(); + $this->linesTerm = array(); + $this->pushConn = 0; + $this->activeConn = array(); + $this->activeHosts = array(); + $this->activeUsers = array(); + $this->activeDevices = array(); + + if (!is_array($data)) + return; + + foreach ($data as $devid=>$users) { + foreach ($users as $user=>$pids) { + foreach ($pids as $pid=>$line) { + if (!is_array($line)) + continue; + + $line['command'] = Utils::GetCommandFromCode($line['command']); + + if ($line["ended"] == 0) { + $this->activeDevices[$devid] = 1; + $this->activeUsers[$user] = 1; + $this->activeConn[$pid] = 1; + $this->activeHosts[$line['ip']] = 1; + + $line["time"] = $this->currenttime - $line['start']; + if ($line['push'] === true) $this->pushConn += 1; + + // ignore push connections + if ($line['push'] === true && ! $this->showPush) + continue; + + if ($this->filter !== false) { + $f = $this->filter; + if (!($line["pid"] == $f || $line["ip"] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) || + preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) )) + continue; + } + + $lastUpdate = $this->currenttime - $line["update"]; + if ($this->currenttime - $line["update"] < 2) + $this->linesActive[$line["update"].$line["pid"]] = $line; + else if (($line['push'] === true && $lastUpdate > ($this->pingInterval+2)) || ($line['push'] !== true && $lastUpdate > 4)) + $this->linesUnknown[$line["update"].$line["pid"]] = $line; + else + $this->linesOpen[$line["update"].$line["pid"]] = $line; + } + else { + // do not show terminated + expired connections + if ($line['ended'] + $this->showTermSec < $this->currenttime) + continue; + + if ($this->filter !== false) { + $f = $this->filter; + if (!($line['pid'] == $f || $line['ip'] == $f || strtolower($line['command']) == strtolower($f) || preg_match("/.*?$f.*?/i", $line['user']) || + preg_match("/.*?$f.*?/i", $line['devagent']) || preg_match("/.*?$f.*?/i", $line['devid']) || preg_match("/.*?$f.*?/i", $line['addinfo']) )) + continue; + } + + $line['time'] = $line['ended'] - $line['start']; + $this->linesTerm[$line['update'].$line['pid']] = $line; + } + } + } + } + + // sort by execution time + krsort($this->linesActive); + krsort($this->linesOpen); + krsort($this->linesUnknown); + krsort($this->linesTerm); + } + + /** + * Prints data to the terminal + * + * @access private + * @return + */ + private function scrOverview() { + $linesAvail = $this->scrSize['height'] - 8; + $lc = 1; + $this->scrPrintAt($lc,0, "\033[1mZ-Push top live statistics\033[0m\t\t\t\t\t". @strftime("%d/%m/%Y %T")."\n"); $lc++; + + $this->scrPrintAt($lc,0, sprintf("Open connections: %d\t\t\t\tUsers:\t %d\tZ-Push: %s ",count($this->activeConn),count($this->activeUsers), $this->getVersion())); $lc++; + $this->scrPrintAt($lc,0, sprintf("Push connections: %d\t\t\t\tDevices: %d\tPHP-MAPI: %s", $this->pushConn, count($this->activeDevices),phpversion("mapi"))); $lc++; + $this->scrPrintAt($lc,0, sprintf(" Hosts:\t %d", $this->pushConn, count($this->activeHosts))); $lc++; + $lc++; + + $this->scrPrintAt($lc,0, "\033[4m". $this->getLine(array('pid'=>'PID', 'ip'=>'IP', 'user'=>'USER', 'command'=>'COMMAND', 'time'=>'TIME', 'devagent'=>'AGENT', 'devid'=>'DEVID', 'addinfo'=>'Additional Information')). str_repeat(" ",20)."\033[0m"); $lc++; + + // print help text if requested + $hl = 0; + if ($this->helpexpire > $this->currenttime) { + $help = $this->scrHelp(); + $linesAvail -= count($help); + $hl = $this->scrSize['height'] - count($help) -1; + foreach ($help as $h) { + $this->scrPrintAt($hl,0, $h); + $hl++; + } + } + + $toPrintActive = $linesAvail; + $toPrintOpen = $linesAvail; + $toPrintUnknown = $linesAvail; + $toPrintTerm = $linesAvail; + + // default view: show all unknown, no terminated and half active+open + if (count($this->linesActive) + count($this->linesOpen) + count($this->linesUnknown) > $linesAvail) { + $toPrintUnknown = count($this->linesUnknown); + $toPrintActive = count($this->linesActive); + $toPrintOpen = $linesAvail-$toPrintUnknown-$toPrintActive; + $toPrintTerm = 0; + } + + if ($this->showOption == self::SHOW_ACTIVE_ONLY) { + $toPrintActive = $linesAvail; + $toPrintOpen = 0; + $toPrintUnknown = 0; + $toPrintTerm = 0; + } + + if ($this->showOption == self::SHOW_UNKNOWN_ONLY) { + $toPrintActive = 0; + $toPrintOpen = 0; + $toPrintUnknown = $linesAvail; + $toPrintTerm = 0; + } + + $linesprinted = 0; + foreach ($this->linesActive as $time=>$l) { + if ($linesprinted >= $toPrintActive) + break; + + $this->scrPrintAt($lc,0, "\033[01m" . $this->getLine($l) ."\033[0m"); + $lc++; + $linesprinted++; + } + + $linesprinted = 0; + foreach ($this->linesOpen as $time=>$l) { + if ($linesprinted >= $toPrintOpen) + break; + + $this->scrPrintAt($lc,0, $this->getLine($l)); + $lc++; + $linesprinted++; + } + + $linesprinted = 0; + foreach ($this->linesUnknown as $time=>$l) { + if ($linesprinted >= $toPrintUnknown) + break; + + $color = "0;31m"; + if ($l['push'] == false && $time - $l["start"] > 30) + $color = "1;31m"; + $this->scrPrintAt($lc,0, "\033[0". $color . $this->getLine($l) ."\033[0m"); + $lc++; + $linesprinted++; + } + + if ($toPrintTerm > 0) + $toPrintTerm = $linesAvail - $lc +6; + + $linesprinted = 0; + foreach ($this->linesTerm as $time=>$l){ + if ($linesprinted >= $toPrintTerm) + break; + + $this->scrPrintAt($lc,0, "\033[01;30m" . $this->getLine($l) ."\033[0m"); + $lc++; + $linesprinted++; + } + + // add the lines used when displaying the help text + $lc += $hl; + $this->scrPrintAt($lc,0, "\033[K"); $lc++; + $this->scrPrintAt($lc,0, "Colorscheme: \033[01mActive \033[0mOpen \033[01;31mUnknown \033[01;30mTerminated\033[0m"); + + // remove old status + if ($this->statusexpire < $this->currenttime) + $this->status = false; + + // show request information and help command + if ($this->starttime + 6 > $this->currenttime) { + $this->status = sprintf("Requesting information (takes up to %dsecs)", $this->pingInterval). str_repeat(".", ($this->currenttime-$this->starttime)) . " type \033[01;31mh\033[00;31m or \033[01;31mhelp\033[00;31m for usage instructions"; + $this->statusexpire = $this->currenttime+1; + } + + + $str = ""; + if (! $this->showPush) + $str .= "\033[00;32mPush: \033[01;32mNo\033[0m "; + + if ($this->showOption == self::SHOW_ACTIVE_ONLY) + $str .= "\033[01;32mActive only\033[0m "; + + if ($this->showOption == self::SHOW_UNKNOWN_ONLY) + $str .= "\033[01;32mUnknown only\033[0m "; + + if ($this->showTermSec != self::SHOW_TERM_DEFAULT_TIME) + $str .= "\033[01;32mTerminated: ". $this->showTermSec. "s\033[0m "; + + if ($this->filter !== false || ($this->status !== false && $this->statusexpire > $this->currenttime)) { + // print filter in green + if ($this->filter !== false) + $str .= "\033[00;32mFilter: \033[01;32m$this->filter\033[0m "; + // print status in red + if ($this->status !== false) + $str .= "\033[00;31m$this->status\033[0m"; + } + $this->scrPrintAt(5,0, $str); + + $this->scrPrintAt(4,0,"Action: \033[01m".$this->action . "\033[0m"); + } + + /** + * Waits for a keystroke and processes the requested command + * + * @access private + * @return + */ + private function readLineProcess() { + $ans = explode("^^", `bash -c "read -n 1 -t 1 ANS ; echo \\\$?^^\\\$ANS;"`); + + if ($ans[0] < 128) { + if (isset($ans[1]) && bin2hex(trim($ans[1])) == "7f") { + $this->action = substr($this->action,0,-1); + } + + if (isset($ans[1]) && $ans[1] != "" ){ + $this->action .= trim(preg_replace("/[^A-Za-z0-9:]/","",$ans[1])); + } + + if (bin2hex($ans[0]) == "30" && bin2hex($ans[1]) == "0a") { + $cmds = explode(':', $this->action); + if ($cmds[0] == "quit" || $cmds[0] == "q" || (isset($cmds[1]) && $cmds[0] == "" && $cmds[1] == "q")) { + $this->topCollector->CollectData(true); + $this->topCollector->ClearLatest(true); + + $this->terminate = true; + } + else if ($cmds[0] == "clear" ) { + $this->topCollector->ClearLatest(true); + $this->topCollector->CollectData(true); + $this->topCollector->ReInitSharedMem(); + } + else if ($cmds[0] == "filter" || $cmds[0] == "f") { + if (!isset($cmds[1]) || $cmds[1] == "") { + $this->filter = false; + $this->status = "No filter"; + $this->statusexpire = $this->currenttime+5; + } + else { + $this->filter = $cmds[1]; + $this->status = false; + } + } + else if ($cmds[0] == "option" || $cmds[0] == "o") { + if (!isset($cmds[1]) || $cmds[1] == "") { + $this->status = sprintf("Option value needs to be specified. See 'help' or 'h' for instructions", $cmds[1]); + $this->statusexpire = $this->currenttime+5; + } + else if ($cmds[1] == "p" || $cmds[1] == "push" || $cmds[1] == "ping") + $this->showPush = !$this->showPush; + else if ($cmds[1] == "a" || $cmds[1] == "active") + $this->showOption = self::SHOW_ACTIVE_ONLY; + else if ($cmds[1] == "u" || $cmds[1] == "unknown") + $this->showOption = self::SHOW_UNKNOWN_ONLY; + else if ($cmds[1] == "d" || $cmds[1] == "default") { + $this->showOption = self::SHOW_DEFAULT; + $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME; + $this->showPush = true; + } + else if (is_numeric($cmds[1])) + $this->showTermSec = $cmds[1]; + else { + $this->status = sprintf("Option '%s' unknown", $cmds[1]); + $this->statusexpire = $this->currenttime+5; + } + } + else if ($cmds[0] == "reset" || $cmds[0] == "r") { + $this->filter = false; + $this->wide = false; + $this->helpexpire = 0; + $this->status = "resetted"; + $this->statusexpire = $this->currenttime+2; + } + else if ($cmds[0] == "wide" || $cmds[0] == "w") { + $this->wide = true; + $this->status = "w i d e view"; + $this->statusexpire = $this->currenttime+2; + } + else if ($cmds[0] == "help" || $cmds[0] == "h") { + $this->helpexpire = $this->currenttime+20; + } + else if (($cmds[0] == "log" || $cmds[0] == "l") && isset($cmds[1]) ) { + if (!file_exists(LOGFILE)) { + $this->status = "Logfile can not be found: ". LOGFILE; + } + else { + system('bash -c "fgrep -a '.escapeshellarg($cmds[1]).' '. LOGFILE .' | less +G" > `tty`'); + $this->status = "Returning from log, updating data"; + } + $this->statusexpire = time()+5; // it might be much "later" now + } + else if (($cmds[0] == "tail" || $cmds[0] == "t")) { + if (!file_exists(LOGFILE)) { + $this->status = "Logfile can not be found: ". LOGFILE; + } + else { + $this->doingTail = true; + $this->scrClear(); + $this->scrPrintAt(1,0,$this->scrAsBold("Press CTRL+C to return to Z-Push-Top\n\n")); + $secondary = ""; + if (isset($cmds[1])) $secondary = " -n 200 | grep ".escapeshellarg($cmds[1]); + system('bash -c "tail -f '. LOGFILE . $secondary . '" > `tty`'); + $this->doingTail = false; + $this->status = "Returning from tail, updating data"; + } + $this->statusexpire = time()+5; // it might be much "later" now + } + + else if ($cmds[0] != "") { + $this->status = sprintf("Command '%s' unknown", $cmds[0]); + $this->statusexpire = $this->currenttime+8; + } + $this->action = ""; + } + } + } + + /** + * Signal handler function + * + * @param int $signo signal number + * + * @access public + * @return + */ + public function SignalHandler($signo) { + // don't terminate if the signal was sent by terminating tail + if (!$this->doingTail) { + $this->topCollector->CollectData(true); + $this->topCollector->ClearLatest(true); + $this->terminate = true; + } + } + + /** + * Returns usage instructions + * + * @return string + * @access public + */ + public function UsageInstructions() { + $help = "Usage:\n\tz-push-top.php\n\n" . + " Z-Push-Top is a live top-like overview of what Z-Push is doing. It does not have specific command line options.\n\n". + " When Z-Push-Top is running you can specify certain actions and options which can be executed (listed below).\n". + " This help information can also be shown inside Z-Push-Top by hitting 'help' or 'h'.\n\n"; + $scrhelp = $this->scrHelp(); + unset($scrhelp[0]); + + $help .= implode("\n", $scrhelp); + $help .= "\n\n"; + return $help; + } + + + /** + * Prints a 'help' text at the end of the page + * + * @access private + * @return array with help lines + */ + private function scrHelp() { + $h = array(); + $secs = $this->helpexpire - $this->currenttime; + $h[] = "Actions supported by Z-Push-Top (help page still displayed for ".$secs."secs)"; + $h[] = " ".$this->scrAsBold("Action")."\t\t".$this->scrAsBold("Comment"); + $h[] = " ".$this->scrAsBold("h")." or ".$this->scrAsBold("help")."\t\tDisplays this information."; + $h[] = " ".$this->scrAsBold("q").", ".$this->scrAsBold("quit")." or ".$this->scrAsBold(":q")."\t\tExits Z-Push-Top."; + $h[] = " ".$this->scrAsBold("w")." or ".$this->scrAsBold("wide")."\t\tTries not to truncate data. Automatically done if more than 180 columns available."; + $h[] = " ".$this->scrAsBold("f:VAL")." or ".$this->scrAsBold("filter:VAL")."\tOnly display connections which contain VAL. This value is case-insensitive."; + $h[] = " ".$this->scrAsBold("f:")." or ".$this->scrAsBold("filter:")."\t\tWithout a search word: resets the filter."; + $h[] = " ".$this->scrAsBold("l:STR")." or ".$this->scrAsBold("log:STR")."\tIssues 'less +G' on the logfile, after grepping on the optional STR."; + $h[] = " ".$this->scrAsBold("t:STR")." or ".$this->scrAsBold("tail:STR")."\tIssues 'tail -f' on the logfile, grepping for optional STR."; + $h[] = " ".$this->scrAsBold("r")." or ".$this->scrAsBold("reset")."\t\tResets 'wide' or 'filter'."; + $h[] = " ".$this->scrAsBold("o:")." or ".$this->scrAsBold("option:")."\t\tSets display options. Valid options specified below"; + $h[] = " ".$this->scrAsBold(" p")." or ".$this->scrAsBold("push")."\t\tLists/not lists active and open push connections."; + $h[] = " ".$this->scrAsBold(" a")." or ".$this->scrAsBold("action")."\t\tLists only active connections."; + $h[] = " ".$this->scrAsBold(" u")." or ".$this->scrAsBold("unknown")."\tLists only unknown connections."; + $h[] = " ".$this->scrAsBold(" 10")." or ".$this->scrAsBold("20")."\t\tLists terminated connections for 10 or 20 seconds. Any other number can be used."; + $h[] = " ".$this->scrAsBold(" d")." or ".$this->scrAsBold("default")."\tUses default options"; + + return $h; + } + + /** + * Encapsulates string with different color escape characters + * + * @param string $text + * + * @access private + * @return string same text as bold + */ + private function scrAsBold($text) { + return "\033[01m" . $text ."\033[0m"; + } + + /** + * Prints one line of precessed data + * + * @param array $l line information + * + * @access private + * @return string + */ + private function getLine($l) { + if ($this->wide === true) + return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],16), $this->ptStr($l['user'],24), $this->ptStr($l['command'],16), $this->ptStr($this->sec2min($l['time']),8), $this->ptStr($l['devagent'],28), $this->ptStr($l['devid'],30, true), $l['addinfo']); + else + return sprintf("%s%s%s%s%s%s%s%s", $this->ptStr($l['pid'],6), $this->ptStr($l['ip'],10), $this->ptStr($l['user'],8), $this->ptStr($l['command'],11), $this->ptStr($this->sec2min($l['time']),6), $this->ptStr($l['devagent'],20), $this->ptStr($l['devid'],12, true), $l['addinfo']); + } + + /** + * Pads and trims string + * + * @param string $string to be trimmed/padded + * @param int $size characters to be considered + * @param boolean $cutmiddle (optional) indicates where to long information should + * be trimmed of, false means at the end + * + * @access private + * @return string + */ + private function ptStr($str, $size, $cutmiddle = false) { + if (strlen($str) < $size) + return str_pad($str, $size); + else if ($cutmiddle == true) { + $cut = ($size-2)/2; + return $this->ptStr(substr($str,0,$cut) ."..". substr($str,(-1)*($cut-1)), $size); + } + else { + return substr($str,0,$size-3).".. "; + } + } + + /** + * Tries to discover the size of the current terminal + * + * @access private + * @return array 'width' and 'height' as keys + */ + private function scrGetSize() { + preg_match_all("/rows.([0-9]+);.columns.([0-9]+);/", strtolower(exec('stty -a | fgrep columns')), $output); + if(sizeof($output) == 3) + return array('width' => $output[2][0], 'height' => $output[1][0]); + + return array('width' => 80, 'height' => 24); + } + + /** + * Returns the version of the current Z-Push installation + * + * @access private + * @return string + */ + private function getVersion() { + if (ZPUSH_VERSION == "SVN checkout" && file_exists(REAL_BASE_PATH.".svn/entries")) { + $svn = file(REAL_BASE_PATH.".svn/entries"); + return "SVN " . substr(trim($svn[4]),stripos($svn[4],"z-push")+7) ." r".trim($svn[3]); + } + return ZPUSH_VERSION; + } + + /** + * Converts seconds in MM:SS + * + * @param int $s seconds + * + * @access private + * @return string + */ + private function sec2min($s) { + if (!is_int($s)) + return $s; + return sprintf("%02.2d:%02.2d", floor($s/60), $s%60); + } + + /** + * Resets the default colors of the terminal + * + * @access private + * @return + */ + private function scrDefaultColors() { + echo "\033[0m"; + } + + /** + * Clears screen of the terminal + * + * @param array $data + * + * @access private + * @return + */ + public function scrClear() { + echo "\033[2J"; + } + + /** + * Prints a text at a specific screen/terminal coordinates + * + * @param int $row row number + * @param int $col column number + * @param string $text to be printed + * + * @access private + * @return + */ + private function scrPrintAt($row, $col, $text="") { + echo "\033[".$row.";".$col."H".$text; + } + +} + +?> \ No newline at end of file