summaryrefslogtreecommitdiff
path: root/webmail/plugins/carddav
diff options
context:
space:
mode:
Diffstat (limited to 'webmail/plugins/carddav')
-rw-r--r--webmail/plugins/carddav/.gitignore1
-rw-r--r--webmail/plugins/carddav/CHANGELOG81
-rw-r--r--webmail/plugins/carddav/LICENSE661
-rw-r--r--webmail/plugins/carddav/README61
-rw-r--r--webmail/plugins/carddav/SQL/mysql.sql35
-rw-r--r--webmail/plugins/carddav/SQL/mysql.update.sql17
-rw-r--r--webmail/plugins/carddav/SQL/postgresql.sql28
-rw-r--r--webmail/plugins/carddav/carddav.php619
-rw-r--r--webmail/plugins/carddav/carddav_addressbook.js43
-rw-r--r--webmail/plugins/carddav/carddav_addressbook.php1038
-rw-r--r--webmail/plugins/carddav/carddav_backend.php556
-rw-r--r--webmail/plugins/carddav/carddav_settings.js88
-rw-r--r--webmail/plugins/carddav/cronjob/.htaccess1
-rw-r--r--webmail/plugins/carddav/cronjob/synchronize.php136
-rw-r--r--webmail/plugins/carddav/jquery.base64.js142
-rw-r--r--webmail/plugins/carddav/localization/de_DE.inc26
-rw-r--r--webmail/plugins/carddav/localization/en_US.inc26
-rw-r--r--webmail/plugins/carddav/localization/fr_FR.inc21
-rw-r--r--webmail/plugins/carddav/localization/it_IT.inc21
-rw-r--r--webmail/plugins/carddav/package.xml80
-rw-r--r--webmail/plugins/carddav/skins/default/carddav.css57
-rw-r--r--webmail/plugins/carddav/skins/default/checked.pngbin0 -> 298 bytes
-rw-r--r--webmail/plugins/carddav/skins/default/sync_act.pngbin0 -> 1924 bytes
-rw-r--r--webmail/plugins/carddav/skins/default/sync_pas.pngbin0 -> 1968 bytes
-rw-r--r--webmail/plugins/carddav/skins/larry/carddav.css57
-rw-r--r--webmail/plugins/carddav/skins/larry/checked.pngbin0 -> 298 bytes
-rw-r--r--webmail/plugins/carddav/skins/larry/sync_act.pngbin0 -> 1724 bytes
-rw-r--r--webmail/plugins/carddav/skins/larry/sync_pas.pngbin0 -> 1686 bytes
28 files changed, 3795 insertions, 0 deletions
diff --git a/webmail/plugins/carddav/.gitignore b/webmail/plugins/carddav/.gitignore
new file mode 100644
index 0000000..7e9e3af
--- /dev/null
+++ b/webmail/plugins/carddav/.gitignore
@@ -0,0 +1 @@
+config.inc.php
diff --git a/webmail/plugins/carddav/CHANGELOG b/webmail/plugins/carddav/CHANGELOG
new file mode 100644
index 0000000..4b66b96
--- /dev/null
+++ b/webmail/plugins/carddav/CHANGELOG
@@ -0,0 +1,81 @@
+Changes from v0.5 to v0.5.1
+- Added PostgreSQL support (Thanks to B5r1oJ0A9G for the PostgreSQL statements!)
+
+Changes from v0.4 to v0.5
+- Added automaticly synchronized CardDAV contacts via cronjob
+- Added larry skin support
+- Added list of CardDAV server URLs
+- Added read only option for CardDAV servers
+- Added SOGo support
+- Added package.xml
+- Major standard skin UI improvements
+- CardDAV backend class update to v0.5.1
+- Minor comment, phpdoc and documentation changes
+
+Changes from v0.3.1 to v0.4
+- Added add functionality for CardDAV contacts
+- Added vCard import functionality for CardDAV addressbooks
+- License change from LGPLv2 to AGPLv3
+- CardDAV backend class update to v0.4.9
+- Added logging functionality
+- Each CardDAV server is now an own addressbook not a group of a global CardDAV addressbook like before
+- Minor bugfixes
+- Improved synchronization
+
+Changes from v0.3 to v0.3.1
+- Bugfix: didn't merged vCards on edit correctly
+
+Changes from v0.2.5 to v0.3
+- Added edit and delete functionality for CardDAV contacts
+- Added sabreDAV support
+- Added ownCloud support
+- CardDAV backend class update to v0.4.6
+- Restructured and cleaned carddav_addressbook class (not completly done yet)
+- Minor improvements (CURL install check, usability improvements, sync don't add empty vCards now, ...)
+- Added IT and FR language files
+
+Changes from v0.2.4 to v0.2.5
+- Bugfix: email and name can now be null
+- Ajax POST contents are now base64 encoded
+- Username and password can now be empty
+- Minor localization changes
+
+Changes from v0.2.3 to v0.2.4
+- Bugfix: last_modified date is now STRING instead of INT
+- CardDAV backend class update to v0.4.2
+
+Changes from v0.2.2 to v0.2.3
+- Added memotoo support
+- CardDAV-Backend class update to v0.4.1
+- Added addressbook search functionality
+- Changed version naming
+- Restructured carddav_addressbook class
+
+Changes from 0.2.1 to 0.2.2
+- CardDAV backend class update to v0.4
+- Added apple addressbook server support
+
+Changes from 0.2 to 0.2.1
+- Show CardDAV contacts list (addressbook pagination) bugfix
+- Autocomplete CardDAV contacts groups bugfix
+- CardDAV groups will now be displayed correctly in the CardDAV contact detail view
+- Hide CardDAV contacts sync button if no CardDAV servers were added
+- Fallback skin path added "skins/default"
+- Minor localization changes
+
+Changes from 0.1 to 0.2
+- Save / delete CardDAV server settings via ajax
+- Minor class structure changes
+- Minor localization changes
+- Minor database changes
+- Initial CardDAV contacts sync
+- Addressbook integration (read only)
+- Manuel CardDAV contacts sync (read only)
+- CardDAV backend class update to v0.3.4
+- Autocomplete CardDAV contacts
+
+release 0.1
+- Save / delete CardDAV server settings
+- Realtime CardDAV server check
+- Multiple CardDAV server for each user
+- English and German localization \ No newline at end of file
diff --git a/webmail/plugins/carddav/LICENSE b/webmail/plugins/carddav/LICENSE
new file mode 100644
index 0000000..2def0e8
--- /dev/null
+++ b/webmail/plugins/carddav/LICENSE
@@ -0,0 +1,661 @@
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>. \ No newline at end of file
diff --git a/webmail/plugins/carddav/README b/webmail/plugins/carddav/README
new file mode 100644
index 0000000..467fa5e
--- /dev/null
+++ b/webmail/plugins/carddav/README
@@ -0,0 +1,61 @@
+Description
+-----------
+This is a CardDAV-Implementation for roundcube 0.6 or higher.
+
+
+Features
+--------
+* Add multiple CardDAV server for each user
+* CardDAV contacts are stored in the local database which provides great performance
+* Tested CardDAV servers: DAViCal, Apple Addressbook Server, meetoo, SabreDAV, ownCloud, SOGo
+* You can read / add / delete / edit CardDAV contacts (vCards)
+* Autocomplete all CardDAV contacts within the compose email view
+* Search for all CardDAV contacts within the addressbook
+* Automaticly synchronized CardDAV contacts (just execute /plugins/carddav/cronjob/synchronize.php within the crontab)
+
+
+Planned features
+----------------
+* Improved search for CardDAV contacts within the addressbook
+
+
+Requirements
+------------
+* MySQL or PostgreSQL
+* CURL
+
+
+Installation
+------------
+* Execute SQL statements from /plugins/carddav/SQL/yourDatabase.sql
+* Add 'carddav' to the plugins array in /config/main.inc.php
+* Copy /plugins/carddav/config.inc.php.dist to /plugins/carddav/config.inc.php
+* Login into your roundcube webmail and add your CardDAV server in the settings
+
+
+Update
+------
+* Execute new SQL statements from /plugins/carddav/SQL/yourDatabase.update.sql
+
+
+Special thanks
+--------------
+* B5r1oJ0A9G for the PostgreSQL statements!
+
+
+CardDAV server list
+-------------------
+* DAViCal: https://example.com/{resource|principal|username}/{collection}/
+* Apple Addressbook Server: https://example.com/addressbooks/users/{resource|principal|username}/{collection}/
+* memotoo: https://sync.memotoo.com/cardDAV/
+* SabreDAV: https://example.com/addressbooks/{resource|principal|username}/{collection}/
+* ownCloud: https://example.com/apps/contacts/carddav.php/addressbooks/{resource|principal|username}/{collection}/
+* SOGo: https://example.com/SOGo/dav/{resource|principal|username}/Contacts/{collection}/
+
+
+Contact
+-------
+* Author: Christian Putzke <christian.putzke@graviox.de>
+* Report feature requests and bugs here: https://github.com/graviox/Roundcube-CardDAV/issues
+* Visit Graviox Studios: http://www.graviox.de/
+* Follow me on Twitter: https://twitter.com/graviox/ \ No newline at end of file
diff --git a/webmail/plugins/carddav/SQL/mysql.sql b/webmail/plugins/carddav/SQL/mysql.sql
new file mode 100644
index 0000000..d0882d8
--- /dev/null
+++ b/webmail/plugins/carddav/SQL/mysql.sql
@@ -0,0 +1,35 @@
+CREATE TABLE IF NOT EXISTS `carddav_contacts` (
+ `carddav_contact_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `carddav_server_id` int(10) unsigned NOT NULL,
+ `user_id` int(10) unsigned NOT NULL,
+ `etag` varchar(64) NOT NULL,
+ `last_modified` varchar(128) NOT NULL,
+ `vcard_id` varchar(64) NOT NULL,
+ `vcard` longtext NOT NULL,
+ `words` text,
+ `firstname` varchar(128) DEFAULT NULL,
+ `surname` varchar(128) DEFAULT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `email` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`carddav_contact_id`),
+ UNIQUE KEY `carddav_server_id` (`carddav_server_id`,`user_id`,`vcard_id`),
+ KEY `user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+CREATE TABLE IF NOT EXISTS `carddav_server` (
+ `carddav_server_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `user_id` int(10) unsigned NOT NULL,
+ `url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
+ `username` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
+ `password` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
+ `label` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
+ `read_only` tinyint(1) NOT NULL,
+ PRIMARY KEY (`carddav_server_id`),
+ KEY `user_id` (`user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;
+
+ALTER TABLE `carddav_contacts`
+ ADD CONSTRAINT `carddav_contacts_ibfk_1` FOREIGN KEY (`carddav_server_id`) REFERENCES `carddav_server` (`carddav_server_id`) ON DELETE CASCADE;
+
+ALTER TABLE `carddav_server`
+ ADD CONSTRAINT `carddav_server_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE CASCADE; \ No newline at end of file
diff --git a/webmail/plugins/carddav/SQL/mysql.update.sql b/webmail/plugins/carddav/SQL/mysql.update.sql
new file mode 100644
index 0000000..0a3a05d
--- /dev/null
+++ b/webmail/plugins/carddav/SQL/mysql.update.sql
@@ -0,0 +1,17 @@
+// updates from version 0.2.3
+ALTER TABLE `carddav_contacts` ADD `last_modified` int(10) unsigned NOT NULL AFTER `etag` ;
+
+// updates from version 0.2.4
+ALTER TABLE `carddav_contacts` CHANGE `last_modified` `last_modified` VARCHAR(128) NOT NULL ;
+
+// updates from version 0.2.5
+ALTER TABLE `carddav_contacts` CHANGE `name` `name` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ,
+CHANGE `email` `email` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ;
+
+// updates from version 0.3
+ALTER TABLE `carddav_contacts` ADD `words` TEXT NULL DEFAULT NULL AFTER `vcard` ,
+ADD `firstname` VARCHAR( 128 ) NULL DEFAULT NULL AFTER `words` ,
+ADD `surname` VARCHAR( 128 ) NULL DEFAULT NULL AFTER `firstname` ;
+
+// updates from version 0.5
+ALTER TABLE `carddav_server` ADD `read_only` tinyint(1) NOT NULL AFTER `label`; \ No newline at end of file
diff --git a/webmail/plugins/carddav/SQL/postgresql.sql b/webmail/plugins/carddav/SQL/postgresql.sql
new file mode 100644
index 0000000..75c79a7
--- /dev/null
+++ b/webmail/plugins/carddav/SQL/postgresql.sql
@@ -0,0 +1,28 @@
+CREATE TABLE IF NOT EXISTS "carddav_server" (
+ "carddav_server_id" serial,
+ "user_id" int NOT NULL REFERENCES "users" ON DELETE CASCADE,
+ "url" varchar(255) NOT NULL,
+ "username" varchar(128) NOT NULL,
+ "password" varchar(128) NOT NULL,
+ "label" varchar(128) NOT NULL,
+ "read_only" int NOT NULL,
+ PRIMARY KEY ("carddav_server_id")
+);
+
+CREATE TABLE IF NOT EXISTS "carddav_contacts" (
+ "carddav_contact_id" serial,
+ "carddav_server_id" int REFERENCES "carddav_server" ON DELETE CASCADE,
+ "user_id" int,
+ "etag" varchar(64) NOT NULL,
+ "last_modified" varchar(128) NOT NULL,
+ "vcard_id" varchar(64),
+ "vcard" text NOT NULL,
+ "words" text,
+ "firstname" varchar(128) DEFAULT NULL,
+ "surname" varchar(128) DEFAULT NULL,
+ "name" varchar(255) DEFAULT NULL,
+ "email" varchar(255) DEFAULT NULL,
+ PRIMARY KEY ("carddav_server_id","user_id","vcard_id")
+);
+
+CREATE INDEX "user_id" ON "carddav_contacts" ("user_id"); \ No newline at end of file
diff --git a/webmail/plugins/carddav/carddav.php b/webmail/plugins/carddav/carddav.php
new file mode 100644
index 0000000..3c40012
--- /dev/null
+++ b/webmail/plugins/carddav/carddav.php
@@ -0,0 +1,619 @@
+<?php
+
+/**
+ * Include required CardDAV classes
+ */
+require_once dirname(__FILE__) . '/carddav_backend.php';
+require_once dirname(__FILE__) . '/carddav_addressbook.php';
+
+/**
+ * Roundcube CardDAV implementation
+ *
+ * This is a CardDAV implementation for roundcube 0.6 or higher. It allows every user to add
+ * multiple CardDAV server in their settings. The CardDAV contacts (vCards) will be synchronized
+ * automaticly with their addressbook.
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @since 06.09.2011
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @version 0.5.1
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+
+class carddav extends rcube_plugin
+{
+ /**
+ * Roundcube CardDAV version
+ *
+ * @constant string
+ */
+ const VERSION = '0.5.1';
+
+ /**
+ * Tasks where the CardDAV plugin is loaded
+ *
+ * @var string
+ */
+ public $task = 'settings|addressbook|mail';
+
+ /**
+ * CardDAV addressbook
+ *
+ * @var string
+ */
+ protected $carddav_addressbook = 'carddav_addressbook';
+
+ /**
+ * Init CardDAV plugin - register actions, include scripts, load texts, add hooks
+ *
+ * @return void
+ */
+ public function init()
+ {
+ $rcmail = rcmail::get_instance();
+ $skin_path = $this->local_skin_path();
+
+ if (!is_dir($skin_path))
+ {
+ $skin_path = 'skins/default';
+ }
+
+ $this->add_texts('localization/', true);
+ $this->include_stylesheet($skin_path . '/carddav.css');
+
+ switch ($rcmail->task)
+ {
+ case 'settings':
+ $this->register_action('plugin.carddav-server', array($this, 'carddav_server'));
+ $this->register_action('plugin.carddav-server-save', array($this, 'carddav_server_save'));
+ $this->register_action('plugin.carddav-server-delete', array($this, 'carddav_server_delete'));
+ $this->include_script('carddav_settings.js');
+ $this->include_script('jquery.base64.js');
+ break;
+
+ case 'addressbook':
+ if ($this->carddav_server_available())
+ {
+ $this->register_action('plugin.carddav-addressbook-sync', array($this, 'carddav_addressbook_sync'));
+ $this->include_script('carddav_addressbook.js');
+ $this->add_hook('addressbooks_list', array($this, 'get_carddav_addressbook_sources'));
+ $this->add_hook('addressbook_get', array($this, 'get_carddav_addressbook'));
+
+ $this->add_button(array(
+ 'command' => 'plugin.carddav-addressbook-sync',
+ 'imagepas' => $skin_path . '/sync_pas.png',
+ 'imageact' => $skin_path . '/sync_act.png',
+ 'width' => 32,
+ 'height' => 32,
+ 'title' => 'carddav.addressbook_sync'
+ ), 'toolbar');
+ }
+ break;
+
+ case 'mail':
+ if ($this->carddav_server_available())
+ {
+ $this->add_hook('addressbooks_list', array($this, 'get_carddav_addressbook_sources'));
+ $this->add_hook('addressbook_get', array($this, 'get_carddav_addressbook'));
+
+ $sources = (array) $rcmail->config->get('autocomplete_addressbooks', array('sql'));
+ $servers = $this->get_carddav_server();
+
+ foreach ($servers as $server)
+ {
+ if (!in_array($this->carddav_addressbook . $server['carddav_server_id'], $sources))
+ {
+ $sources[] = $this->carddav_addressbook . $server['carddav_server_id'];
+ $rcmail->config->set('autocomplete_addressbooks', $sources);
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ /**
+ * Extend the original local_skin_path method with the default skin path as fallback
+ *
+ * @param boolean $include_plugins_directory Include plugins directory
+ * @return string $skin_path Roundcubes skin path
+ */
+ public function local_skin_path($include_plugins_directory = false)
+ {
+ $skin_path = parent::local_skin_path();
+
+ if (!is_dir($skin_path))
+ {
+ $skin_path = 'skins/default';
+ }
+
+ if ($include_plugins_directory === true)
+ {
+ $skin_path = 'plugins/carddav/' . $skin_path;
+ }
+
+ return $skin_path;
+ }
+
+ /**
+ * Get all CardDAV servers from the current user
+ *
+ * @param boolean $carddav_server_id CardDAV server id to load a single CardDAV server
+ * @return array CardDAV server array with label, url, username, password (encrypted)
+ */
+ public function get_carddav_server($carddav_server_id = false)
+ {
+ $servers = array();
+ $rcmail = rcmail::get_instance();
+ $user_id = $rcmail->user->data['user_id'];
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_server')."
+ WHERE
+ user_id = ?
+ ".($carddav_server_id !== false ? " AND carddav_server_id = ?" : null)."
+ ";
+
+ $result = $rcmail->db->query($query, $user_id, $carddav_server_id);
+
+ while($server = $rcmail->db->fetch_assoc($result))
+ {
+ $servers[] = $server;
+ }
+
+ return $servers;
+ }
+
+ /**
+ * Render available CardDAV server list in HTML
+ *
+ * @return string HTML rendered CardDAV server list
+ */
+ protected function get_carddav_server_list()
+ {
+ $servers = $this->get_carddav_server();
+
+ if (!empty($servers))
+ {
+ $skin_path = $this->local_skin_path(true);
+ $content = html::div(array('class' => 'carddav_headline'), $this->gettext('settings_server'));
+ $table = new html_table(array(
+ 'cols' => 6,
+ 'class' => 'carddav_server_list'
+ ));
+
+ $table->add_header(array('width' => '13%'), $this->gettext('settings_label'));
+ $table->add_header(array('width' => '36%'), $this->gettext('server'));
+ $table->add_header(array('width' => '13%'), $this->gettext('username'));
+ $table->add_header(array('width' => '13%'), $this->gettext('password'));
+ $table->add_header(array('width' => '13%'), $this->gettext('settings_read_only'));
+ $table->add_header(array('width' => '13%'), null);
+
+ foreach ($servers as $server)
+ {
+ // $rcmail->output->button() seems not to work within ajax requests so we build the button manually
+ $delete_submit = '<input
+ type="button"
+ value="'.$this->gettext('delete').'"
+ onclick="return rcmail.command(\'plugin.carddav-server-delete\', \''.$server['carddav_server_id'].'\', this)"
+ class="button mainaction"
+ />';
+
+ $table->add(array(), $server['label']);
+ $table->add(array(), $server['url']);
+ $table->add(array(), $server['username']);
+ $table->add(array(), '**********');
+ $table->add(array(), ($server['read_only'] ? html::img($skin_path . '/checked.png') : null));
+ $table->add(array(), $delete_submit);
+ }
+
+ $content .= html::div(array('class' => 'carddav_container'), $table->show());
+ return $content;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Get CardDAV addressbook instance
+ *
+ * @param array $addressbook Array with all available addressbooks
+ * @return array Array with all available addressbooks
+ */
+ public function get_carddav_addressbook($addressbook)
+ {
+ $servers = $this->get_carddav_server();
+
+ foreach ($servers as $server)
+ {
+ if ($addressbook['id'] === $this->carddav_addressbook . $server['carddav_server_id'])
+ {
+ $addressbook['instance'] = new carddav_addressbook($server['carddav_server_id'], $server['label'], ($server['read_only'] == 1 ? true : false));
+ }
+ }
+
+ return $addressbook;
+ }
+
+ /**
+ * Get CardDAV addressbook source
+ *
+ * @param array $addressbook Array with all available addressbooks sources
+ * @return array Array with all available addressbooks sources
+ */
+ public function get_carddav_addressbook_sources($addressbook)
+ {
+ $servers = $this->get_carddav_server();
+
+ foreach ($servers as $server)
+ {
+ $carddav_addressbook = new carddav_addressbook($server['carddav_server_id'], $server['label'], ($server['read_only'] == 1 ? true : false));
+
+ $addressbook['sources'][$this->carddav_addressbook . $server['carddav_server_id']] = array(
+ 'id' => $this->carddav_addressbook . $server['carddav_server_id'],
+ 'name' => $server['label'],
+ 'readonly' => $carddav_addressbook->readonly,
+ 'groups' => $carddav_addressbook->groups
+ );
+ }
+
+ return $addressbook;
+ }
+
+ /**
+ * Check if CURL is installed or not
+ *
+ * @return boolean
+ */
+ private function check_curl_installed()
+ {
+ if (function_exists('curl_init'))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Synchronize CardDAV addressbook
+ *
+ * @param boolean $carddav_server_id CardDAV server id to synchronize a single CardDAV server
+ * @param boolean $ajax Within a ajax request or not
+ * @return void
+ */
+ public function carddav_addressbook_sync($carddav_server_id = false, $ajax = true)
+ {
+ $servers = $this->get_carddav_server();
+ $result = false;
+
+ foreach ($servers as $server)
+ {
+ if ($carddav_server_id === false || $carddav_server_id == $server['carddav_server_id'])
+ {
+ $carddav_addressbook = new carddav_addressbook($server['carddav_server_id'], $server['label'], ($server['read_only'] == 1 ? true : false));
+ $result = $carddav_addressbook->carddav_addressbook_sync($server);
+ }
+ }
+
+ if ($ajax === true)
+ {
+ $rcmail = rcmail::get_instance();
+
+ if ($result === true)
+ {
+ $rcmail->output->command('plugin.carddav_addressbook_message', array(
+ 'message' => $this->gettext('addressbook_synced'),
+ 'check' => true
+ ));
+ }
+ else
+ {
+ $rcmail->output->command('plugin.carddav_addressbook_message', array(
+ 'message' => $this->gettext('addressbook_sync_failed'),
+ 'check' => false
+ ));
+ }
+ }
+ }
+
+ /**
+ * Render CardDAV server settings
+ *
+ * @return void
+ */
+ public function carddav_server()
+ {
+ $rcmail = rcmail::get_instance();
+ $this->register_handler('plugin.body', array($this, 'carddav_server_form'));
+ $rcmail->output->set_pagetitle($this->gettext('settings'));
+ $rcmail->output->send('plugin');
+ }
+
+ /**
+ * Check if CardDAV server are available in the local database
+ *
+ * @return boolean If CardDAV-Server are available in the local database return true else false
+ */
+ protected function carddav_server_available()
+ {
+ $rcmail = rcmail::get_instance();
+ $user_id = $rcmail->user->data['user_id'];
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_server')."
+ WHERE
+ user_id = ?
+ ";
+
+ $result = $rcmail->db->query($query, $user_id);
+
+ if ($rcmail->db->num_rows($result))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Check if it's possible to connect to the CardDAV server
+ *
+ * @return boolean
+ */
+ public function carddav_server_check_connection()
+ {
+ $url = parse_input_value(base64_decode($_POST['_server_url']));
+ $username = parse_input_value(base64_decode($_POST['_username']));
+ $password = parse_input_value(base64_decode($_POST['_password']));
+
+ $carddav_backend = new carddav_backend($url);
+ $carddav_backend->set_auth($username, $password);
+
+ return $carddav_backend->check_connection();
+ }
+
+ /**
+ * Render CardDAV server settings formular and register JavaScript actions
+ *
+ * @return string HTML CardDAV server formular
+ */
+ public function carddav_server_form()
+ {
+ $rcmail = rcmail::get_instance();
+ $boxcontent = null;
+
+ if ($this->check_curl_installed())
+ {
+ $input_label = new html_inputfield(array(
+ 'name' => '_label',
+ 'id' => '_label',
+ 'size' => '17'
+ ));
+
+ $input_server_url = new html_inputfield(array(
+ 'name' => '_server_url',
+ 'id' => '_server_url',
+ 'size' => '44'
+ ));
+
+ $input_username = new html_inputfield(array(
+ 'name' => '_username',
+ 'id' => '_username',
+ 'size' => '17'
+ ));
+
+ $input_password = new html_passwordfield(array(
+ 'name' => '_password',
+ 'id' => '_password',
+ 'size' => '17'
+ ));
+
+ $input_read_only = new html_checkbox(array(
+ 'name' => '_read_only',
+ 'id' => '_read_only',
+ 'value' => 1
+ ));
+
+ $input_submit = $rcmail->output->button(array(
+ 'command' => 'plugin.carddav-server-save',
+ 'type' => 'input',
+ 'class' => 'button mainaction',
+ 'label' => 'save'
+ ));
+
+ $table = new html_table(array(
+ 'cols' => 6,
+ 'class' => 'carddav_server_list'
+ ));
+
+ $table->add_header(array('width' => '13%'), $this->gettext('settings_label'));
+ $table->add_header(array('width' => '35%'), $this->gettext('server'));
+ $table->add_header(array('width' => '13%'), $this->gettext('username'));
+ $table->add_header(array('width' => '13%'), $this->gettext('password'));
+ $table->add_header(array('width' => '13%'), $this->gettext('settings_read_only'));
+ $table->add_header(array('width' => '13%'), null);
+
+ $table->add(array(), $input_label->show());
+ $table->add(array(), $input_server_url->show());
+ $table->add(array(), $input_username->show());
+ $table->add(array(), $input_password->show());
+ $table->add(array(), $input_read_only->show());
+ $table->add(array(), $input_submit);
+
+ $boxcontent .= html::div(array('class' => 'carddav_headline'), $this->gettext('settings_server_form'));
+ $boxcontent .= html::div(array('class' => 'carddav_container'), $table->show());;
+ }
+ else
+ {
+ $rcmail->output->show_message($this->gettext('settings_curl_not_installed'), 'error');
+ }
+
+ $output = html::div(
+ array('class' => 'box carddav'),
+ html::div(array('class' => 'boxtitle'), $this->gettext('settings')).
+ html::div(array('class' => 'boxcontent', 'id' => 'carddav_server_list'), $this->get_carddav_server_list()).
+ html::div(array('class' => 'boxcontent'), $boxcontent).
+ html::div(array('class' => 'boxcontent'), $this->get_carddav_url_list())
+ );
+
+ return $output;
+ }
+
+ /**
+ * Render a CardDAV server example URL list
+ *
+ * @return string $content HTML CardDAV server example URL list
+ */
+ public function get_carddav_url_list()
+ {
+ $content = null;
+
+ $table = new html_table(array(
+ 'cols' => 2,
+ 'class' => 'carddav_server_list'
+ ));
+
+ $table->add(array(), 'DAViCal');
+ $table->add(array(), 'https://example.com/{resource|principal|username}/{collection}/');
+
+ $table->add(array(), 'Apple Addressbook Server');
+ $table->add(array(), 'https://example.com/addressbooks/users/{resource|principal|username}/{collection}/');
+
+ $table->add(array(), 'memotoo');
+ $table->add(array(), 'https://sync.memotoo.com/cardDAV/');
+
+ $table->add(array(), 'SabreDAV');
+ $table->add(array(), 'https://example.com/addressbooks/{resource|principal|username}/{collection}/');
+
+ $table->add(array(), 'ownCloud');
+ $table->add(array(), 'https://example.com/apps/contacts/carddav.php/addressbooks/{resource|principal|username}/{collection}/');
+
+ $table->add(array(), 'SOGo');
+ $table->add(array(), 'https://example.com/SOGo/dav/{resource|principal|username}/Contacts/{collection}/');
+
+ $content .= html::div(array('class' => 'carddav_headline example_server_list'), $this->gettext('settings_example_server_list'));
+ $content .= html::div(array('class' => 'carddav_container'), $table->show());
+
+ return $content;
+ }
+
+ /**
+ * Save CardDAV server and execute first CardDAV contact sync
+ *
+ * @return void
+ */
+ public function carddav_server_save()
+ {
+ $rcmail = rcmail::get_instance();
+
+ if ($this->carddav_server_check_connection())
+ {
+ $user_id = $rcmail->user->data['user_id'];
+ $url = parse_input_value(base64_decode($_POST['_server_url']));
+ $username = parse_input_value(base64_decode($_POST['_username']));
+ $password = parse_input_value(base64_decode($_POST['_password']));
+ $label = parse_input_value(base64_decode($_POST['_label']));
+ $read_only = (int) parse_input_value(base64_decode($_POST['_read_only']));
+
+ $query = "
+ INSERT INTO
+ ".get_table_name('carddav_server')." (user_id, url, username, password, label, read_only)
+ VALUES
+ (?, ?, ?, ?, ?, ?)
+ ";
+
+ $rcmail->db->query($query, $user_id, $url, $username, $rcmail->encrypt($password), $label, $read_only);
+
+ if ($rcmail->db->affected_rows())
+ {
+ $this->carddav_addressbook_sync($rcmail->db->insert_id(), false);
+
+ $rcmail->output->command('plugin.carddav_server_message', array(
+ 'server_list' => $this->get_carddav_server_list(),
+ 'message' => $this->gettext('settings_saved'),
+ 'check' => true
+ ));
+ }
+ else
+ {
+ $rcmail->output->command('plugin.carddav_server_message', array(
+ 'message' => $this->gettext('settings_save_failed'),
+ 'check' => false
+ ));
+ }
+ }
+ else
+ {
+ $rcmail->output->command('plugin.carddav_server_message', array(
+ 'message' => $this->gettext('settings_no_connection'),
+ 'check' => false
+ ));
+ }
+ }
+
+ /**
+ * Delete CardDAV server and all related local contacts
+ *
+ * @return void
+ */
+ public function carddav_server_delete()
+ {
+ $rcmail = rcmail::get_instance();
+ $user_id = $rcmail->user->data['user_id'];
+ $carddav_server_id = parse_input_value(base64_decode($_POST['_carddav_server_id']));
+
+ $query = "
+ DELETE FROM
+ ".get_table_name('carddav_server')."
+ WHERE
+ user_id = ?
+ AND
+ carddav_server_id = ?
+ ";
+
+ $rcmail->db->query($query, $user_id, $carddav_server_id);
+
+ if ($rcmail->db->affected_rows())
+ {
+ $rcmail->output->command('plugin.carddav_server_message', array(
+ 'server_list' => $this->get_carddav_server_list(),
+ 'message' => $this->gettext('settings_deleted'),
+ 'check' => true
+ ));
+ }
+ else
+ {
+ $rcmail->output->command('plugin.carddav_server_message', array(
+ 'message' => $this->gettext('settings_delete_failed'),
+ 'check' => false
+ ));
+ }
+ }
+
+ /**
+ * Extended write log with pre defined logfile name and add version before the message content
+ *
+ * @param string $message Error log message
+ * @return void
+ */
+ public function write_log($message)
+ {
+ write_log('CardDAV', 'v' . self::VERSION . ' | ' . $message);
+ }
+}
diff --git a/webmail/plugins/carddav/carddav_addressbook.js b/webmail/plugins/carddav/carddav_addressbook.js
new file mode 100644
index 0000000..ed663e8
--- /dev/null
+++ b/webmail/plugins/carddav/carddav_addressbook.js
@@ -0,0 +1,43 @@
+/**
+ * Roundcube CardDAV addressbook extension
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @since 22.09.2011
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @version 0.5.1
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+
+if (window.rcmail)
+{
+ rcmail.addEventListener('init', function(evt)
+ {
+ rcmail.enable_command('plugin.carddav-addressbook-sync', true);
+ rcmail.addEventListener('plugin.carddav_addressbook_message', carddav_addressbook_message);
+
+ rcmail.register_command('plugin.carddav-addressbook-sync', function()
+ {
+ rcmail.http_post(
+ 'plugin.carddav-addressbook-sync',
+ '',
+ rcmail.display_message(rcmail.gettext('addressbook_sync_loading', 'carddav'), 'loading')
+ );
+ }, true);
+ });
+
+ function carddav_addressbook_message(response)
+ {
+ if (response.check)
+ {
+ rcmail.display_message(response.message, 'confirmation');
+ }
+ else
+ {
+ rcmail.display_message(response.message, 'error');
+ }
+ }
+
+} \ No newline at end of file
diff --git a/webmail/plugins/carddav/carddav_addressbook.php b/webmail/plugins/carddav/carddav_addressbook.php
new file mode 100644
index 0000000..a7ca7b8
--- /dev/null
+++ b/webmail/plugins/carddav/carddav_addressbook.php
@@ -0,0 +1,1038 @@
+<?php
+
+/**
+ * Roundcube CardDAV addressbook extension
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @since 12.09.2011
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @version 0.5.1
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+class carddav_addressbook extends rcube_addressbook
+{
+ /**
+ * Database primary key
+ *
+ * @var string
+ */
+ public $primary_key = 'carddav_contact_id';
+
+ /**
+ * Sets addressbook readonly (true) or not (false)
+ *
+ * @var boolean
+ */
+ public $readonly = false;
+
+ /**
+ * Allow addressbook groups (true) or not (false)
+ *
+ * @var boolean
+ */
+ public $groups = false;
+
+ /**
+ * Internal addressbook group id
+ *
+ * @var string
+ */
+ public $group_id;
+
+ /**
+ * Search filters
+ *
+ * @var array
+ */
+ private $filter;
+
+ /**
+ * Result set
+ *
+ * @var rcube_result_set
+ */
+ private $result;
+
+ /**
+ * Translated addressbook name
+ *
+ * @var string
+ */
+ private $name;
+
+ /**
+ * CardDAV server id
+ *
+ * @var integer
+ */
+ private $carddav_server_id = false;
+
+ /**
+ * Single and searchable database table columns
+ *
+ * @var array
+ */
+ private $table_cols = array('name', 'firstname', 'surname', 'email');
+
+ /**
+ * vCard fields used for the fulltext search (database column: words)
+ *
+ * @var array
+ */
+ private $fulltext_cols = array('name', 'firstname', 'surname', 'middlename', 'nickname',
+ 'jobtitle', 'organization', 'department', 'maidenname', 'email', 'phone',
+ 'address', 'street', 'locality', 'zipcode', 'region', 'country', 'website', 'im', 'notes');
+
+ /**
+ * vCard fields that will be displayed in the addressbook
+ *
+ * @var array
+ */
+ public $coltypes = array('name', 'firstname', 'surname', 'middlename', 'prefix', 'suffix', 'nickname',
+ 'jobtitle', 'organization', 'department', 'assistant', 'manager',
+ 'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
+ 'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
+
+ /**
+ * Id list separator
+ *
+ * @constant string
+ */
+ const SEPARATOR = ',';
+
+ /**
+ * Init CardDAV addressbook
+ *
+ * @param string $carddav_server_id Translated addressbook name
+ * @param integer $name CardDAV server id
+ * @return void
+ */
+ public function __construct($carddav_server_id, $name, $readonly)
+ {
+ $this->ready = true;
+ $this->name = $name;
+ $this->carddav_server_id = $carddav_server_id;
+ $this->readonly = $readonly;
+ }
+
+ /**
+ * Get translated addressbook name
+ *
+ * @return string $this->name Translated addressbook name
+ */
+ public function get_name()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Get all CardDAV adressbook contacts
+ *
+ * @param array $limit Limits (limit, offset)
+ * @return array CardDAV adressbook contacts
+ */
+ private function get_carddav_addressbook_contacts($limit = array())
+ {
+ $rcmail = rcmail::get_instance();
+ $carddav_addressbook_contacts = array();
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_contacts')."
+ WHERE
+ user_id = ?
+ AND
+ carddav_server_id = ?
+ ".$this->get_search_set()."
+ ORDER BY
+ name ASC
+ ";
+
+ if (empty($limit))
+ {
+ $result = $rcmail->db->query($query, $rcmail->user->data['user_id'], $this->carddav_server_id);
+ }
+ else
+ {
+ $result = $rcmail->db->limitquery($query, $limit['start'], $limit['length'], $rcmail->user->data['user_id'], $this->carddav_server_id);
+ }
+
+ if ($rcmail->db->num_rows($result))
+ {
+ while ($contact = $rcmail->db->fetch_assoc($result))
+ {
+ $carddav_addressbook_contacts[$contact['vcard_id']] = $contact;
+ }
+ }
+
+ return $carddav_addressbook_contacts;
+ }
+
+ /**
+ * Get one CardDAV adressbook contact
+ *
+ * @param integer $carddav_contact_id CardDAV contact id
+ * @return array CardDAV adressbook contact
+ */
+ private function get_carddav_addressbook_contact($carddav_contact_id)
+ {
+ $rcmail = rcmail::get_instance();
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_contacts')."
+ WHERE
+ user_id = ?
+ AND
+ carddav_contact_id = ?
+ ";
+
+ $result = $rcmail->db->query($query, $rcmail->user->data['user_id'], $carddav_contact_id);
+
+ if ($rcmail->db->num_rows($result))
+ {
+ return $rcmail->db->fetch_assoc($result);
+ }
+
+ return false;
+ }
+
+ /**
+ * Get count of CardDAV contacts specified CardDAV addressbook
+ *
+ * @return integer Count of the CardDAV contacts
+ */
+ private function get_carddav_addressbook_contacts_count()
+ {
+ $rcmail = rcmail::get_instance();
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_contacts')."
+ WHERE
+ user_id = ?
+ AND
+ carddav_server_id = ?
+ ".$this->get_search_set()."
+ ORDER BY
+ name ASC
+ ";
+
+ $result = $rcmail->db->query($query, $rcmail->user->data['user_id'], $this->carddav_server_id);
+
+ return $rcmail->db->num_rows($result);
+ }
+
+ /**
+ * Get result set
+ *
+ * @return $this->result rcube_result_set Roundcube result set
+ */
+ public function get_result()
+ {
+ return $this->result;
+ }
+
+ /**
+ *
+ *
+ * @param integer $carddav_contact_id CardDAV contact id
+ * @param boolean $assoc Define if result should be an assoc array or rcube_result_set
+ * @return mixed Returns contact as an assoc array or rcube_result_set
+ */
+ public function get_record($carddav_contact_id, $assoc = false)
+ {
+ $contact = $this->get_carddav_addressbook_contact($carddav_contact_id);
+ $contact['ID'] = $contact[$this->primary_key];
+
+ unset($contact['email']);
+
+ $vcard = new rcube_vcard($contact['vcard']);
+ $contact += $vcard->get_assoc();
+
+ $this->result = new rcube_result_set(1);
+ $this->result->add($contact);
+
+ if ($assoc === true)
+ {
+ return $contact;
+ }
+ else
+ {
+ return $this->result;
+ }
+ }
+
+ /**
+ * Getter for saved search properties
+ *
+ * @return $this->filter array Search properties
+ */
+ public function get_search_set()
+ {
+ return $this->filter;
+ }
+
+ /**
+ * Save a search string for future listings
+ *
+ * @param string $filter SQL params to use in listing method
+ * @return void
+ */
+ public function set_search_set($filter)
+ {
+ $this->filter = $filter;
+ }
+
+ /**
+ * Set database search filter
+ *
+ * @param mixed $fields Database field names
+ * @param string $value Searched value
+ * @return void
+ */
+ public function set_filter($fields, $value)
+ {
+ $rcmail = rcmail::get_instance();
+ $filter = null;
+
+ if (is_array($fields))
+ {
+ $filter = "AND (";
+
+ foreach ($fields as $field)
+ {
+ if (in_array($field, $this->table_cols) || $fields == $this->primary_key)
+ {
+ $filter .= $rcmail->db->ilike($field, '%'.$value.'%')." OR ";
+ }
+ }
+
+ $filter = substr($filter, 0, -4);
+ $filter .= ")";
+ }
+ else
+ {
+ if (in_array($fields, $this->table_cols) || $fields == $this->primary_key)
+ {
+ $filter = " AND ".$rcmail->db->ilike($fields, '%'.$value.'%');
+ }
+ else if ($fields == '*')
+ {
+ $filter = " AND ".$rcmail->db->ilike('words', '%'.$value.'%');
+ }
+ }
+
+ $this->set_search_set($filter);
+ }
+
+ /**
+ * Sets internal addressbook group id
+ *
+ * @param string $group_id Internal addressbook group id
+ * @return void
+ */
+ public function set_group($group_id)
+ {
+ $this->group_id = $group_id;
+ }
+
+ /**
+ * Reset cached filters and results
+ *
+ * @return void
+ */
+ public function reset()
+ {
+ $this->result = null;
+ $this->filter = null;
+ }
+
+ /**
+ * Synchronize CardDAV-Addressbook
+ *
+ * @param array $server CardDAV server array
+ * @param integer $carddav_contact_id CardDAV contact id
+ * @param string $vcard_id vCard id
+ * @return boolean if no error occurred "true" else "false"
+ */
+ public function carddav_addressbook_sync($server, $carddav_contact_id = null, $vcard_id = null)
+ {
+ $rcmail = rcmail::get_instance();
+ $any_data_synced = false;
+
+ self::write_log('Starting CardDAV-Addressbook synchronization');
+
+ $carddav_backend = new carddav_backend($server['url']);
+ $carddav_backend->set_auth($server['username'], $rcmail->decrypt($server['password']));
+
+ if ($carddav_backend->check_connection())
+ {
+ self::write_log('Connected to the CardDAV-Server ' . $server['url']);
+
+ if ($vcard_id !== null)
+ {
+ $elements = $carddav_backend->get_xml_vcard($vcard_id);
+
+ if ($carddav_contact_id !== null)
+ {
+ $carddav_addressbook_contact = $this->get_carddav_addressbook_contact($carddav_contact_id);
+ $carddav_addressbook_contacts = array(
+ $carddav_addressbook_contact['vcard_id'] => $carddav_addressbook_contact
+ );
+ }
+ }
+ else
+ {
+ $elements = $carddav_backend->get(false);
+ $carddav_addressbook_contacts = $this->get_carddav_addressbook_contacts();
+ }
+
+ try
+ {
+ $xml = new SimpleXMLElement($elements);
+
+ if (!empty($xml->element))
+ {
+ foreach ($xml->element as $element)
+ {
+ $element_id = (string) $element->id;
+ $element_etag = (string) $element->etag;
+ $element_last_modified = (string) $element->last_modified;
+
+ if (isset($carddav_addressbook_contacts[$element_id]))
+ {
+ if ($carddav_addressbook_contacts[$element_id]['etag'] != $element_etag ||
+ $carddav_addressbook_contacts[$element_id]['last_modified'] != $element_last_modified)
+ {
+ $carddav_content = array(
+ 'vcard' => $carddav_backend->get_vcard($element_id),
+ 'vcard_id' => $element_id,
+ 'etag' => $element_etag,
+ 'last_modified' => $element_last_modified
+ );
+
+ if ($this->carddav_addressbook_update($carddav_content))
+ {
+ $any_data_synced = true;
+ }
+ }
+ }
+ else
+ {
+ $carddav_content = array(
+ 'vcard' => $carddav_backend->get_vcard($element_id),
+ 'vcard_id' => $element_id,
+ 'etag' => $element_etag,
+ 'last_modified' => $element_last_modified
+ );
+
+ if (!empty($carddav_content['vcard']))
+ {
+ if ($this->carddav_addressbook_add($carddav_content))
+ {
+ $any_data_synced = true;
+ }
+ }
+ }
+
+ unset($carddav_addressbook_contacts[$element_id]);
+ }
+ }
+ else
+ {
+ $logging_message = 'No CardDAV XML-Element found!';
+ if ($carddav_contact_id !== null && $vcard_id !== null)
+ {
+ self::write_log($logging_message . ' The CardDAV-Server does not have a contact with the vCard id ' . $vcard_id);
+ }
+ else
+ {
+ self::write_log($logging_message . ' The CardDAV-Server seems to have no contacts');
+ }
+ }
+
+ if (!empty($carddav_addressbook_contacts))
+ {
+ foreach ($carddav_addressbook_contacts as $vcard_id => $etag)
+ {
+ if ($this->carddav_addressbook_delete($vcard_id))
+ {
+ $any_data_synced = true;
+ }
+ }
+ }
+
+ if ($any_data_synced === false)
+ {
+ self::write_log('all CardDAV-Data are synchronous, nothing todo!');
+ }
+
+ self::write_log('Syncronization complete!');
+ }
+ catch (Exception $e)
+ {
+ self::write_log('CardDAV-Server XML-Response is malformed. Synchronization aborted!');
+ return false;
+ }
+ }
+ else
+ {
+ self::write_log('Couldn\'t connect to the CardDAV-Server ' . $server['url']);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds a vCard to the CardDAV addressbook
+ *
+ * @param array $carddav_content CardDAV contents (vCard id, etag, last modified, etc.)
+ * @return boolean
+ */
+ private function carddav_addressbook_add($carddav_content)
+ {
+ $rcmail = rcmail::get_instance();
+ $vcard = new rcube_vcard($carddav_content['vcard']);
+
+ $query = "
+ INSERT INTO
+ ".get_table_name('carddav_contacts')." (carddav_server_id, user_id, etag, last_modified, vcard_id, vcard, words, firstname, surname, name, email)
+ VALUES
+ (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ ";
+
+ $database_column_contents = $this->get_database_column_contents($vcard->get_assoc());
+
+ $result = $rcmail->db->query(
+ $query,
+ $this->carddav_server_id,
+ $rcmail->user->data['user_id'],
+ $carddav_content['etag'],
+ $carddav_content['last_modified'],
+ $carddav_content['vcard_id'],
+ $carddav_content['vcard'],
+ $database_column_contents['words'],
+ $database_column_contents['firstname'],
+ $database_column_contents['surname'],
+ $database_column_contents['name'],
+ $database_column_contents['email']
+ );
+
+ if ($rcmail->db->affected_rows($result))
+ {
+ self::write_log('Added CardDAV-Contact to the local database with the vCard id ' . $carddav_content['vcard_id']);
+ return true;
+ }
+ else
+ {
+ self::write_log('Couldn\'t add CardDAV-Contact to the local database with the vCard id ' . $carddav_content['vcard_id']);
+ return false;
+ }
+
+ }
+
+ /**
+ * Updates a vCard in the CardDAV-Addressbook
+ *
+ * @param array $carddav_content CardDAV contents (vCard id, etag, last modified, etc.)
+ * @return boolean
+ */
+ private function carddav_addressbook_update($carddav_content)
+ {
+ $rcmail = rcmail::get_instance();
+ $vcard = new rcube_vcard($carddav_content['vcard']);
+
+ $database_column_contents = $this->get_database_column_contents($vcard->get_assoc());
+
+ $query = "
+ UPDATE
+ ".get_table_name('carddav_contacts')."
+ SET
+ etag = ?,
+ last_modified = ?,
+ vcard = ?,
+ words = ?,
+ firstname = ?,
+ surname = ?,
+ name = ?,
+ email = ?
+ WHERE
+ vcard_id = ?
+ AND
+ carddav_server_id = ?
+ AND
+ user_id = ?
+ ";
+
+ $result = $rcmail->db->query(
+ $query,
+ $carddav_content['etag'],
+ $carddav_content['last_modified'],
+ $carddav_content['vcard'],
+ $database_column_contents['words'],
+ $database_column_contents['firstname'],
+ $database_column_contents['surname'],
+ $database_column_contents['name'],
+ $database_column_contents['email'],
+ $carddav_content['vcard_id'],
+ $this->carddav_server_id,
+ $rcmail->user->data['user_id']
+ );
+
+ if ($rcmail->db->affected_rows($result))
+ {
+ self::write_log('CardDAV-Contact updated in the local database with the vCard id ' . $carddav_content['vcard_id']);
+ return true;
+ }
+ else
+ {
+ self::write_log('Couldn\'t update CardDAV-Contact in the local database with the vCard id ' . $carddav_content['vcard_id']);
+ return false;
+ }
+ }
+
+ /**
+ * Deletes a vCard from the CardDAV addressbook
+ *
+ * @param string $vcard_id vCard id
+ * @return boolean
+ */
+ private function carddav_addressbook_delete($vcard_id)
+ {
+ $rcmail = rcmail::get_instance();
+
+ $query = "
+ DELETE FROM
+ ".get_table_name('carddav_contacts')."
+ WHERE
+ vcard_id = ?
+ AND
+ carddav_server_id = ?
+ AND
+ user_id = ?
+ ";
+
+ $result = $rcmail->db->query($query, $vcard_id, $this->carddav_server_id, $rcmail->user->data['user_id']);
+
+ if ($rcmail->db->affected_rows($result))
+ {
+ self::write_log('CardDAV-Contact deleted from the local database with the vCard id ' . $vcard_id);
+ return true;
+ }
+ else
+ {
+ self::write_log('Couldn\'t delete CardDAV-Contact from the local database with the vCard id ' . $vcard_id);
+ return false;
+ }
+ }
+
+ /**
+ * Adds a CardDAV server contact
+ *
+ * @param string $vcard vCard
+ * @return boolean
+ */
+ private function carddav_add($vcard)
+ {
+ $rcmail = rcmail::get_instance();
+ $server = current(carddav::get_carddav_server($this->carddav_server_id));
+ $carddav_backend = new carddav_backend($server['url']);
+ $carddav_backend->set_auth($server['username'], $rcmail->decrypt($server['password']));
+
+ if ($carddav_backend->check_connection())
+ {
+ $vcard_id = $carddav_backend->add($vcard);
+ $this->carddav_addressbook_sync($server, false, $vcard_id);
+
+ return $rcmail->db->insert_id(get_table_name('carddav_contacts'));
+ }
+
+ return false;
+ }
+
+ /**
+ * Updates a CardDAV server contact
+ *
+ * @param integer $carddav_contact_id CardDAV contact id
+ * @param string $vcard The new vCard
+ * @return boolean
+ */
+ private function carddav_update($carddav_contact_id, $vcard)
+ {
+ $rcmail = rcmail::get_instance();
+ $contact = $this->get_carddav_addressbook_contact($carddav_contact_id);
+ $server = current(carddav::get_carddav_server($this->carddav_server_id));
+ $carddav_backend = new carddav_backend($server['url']);
+ $carddav_backend->set_auth($server['username'], $rcmail->decrypt($server['password']));
+
+ if ($carddav_backend->check_connection())
+ {
+ $carddav_backend->update($vcard, $contact['vcard_id']);
+ $this->carddav_addressbook_sync($server, $carddav_contact_id, $contact['vcard_id']);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Deletes the CardDAV server contact
+ *
+ * @param array $carddav_contact_ids CardDAV contact ids
+ * @return mixed affected CardDAV contacts or false
+ */
+ private function carddav_delete($carddav_contact_ids)
+ {
+ $rcmail = rcmail::get_instance();
+ $server = current(carddav::get_carddav_server($this->carddav_server_id));
+ $carddav_backend = new carddav_backend($server['url']);
+ $carddav_backend->set_auth($server['username'], $rcmail->decrypt($server['password']));
+
+ if ($carddav_backend->check_connection())
+ {
+ foreach ($carddav_contact_ids as $carddav_contact_id)
+ {
+ $contact = $this->get_carddav_addressbook_contact($carddav_contact_id);
+ $carddav_backend->delete($contact['vcard_id']);
+ $this->carddav_addressbook_sync($server, $carddav_contact_id, $contact['vcard_id']);
+ }
+
+ return count($carddav_contact_ids);
+ }
+
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::list_groups()
+ * @param string $search
+ * @return boolean
+ */
+ public function list_groups($search = null)
+ {
+ return false;
+ }
+
+ /**
+ * Returns a list of CardDAV adressbook contacts
+ *
+ * @param string $columns Database columns
+ * @param integer $subset Subset for result limits
+ * @return rcube_result_set $this->result List of CardDAV adressbook contacts
+ */
+ public function list_records($columns = null, $subset = 0)
+ {
+ $this->result = $this->count();
+ $limit = array(
+ 'start' => ($subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first),
+ 'length' => ($subset != 0 ? abs($subset) : $this->page_size)
+ );
+
+ $contacts = $this->get_carddav_addressbook_contacts($limit);
+
+ if (!empty($contacts))
+ {
+ foreach ($contacts as $contact)
+ {
+ $record = array();
+ $record['ID'] = $contact[$this->primary_key];
+
+ if ($columns === null)
+ {
+ $vcard = new rcube_vcard($contact['vcard']);
+ $record += $vcard->get_assoc();
+ }
+ else
+ {
+ $record['name'] = $contact['name'];
+ $record['email'] = explode(', ', $contact['email']);
+ }
+
+ $this->result->add($record);
+ }
+ }
+
+ return $this->result;
+ }
+
+ /**
+ * Search and autocomplete contacts in the mail view
+ *
+ * @return rcube_result_set $this->result List of searched CardDAV adressbook contacts
+ */
+ private function search_carddav_addressbook_contacts()
+ {
+ $rcmail = rcmail::get_instance();
+ $this->result = $this->count();
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_contacts')."
+ WHERE
+ user_id = ?
+ ".$this->get_search_set()."
+ ORDER BY
+ name ASC
+ ";
+
+ $result = $rcmail->db->query($query, $rcmail->user->data['user_id']);
+
+ if ($rcmail->db->num_rows($result))
+ {
+ while ($contact = $rcmail->db->fetch_assoc($result))
+ {
+ $record['name'] = $contact['name'];
+ $record['email'] = explode(', ', $contact['email']);
+
+ $this->result->add($record);
+ }
+
+ }
+
+ return $this->result;
+ }
+
+ /**
+ * Search method (autocomplete, addressbook)
+ *
+ * @param array $fields Search in these fields
+ * @param string $value Search value
+ * @param boolean $strict
+ * @param boolean $select
+ * @param boolean $nocount
+ * @param array $required
+ * @return rcube_result_set List of searched CardDAV-Adressbook contacts
+ */
+ public function search($fields, $value, $strict = false, $select = true, $nocount = false, $required = array())
+ {
+ $this->set_filter($fields, $value);
+ return $this->search_carddav_addressbook_contacts();
+ }
+
+ /**
+ * Count CardDAV contacts for a specified CardDAV addressbook and return the result set
+ *
+ * @return rcube_result_set
+ */
+ public function count()
+ {
+ $count = $this->get_carddav_addressbook_contacts_count();
+ return new rcube_result_set($count, ($this->list_page - 1) * $this->page_size);
+ }
+
+ /**
+ * @see rcube_addressbook::get_record_groups()
+ * @param integer $id
+ * @return boolean
+ */
+ public function get_record_groups($id)
+ {
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::create_group()
+ * @param string $name
+ * @return boolean
+ */
+ public function create_group($name)
+ {
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::delete_group()
+ * @param integer $gid
+ * @return boolean
+ */
+ public function delete_group($gid)
+ {
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::rename_group()
+ * @param integer $gid
+ * @param string $newname
+ * @return boolean
+ */
+ public function rename_group($gid, $newname)
+ {
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::add_to_group()
+ * @param integer $group_id
+ * @param array $ids
+ * @return boolean
+ */
+ public function add_to_group($group_id, $ids)
+ {
+ return false;
+ }
+
+ /**
+ * @see rcube_addressbook::remove_from_group()
+ * @param integer $group_id
+ * @param array $ids
+ * @return boolean
+ */
+ public function remove_from_group($group_id, $ids)
+ {
+ return false;
+ }
+
+ /**
+ * Creates a new CardDAV addressbook contact
+ *
+ * @param array $save_data Associative array with save data
+ * @param boolean $check Check if the e-mail address already exists
+ * @return mixed The created record ID on success or false on error
+ */
+ function insert($save_data, $check = false)
+ {
+ $rcmail = rcmail::get_instance();
+
+ if (!is_array($save_data))
+ {
+ return false;
+ }
+
+ if ($check !== false)
+ {
+ foreach ($save_data as $col => $values)
+ {
+ if (strpos($col, 'email') === 0)
+ {
+ foreach ((array)$values as $email)
+ {
+ if ($existing = $this->search('email', $email, false, false))
+ {
+ break 2;
+ }
+ }
+ }
+ }
+ }
+
+ $database_column_contents = $this->get_database_column_contents($save_data);
+ return $this->carddav_add($database_column_contents['vcard']);
+ }
+
+ /**
+ * Updates a CardDAV addressbook contact
+ *
+ * @param integer $carddav_contact_id CardDAV contact id
+ * @param array $save_data vCard parameters
+ * @return boolean
+ */
+ public function update($carddav_contact_id, $save_data)
+ {
+ $record = $this->get_record($carddav_contact_id, true);
+ $database_column_contents = $this->get_database_column_contents($save_data, $record);
+
+ return $this->carddav_update($carddav_contact_id, $database_column_contents['vcard']);
+ }
+
+ /**
+ * Deletes one or more CardDAV addressbook contacts
+ *
+ * @param array $carddav_contact_ids Record identifiers
+ * @param boolean $force
+ * @return boolean
+ */
+ public function delete($carddav_contact_ids, $force = true)
+ {
+ if (!is_array($carddav_contact_ids))
+ {
+ $carddav_contact_ids = explode(self::SEPARATOR, $carddav_contact_ids);
+ }
+ return $this->carddav_delete($carddav_contact_ids);
+ }
+
+ /**
+ * Convert vCard changes and return database relevant fileds including contents
+ *
+ * @param array $save_data New vCard values
+ * @param array $record Original vCard
+ * @return array $database_column_contents Database column contents
+ */
+ private function get_database_column_contents($save_data, $record = array())
+ {
+ $words = '';
+ $database_column_contents = array();
+
+ $vcard = new rcube_vcard($record['vcard'] ? $record['vcard'] : $save_data['vcard']);
+ $vcard->reset();
+
+ foreach ($save_data as $key => $values)
+ {
+ list($field, $section) = explode(':', $key);
+ $fulltext = in_array($field, $this->fulltext_cols);
+
+ foreach ((array)$values as $value)
+ {
+ if (isset($value))
+ {
+ $vcard->set($field, $value, $section);
+ }
+
+ if ($fulltext && is_array($value))
+ {
+ $words .= ' ' . self::normalize_string(join(" ", $value));
+ }
+ else if ($fulltext && strlen($value) >= 3)
+ {
+ $words .= ' ' . self::normalize_string($value);
+ }
+ }
+ }
+
+ $database_column_contents['vcard'] = $vcard->export(false);
+
+ foreach ($this->table_cols as $column)
+ {
+ $key = $column;
+
+ if (!isset($save_data[$key]))
+ {
+ $key .= ':home';
+ }
+ if (isset($save_data[$key]))
+ {
+ $database_column_contents[$column] = is_array($save_data[$key]) ? implode(',', $save_data[$key]) : $save_data[$key];
+ }
+ }
+
+ $database_column_contents['email'] = implode(', ', $vcard->email);
+ $database_column_contents['words'] = trim(implode(' ', array_unique(explode(' ', $words))));
+
+ return $database_column_contents;
+ }
+
+ /**
+ * Extended write log with pre defined logfile name and add version before the message content
+ *
+ * @param string $message Log message
+ * @return void
+ */
+ public function write_log($message)
+ {
+ carddav::write_log(' carddav_server_id: ' . $this->carddav_server_id . ' | ' . $message);
+ }
+}
diff --git a/webmail/plugins/carddav/carddav_backend.php b/webmail/plugins/carddav/carddav_backend.php
new file mode 100644
index 0000000..75e122d
--- /dev/null
+++ b/webmail/plugins/carddav/carddav_backend.php
@@ -0,0 +1,556 @@
+<?php
+
+/**
+ * CardDAV PHP
+ *
+ * simple CardDAV query
+ * --------------------
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * echo $carddav->get();
+ *
+ *
+ * Simple vCard query
+ * ------------------
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * echo $carddav->get_vcard('0126FFB4-2EB74D0A-302EA17F');
+ *
+ *
+ * XML vCard query
+ * ------------------
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * echo $carddav->get_xml_vcard('0126FFB4-2EB74D0A-302EA17F');
+ *
+ *
+ * Check CardDAV server connection
+ * -------------------------------
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * var_dump($carddav->check_connection());
+ *
+ *
+ * CardDAV delete query
+ * --------------------
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * $carddav->delete('0126FFB4-2EB74D0A-302EA17F');
+ *
+ *
+ * CardDAV add query
+ * --------------------
+ * $vcard = 'BEGIN:VCARD
+ * VERSION:3.0
+ * UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
+ * FN:Christian Putzke
+ * N:Christian;Putzke;;;
+ * EMAIL;TYPE=OTHER:christian.putzke@graviox.de
+ * END:VCARD';
+ *
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * $vcard_id = $carddav->add($vcard);
+ *
+ *
+ * CardDAV update query
+ * --------------------
+ * $vcard = 'BEGIN:VCARD
+ * VERSION:3.0
+ * UID:1f5ea45f-b28a-4b96-25as-ed4f10edf57b
+ * FN:Christian Putzke
+ * N:Christian;Putzke;;;
+ * EMAIL;TYPE=OTHER:christian.putzke@graviox.de
+ * END:VCARD';
+ *
+ * $carddav = new carddav_backend('https://davical.example.com/user/contacts/');
+ * $carddav->set_auth('username', 'password');
+ * $carddav->update($vcard, '0126FFB4-2EB74D0A-302EA17F');
+ *
+ *
+ * CardDAV server list
+ * -------------------
+ * DAViCal: https://example.com/{resource|principal|username}/{collection}/
+ * Apple Addressbook Server: https://example.com/addressbooks/users/{resource|principal|username}/{collection}/
+ * memotoo: https://sync.memotoo.com/cardDAV/
+ * SabreDAV: https://example.com/addressbooks/{resource|principal|username}/{collection}/
+ * ownCloud: https://example.com/apps/contacts/carddav.php/addressbooks/{resource|principal|username}/{collection}/
+ * SOGo: http://sogo-demo.inverse.ca/SOGo/dav/{resource|principal|username}/Contacts/{collection}/
+ *
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @since 20.07.2011
+ * @version 0.5.1
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+
+class carddav_backend
+{
+ /**
+ * CardDAV PHP Version
+ *
+ * @constant string
+ */
+ const VERSION = '0.5.1';
+
+ /**
+ * User agent displayed in http requests
+ *
+ * @constant string
+ */
+ const USERAGENT = 'CardDAV PHP/';
+
+ /**
+ * CardDAV server url
+ *
+ * @var string
+ */
+ private $url = null;
+
+ /**
+ * CardDAV server url_parts
+ *
+ * @var array
+ */
+ private $url_parts = null;
+
+ /**
+ * Authentication string
+ *
+ * @var string
+ */
+ private $auth = null;
+
+ /**
+ * Authentication: username
+ *
+ * @var string
+ */
+ private $username = null;
+
+ /**
+ * Authentication: password
+ *
+ * @var string
+ */
+ private $password = null;
+
+ /**
+ * Characters used for vCard id generation
+ *
+ * @var array
+ */
+ private $vcard_id_chars = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'A', 'B', 'C', 'D', 'E', 'F');
+
+ /**
+ * CardDAV server connection (curl handle)
+ *
+ * @var resource
+ */
+ private $curl;
+
+ /**
+ * Constructor
+ * Sets the CardDAV server url
+ *
+ * @param string $url CardDAV server url
+ * @return void
+ */
+ public function __construct($url = null)
+ {
+ if ($url !== null)
+ {
+ $this->set_url($url);
+ }
+ }
+
+ /**
+ * Sets the CardDAV server url
+ *
+ * @param string $url CardDAV server url
+ * @return void
+ */
+ public function set_url($url)
+ {
+ $this->url = $url;
+
+ if (substr($this->url, -1, 1) !== '/')
+ {
+ $this->url = $this->url . '/';
+ }
+
+ $this->url_parts = parse_url($this->url);
+ }
+
+ /**
+ * Sets authentication string
+ *
+ * @param string $username CardDAV server username
+ * @param string $password CardDAV server password
+ * @return void
+ */
+ public function set_auth($username, $password)
+ {
+ $this->username = $username;
+ $this->password = $password;
+ $this->auth = $username . ':' . $password;
+ }
+
+ /**
+ * Gets propfind XML response from the CardDAV server
+ *
+ * @param boolean $include_vcards vCards include vCards in the response (simplified only)
+ * @param boolean $raw Get response raw or simplified
+ * @return string Raw or simplified XML response
+ */
+ public function get($include_vcards = true, $raw = false)
+ {
+ $response = $this->query($this->url, 'PROPFIND');
+
+ if ($response === false || $raw === true)
+ {
+ return $response;
+ }
+ else
+ {
+ return $this->simplify($response, $include_vcards);
+ }
+ }
+
+ /**
+ * Gets a clean vCard from the CardDAV server
+ *
+ * @param string $vcard_id vCard id on the CardDAV server
+ * @return string vCard (text/vcard)
+ */
+ public function get_vcard($vcard_id)
+ {
+ $vcard_id = str_replace('.vcf', null, $vcard_id);
+ return $this->query($this->url . $vcard_id . '.vcf', 'GET');
+ }
+
+ /**
+ * Gets a vCard + XML from the CardDAV Server
+ *
+ * @param string $vcard_id vCard id on the CardDAV Server
+ * @param boolean $raw Get response raw or simplified
+ * @return string Raw or simplified vCard (text/xml)
+ */
+ public function get_xml_vcard($vcard_id, $raw = false)
+ {
+ $vcard_id = str_replace('.vcf', null, $vcard_id);
+
+ $xml = new XMLWriter();
+ $xml->openMemory();
+ $xml->setIndent(4);
+ $xml->startDocument('1.0', 'utf-8');
+ $xml->startElement('C:addressbook-multiget');
+ $xml->writeAttribute('xmlns:D', 'DAV:');
+ $xml->writeAttribute('xmlns:C', 'urn:ietf:params:xml:ns:carddav');
+ $xml->startElement('D:prop');
+ $xml->writeElement('D:getetag');
+ $xml->writeElement('D:getlastmodified');
+ $xml->endElement();
+ $xml->writeElement('D:href', $this->url_parts['path'] . $vcard_id . '.vcf');
+ $xml->endElement();
+ $xml->endDocument();
+
+ $response = $this->query($this->url, 'REPORT', $xml->outputMemory(), 'text/xml');
+
+ if ($raw === true || $response === false)
+ {
+ return $response;
+ }
+ else
+ {
+ return $this->simplify($response, true);
+ }
+ }
+
+ /**
+ * Checks if the CardDAV server is reachable
+ *
+ * @return boolean
+ */
+ public function check_connection()
+ {
+ return $this->query($this->url, 'OPTIONS', null, null, true);
+ }
+
+ /**
+ * Cleans the vCard
+ *
+ * @param string $vcard vCard
+ * @return string $vcard vCard
+ */
+ private function clean_vcard($vcard)
+ {
+ $vcard = str_replace("\t", null, $vcard);
+
+ return $vcard;
+ }
+
+ /**
+ * Deletes an entry from the CardDAV server
+ *
+ * @param string $vcard_id vCard id on the CardDAV server
+ * @return boolean
+ */
+ public function delete($vcard_id)
+ {
+ return $this->query($this->url . $vcard_id . '.vcf', 'DELETE', null, null, true);
+ }
+
+ /**
+ * Adds an entry to the CardDAV server
+ *
+ * @param string $vcard vCard
+ * @return string The new vCard id
+ */
+ public function add($vcard)
+ {
+ $vcard_id = $this->generate_vcard_id();
+ $vcard = $this->clean_vcard($vcard);
+
+ if ($this->query($this->url . $vcard_id . '.vcf', 'PUT', $vcard, 'text/vcard', true) === true)
+ {
+ return $vcard_id;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Updates an entry to the CardDAV server
+ *
+ * @param string $vcard vCard
+ * @param string $vcard_id vCard id on the CardDAV server
+ * @return boolean
+ */
+ public function update($vcard, $vcard_id)
+ {
+ $vcard_id = str_replace('.vcf', null, $vcard_id);
+ $vcard = $this->clean_vcard($vcard);
+
+ return $this->query($this->url . $vcard_id . '.vcf', 'PUT', $vcard, 'text/vcard', true);
+ }
+
+ /**
+ * Simplify CardDAV XML response
+ *
+ * @param string $response CardDAV XML response
+ * @param boolean $include_vcards Include vCards or not
+ * @return string Simplified CardDAV XML response
+ */
+ private function simplify($response, $include_vcards = true)
+ {
+ $response = $this->clean_response($response);
+ $xml = new SimpleXMLElement($response);
+
+ $simplified_xml = new XMLWriter();
+ $simplified_xml->openMemory();
+ $simplified_xml->setIndent(4);
+
+ $simplified_xml->startDocument('1.0', 'utf-8');
+ $simplified_xml->startElement('response');
+
+ foreach ($xml->response as $response)
+ {
+ if (preg_match('/vcard/', $response->propstat->prop->getcontenttype) || preg_match('/vcf/', $response->href))
+ {
+ $id = basename($response->href);
+ $id = str_replace('.vcf', null, $id);
+
+ if (!empty($id))
+ {
+ $simplified_xml->startElement('element');
+ $simplified_xml->writeElement('id', $id);
+ $simplified_xml->writeElement('etag', str_replace('"', null, $response->propstat->prop->getetag));
+ $simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
+
+ if ($include_vcards === true)
+ {
+ $simplified_xml->writeElement('vcard', $this->get_vcard($id));
+ }
+ $simplified_xml->endElement();
+ }
+ }
+ else if (preg_match('/unix-directory/', $response->propstat->prop->getcontenttype))
+ {
+ if (isset($response->propstat->prop->href))
+ {
+ $href = $response->propstat->prop->href;
+ }
+ else if (isset($response->href))
+ {
+ $href = $response->href;
+ }
+ else
+ {
+ $href = null;
+ }
+
+ $url = str_replace($this->url_parts['path'], null, $this->url) . $href;
+ $simplified_xml->startElement('addressbook_element');
+ $simplified_xml->writeElement('display_name', $response->propstat->prop->displayname);
+ $simplified_xml->writeElement('url', $url);
+ $simplified_xml->writeElement('last_modified', $response->propstat->prop->getlastmodified);
+ $simplified_xml->endElement();
+ }
+ }
+
+ $simplified_xml->endElement();
+ $simplified_xml->endDocument();
+
+ return $simplified_xml->outputMemory();
+ }
+
+ /**
+ * Cleans CardDAV XML response
+ *
+ * @param string $response CardDAV XML response
+ * @return string $response Cleaned CardDAV XML response
+ */
+ private function clean_response($response)
+ {
+ $response = utf8_encode($response);
+ $response = str_replace('D:', null, $response);
+ $response = str_replace('d:', null, $response);
+ $response = str_replace('C:', null, $response);
+ $response = str_replace('c:', null, $response);
+
+ return $response;
+ }
+
+ /**
+ * Curl initialization
+ *
+ * @return void
+ */
+ public function curl_init()
+ {
+ if (empty($this->curl))
+ {
+ $this->curl = curl_init();
+ curl_setopt($this->curl, CURLOPT_HEADER, false);
+ curl_setopt($this->curl, CURLOPT_SSL_VERIFYHOST, false);
+ curl_setopt($this->curl, CURLOPT_SSL_VERIFYPEER, false);
+ curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($this->curl, CURLOPT_USERAGENT, self::USERAGENT.self::VERSION);
+
+ if ($this->auth !== null)
+ {
+ curl_setopt($this->curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
+ curl_setopt($this->curl, CURLOPT_USERPWD, $this->auth);
+ }
+ }
+ }
+
+ /**
+ * Quries the CardDAV server via curl and returns the response
+ *
+ * @param string $url CardDAV server URL
+ * @param string $method HTTP-Method like (OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, COPY, MOVE)
+ * @param string $content Content for CardDAV queries
+ * @param string $content_type Set content type
+ * @param boolean $return_boolean Return just a boolean
+ * @return string CardDAV XML response
+ */
+ private function query($url, $method, $content = null, $content_type = null, $return_boolean = false)
+ {
+ $this->curl_init();
+
+ curl_setopt($this->curl, CURLOPT_URL, $url);
+ curl_setopt($this->curl, CURLOPT_CUSTOMREQUEST, $method);
+
+ if ($content !== null)
+ {
+ curl_setopt($this->curl, CURLOPT_POST, true);
+ curl_setopt($this->curl, CURLOPT_POSTFIELDS, $content);
+ }
+ else
+ {
+ curl_setopt($this->curl, CURLOPT_POST, false);
+ curl_setopt($this->curl, CURLOPT_POSTFIELDS, null);
+ }
+
+ if ($content_type !== null)
+ {
+ curl_setopt($this->curl, CURLOPT_HTTPHEADER, array('Content-type: '.$content_type));
+ }
+ else
+ {
+ curl_setopt($this->curl, CURLOPT_HTTPHEADER, array());
+ }
+
+ $response = curl_exec($this->curl);
+ $http_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE);
+
+ if (in_array($http_code, array(200, 207)))
+ {
+ return ($return_boolean === true ? true : $response);
+ }
+ else if ($return_boolean === true && in_array($http_code, array(201, 204)))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Returns a valid and unused vCard id
+ *
+ * @return string Valid vCard id
+ */
+ private function generate_vcard_id()
+ {
+ $id = null;
+
+ for ($number = 0; $number <= 25; $number ++)
+ {
+ if ($number == 8 || $number == 17)
+ {
+ $id .= '-';
+ }
+ else
+ {
+ $id .= $this->vcard_id_chars[mt_rand(0, (count($this->vcard_id_chars) - 1))];
+ }
+ }
+
+ $carddav = new carddav_backend($this->url);
+ $carddav->set_auth($this->username, $this->password);
+
+ if ($carddav->query($this->url . $id . '.vcf', 'GET', null, null, true))
+ {
+ return $this->generate_vcard_id();
+ }
+ else
+ {
+ return $id;
+ }
+ }
+
+ /**
+ * Destructor
+ * Close curl connection if it's open
+ *
+ * @return void
+ */
+ public function __destruct()
+ {
+ if (!empty($this->curl))
+ {
+ curl_close($this->curl);
+ }
+ }
+}
diff --git a/webmail/plugins/carddav/carddav_settings.js b/webmail/plugins/carddav/carddav_settings.js
new file mode 100644
index 0000000..e1c4694
--- /dev/null
+++ b/webmail/plugins/carddav/carddav_settings.js
@@ -0,0 +1,88 @@
+/**
+ * Roundcube CardDAV addressbook extension
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @since 12.09.2011
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @version 0.5.1
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+
+if (window.rcmail)
+{
+ rcmail.addEventListener('init', function(evt)
+ {
+ var tab = $('<span>').attr('id', 'settingstabplugincarddav-server').addClass('tablink');
+
+ var button = $('<a>').attr('href', rcmail.env.comm_path+'&_action=plugin.carddav-server').
+ html(rcmail.gettext('settings_tab', 'carddav')).appendTo(tab);
+
+ rcmail.add_element(tab, 'tabs');
+ rcmail.addEventListener('plugin.carddav_server_message', carddav_server_message);
+
+ rcmail.register_command('plugin.carddav-server-save', carddav_server_add, true);
+
+ rcmail.register_command('plugin.carddav-server-delete', function(carddav_server_id)
+ {
+ rcmail.http_post(
+ 'plugin.carddav-server-delete',
+ '_carddav_server_id=' + $.base64Encode(carddav_server_id),
+ rcmail.display_message(rcmail.gettext('settings_delete_loading', 'carddav'), 'loading')
+ );
+ }, true);
+
+ $('#_label').keypress(carddav_server_add_enter_event);
+ $('#_server_url').keypress(carddav_server_add_enter_event);
+ $('#_username').keypress(carddav_server_add_enter_event);
+ $('#_password').keypress(carddav_server_add_enter_event);
+ });
+
+ function carddav_server_add_enter_event(e)
+ {
+ if (e.keyCode == 13)
+ {
+ carddav_server_add();
+ }
+ };
+
+ function carddav_server_add()
+ {
+ var input_label = rcube_find_object('_label');
+ var input_url = rcube_find_object('_server_url');
+ var input_username = rcube_find_object('_username');
+ var input_password = rcube_find_object('_password');
+ var input_read_only = rcube_find_object('_read_only');
+
+ if (input_label.value == '' || input_url.value == '')
+ {
+ rcmail.display_message(rcmail.gettext('settings_empty_values', 'carddav'), 'error');
+ }
+ else
+ {
+ rcmail.http_post(
+ 'plugin.carddav-server-save',
+ '_label=' + $.base64Encode(input_label.value) + '&_server_url=' + $.base64Encode(input_url.value) + '&_username=' + $.base64Encode(input_username.value) + '&_password=' + $.base64Encode(input_password.value) + '&_read_only=' + $.base64Encode(input_read_only.checked === true ? '1' : '0'),
+ rcmail.display_message(rcmail.gettext('settings_init_server', 'carddav'), 'loading')
+ );
+ }
+ }
+
+ function carddav_server_message(response)
+ {
+ if (response.check)
+ {
+ $('#carddav_server_list').slideUp();
+ $('#carddav_server_list').html(response.server_list);
+ $('#carddav_server_list').slideDown();
+
+ rcmail.display_message(response.message, 'confirmation');
+ }
+ else
+ {
+ rcmail.display_message(response.message, 'error');
+ }
+ }
+} \ No newline at end of file
diff --git a/webmail/plugins/carddav/cronjob/.htaccess b/webmail/plugins/carddav/cronjob/.htaccess
new file mode 100644
index 0000000..3418e55
--- /dev/null
+++ b/webmail/plugins/carddav/cronjob/.htaccess
@@ -0,0 +1 @@
+deny from all \ No newline at end of file
diff --git a/webmail/plugins/carddav/cronjob/synchronize.php b/webmail/plugins/carddav/cronjob/synchronize.php
new file mode 100644
index 0000000..52cfff9
--- /dev/null
+++ b/webmail/plugins/carddav/cronjob/synchronize.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * Roundcube CardDAV synchronization
+ *
+ * @author Christian Putzke <christian.putzke@graviox.de>
+ * @copyright Christian Putzke @ Graviox Studios
+ * @since 31.03.2012
+ * @link http://www.graviox.de/
+ * @link https://twitter.com/graviox/
+ * @version 0.5
+ * @license http://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later
+ *
+ */
+class carddav_synchronization_cronjob
+{
+ /**
+ * @var string $doc_root Roundcubes document root
+ */
+ private $doc_root;
+
+ /**
+ * @var string $rc_inset Roundcubes iniset script
+ */
+ private $rc_iniset = 'program/include/iniset.php';
+
+ /**
+ * Init CardDAV synchronization cronjob
+ *
+ * @param boolean $init Initialization
+ */
+ public function __construct($init = true)
+ {
+ if ($init === true)
+ {
+ $this->init();
+ }
+ }
+
+ /**
+ * Init CardDAV synchronization cronjob
+ *
+ * @return void
+ */
+ public function init()
+ {
+ $this->detect_document_root();
+ $this->include_rc_iniset();
+ }
+
+ /**
+ * Detect Roundcubes real document root
+ *
+ * @return void
+ */
+ private function detect_document_root()
+ {
+ $dir = dirname(__FILE__);
+ $dir = str_replace('plugins\\carddav\\cronjob', null, $dir);
+ $this->doc_root = str_replace('plugins/carddav/cronjob', null, $dir);
+ define('INSTALL_PATH', $this->doc_root);
+ }
+
+ /**
+ * Include Roundcubes initset so that the internal Roundcube functions can be used inside this cronjob
+ *
+ * @return void
+ */
+ private function include_rc_iniset()
+ {
+ if (file_exists($this->doc_root . $this->rc_iniset))
+ {
+ chdir($this->doc_root);
+ require_once $this->doc_root . '/program/include/iniset.php';
+ }
+ else
+ {
+ die('Can\'t detect file path correctly! I got this as Roundcubes document root: '. $this->doc_root);
+ }
+ }
+
+ /**
+ * Get all CardDAV servers
+ *
+ * @return array $servers CardDAV server array with label, url, username, password (encrypted)
+ */
+ private function get_carddav_servers()
+ {
+ $servers = array();
+ $rcmail = rcmail::get_instance();
+
+ $query = "
+ SELECT
+ *
+ FROM
+ ".get_table_name('carddav_server')."
+ ";
+
+ $result = $rcmail->db->query($query);
+
+ while($server = $rcmail->db->fetch_assoc($result))
+ {
+ $servers[] = $server;
+ }
+
+ return $servers;
+ }
+
+ /**
+ * Synchronize all available CardDAV servers
+ *
+ * @return void
+ */
+ public function synchronize()
+ {
+ $servers = $this->get_carddav_servers();
+ $rcmail = rcmail::get_instance();
+
+ carddav::write_log('CRONJOB: Starting automatic CardDAV synchronization!');
+
+ if (!empty($servers))
+ {
+ foreach ($servers as $server)
+ {
+ $rcmail->user->data['user_id'] = $server['user_id'];
+ $carddav_addressbook = new carddav_addressbook($server['carddav_server_id'], $server['label'], false);
+ $carddav_addressbook->carddav_addressbook_sync($server);
+ }
+ }
+
+ carddav::write_log('CRONJOB: Automatic CardDAV synchronization finished!');
+ }
+}
+
+$cronjob = new carddav_synchronization_cronjob();
+$cronjob->synchronize(); \ No newline at end of file
diff --git a/webmail/plugins/carddav/jquery.base64.js b/webmail/plugins/carddav/jquery.base64.js
new file mode 100644
index 0000000..763b08f
--- /dev/null
+++ b/webmail/plugins/carddav/jquery.base64.js
@@ -0,0 +1,142 @@
+
+ /**
+ * jQuery BASE64 functions
+ *
+ * <code>
+ * Encodes the given data with base64.
+ * String $.base64Encode ( String str )
+ * <br />
+ * Decodes a base64 encoded data.
+ * String $.base64Decode ( String str )
+ * </code>
+ *
+ * Encodes and Decodes the given data in base64.
+ * This encoding is designed to make binary data survive transport through transport layers that are not 8-bit clean, such as mail bodies.
+ * Base64-encoded data takes about 33% more space than the original data.
+ * This javascript code is used to encode / decode data using base64 (this encoding is designed to make binary data survive transport through transport layers that are not 8-bit clean). Script is fully compatible with UTF-8 encoding. You can use base64 encoded data as simple encryption mechanism.
+ * If you plan using UTF-8 encoding in your project don't forget to set the page encoding to UTF-8 (Content-Type meta tag).
+ * This function orginally get from the WebToolkit and rewrite for using as the jQuery plugin.
+ *
+ * Example
+ * Code
+ * <code>
+ * $.base64Encode("I'm Persian.");
+ * </code>
+ * Result
+ * <code>
+ * "SSdtIFBlcnNpYW4u"
+ * </code>
+ * Code
+ * <code>
+ * $.base64Decode("SSdtIFBlcnNpYW4u");
+ * </code>
+ * Result
+ * <code>
+ * "I'm Persian."
+ * </code>
+ *
+ * @alias Muhammad Hussein Fattahizadeh < muhammad [AT] semnanweb [DOT] com >
+ * @link http://www.semnanweb.com/jquery-plugin/base64.html
+ * @see http://www.webtoolkit.info/
+ * @license http://www.gnu.org/licenses/gpl.html [GNU General Public License]
+ * @param {jQuery} {base64Encode:function(input))
+ * @param {jQuery} {base64Decode:function(input))
+ * @return string
+ */
+
+ (function($){
+
+ var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+
+ var uTF8Encode = function(string) {
+ string = string.replace(/\x0d\x0a/g, "\x0a");
+ var output = "";
+ for (var n = 0; n < string.length; n++) {
+ var c = string.charCodeAt(n);
+ if (c < 128) {
+ output += String.fromCharCode(c);
+ } else if ((c > 127) && (c < 2048)) {
+ output += String.fromCharCode((c >> 6) | 192);
+ output += String.fromCharCode((c & 63) | 128);
+ } else {
+ output += String.fromCharCode((c >> 12) | 224);
+ output += String.fromCharCode(((c >> 6) & 63) | 128);
+ output += String.fromCharCode((c & 63) | 128);
+ }
+ }
+ return output;
+ };
+
+ var uTF8Decode = function(input) {
+ var string = "";
+ var i = 0;
+ var c = c1 = c2 = 0;
+ while ( i < input.length ) {
+ c = input.charCodeAt(i);
+ if (c < 128) {
+ string += String.fromCharCode(c);
+ i++;
+ } else if ((c > 191) && (c < 224)) {
+ c2 = input.charCodeAt(i+1);
+ string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
+ i += 2;
+ } else {
+ c2 = input.charCodeAt(i+1);
+ c3 = input.charCodeAt(i+2);
+ string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
+ i += 3;
+ }
+ }
+ return string;
+ }
+
+ $.extend({
+ base64Encode: function(input) {
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+ input = uTF8Encode(input);
+ while (i < input.length) {
+ chr1 = input.charCodeAt(i++);
+ chr2 = input.charCodeAt(i++);
+ chr3 = input.charCodeAt(i++);
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+ if (isNaN(chr2)) {
+ enc3 = enc4 = 64;
+ } else if (isNaN(chr3)) {
+ enc4 = 64;
+ }
+ output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
+ }
+ return output;
+ },
+ base64Decode: function(input) {
+ var output = "";
+ var chr1, chr2, chr3;
+ var enc1, enc2, enc3, enc4;
+ var i = 0;
+ input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
+ while (i < input.length) {
+ enc1 = keyString.indexOf(input.charAt(i++));
+ enc2 = keyString.indexOf(input.charAt(i++));
+ enc3 = keyString.indexOf(input.charAt(i++));
+ enc4 = keyString.indexOf(input.charAt(i++));
+ chr1 = (enc1 << 2) | (enc2 >> 4);
+ chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
+ chr3 = ((enc3 & 3) << 6) | enc4;
+ output = output + String.fromCharCode(chr1);
+ if (enc3 != 64) {
+ output = output + String.fromCharCode(chr2);
+ }
+ if (enc4 != 64) {
+ output = output + String.fromCharCode(chr3);
+ }
+ }
+ output = uTF8Decode(output);
+ return output;
+ }
+ });
+ })(jQuery); \ No newline at end of file
diff --git a/webmail/plugins/carddav/localization/de_DE.inc b/webmail/plugins/carddav/localization/de_DE.inc
new file mode 100644
index 0000000..a00fb09
--- /dev/null
+++ b/webmail/plugins/carddav/localization/de_DE.inc
@@ -0,0 +1,26 @@
+<?php
+
+$labels = array();
+$labels['settings'] = 'CardDAV Einstellungen';
+$labels['settings_server'] = 'CardDAV Server';
+$labels['settings_server_form'] = 'F&uuml;ge deinen CardDAV Server hinzu';
+$labels['settings_example_server_list'] = 'CardDAV Server Beispiel URLs';
+$labels['settings_tab'] = 'CardDAV';
+$labels['settings_label'] = 'Label';
+$labels['settings_read_only'] = 'Nur Lesezugriff';
+$labels['settings_curl_not_installed'] = 'Die PHP Erweiterung CURL ist nicht installiert. Bitte installiere CURL um das CardDAV Plugin nutzen zu k&ouml;nnen.';
+$labels['addressbook_contacts'] = 'CardDAV Kontakte';
+$labels['addressbook_sync'] = 'CardDAV Adressbuch synchronisieren';
+
+$messages = array();
+$messages['settings_empty_values'] = 'Bitte gebe ein Label und die URL an';
+$messages['settings_saved'] = 'CardDAV Server Einstellungen und vCards erfolgreich gespeichert';
+$messages['settings_save_failed'] = 'Beim Speichern der CardDAV Server Einstellungen ist ein Fehler aufgetreten';
+$messages['settings_delete_loading'] = 'L&ouml;sche CardDAV Server Einstellungen und dazugeh&ouml;rige lokale Kontakte';
+$messages['settings_deleted'] = 'CardDAV Server Einstellungen erfolgreich gel&ouml;scht';
+$messages['settings_delete_failed'] = 'Beim L&ouml;schen der CardDAV Server Einstellungen ist ein Fehler aufgetreten';
+$messages['settings_no_connection'] = 'Konnte keine Verbindung zum CardDAV Server aufbauen';
+$messages['settings_init_server'] = 'Überpr&uuml;fe die Verbindung zum CardDAV Server und importiere vCards';
+$messages['addressbook_synced'] = 'CardDAV Kontakte erfolgreich synchronisiert';
+$messages['addressbook_sync_failed'] = 'Bei der Synchronisierung der CardDAV Kontakte ist ein Fehler aufgetreten';
+$messages['addressbook_sync_loading'] = 'Synchronisiere CardDAV Kontakte'; \ No newline at end of file
diff --git a/webmail/plugins/carddav/localization/en_US.inc b/webmail/plugins/carddav/localization/en_US.inc
new file mode 100644
index 0000000..78a24ff
--- /dev/null
+++ b/webmail/plugins/carddav/localization/en_US.inc
@@ -0,0 +1,26 @@
+<?php
+
+$labels = array();
+$labels['settings'] = 'CardDAV settings';
+$labels['settings_server'] = 'CardDAV server';
+$labels['settings_server_form'] = 'Add your CardDAV server';
+$labels['settings_example_server_list'] = 'CardDAV server example URLs';
+$labels['settings_tab'] = 'CardDAV';
+$labels['settings_label'] = 'Label';
+$labels['settings_read_only'] = 'Read only';
+$labels['settings_curl_not_installed'] = 'The PHP extension CURL is not installed! Please install CURL to use the CardDAV plugin.';
+$labels['addressbook_contacts'] = 'CardDAV contacts';
+$labels['addressbook_sync'] = 'Synchronize CardDAV addressbook';
+
+$messages = array();
+$messages['settings_empty_values'] = 'Please fill out all Label and URL';
+$messages['settings_saved'] = 'CardDAV server settings and vCards saved';
+$messages['settings_save_failed'] = 'Failed to save CardDAV server settings';
+$messages['settings_delete_loading'] = 'Delete CardDAV server settings and related local contacts';
+$messages['settings_deleted'] = 'CardDAV server settings deleted';
+$messages['settings_delete_failed'] = 'Failed to delete CardDAV server settings';
+$messages['settings_no_connection'] = 'Can\'t connect to the CardDAV server';
+$messages['settings_init_server'] = 'Checking CardDAV server connection and import vCards';
+$messages['addressbook_synced'] = 'CardDAV contacts synchronized';
+$messages['addressbook_sync_failed'] = 'An error occurred while synchronizing the CardDAV contacts';
+$messages['addressbook_sync_loading'] = 'Synchronize CardDAV contacts'; \ No newline at end of file
diff --git a/webmail/plugins/carddav/localization/fr_FR.inc b/webmail/plugins/carddav/localization/fr_FR.inc
new file mode 100644
index 0000000..ec97655
--- /dev/null
+++ b/webmail/plugins/carddav/localization/fr_FR.inc
@@ -0,0 +1,21 @@
+<?php
+
+$labels = array();
+$labels['settings'] = 'CardDAV-Settings';
+$labels['settings_tab'] = 'CardDAV';
+$labels['settings_label'] = 'Label';
+$labels['addressbook_contacts'] = 'CardDAV-Contacts';
+$labels['addressbook_sync'] = 'Synchronise CardDAV-Addressbook';
+
+$messages = array();
+$messages['settings_empty_values'] = 'Introduisez un nom et l\'URL de votre CardDav';
+$messages['settings_saved'] = 'Les paramètres CardDAV-Server et vCards sauvergardés';
+$messages['settings_save_failed'] = 'Echec de la sauvagarde des paramètres CardDAV-Server';
+$messages['settings_delete_loading'] = 'Effacer les paramètres CardDAV-Server et les contacts du serveur local';
+$messages['settings_deleted'] = 'Paramètres CardDAV-Server effacés';
+$messages['settings_delete_failed'] = 'Impossible d\'effacer les paramètres CardDAV-Server';
+$messages['settings_no_connection'] = 'Connexion au CardDAV-Server ompossible';
+$messages['settings_init_server'] = 'Vérification de la connexion au CardDAV-Server et importation des adresses vCards';
+$messages['addressbook_synced'] = 'CardDAV-Contacts synchronisés';
+$messages['addressbook_sync_failed'] = 'Une erreur s\'est produite lors de la synchronisation avec CardDAV-Contacts';
+$messages['addressbook_sync_loading'] = 'Synchronisation CardDAV-Contacts'; \ No newline at end of file
diff --git a/webmail/plugins/carddav/localization/it_IT.inc b/webmail/plugins/carddav/localization/it_IT.inc
new file mode 100644
index 0000000..3a39370
--- /dev/null
+++ b/webmail/plugins/carddav/localization/it_IT.inc
@@ -0,0 +1,21 @@
+<?php
+
+$labels = array();
+$labels['settings'] = 'Impostazioni CardDAV';
+$labels['settings_tab'] = 'CardDAV';
+$labels['settings_label'] = 'Descrizione';
+$labels['addressbook_contacts'] = 'Contatti CardDAV';
+$labels['addressbook_sync'] = 'Sincronizza la rubrica CardDAV';
+
+$messages = array();
+$messages['settings_empty_values'] = 'Per favore, completa tutti i campi';
+$messages['settings_saved'] = 'Impostazioni del server CardDAV e vCard salvati';
+$messages['settings_save_failed'] = 'Errore durante il salvataggio delle impostazioni del server CardDAV';
+$messages['settings_delete_loading'] = 'Elimina le impostazioni del server CardDAV e i contatti locali corrispondenti';
+$messages['settings_deleted'] = 'Impostazioni del server CardDAV eliminate';
+$messages['settings_delete_failed'] = 'Errore durante l\'eliminazione delle impostazioni del server CardDAV';
+$messages['settings_no_connection'] = 'Impossibile connettersi al server CardDAV';
+$messages['settings_init_server'] = 'Verifica della connessione al server CardDAV e importazione delle vCard';
+$messages['addressbook_synced'] = 'Contatti CardDAV sincronizzati';
+$messages['addressbook_sync_failed'] = 'Errore durante la sincronizzazione dei contatti CardDAV';
+$messages['addressbook_sync_loading'] = 'Sincronizzazione dei contatti CardDAV'; \ No newline at end of file
diff --git a/webmail/plugins/carddav/package.xml b/webmail/plugins/carddav/package.xml
new file mode 100644
index 0000000..1d58c65
--- /dev/null
+++ b/webmail/plugins/carddav/package.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+ http://pear.php.net/dtd/tasks-1.0.xsd
+ http://pear.php.net/dtd/package-2.0
+ http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Roundcube CardDAV</name>
+ <uri>https://github.com/graviox/Roundcube-CardDAV</uri>
+ <summary>CardDAV implementation for Roundcube</summary>
+ <description>
+ This is a CardDAV implementation for Roundcube 0.6 or higher.
+ It allows every user to add multiple CardDAV server in their settings.
+ The CardDAV contacts (vCards) will be synchronized automaticly with their roundcube addressbook.
+ </description>
+ <lead>
+ <name>Christian Putzke</name>
+ <email>christian.putzke@graviox.de</email>
+ <active>yes</active>
+ </lead>
+ <date>2012-04-11</date>
+ <version>
+ <release>0.5.1</release>
+ </version>
+ <stability>
+ <release>stable</release>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3+</license>
+ <notes>-</notes>
+ <contents>
+ <dir baseinstalldir="/" name="/">
+ <file name="carddav.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="carddav_addressbook.js" role="data">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="carddav_addressbook.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="carddav_backend.php" role="php">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="carddav_settings.js" role="data">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="jquery.base64.js" role="data">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="cronjob/synchronize.php" role="data">
+ <tasks:replace from="@name@" to="name" type="package-info"/>
+ <tasks:replace from="@package_version@" to="version" type="package-info"/>
+ </file>
+ <file name="localization/de_DE.inc" role="data"></file>
+ <file name="localization/en_US.inc" role="data"></file>
+ <file name="localization/fr_FR.inc" role="data"></file>
+ <file name="localization/it_IT.inc" role="data"></file>
+ <file name="skins/default/sync_act.png" role="data"></file>
+ <file name="skins/default/sync_pas.png" role="data"></file>
+ <file name="SQL/mysql.sql" role="data"></file>
+ <file name="SQL/mysql.update.sql" role="data"></file>
+ </dir>
+ <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.1</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease/>
+</package> \ No newline at end of file
diff --git a/webmail/plugins/carddav/skins/default/carddav.css b/webmail/plugins/carddav/skins/default/carddav.css
new file mode 100644
index 0000000..3a320b3
--- /dev/null
+++ b/webmail/plugins/carddav/skins/default/carddav.css
@@ -0,0 +1,57 @@
+.carddav .button {
+ border-radius: 5px;
+ border: 1px solid #aaa;
+ text-shadow: 1px 1px 0px #fff;
+ cursor: pointer;
+}
+
+.carddav .carddav_headline {
+ font-size: 120%;
+ text-shadow: 1px 1px 0px #fff;
+ font-weight: bold;
+ margin-bottom: 10px;
+}
+
+.carddav .carddav_container {
+ margin: 0 10px;
+ background: #eaeaea;
+ border-radius: 5px;
+}
+
+.carddav .carddav_server_list {
+ width: 100%;
+ border-collapse: collapse;
+ text-shadow: 1px 1px 0px #fff;
+}
+
+.carddav_server_list thead td {
+ font-weight: bold;
+ padding: 10px;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #eee;
+}
+
+.carddav_server_list thead td:first-child {
+ border-top-left-radius: 5px;
+}
+
+.carddav_server_list thead td:last-child {
+ border-top-right-radius: 5px;
+}
+
+.carddav_server_list tbody tr:hover {
+ background-color: #def;
+}
+
+.carddav_server_list tbody tr:hover td {
+ border-radius: 5px;
+}
+
+.carddav_server_list tbody td {
+ padding: 5px 15px;
+}
+
+.carddav .carddav_headline.example_server_list {
+ margin-top: 30px;
+ font-size: 100%;
+} \ No newline at end of file
diff --git a/webmail/plugins/carddav/skins/default/checked.png b/webmail/plugins/carddav/skins/default/checked.png
new file mode 100644
index 0000000..a73b8d8
--- /dev/null
+++ b/webmail/plugins/carddav/skins/default/checked.png
Binary files differ
diff --git a/webmail/plugins/carddav/skins/default/sync_act.png b/webmail/plugins/carddav/skins/default/sync_act.png
new file mode 100644
index 0000000..0a9ff13
--- /dev/null
+++ b/webmail/plugins/carddav/skins/default/sync_act.png
Binary files differ
diff --git a/webmail/plugins/carddav/skins/default/sync_pas.png b/webmail/plugins/carddav/skins/default/sync_pas.png
new file mode 100644
index 0000000..cca4c75
--- /dev/null
+++ b/webmail/plugins/carddav/skins/default/sync_pas.png
Binary files differ
diff --git a/webmail/plugins/carddav/skins/larry/carddav.css b/webmail/plugins/carddav/skins/larry/carddav.css
new file mode 100644
index 0000000..3a320b3
--- /dev/null
+++ b/webmail/plugins/carddav/skins/larry/carddav.css
@@ -0,0 +1,57 @@
+.carddav .button {
+ border-radius: 5px;
+ border: 1px solid #aaa;
+ text-shadow: 1px 1px 0px #fff;
+ cursor: pointer;
+}
+
+.carddav .carddav_headline {
+ font-size: 120%;
+ text-shadow: 1px 1px 0px #fff;
+ font-weight: bold;
+ margin-bottom: 10px;
+}
+
+.carddav .carddav_container {
+ margin: 0 10px;
+ background: #eaeaea;
+ border-radius: 5px;
+}
+
+.carddav .carddav_server_list {
+ width: 100%;
+ border-collapse: collapse;
+ text-shadow: 1px 1px 0px #fff;
+}
+
+.carddav_server_list thead td {
+ font-weight: bold;
+ padding: 10px;
+ background-color: #e5e5e5;
+ border-bottom: 1px solid #eee;
+}
+
+.carddav_server_list thead td:first-child {
+ border-top-left-radius: 5px;
+}
+
+.carddav_server_list thead td:last-child {
+ border-top-right-radius: 5px;
+}
+
+.carddav_server_list tbody tr:hover {
+ background-color: #def;
+}
+
+.carddav_server_list tbody tr:hover td {
+ border-radius: 5px;
+}
+
+.carddav_server_list tbody td {
+ padding: 5px 15px;
+}
+
+.carddav .carddav_headline.example_server_list {
+ margin-top: 30px;
+ font-size: 100%;
+} \ No newline at end of file
diff --git a/webmail/plugins/carddav/skins/larry/checked.png b/webmail/plugins/carddav/skins/larry/checked.png
new file mode 100644
index 0000000..a73b8d8
--- /dev/null
+++ b/webmail/plugins/carddav/skins/larry/checked.png
Binary files differ
diff --git a/webmail/plugins/carddav/skins/larry/sync_act.png b/webmail/plugins/carddav/skins/larry/sync_act.png
new file mode 100644
index 0000000..9c4cc82
--- /dev/null
+++ b/webmail/plugins/carddav/skins/larry/sync_act.png
Binary files differ
diff --git a/webmail/plugins/carddav/skins/larry/sync_pas.png b/webmail/plugins/carddav/skins/larry/sync_pas.png
new file mode 100644
index 0000000..83e6ae4
--- /dev/null
+++ b/webmail/plugins/carddav/skins/larry/sync_pas.png
Binary files differ