changeset 3:f6fe4b6ae66a

calendar plugin nearly as distributed
author Charlie Root
date Sat, 13 Jan 2018 08:56:12 -0500
parents c828b0fd4a6e
children 888e774ee983
files plugins/calendar/LICENSE plugins/calendar/README plugins/calendar/UPGRADING plugins/calendar/calendar.php plugins/calendar/calendar_base.js plugins/calendar/calendar_ui.js plugins/calendar/config.inc.php plugins/calendar/config.inc.php.dist plugins/calendar/drivers/calendar_driver.php plugins/calendar/drivers/database/SQL/mysql.initial.sql plugins/calendar/drivers/database/SQL/mysql/2012080600.sql plugins/calendar/drivers/database/SQL/mysql/2013011000.sql plugins/calendar/drivers/database/SQL/mysql/2013042700.sql plugins/calendar/drivers/database/SQL/mysql/2013051600.sql plugins/calendar/drivers/database/SQL/mysql/2013071800.sql plugins/calendar/drivers/database/SQL/mysql/2014040900.sql plugins/calendar/drivers/database/SQL/mysql/2015022700.sql plugins/calendar/drivers/database/SQL/postgres.initial.sql plugins/calendar/drivers/database/SQL/postgres/2012080600.sql plugins/calendar/drivers/database/SQL/postgres/2013011000.sql plugins/calendar/drivers/database/SQL/postgres/2013042700.sql plugins/calendar/drivers/database/SQL/postgres/2013051600.sql plugins/calendar/drivers/database/SQL/postgres/2013071800.sql plugins/calendar/drivers/database/SQL/postgres/2014040900.sql plugins/calendar/drivers/database/SQL/postgres/2015022700.sql plugins/calendar/drivers/database/SQL/sqlite.initial.sql plugins/calendar/drivers/database/SQL/sqlite/2013011000.sql plugins/calendar/drivers/database/SQL/sqlite/2013042700.sql plugins/calendar/drivers/database/SQL/sqlite/2013051600.sql plugins/calendar/drivers/database/SQL/sqlite/2013071800.sql plugins/calendar/drivers/database/SQL/sqlite/2014040900.sql plugins/calendar/drivers/database/SQL/sqlite/2015022700.sql plugins/calendar/drivers/database/database_driver.php plugins/calendar/drivers/kolab/SQL/mysql.initial.sql plugins/calendar/drivers/kolab/SQL/mysql/2012080600.sql plugins/calendar/drivers/kolab/SQL/mysql/2013011000.sql plugins/calendar/drivers/kolab/SQL/mysql/2014041700.sql plugins/calendar/drivers/kolab/SQL/mysql/2014082600.sql plugins/calendar/drivers/kolab/SQL/oracle.initial.sql plugins/calendar/drivers/kolab/SQL/postgres.initial.sql plugins/calendar/drivers/kolab/kolab_calendar.php plugins/calendar/drivers/kolab/kolab_driver.php plugins/calendar/drivers/kolab/kolab_invitation_calendar.php plugins/calendar/drivers/kolab/kolab_user_calendar.php plugins/calendar/drivers/ldap/resources_driver_ldap.php plugins/calendar/drivers/resources_driver.php plugins/calendar/lib/Horde_Date.php plugins/calendar/lib/Horde_Date_Recurrence.php plugins/calendar/lib/calendar_itip.php plugins/calendar/lib/calendar_recurrence.php plugins/calendar/lib/calendar_ui.php plugins/calendar/lib/js/fullcalendar.js plugins/calendar/lib/js/jquery.miniColors.min.js plugins/calendar/localization/bg_BG.inc plugins/calendar/localization/ca_ES.inc plugins/calendar/localization/cs_CZ.inc plugins/calendar/localization/da_DK.inc plugins/calendar/localization/de_CH.inc plugins/calendar/localization/de_DE.inc plugins/calendar/localization/en_US.inc plugins/calendar/localization/es_AR.inc plugins/calendar/localization/es_ES.inc plugins/calendar/localization/et_EE.inc plugins/calendar/localization/fi_FI.inc plugins/calendar/localization/fr_FR.inc plugins/calendar/localization/he.inc plugins/calendar/localization/hr.inc plugins/calendar/localization/hu_HU.inc plugins/calendar/localization/it_IT.inc plugins/calendar/localization/ja_JP.inc plugins/calendar/localization/ku_IQ.inc plugins/calendar/localization/nl_NL.inc plugins/calendar/localization/pl.inc plugins/calendar/localization/pl_PL.inc plugins/calendar/localization/pt_BR.inc plugins/calendar/localization/pt_PT.inc plugins/calendar/localization/ro.inc plugins/calendar/localization/ru_RU.inc plugins/calendar/localization/sk_SK.inc plugins/calendar/localization/sl_SI.inc plugins/calendar/localization/sv.inc plugins/calendar/localization/sv_SE.inc plugins/calendar/localization/th_TH.inc plugins/calendar/localization/tr_TR.inc plugins/calendar/localization/uk_UA.inc plugins/calendar/localization/vi.inc plugins/calendar/localization/vi_VN.inc plugins/calendar/localization/zh_CN.inc plugins/calendar/localization/zh_TW.inc plugins/calendar/print.js plugins/calendar/skins/classic/README plugins/calendar/skins/classic/calendar.css plugins/calendar/skins/classic/fullcalendar.css plugins/calendar/skins/classic/iehacks.css plugins/calendar/skins/classic/images/attendee-status.gif plugins/calendar/skins/classic/images/badge_confidential.gif plugins/calendar/skins/classic/images/badge_confidential.png plugins/calendar/skins/classic/images/badge_private.gif plugins/calendar/skins/classic/images/badge_private.png plugins/calendar/skins/classic/images/calendar-blue.png plugins/calendar/skins/classic/images/calendar.gif plugins/calendar/skins/classic/images/calendar.png plugins/calendar/skins/classic/images/calendars.gif plugins/calendar/skins/classic/images/calendars.png plugins/calendar/skins/classic/images/eventicons.gif plugins/calendar/skins/classic/images/export.png plugins/calendar/skins/classic/images/freebusy-colors.gif plugins/calendar/skins/classic/images/freebusy-colors.png plugins/calendar/skins/classic/images/invitation.png plugins/calendar/skins/classic/images/listheader.gif plugins/calendar/skins/classic/images/loading_blue.gif plugins/calendar/skins/classic/images/minicolors-all.png plugins/calendar/skins/classic/images/minicolors-handles.gif plugins/calendar/skins/classic/images/preview.png plugins/calendar/skins/classic/images/print.png plugins/calendar/skins/classic/images/spacer.gif plugins/calendar/skins/classic/images/toggle.gif plugins/calendar/skins/classic/images/toolbar.gif plugins/calendar/skins/classic/images/toolbar.png plugins/calendar/skins/classic/jquery.miniColors.css plugins/calendar/skins/classic/print.css plugins/calendar/skins/classic/print.iehacks.css plugins/calendar/skins/classic/templates/attachment.html plugins/calendar/skins/classic/templates/calendar.html plugins/calendar/skins/classic/templates/eventedit.html plugins/calendar/skins/classic/templates/freebusylegend.html plugins/calendar/skins/classic/templates/itipattend.html plugins/calendar/skins/classic/templates/kolabacl.html plugins/calendar/skins/classic/templates/kolabform.html plugins/calendar/skins/classic/templates/print.html plugins/calendar/skins/larry/README plugins/calendar/skins/larry/calendar.css plugins/calendar/skins/larry/fullcalendar.css plugins/calendar/skins/larry/iehacks.css plugins/calendar/skins/larry/images/attendee-status.gif plugins/calendar/skins/larry/images/attendee-status.png plugins/calendar/skins/larry/images/autocomplete.png plugins/calendar/skins/larry/images/badge.png plugins/calendar/skins/larry/images/badge_cancelled.png plugins/calendar/skins/larry/images/badge_confidential.png plugins/calendar/skins/larry/images/badge_private.png plugins/calendar/skins/larry/images/calendar.png plugins/calendar/skins/larry/images/calendars.png plugins/calendar/skins/larry/images/eventicons.png plugins/calendar/skins/larry/images/focusview.png plugins/calendar/skins/larry/images/freebusy-colors.png plugins/calendar/skins/larry/images/ical-attachment.png plugins/calendar/skins/larry/images/invitation.png plugins/calendar/skins/larry/images/loading_blue.gif plugins/calendar/skins/larry/images/minicolors-all.png plugins/calendar/skins/larry/images/minicolors-handles.gif plugins/calendar/skins/larry/images/sendinvitation.png plugins/calendar/skins/larry/images/toggle.gif plugins/calendar/skins/larry/images/toolbar.png plugins/calendar/skins/larry/jquery.miniColors.css plugins/calendar/skins/larry/print.css plugins/calendar/skins/larry/print.iehacks.css plugins/calendar/skins/larry/templates/attachment.html plugins/calendar/skins/larry/templates/calendar.html plugins/calendar/skins/larry/templates/dialog.html plugins/calendar/skins/larry/templates/eventedit.html plugins/calendar/skins/larry/templates/eventshow.html plugins/calendar/skins/larry/templates/freebusylegend.html plugins/calendar/skins/larry/templates/itipattend.html plugins/calendar/skins/larry/templates/kolabacl.html plugins/calendar/skins/larry/templates/kolabform.html plugins/calendar/skins/larry/templates/print.html
diffstat 167 files changed, 42712 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/LICENSE	Sat Jan 13 08:56:12 2018 -0500
@@ -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/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/README	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,90 @@
+A calendar module for Roundcube
+-------------------------------
+
+This plugin currently supports a local database as well as a Kolab groupware
+server as backends for calendar and event storage. For both drivers, some
+initialization of the local database is necessary. To do so, execute the
+SQL commands in drivers/<yourchoice>/SQL/<yourdatabase>.initial.sql
+
+The client-side calendar UI relies on the "fullcalendar" project by Adam Arshaw
+with extensions made for the use in Roundcube. All changes are published in
+an official fork at https://github.com/roundcube/fullcalendar
+
+For some general calendar-based operations such as alarms handling or iCal
+parsing/exporting this plugins requires the `libcalendaring` plugin which
+is also part of the Kolab Roundcube Plugins repository. Make sure that plugin
+is installed and configured correctly.
+
+For recurring event computation, some utility classes from the Horde project
+are used. They are packaged in a slightly modified version with this plugin.
+
+IMPORTANT
+---------
+
+The calendar module makes heavy use of PHP's DateTime as well as DateInterval
+classes. The latter one requires at least PHP 5.3.0 to run.
+
+
+REQUIREMENTS
+------------
+
+Some functions are shared with other plugins and therefore being moved to
+library plugins. Thus in order to run the calendar plugin, you also need the
+following plugins installed:
+
+* libcalendaring [1]
+* libkolab [1] (when using the 'kolab' driver)
+
+
+INSTALLATION
+------------
+
+The preferred and automated way to install the calendar with all requirements
+is via the Roundcube plugin repository: https://plugins.roundcube.net
+
+For a manual installation of the calendar plugin (and its dependencies),
+execute the following steps. This will set it up with the database backend
+driver.
+
+1. Get the source from git
+
+  $ cd /tmp
+  $ git clone https://git.kolab.org/diffusion/RPK/roundcubemail-plugins-kolab.git
+  $ cd /<path-to-roundcube>/plugins
+  $ cp -r /tmp/roundcubemail-plugins-kolab/plugins/calendar .
+  $ cp -r /tmp/roundcubemail-plugins-kolab/plugins/libcalendaring .
+
+2. Install the dependencies with Composer
+
+(This has to be done from the Roundcube root directory)
+
+  $ cd /<path-to-roundcube>
+  $ php composer.phar require sabre/vobject 3.3.3
+
+Download the composer.phar script from https://getcomposer.org
+
+3. Create calendar plugin configuration
+
+  $ cd calendar/
+  $ cp config.inc.php.dist config.inc.php
+  $ edit config.inc.php
+
+4. Initialize the calendar database tables
+
+  $ mysql roundcubemail < drivers/database/SQL/mysql.initial.sql
+
+5. Enable the calendar plugin
+
+  $ cd ../../
+  $ edit config/config.inc.php
+
+Add 'calendar' to the list of active plugins:
+
+  $config['plugins'] = array(
+    (...)
+    'calendar',
+  );
+
+
+
+[1] https://git.kolab.org/diffusion/RPK/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/UPGRADING	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,17 @@
+UPGRADING instructions
+======================
+
+To update database schema, depending on the driver you're using,
+please run in Roundcube bin/ directory:
+
+updatedb.sh --package=calendar-<driver> --version=<version> \
+  --dir=../plugins/calendar/drivers/<driver>/SQL
+
+[*] Replace <driver> with "database" or "kolab" (without quotes)
+[*] Replace <version> with Roundcube version e.g. 0.9.0
+[*] Roundcube should be upgraded before plugin upgrades
+
+Example:
+
+updatedb.sh --package=calendar-kolab --version=0.9.0 \
+  --dir=../plugins/calendar/drivers/kolab/SQL
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/calendar.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3464 @@
+<?php
+
+/**
+ * Calendar plugin for Roundcube webmail
+ *
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+class calendar extends rcube_plugin
+{
+  const FREEBUSY_UNKNOWN = 0;
+  const FREEBUSY_FREE = 1;
+  const FREEBUSY_BUSY = 2;
+  const FREEBUSY_TENTATIVE = 3;
+  const FREEBUSY_OOF = 4;
+
+  const SESSION_KEY = 'calendar_temp';
+
+  public $task = '?(?!logout).*';
+  public $rc;
+  public $lib;
+  public $resources_dir;
+  public $home;  // declare public to be used in other classes
+  public $urlbase;
+  public $timezone;
+  public $timezone_offset;
+  public $gmt_offset;
+  public $ui;
+
+  public $defaults = array(
+    'calendar_default_view' => "agendaWeek",
+    'calendar_timeslots'    => 2,
+    'calendar_work_start'   => 6,
+    'calendar_work_end'     => 18,
+    'calendar_agenda_range' => 60,
+    'calendar_agenda_sections' => 'smart',
+    'calendar_event_coloring'  => 0,
+    'calendar_time_indicator'  => true,
+    'calendar_allow_invite_shared' => false,
+    'calendar_itip_send_option'    => 3,
+    'calendar_itip_after_action'   => 0,
+  );
+
+// These are implemented with __get()
+//  private $ical;
+//  private $itip;
+//  private $driver;
+
+
+  /**
+   * Plugin initialization.
+   */
+  function init()
+  {
+    $this->rc = rcube::get_instance();
+
+    $this->register_task('calendar', 'calendar');
+
+    // load calendar configuration
+    $this->load_config();
+
+    // catch iTIP confirmation requests that don're require a valid session
+    if ($this->rc->action == 'attend' && !empty($_REQUEST['_t'])) {
+      $this->add_hook('startup', array($this, 'itip_attend_response'));
+    }
+    else if ($this->rc->action == 'feed' && !empty($_REQUEST['_cal'])) {
+      $this->add_hook('startup', array($this, 'ical_feed_export'));
+    }
+    else if ($this->rc->task != 'login') {
+      // default startup routine
+      $this->add_hook('startup', array($this, 'startup'));
+    }
+
+    $this->add_hook('user_delete', array($this, 'user_delete'));
+  }
+
+  /**
+   * Setup basic plugin environment and UI
+   */
+  protected function setup()
+  {
+    $this->require_plugin('libcalendaring');
+
+    $this->lib             = libcalendaring::get_instance();
+    $this->timezone        = $this->lib->timezone;
+    $this->gmt_offset      = $this->lib->gmt_offset;
+    $this->dst_active      = $this->lib->dst_active;
+    $this->timezone_offset = $this->gmt_offset / 3600 - $this->dst_active;
+
+    // load localizations
+    $this->add_texts('localization/', $this->rc->task == 'calendar' && (!$this->rc->action || $this->rc->action == 'print'));
+
+    require($this->home . '/lib/calendar_ui.php');
+    $this->ui = new calendar_ui($this);
+  }
+
+  /**
+   * Startup hook
+   */
+  public function startup($args)
+  {
+    // the calendar module can be enabled/disabled by the kolab_auth plugin
+    if ($this->rc->config->get('calendar_disabled', false) || !$this->rc->config->get('calendar_enabled', true))
+        return;
+
+    $this->setup();
+
+    // load Calendar user interface
+    if (!$this->rc->output->ajax_call && (!$this->rc->output->env['framed'] || $args['action'] == 'preview')) {
+      $this->ui->init();
+
+      // settings are required in (almost) every GUI step
+      if ($args['action'] != 'attend')
+        $this->rc->output->set_env('calendar_settings', $this->load_settings());
+    }
+
+    if ($args['task'] == 'calendar' && $args['action'] != 'save-pref') {
+      if ($args['action'] != 'upload') {
+        $this->load_driver();
+      }
+
+      // register calendar actions
+      $this->register_action('index', array($this, 'calendar_view'));
+      $this->register_action('event', array($this, 'event_action'));
+      $this->register_action('calendar', array($this, 'calendar_action'));
+      $this->register_action('count', array($this, 'count_events'));
+      $this->register_action('load_events', array($this, 'load_events'));
+      $this->register_action('export_events', array($this, 'export_events'));
+      $this->register_action('import_events', array($this, 'import_events'));
+      $this->register_action('upload', array($this, 'attachment_upload'));
+      $this->register_action('get-attachment', array($this, 'attachment_get'));
+      $this->register_action('freebusy-status', array($this, 'freebusy_status'));
+      $this->register_action('freebusy-times', array($this, 'freebusy_times'));
+      $this->register_action('randomdata', array($this, 'generate_randomdata'));
+      $this->register_action('print', array($this,'print_view'));
+      $this->register_action('mailimportitip', array($this, 'mail_import_itip'));
+      $this->register_action('mailimportattach', array($this, 'mail_import_attachment'));
+      $this->register_action('mailtoevent', array($this, 'mail_message2event'));
+      $this->register_action('inlineui', array($this, 'get_inline_ui'));
+      $this->register_action('check-recent', array($this, 'check_recent'));
+      $this->register_action('itip-status', array($this, 'event_itip_status'));
+      $this->register_action('itip-remove', array($this, 'event_itip_remove'));
+      $this->register_action('itip-decline-reply', array($this, 'mail_itip_decline_reply'));
+      $this->register_action('itip-delegate', array($this, 'mail_itip_delegate'));
+      $this->register_action('resources-list', array($this, 'resources_list'));
+      $this->register_action('resources-owner', array($this, 'resources_owner'));
+      $this->register_action('resources-calendar', array($this, 'resources_calendar'));
+      $this->register_action('resources-autocomplete', array($this, 'resources_autocomplete'));
+      $this->add_hook('refresh', array($this, 'refresh'));
+
+      // remove undo information...
+      if ($undo = $_SESSION['calendar_event_undo']) {
+        // ...after timeout
+        $undo_time = $this->rc->config->get('undo_timeout', 0);
+        if ($undo['ts'] < time() - $undo_time) {
+          $this->rc->session->remove('calendar_event_undo');
+          // @TODO: do EXPUNGE on kolab objects?
+        }
+      }
+    }
+    else if ($args['task'] == 'settings') {
+      // add hooks for Calendar settings
+      $this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list'));
+      $this->add_hook('preferences_list', array($this, 'preferences_list'));
+      $this->add_hook('preferences_save', array($this, 'preferences_save')); 
+    }
+    else if ($args['task'] == 'mail') {
+      // hooks to catch event invitations on incoming mails
+      if ($args['action'] == 'show' || $args['action'] == 'preview') {
+        $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
+      }
+
+      // add 'Create event' item to message menu
+      if ($this->api->output->type == 'html') {
+        $this->api->add_content(html::tag('li', null, 
+          $this->api->output->button(array(
+            'command'  => 'calendar-create-from-mail',
+            'label'    => 'calendar.createfrommail',
+            'type'     => 'link',
+            'classact' => 'icon calendarlink active',
+            'class'    => 'icon calendarlink',
+            'innerclass' => 'icon calendar',
+          ))),
+          'messagemenu');
+
+        $this->api->output->add_label('calendar.createfrommail');
+      }
+
+      $this->add_hook('messages_list', array($this, 'mail_messages_list'));
+      $this->add_hook('message_compose', array($this, 'mail_message_compose'));
+    }
+    else if ($args['task'] == 'addressbook') {
+      if ($this->rc->config->get('calendar_contact_birthdays')) {
+        $this->add_hook('contact_update', array($this, 'contact_update'));
+        $this->add_hook('contact_create', array($this, 'contact_update'));
+      }
+    }
+
+    // add hooks to display alarms
+    $this->add_hook('pending_alarms', array($this, 'pending_alarms'));
+    $this->add_hook('dismiss_alarms', array($this, 'dismiss_alarms'));
+  }
+
+  /**
+   * Helper method to load the backend driver according to local config
+   */
+  private function load_driver()
+  {
+    if (is_object($this->driver))
+      return;
+
+    $driver_name = $this->rc->config->get('calendar_driver', 'database');
+    $driver_class = $driver_name . '_driver';
+
+    require_once($this->home . '/drivers/calendar_driver.php');
+    require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
+
+    $this->driver = new $driver_class($this);
+
+    if ($this->driver->undelete)
+      $this->driver->undelete = $this->rc->config->get('undo_timeout', 0) > 0;
+  }
+
+  /**
+   * Load iTIP functions
+   */
+  private function load_itip()
+  {
+    if (!$this->itip) {
+      require_once($this->home . '/lib/calendar_itip.php');
+      $this->itip = new calendar_itip($this);
+      
+      if ($this->rc->config->get('kolab_invitation_calendars'))
+        $this->itip->set_rsvp_actions(array('accepted','tentative','declined','delegated','needs-action'));
+    }
+
+    return $this->itip;
+  }
+
+  /**
+   * Load iCalendar functions
+   */
+  public function get_ical()
+  {
+    if (!$this->ical) {
+      $this->ical = libcalendaring::get_ical();
+    }
+    
+    return $this->ical;
+  }
+
+  /**
+   * Get properties of the calendar this user has specified as default
+   */
+  public function get_default_calendar($sensitivity = null, $calendars = null)
+  {
+    if ($calendars === null) {
+      $calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL | calendar_driver::FILTER_WRITEABLE);
+    }
+
+    $default_id = $this->rc->config->get('calendar_default_calendar');
+    $calendar   = $calendars[$default_id] ?: null;
+
+    if (!$calendar || $sensitivity) {
+      foreach ($calendars as $cal) {
+        if ($sensitivity && $cal['subtype'] == $sensitivity) {
+          $calendar = $cal;
+          break;
+        }
+        if ($cal['default'] && $cal['editable']) {
+          $calendar = $cal;
+        }
+        if ($cal['editable']) {
+          $first = $cal;
+        }
+      }
+    }
+
+    return $calendar ?: $first;
+  }
+
+
+  /**
+   * Render the main calendar view from skin template
+   */
+  function calendar_view()
+  {
+    $this->rc->output->set_pagetitle($this->gettext('calendar'));
+
+    // Add CSS stylesheets to the page header
+    $this->ui->addCSS();
+
+    // Add JS files to the page header
+    $this->ui->addJS();
+
+    $this->ui->init_templates();
+    $this->rc->output->add_label('lowest','low','normal','high','highest','delete','cancel','uploading','noemailwarning','close');
+
+    // initialize attendees autocompletion
+    $this->rc->autocomplete_init();
+
+    $this->rc->output->set_env('timezone', $this->timezone->getName());
+    $this->rc->output->set_env('calendar_driver', $this->rc->config->get('calendar_driver'), false);
+    $this->rc->output->set_env('calendar_resources', (bool)$this->rc->config->get('calendar_resources_driver'));
+    $this->rc->output->set_env('identities-selector', $this->ui->identity_select(array('id' => 'edit-identities-list', 'aria-label' => $this->gettext('roleorganizer'))));
+
+    $view = rcube_utils::get_input_value('view', rcube_utils::INPUT_GPC);
+    if (in_array($view, array('agendaWeek', 'agendaDay', 'month', 'table')))
+      $this->rc->output->set_env('view', $view);
+
+    if ($date = rcube_utils::get_input_value('date', rcube_utils::INPUT_GPC))
+      $this->rc->output->set_env('date', $date);
+
+    if ($msgref = rcube_utils::get_input_value('itip', rcube_utils::INPUT_GPC))
+      $this->rc->output->set_env('itip_events', $this->itip_events($msgref));
+
+    $this->rc->output->send("calendar.calendar");
+  }
+
+  /**
+   * Handler for preferences_sections_list hook.
+   * Adds Calendar settings sections into preferences sections list.
+   *
+   * @param array Original parameters
+   * @return array Modified parameters
+   */
+  function preferences_sections_list($p)
+  {
+    $p['list']['calendar'] = array(
+      'id' => 'calendar', 'section' => $this->gettext('calendar'),
+    );
+
+    return $p;
+  }
+
+  /**
+   * Handler for preferences_list hook.
+   * Adds options blocks into Calendar settings sections in Preferences.
+   *
+   * @param array Original parameters
+   * @return array Modified parameters
+   */
+  function preferences_list($p)
+  {
+    if ($p['section'] != 'calendar') {
+      return $p;
+    }
+
+    $no_override = array_flip((array)$this->rc->config->get('dont_override'));
+
+    $p['blocks']['view']['name'] = $this->gettext('mainoptions');
+
+    if (!isset($no_override['calendar_default_view'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_default_view';
+      $select = new html_select(array('name' => '_default_view', 'id' => $field_id));
+      $select->add($this->gettext('day'), "agendaDay");
+      $select->add($this->gettext('week'), "agendaWeek");
+      $select->add($this->gettext('month'), "month");
+      $select->add($this->gettext('agenda'), "table");
+      $p['blocks']['view']['options']['default_view'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('default_view'))),
+        'content' => $select->show($this->rc->config->get('calendar_default_view', $this->defaults['calendar_default_view'])),
+      );
+    }
+
+    if (!isset($no_override['calendar_timeslots'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_timeslot';
+      $choices = array('1', '2', '3', '4', '6');
+      $select = new html_select(array('name' => '_timeslots', 'id' => $field_id));
+      $select->add($choices);
+      $p['blocks']['view']['options']['timeslots'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('timeslots'))),
+        'content' => $select->show(strval($this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']))),
+      );
+    }
+
+    if (!isset($no_override['calendar_first_day'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_firstday';
+      $select = new html_select(array('name' => '_first_day', 'id' => $field_id));
+      $select->add($this->gettext('sunday'), '0');
+      $select->add($this->gettext('monday'), '1');
+      $select->add($this->gettext('tuesday'), '2');
+      $select->add($this->gettext('wednesday'), '3');
+      $select->add($this->gettext('thursday'), '4');
+      $select->add($this->gettext('friday'), '5');
+      $select->add($this->gettext('saturday'), '6');
+      $p['blocks']['view']['options']['first_day'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('first_day'))),
+        'content' => $select->show(strval($this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']))),
+      );
+    }
+
+    if (!isset($no_override['calendar_first_hour'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $time_format = $this->rc->config->get('time_format', libcalendaring::to_php_date_format($this->rc->config->get('calendar_time_format', $this->defaults['calendar_time_format'])));
+      $select_hours = new html_select();
+      for ($h = 0; $h < 24; $h++)
+        $select_hours->add(date($time_format, mktime($h, 0, 0)), $h);
+
+      $field_id = 'rcmfd_firsthour';
+      $p['blocks']['view']['options']['first_hour'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('first_hour'))),
+        'content' => $select_hours->show($this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']), array('name' => '_first_hour', 'id' => $field_id)),
+      );
+    }
+
+    if (!isset($no_override['calendar_work_start'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_workstart';
+      $p['blocks']['view']['options']['workinghours'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('workinghours'))),
+        'content' => $select_hours->show($this->rc->config->get('calendar_work_start', $this->defaults['calendar_work_start']), array('name' => '_work_start', 'id' => $field_id)) .
+        ' &mdash; ' . $select_hours->show($this->rc->config->get('calendar_work_end', $this->defaults['calendar_work_end']), array('name' => '_work_end', 'id' => $field_id)),
+      );
+    }
+
+    if (!isset($no_override['calendar_event_coloring'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_coloring';
+      $select_colors = new html_select(array('name' => '_event_coloring', 'id' => $field_id));
+      $select_colors->add($this->gettext('coloringmode0'), 0);
+      $select_colors->add($this->gettext('coloringmode1'), 1);
+      $select_colors->add($this->gettext('coloringmode2'), 2);
+      $select_colors->add($this->gettext('coloringmode3'), 3);
+
+      $p['blocks']['view']['options']['eventcolors'] = array(
+        'title' => html::label($field_id . 'value', rcube::Q($this->gettext('eventcoloring'))),
+        'content' => $select_colors->show($this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring'])),
+      );
+    }
+
+    // loading driver is expensive, don't do it if not needed
+    $this->load_driver();
+
+    if (!isset($no_override['calendar_default_alarm_type']) || !isset($no_override['calendar_default_alarm_offset'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+
+      $alarm_type = $alarm_offset = '';
+
+      if (!isset($no_override['calendar_default_alarm_type'])) {
+        $field_id    = 'rcmfd_alarm';
+        $select_type = new html_select(array('name' => '_alarm_type', 'id' => $field_id));
+        $select_type->add($this->gettext('none'), '');
+
+        foreach ($this->driver->alarm_types as $type) {
+          $select_type->add($this->rc->gettext(strtolower("alarm{$type}option"), 'libcalendaring'), $type);
+        }
+
+        $alarm_type = $select_type->show($this->rc->config->get('calendar_default_alarm_type', ''));
+      }
+
+      if (!isset($no_override['calendar_default_alarm_offset'])) {
+        $field_id      = 'rcmfd_alarm';
+        $input_value   = new html_inputfield(array('name' => '_alarm_value', 'id' => $field_id . 'value', 'size' => 3));
+        $select_offset = new html_select(array('name' => '_alarm_offset', 'id' => $field_id . 'offset'));
+
+        foreach (array('-M','-H','-D','+M','+H','+D') as $trigger) {
+          $select_offset->add($this->rc->gettext('trigger' . $trigger, 'libcalendaring'), $trigger);
+        }
+
+        $preset = libcalendaring::parse_alarm_value($this->rc->config->get('calendar_default_alarm_offset', '-15M'));
+        $alarm_offset = $input_value->show($preset[0]) . ' ' . $select_offset->show($preset[1]);
+      }
+
+      $p['blocks']['view']['options']['alarmtype'] = array(
+        'title' => html::label($field_id, rcube::Q($this->gettext('defaultalarmtype'))),
+        'content' => $alarm_type . ' ' . $alarm_offset,
+      );
+    }
+
+    if (!isset($no_override['calendar_default_calendar'])) {
+      if (!$p['current']) {
+        $p['blocks']['view']['content'] = true;
+        return $p;
+      }
+      // default calendar selection
+      $field_id = 'rcmfd_default_calendar';
+      $select_cal = new html_select(array('name' => '_default_calendar', 'id' => $field_id, 'is_escaped' => true));
+      foreach ((array)$this->driver->list_calendars(calendar_driver::FILTER_PERSONAL | calendar_driver::FILTER_ACTIVE) as $id => $prop) {
+        $select_cal->add($prop['name'], strval($id));
+        if ($prop['default'])
+          $default_calendar = $id;
+      }
+      $p['blocks']['view']['options']['defaultcalendar'] = array(
+        'title' => html::label($field_id . 'value', rcube::Q($this->gettext('defaultcalendar'))),
+        'content' => $select_cal->show($this->rc->config->get('calendar_default_calendar', $default_calendar)),
+      );
+    }
+
+    $p['blocks']['itip']['name'] = $this->gettext('itipoptions');
+
+    // Invitations handling
+    if (!isset($no_override['calendar_itip_after_action'])) {
+      if (!$p['current']) {
+        $p['blocks']['itip']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_after_action';
+      $select   = new html_select(array('name' => '_after_action', 'id' => $field_id,
+        'onchange' => "\$('#{$field_id}_select')[this.value == 4 ? 'show' : 'hide']()"));
+
+      $select->add($this->gettext('afternothing'), '');
+      $select->add($this->gettext('aftertrash'), 1);
+      $select->add($this->gettext('afterdelete'), 2);
+      $select->add($this->gettext('afterflagdeleted'), 3);
+      $select->add($this->gettext('aftermoveto'), 4);
+
+      $val = $this->rc->config->get('calendar_itip_after_action', $this->defaults['calendar_itip_after_action']);
+      if ($val !== null && $val !== '' && !is_int($val)) {
+        $folder = $val;
+        $val    = 4;
+      }
+
+      $folders = $this->rc->folder_selector(array(
+          'id'            => $field_id . '_select',
+          'name'          => '_after_action_folder',
+          'maxlength'     => 30,
+          'folder_filter' => 'mail',
+          'folder_rights' => 'w',
+          'style'         => $val !== 4 ? 'display:none' : '',
+      ));
+
+      $p['blocks']['itip']['options']['after_action'] = array(
+        'title'   => html::label($field_id, rcube::Q($this->gettext('afteraction'))),
+        'content' => $select->show($val) . $folders->show($folder),
+      );
+    }
+
+    // category definitions
+    if (!$this->driver->nocategories && !isset($no_override['calendar_categories'])) {
+        $p['blocks']['categories']['name'] = $this->gettext('categories');
+
+        if (!$p['current']) {
+          $p['blocks']['categories']['content'] = true;
+          return $p;
+        }
+
+        $categories = (array) $this->driver->list_categories();
+        $categories_list = '';
+        foreach ($categories as $name => $color) {
+          $key = md5($name);
+          $field_class = 'rcmfd_category_' . str_replace(' ', '_', $name);
+          $category_remove = new html_inputfield(array('type' => 'button', 'value' => 'X', 'class' => 'button', 'onclick' => '$(this).parent().remove()', 'title' => $this->gettext('remove_category')));
+          $category_name  = new html_inputfield(array('name' => "_categories[$key]", 'class' => $field_class, 'size' => 30, 'disabled' => $this->driver->categoriesimmutable));
+          $category_color = new html_inputfield(array('name' => "_colors[$key]", 'class' => "$field_class colors", 'size' => 6));
+          $hidden = $this->driver->categoriesimmutable ? html::tag('input', array('type' => 'hidden', 'name' => "_categories[$key]", 'value' => $name)) : '';
+          $categories_list .= html::div(null, $hidden . $category_name->show($name) . '&nbsp;' . $category_color->show($color) . '&nbsp;' . $category_remove->show());
+        }
+
+        $p['blocks']['categories']['options']['category_' . $name] = array(
+          'content' => html::div(array('id' => 'calendarcategories'), $categories_list),
+        );
+
+        $field_id = 'rcmfd_new_category';
+        $new_category = new html_inputfield(array('name' => '_new_category', 'id' => $field_id, 'size' => 30));
+        $add_category = new html_inputfield(array('type' => 'button', 'class' => 'button', 'value' => $this->gettext('add_category'),  'onclick' => "rcube_calendar_add_category()"));
+        $p['blocks']['categories']['options']['categories'] = array(
+          'content' => $new_category->show('') . '&nbsp;' . $add_category->show(),
+        );
+
+        $this->rc->output->add_script('function rcube_calendar_add_category(){
+          var name = $("#rcmfd_new_category").val();
+          if (name.length) {
+            var input = $("<input>").attr("type", "text").attr("name", "_categories[]").attr("size", 30).val(name);
+            var color = $("<input>").attr("type", "text").attr("name", "_colors[]").attr("size", 6).addClass("colors").val("000000");
+            var button = $("<input>").attr("type", "button").attr("value", "X").addClass("button").click(function(){ $(this).parent().remove() });
+            $("<div>").append(input).append("&nbsp;").append(color).append("&nbsp;").append(button).appendTo("#calendarcategories");
+            color.miniColors({ colorValues:(rcmail.env.mscolors || []) });
+            $("#rcmfd_new_category").val("");
+          }
+        }');
+
+        $this->rc->output->add_script('$("#rcmfd_new_category").keypress(function(event){
+          if (event.which == 13) {
+            rcube_calendar_add_category();
+            event.preventDefault();
+          }
+        });
+        ', 'docready');
+
+        // load miniColors js/css files
+        jqueryui::miniColors();
+    }
+
+    // virtual birthdays calendar
+    if (!isset($no_override['calendar_contact_birthdays'])) {
+      $p['blocks']['birthdays']['name'] = $this->gettext('birthdayscalendar');
+
+      if (!$p['current']) {
+        $p['blocks']['birthdays']['content'] = true;
+        return $p;
+      }
+
+      $field_id = 'rcmfd_contact_birthdays';
+      $input    = new html_checkbox(array('name' => '_contact_birthdays', 'id' => $field_id, 'value' => 1, 'onclick' => '$(".calendar_birthday_props").prop("disabled",!this.checked)'));
+
+      $p['blocks']['birthdays']['options']['contact_birthdays'] = array(
+        'title'   => html::label($field_id, $this->gettext('displaybirthdayscalendar')),
+        'content' => $input->show($this->rc->config->get('calendar_contact_birthdays')?1:0),
+      );
+
+      $input_attrib = array(
+        'class' => 'calendar_birthday_props',
+        'disabled' => !$this->rc->config->get('calendar_contact_birthdays'),
+      );
+
+      $sources = array();
+      $checkbox = new html_checkbox(array('name' => '_birthday_adressbooks[]') + $input_attrib);
+      foreach ($this->rc->get_address_sources(false, true) as $source) {
+        $active = in_array($source['id'], (array)$this->rc->config->get('calendar_birthday_adressbooks', array())) ? $source['id'] : '';
+        $sources[] = html::label(null, $checkbox->show($active, array('value' => $source['id'])) . '&nbsp;' . rcube::Q($source['realname'] ?: $source['name']));
+      }
+
+      $p['blocks']['birthdays']['options']['birthday_adressbooks'] = array(
+        'title'   => rcube::Q($this->gettext('birthdayscalendarsources')),
+        'content' => join(html::br(), $sources),
+      );
+
+      $field_id = 'rcmfd_birthdays_alarm';
+      $select_type = new html_select(array('name' => '_birthdays_alarm_type', 'id' => $field_id) + $input_attrib);
+      $select_type->add($this->gettext('none'), '');
+      foreach ($this->driver->alarm_types as $type) {
+        $select_type->add($this->rc->gettext(strtolower("alarm{$type}option"), 'libcalendaring'), $type);
+      }
+
+      $input_value = new html_inputfield(array('name' => '_birthdays_alarm_value', 'id' => $field_id . 'value', 'size' => 3) + $input_attrib);
+      $select_offset = new html_select(array('name' => '_birthdays_alarm_offset', 'id' => $field_id . 'offset') + $input_attrib);
+      foreach (array('-M','-H','-D') as $trigger)
+        $select_offset->add($this->rc->gettext('trigger' . $trigger, 'libcalendaring'), $trigger);
+
+      $preset = libcalendaring::parse_alarm_value($this->rc->config->get('calendar_birthdays_alarm_offset', '-1D'));
+      $p['blocks']['birthdays']['options']['birthdays_alarmoffset'] = array(
+        'title' => html::label($field_id . 'value', rcube::Q($this->gettext('showalarms'))),
+        'content' => $select_type->show($this->rc->config->get('calendar_birthdays_alarm_type', '')) . ' ' . $input_value->show($preset[0]) . '&nbsp;' . $select_offset->show($preset[1]),
+      );
+    }
+
+    return $p;
+  }
+
+  /**
+   * Handler for preferences_save hook.
+   * Executed on Calendar settings form submit.
+   *
+   * @param array Original parameters
+   * @return array Modified parameters
+   */
+  function preferences_save($p)
+  {
+    if ($p['section'] == 'calendar') {
+      $this->load_driver();
+
+      // compose default alarm preset value
+      $alarm_offset  = rcube_utils::get_input_value('_alarm_offset', rcube_utils::INPUT_POST);
+      $alarm_value   = rcube_utils::get_input_value('_alarm_value', rcube_utils::INPUT_POST);
+      $default_alarm = $alarm_offset[0] . intval($alarm_value) . $alarm_offset[1];
+
+      $birthdays_alarm_offset = rcube_utils::get_input_value('_birthdays_alarm_offset', rcube_utils::INPUT_POST);
+      $birthdays_alarm_value  = rcube_utils::get_input_value('_birthdays_alarm_value', rcube_utils::INPUT_POST);
+      $birthdays_alarm_value  = $birthdays_alarm_offset[0] . intval($birthdays_alarm_value) . $birthdays_alarm_offset[1];
+
+      $p['prefs'] = array(
+        'calendar_default_view' => rcube_utils::get_input_value('_default_view', rcube_utils::INPUT_POST),
+        'calendar_timeslots'    => intval(rcube_utils::get_input_value('_timeslots', rcube_utils::INPUT_POST)),
+        'calendar_first_day'    => intval(rcube_utils::get_input_value('_first_day', rcube_utils::INPUT_POST)),
+        'calendar_first_hour'   => intval(rcube_utils::get_input_value('_first_hour', rcube_utils::INPUT_POST)),
+        'calendar_work_start'   => intval(rcube_utils::get_input_value('_work_start', rcube_utils::INPUT_POST)),
+        'calendar_work_end'     => intval(rcube_utils::get_input_value('_work_end', rcube_utils::INPUT_POST)),
+        'calendar_event_coloring'       => intval(rcube_utils::get_input_value('_event_coloring', rcube_utils::INPUT_POST)),
+        'calendar_default_alarm_type'   => rcube_utils::get_input_value('_alarm_type', rcube_utils::INPUT_POST),
+        'calendar_default_alarm_offset' => $default_alarm,
+        'calendar_default_calendar'     => rcube_utils::get_input_value('_default_calendar', rcube_utils::INPUT_POST),
+        'calendar_date_format' => null,  // clear previously saved values
+        'calendar_time_format' => null,
+        'calendar_contact_birthdays'    => rcube_utils::get_input_value('_contact_birthdays', rcube_utils::INPUT_POST) ? true : false,
+        'calendar_birthday_adressbooks' => (array) rcube_utils::get_input_value('_birthday_adressbooks', rcube_utils::INPUT_POST),
+        'calendar_birthdays_alarm_type'   => rcube_utils::get_input_value('_birthdays_alarm_type', rcube_utils::INPUT_POST),
+        'calendar_birthdays_alarm_offset' => $birthdays_alarm_value ?: null,
+        'calendar_itip_after_action'      => intval(rcube_utils::get_input_value('_after_action', rcube_utils::INPUT_POST)),
+      );
+
+      if ($p['prefs']['calendar_itip_after_action'] == 4) {
+        $p['prefs']['calendar_itip_after_action'] = rcube_utils::get_input_value('_after_action_folder', rcube_utils::INPUT_POST, true);
+      }
+
+      // categories
+      if (!$this->driver->nocategories) {
+        $old_categories = $new_categories = array();
+        foreach ($this->driver->list_categories() as $name => $color) {
+          $old_categories[md5($name)] = $name;
+        }
+
+        $categories = (array) rcube_utils::get_input_value('_categories', rcube_utils::INPUT_POST);
+        $colors     = (array) rcube_utils::get_input_value('_colors', rcube_utils::INPUT_POST);
+
+        foreach ($categories as $key => $name) {
+          $color = preg_replace('/^#/', '', strval($colors[$key]));
+
+          // rename categories in existing events -> driver's job
+          if ($oldname = $old_categories[$key]) {
+            $this->driver->replace_category($oldname, $name, $color);
+            unset($old_categories[$key]);
+          }
+          else
+            $this->driver->add_category($name, $color);
+
+          $new_categories[$name] = $color;
+        }
+
+        // these old categories have been removed, alter events accordingly -> driver's job
+        foreach ((array)$old_categories[$key] as $key => $name) {
+          $this->driver->remove_category($name);
+        }
+
+        $p['prefs']['calendar_categories'] = $new_categories;
+      }
+    }
+
+    return $p;
+  }
+
+  /**
+   * Dispatcher for calendar actions initiated by the client
+   */
+  function calendar_action()
+  {
+    $action = rcube_utils::get_input_value('action', rcube_utils::INPUT_GPC);
+    $cal    = rcube_utils::get_input_value('c', rcube_utils::INPUT_GPC);
+    $success = $reload = false;
+
+    if (isset($cal['showalarms']))
+      $cal['showalarms'] = intval($cal['showalarms']);
+
+    switch ($action) {
+      case "form-new":
+      case "form-edit":
+        echo $this->ui->calendar_editform($action, $cal);
+        exit;
+      case "new":
+        $success = $this->driver->create_calendar($cal);
+        $reload = true;
+        break;
+      case "edit":
+        $success = $this->driver->edit_calendar($cal);
+        $reload = true;
+        break;
+      case "delete":
+        if ($success = $this->driver->delete_calendar($cal))
+          $this->rc->output->command('plugin.destroy_source', array('id' => $cal['id']));
+        break;
+      case "subscribe":
+        if (!$this->driver->subscribe_calendar($cal))
+          $this->rc->output->show_message($this->gettext('errorsaving'), 'error');
+        else {
+          $calendars = $this->driver->list_calendars();
+          $calendar  = $calendars[$cal['id']];
+
+          // find parent folder and check if it's a "user calendar"
+          // if it's also activated we need to refresh it (#5340)
+          while ($calendar['parent']) {
+            if (isset($calendars[$calendar['parent']]))
+              $calendar = $calendars[$calendar['parent']];
+            else
+              break;
+          }
+
+          if ($calendar['id'] != $cal['id'] && $calendar['active'] && $calendar['group'] == "other user")
+            $this->rc->output->command('plugin.refresh_source', $calendar['id']);
+        }
+        return;
+      case "search":
+        $results    = array();
+        $color_mode = $this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring']);
+        $query      = rcube_utils::get_input_value('q', rcube_utils::INPUT_GPC);
+        $source     = rcube_utils::get_input_value('source', rcube_utils::INPUT_GPC);
+
+        foreach ((array) $this->driver->search_calendars($query, $source) as $id => $prop) {
+          $editname = $prop['editname'];
+          unset($prop['editname']);  // force full name to be displayed
+          $prop['active'] = false;
+
+          // let the UI generate HTML and CSS representation for this calendar
+          $html = $this->ui->calendar_list_item($id, $prop, $jsenv);
+          $cal = $jsenv[$id];
+          $cal['editname'] = $editname;
+          $cal['html'] = $html;
+          if (!empty($prop['color']))
+            $cal['css'] = $this->ui->calendar_css_classes($id, $prop, $color_mode);
+
+          $results[] = $cal;
+        }
+        // report more results available
+        if ($this->driver->search_more_results)
+          $this->rc->output->show_message('autocompletemore', 'info');
+
+        $this->rc->output->command('multi_thread_http_response', $results, rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC));
+        return;
+    }
+    
+    if ($success)
+      $this->rc->output->show_message('successfullysaved', 'confirmation');
+    else {
+      $error_msg = $this->gettext('errorsaving') . ($this->driver->last_error ? ': ' . $this->driver->last_error :'');
+      $this->rc->output->show_message($error_msg, 'error');
+    }
+
+    $this->rc->output->command('plugin.unlock_saving');
+
+    if ($success && $reload)
+      $this->rc->output->command('plugin.reload_view');
+  }
+  
+  
+  /**
+   * Dispatcher for event actions initiated by the client
+   */
+  function event_action()
+  {
+    $action = rcube_utils::get_input_value('action', rcube_utils::INPUT_GPC);
+    $event  = rcube_utils::get_input_value('e', rcube_utils::INPUT_POST, true);
+    $success = $reload = $got_msg = false;
+    
+    // force notify if hidden + active
+    if ((int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']) === 1)
+      $event['_notify'] = 1;
+
+    // read old event data in order to find changes
+    if (($event['_notify'] || $event['_decline']) && $action != 'new') {
+      $old = $this->driver->get_event($event);
+
+      // load main event if savemode is 'all' or if deleting 'future' events
+      if (($event['_savemode'] == 'all' || ($event['_savemode'] == 'future' && $action == 'remove' && !$event['_decline'])) && $old['recurrence_id']) {
+        $old['id'] = $old['recurrence_id'];
+        $old = $this->driver->get_event($old);
+      }
+    }
+
+    switch ($action) {
+      case "new":
+        // create UID for new event
+        $event['uid'] = $this->generate_uid();
+        $this->write_preprocess($event, $action);
+        if ($success = $this->driver->new_event($event)) {
+          $event['id'] = $event['uid'];
+          $event['_savemode'] = 'all';
+          $this->cleanup_event($event);
+          $this->event_save_success($event, null, $action, true);
+        }
+        $reload = $success && $event['recurrence'] ? 2 : 1;
+        break;
+        
+      case "edit":
+        $this->write_preprocess($event, $action);
+        if ($success = $this->driver->edit_event($event)) {
+          $this->cleanup_event($event);
+          $this->event_save_success($event, $old, $action, $success);
+        }
+        $reload = $success && ($event['recurrence'] || $event['_savemode'] || $event['_fromcalendar']) ? 2 : 1;
+        break;
+      
+      case "resize":
+        $this->write_preprocess($event, $action);
+        if ($success = $this->driver->resize_event($event)) {
+          $this->event_save_success($event, $old, $action, $success);
+        }
+        $reload = $event['_savemode'] ? 2 : 1;
+        break;
+      
+      case "move":
+        $this->write_preprocess($event, $action);
+        if ($success = $this->driver->move_event($event)) {
+          $this->event_save_success($event, $old, $action, $success);
+        }
+        $reload  = $success && $event['_savemode'] ? 2 : 1;
+        break;
+      
+      case "remove":
+        // remove previous deletes
+        $undo_time = $this->driver->undelete ? $this->rc->config->get('undo_timeout', 0) : 0;
+        $this->rc->session->remove('calendar_event_undo');
+        
+        // search for event if only UID is given
+        if (!isset($event['calendar']) && $event['uid']) {
+          if (!($event = $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE))) {
+            break;
+          }
+          $undo_time = 0;
+        }
+
+        $success = $this->driver->remove_event($event, $undo_time < 1);
+        $reload = (!$success || $event['_savemode']) ? 2 : 1;
+
+        if ($undo_time > 0 && $success) {
+          $_SESSION['calendar_event_undo'] = array('ts' => time(), 'data' => $event);
+          // display message with Undo link.
+          $msg = html::span(null, $this->gettext('successremoval'))
+            . ' ' . html::a(array('onclick' => sprintf("%s.http_request('event', 'action=undo', %s.display_message('', 'loading'))",
+              rcmail_output::JS_OBJECT_NAME, rcmail_output::JS_OBJECT_NAME)), $this->gettext('undo'));
+          $this->rc->output->show_message($msg, 'confirmation', null, true, $undo_time);
+          $got_msg = true;
+        }
+        else if ($success) {
+          $this->rc->output->show_message('calendar.successremoval', 'confirmation');
+          $got_msg = true;
+        }
+
+        // send cancellation for the main event
+        if ($event['_savemode'] == 'all') {
+          unset($old['_instance'], $old['recurrence_date'], $old['recurrence_id']);
+        }
+        // send an update for the main event's recurrence rule instead of a cancellation message
+        else if ($event['_savemode'] == 'future' && $success !== false && $success !== true) {
+          $event['_savemode'] = 'all';  // force event_save_success() to load master event
+          $action = 'edit';
+          $success = true;
+        }
+
+        // send iTIP reply that participant has declined the event
+        if ($success && $event['_decline']) {
+          $emails = $this->get_user_emails();
+          foreach ($old['attendees'] as $i => $attendee) {
+            if ($attendee['role'] == 'ORGANIZER')
+              $organizer = $attendee;
+            else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+              $old['attendees'][$i]['status'] = 'DECLINED';
+              $reply_sender = $attendee['email'];
+            }
+          }
+
+          if ($event['_savemode'] == 'future' && $event['id'] != $old['id']) {
+            $old['thisandfuture'] = true;
+          }
+
+          $itip = $this->load_itip();
+          $itip->set_sender_email($reply_sender);
+          if ($organizer && $itip->send_itip_message($old, 'REPLY', $organizer, 'itipsubjectdeclined', 'itipmailbodydeclined'))
+            $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
+          else
+            $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+        }
+        else if ($success) {
+          $this->event_save_success($event, $old, $action, $success);
+        }
+        break;
+
+      case "undo":
+        // Restore deleted event
+        $event  = $_SESSION['calendar_event_undo']['data'];
+
+        if ($event)
+          $success = $this->driver->restore_event($event);
+
+        if ($success) {
+          $this->rc->session->remove('calendar_event_undo');
+          $this->rc->output->show_message('calendar.successrestore', 'confirmation');
+          $got_msg = true;
+          $reload = 2;
+        }
+
+        break;
+
+      case "rsvp":
+        $itip_sending  = $this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']);
+        $status        = rcube_utils::get_input_value('status', rcube_utils::INPUT_POST);
+        $attendees     = rcube_utils::get_input_value('attendees', rcube_utils::INPUT_POST);
+        $reply_comment = $event['comment'];
+
+        $this->write_preprocess($event, 'edit');
+        $ev = $this->driver->get_event($event);
+        $ev['attendees'] = $event['attendees'];
+        $ev['free_busy'] = $event['free_busy'];
+        $ev['_savemode'] = $event['_savemode'];
+        $ev['comment']   = $reply_comment;
+
+        // send invitation to delegatee + add it as attendee
+        if ($status == 'delegated' && $event['to']) {
+          $itip = $this->load_itip();
+          if ($itip->delegate_to($ev, $event['to'], (bool)$event['rsvp'], $attendees)) {
+            $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
+            $noreply = false;
+          }
+        }
+
+        $event = $ev;
+
+        // compose a list of attendees affected by this change
+        $updated_attendees = array_filter(array_map(function($j) use ($event) {
+          return $event['attendees'][$j];
+        }, $attendees));
+
+        if ($success = $this->driver->edit_rsvp($event, $status, $updated_attendees)) {
+          $noreply = rcube_utils::get_input_value('noreply', rcube_utils::INPUT_GPC);
+          $noreply = intval($noreply) || $status == 'needs-action' || $itip_sending === 0;
+          $reload  = $event['calendar'] != $ev['calendar'] || $event['recurrence'] ? 2 : 1;
+          $organizer = null;
+          $emails = $this->get_user_emails();
+
+          foreach ($event['attendees'] as $i => $attendee) {
+            if ($attendee['role'] == 'ORGANIZER') {
+              $organizer = $attendee;
+            }
+            else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+              $reply_sender = $attendee['email'];
+            }
+          }
+
+          if (!$noreply) {
+            $itip = $this->load_itip();
+            $itip->set_sender_email($reply_sender);
+            $event['thisandfuture'] = $event['_savemode'] == 'future';
+            if ($organizer && $itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
+              $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
+            else
+              $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+          }
+
+          // refresh all calendars
+          if ($event['calendar'] != $ev['calendar']) {
+            $this->rc->output->command('plugin.refresh_calendar', array('source' => null, 'refetch' => true));
+            $reload = 0;
+          }
+        }
+        break;
+
+      case "dismiss":
+        $event['ids'] = explode(',', $event['id']);
+        $plugin = $this->rc->plugins->exec_hook('dismiss_alarms', $event);
+        $success = $plugin['success'];
+        foreach ($event['ids'] as $id) {
+            if (strpos($id, 'cal:') === 0)
+                $success |= $this->driver->dismiss_alarm(substr($id, 4), $event['snooze']);
+        }
+        break;
+
+      case "changelog":
+        $data = $this->driver->get_event_changelog($event);
+        if (is_array($data) && !empty($data)) {
+          $lib = $this->lib;
+          $dtformat = $this->rc->config->get('date_format') . ' ' . $this->rc->config->get('time_format');
+          array_walk($data, function(&$change) use ($lib, $dtformat) {
+            if ($change['date']) {
+              $dt = $lib->adjust_timezone($change['date']);
+              if ($dt instanceof DateTime)
+                $change['date'] = $this->rc->format_date($dt, $dtformat, false);
+            }
+          });
+          $this->rc->output->command('plugin.render_event_changelog', $data);
+        }
+        else {
+          $this->rc->output->command('plugin.render_event_changelog', false);
+        }
+        $got_msg = true;
+        $reload = false;
+        break;
+
+      case "diff":
+        $data = $this->driver->get_event_diff($event, $event['rev1'], $event['rev2']);
+        if (is_array($data)) {
+          // convert some properties, similar to self::_client_event()
+          $lib = $this->lib;
+          array_walk($data['changes'], function(&$change, $i) use ($event, $lib) {
+            // convert date cols
+            foreach (array('start','end','created','changed') as $col) {
+              if ($change['property'] == $col) {
+                $change['old'] = $lib->adjust_timezone($change['old'], strlen($change['old']) == 10)->format('c');
+                $change['new'] = $lib->adjust_timezone($change['new'], strlen($change['new']) == 10)->format('c');
+              }
+            }
+            // create textual representation for alarms and recurrence
+            if ($change['property'] == 'alarms') {
+              if (is_array($change['old']))
+                $change['old_'] = libcalendaring::alarm_text($change['old']);
+              if (is_array($change['new']))
+                $change['new_'] = libcalendaring::alarm_text(array_merge((array)$change['old'], $change['new']));
+            }
+            if ($change['property'] == 'recurrence') {
+              if (is_array($change['old']))
+                $change['old_'] = $lib->recurrence_text($change['old']);
+              if (is_array($change['new']))
+                $change['new_'] = $lib->recurrence_text(array_merge((array)$change['old'], $change['new']));
+            }
+            if ($change['property'] == 'attachments') {
+              if (is_array($change['old']))
+                $change['old']['classname'] = rcube_utils::file2class($change['old']['mimetype'], $change['old']['name']);
+              if (is_array($change['new']))
+                $change['new']['classname'] = rcube_utils::file2class($change['new']['mimetype'], $change['new']['name']);
+            }
+            // compute a nice diff of description texts
+            if ($change['property'] == 'description') {
+              $change['diff_'] = libkolab::html_diff($change['old'], $change['new']);
+            }
+          });
+          $this->rc->output->command('plugin.event_show_diff', $data);
+        }
+        else {
+          $this->rc->output->command('display_message', $this->gettext('objectdiffnotavailable'), 'error');
+        }
+        $got_msg = true;
+        $reload = false;
+        break;
+
+      case "show":
+        if ($event = $this->driver->get_event_revison($event, $event['rev'])) {
+          $this->rc->output->command('plugin.event_show_revision', $this->_client_event($event));
+        }
+        else {
+          $this->rc->output->command('display_message', $this->gettext('objectnotfound'), 'error');
+        }
+        $got_msg = true;
+        $reload = false;
+        break;
+
+      case "restore":
+        if ($success = $this->driver->restore_event_revision($event, $event['rev'])) {
+          $_event = $this->driver->get_event($event);
+          $reload = $_event['recurrence'] ? 2 : 1;
+          $this->rc->output->command('display_message', $this->gettext(array('name' => 'objectrestoresuccess', 'vars' => array('rev' => $event['rev']))), 'confirmation');
+          $this->rc->output->command('plugin.close_history_dialog');
+        }
+        else {
+          $this->rc->output->command('display_message', $this->gettext('objectrestoreerror'), 'error');
+          $reload = 0;
+        }
+        $got_msg = true;
+        break;
+    }
+
+    // show confirmation/error message
+    if (!$got_msg) {
+      if ($success)
+        $this->rc->output->show_message('successfullysaved', 'confirmation');
+      else
+        $this->rc->output->show_message('calendar.errorsaving', 'error');
+    }
+
+    // unlock client
+    $this->rc->output->command('plugin.unlock_saving');
+
+    // update event object on the client or trigger a complete refretch if too complicated
+    if ($reload) {
+      $args = array('source' => $event['calendar']);
+      if ($reload > 1)
+        $args['refetch'] = true;
+      else if ($success && $action != 'remove')
+        $args['update'] = $this->_client_event($this->driver->get_event($event), true);
+      $this->rc->output->command('plugin.refresh_calendar', $args);
+    }
+  }
+
+  /**
+   * Helper method sending iTip notifications after successful event updates
+   */
+  private function event_save_success(&$event, $old, $action, $success)
+  {
+    // $success is a new event ID
+    if ($success !== true) {
+      // send update notification on the main event
+      if ($event['_savemode'] == 'future' && $event['_notify'] && $old['attendees'] && $old['recurrence_id']) {
+        $master = $this->driver->get_event(array('id' => $old['recurrence_id'], 'calendar' => $old['calendar']), 0, true);
+        unset($master['_instance'], $master['recurrence_date']);
+
+        $sent = $this->notify_attendees($master, null, $action, $event['_comment'], false);
+        if ($sent < 0)
+          $this->rc->output->show_message('calendar.errornotifying', 'error');
+
+        $event['attendees'] = $master['attendees'];  // this tricks us into the next if clause
+      }
+
+      // delete old reference if saved as new
+      if ($event['_savemode'] == 'future' || $event['_savemode'] == 'new') {
+        $old = null;
+      }
+
+      $event['id'] = $success;
+      $event['_savemode'] = 'all';
+    }
+
+    // send out notifications
+    if ($event['_notify'] && ($event['attendees'] || $old['attendees'])) {
+      $_savemode = $event['_savemode'];
+
+      // send notification for the main event when savemode is 'all'
+      if ($action != 'remove' && $_savemode == 'all' && ($event['recurrence_id'] || $old['recurrence_id'] || ($old && $old['id'] != $event['id']))) {
+        $event['id'] = $event['recurrence_id'] ?: ($old['recurrence_id'] ?: $old['id']);
+        $event = $this->driver->get_event($event, 0, true);
+        unset($event['_instance'], $event['recurrence_date']);
+      }
+      else {
+        // make sure we have the complete record
+        $event = $action == 'remove' ? $old : $this->driver->get_event($event, 0, true);
+      }
+
+      $event['_savemode'] = $_savemode;
+
+      if ($old) {
+        $old['thisandfuture'] = $_savemode == 'future';
+      }
+
+      // only notify if data really changed (TODO: do diff check on client already)
+      if (!$old || $action == 'remove' || self::event_diff($event, $old)) {
+        $sent = $this->notify_attendees($event, $old, $action, $event['_comment']);
+        if ($sent > 0)
+          $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
+        else if ($sent < 0)
+          $this->rc->output->show_message('calendar.errornotifying', 'error');
+      }
+    }
+  }
+
+  /**
+   * Handler for load-requests from fullcalendar
+   * This will return pure JSON formatted output
+   */
+  function load_events()
+  {
+    $events = $this->driver->load_events(
+      rcube_utils::get_input_value('start', rcube_utils::INPUT_GET),
+      rcube_utils::get_input_value('end', rcube_utils::INPUT_GET),
+      ($query = rcube_utils::get_input_value('q', rcube_utils::INPUT_GET)),
+      rcube_utils::get_input_value('source', rcube_utils::INPUT_GET)
+    );
+    echo $this->encode($events, !empty($query));
+    exit;
+  }
+
+  /**
+   * Handler for requests fetching event counts for calendars
+   */
+  public function count_events()
+  {
+    // don't update session on these requests (avoiding race conditions)
+    $this->rc->session->nowrite = true;
+
+    $start = rcube_utils::get_input_value('start', rcube_utils::INPUT_GET);
+    if (!$start) {
+      $start = new DateTime('today 00:00:00', $this->timezone);
+      $start = $start->format('U');
+    }
+
+    $counts = $this->driver->count_events(
+      rcube_utils::get_input_value('source', rcube_utils::INPUT_GET),
+      $start,
+      rcube_utils::get_input_value('end', rcube_utils::INPUT_GET)
+    );
+
+    $this->rc->output->command('plugin.update_counts', array('counts' => $counts));
+  }
+
+  /**
+   * Load event data from an iTip message attachment
+   */
+  public function itip_events($msgref)
+  {
+    $path = explode('/', $msgref);
+    $msg = array_pop($path);
+    $mbox = join('/', $path);
+    list($uid, $mime_id) = explode('#', $msg);
+    $events = array();
+
+    if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) {
+      $partstat = 'NEEDS-ACTION';
+/*
+      $user_emails = $this->lib->get_user_emails();
+      foreach ($event['attendees'] as $attendee) {
+        if (in_array($attendee['email'], $user_emails)) {
+          $partstat = $attendee['status'];
+          break;
+        }
+      }
+*/
+      $event['id'] = $event['uid'];
+      $event['temporary'] = true;
+      $event['readonly'] = true;
+      $event['calendar'] = '--invitation--itip';
+      $event['className'] = 'fc-invitation-' . strtolower($partstat);
+      $event['_mbox'] = $mbox;
+      $event['_uid']  = $uid;
+      $event['_part'] = $mime_id;
+
+      $events[] = $this->_client_event($event, true);
+
+      // add recurring instances
+      if (!empty($event['recurrence'])) {
+        foreach ($this->driver->get_recurring_events($event, $event['start']) as $recurring) {
+          $recurring['temporary'] = true;
+          $recurring['readonly'] = true;
+          $recurring['calendar'] = '--invitation--itip';
+          $events[] = $this->_client_event($recurring, true);
+        }
+      }
+    }
+
+    return $events;
+  }
+
+  /**
+   * Handler for keep-alive requests
+   * This will check for updated data in active calendars and sync them to the client
+   */
+  public function refresh($attr)
+  {
+     // refresh the entire calendar every 10th time to also sync deleted events
+    if (rand(0,10) == 10) {
+        $this->rc->output->command('plugin.refresh_calendar', array('refetch' => true));
+        return;
+    }
+
+    $counts = array();
+
+    foreach ($this->driver->list_calendars(calendar_driver::FILTER_ACTIVE) as $cal) {
+      $events = $this->driver->load_events(
+        rcube_utils::get_input_value('start', rcube_utils::INPUT_GPC),
+        rcube_utils::get_input_value('end', rcube_utils::INPUT_GPC),
+        rcube_utils::get_input_value('q', rcube_utils::INPUT_GPC),
+        $cal['id'],
+        1,
+        $attr['last']
+      );
+
+      foreach ($events as $event) {
+        $this->rc->output->command('plugin.refresh_calendar',
+          array('source' => $cal['id'], 'update' => $this->_client_event($event)));
+      }
+
+      // refresh count for this calendar
+      if ($cal['counts']) {
+        $today = new DateTime('today 00:00:00', $this->timezone);
+        $counts += $this->driver->count_events($cal['id'], $today->format('U'));
+      }
+    }
+
+    if (!empty($counts)) {
+      $this->rc->output->command('plugin.update_counts', array('counts' => $counts));
+    }
+  }
+
+  /**
+   * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests.
+   * This will check for pending notifications and pass them to the client
+   */
+  public function pending_alarms($p)
+  {
+    $this->load_driver();
+    $time = $p['time'] ?: time();
+    if ($alarms = $this->driver->pending_alarms($time)) {
+      foreach ($alarms as $alarm) {
+        $alarm['id'] = 'cal:' . $alarm['id'];  // prefix ID with cal:
+        $p['alarms'][] = $alarm;
+      }
+    }
+
+    // get alarms for birthdays calendar
+    if ($this->rc->config->get('calendar_contact_birthdays') && $this->rc->config->get('calendar_birthdays_alarm_type') == 'DISPLAY') {
+      $cache = $this->rc->get_cache('calendar.birthdayalarms', 'db');
+
+      foreach ($this->driver->load_birthday_events($time, $time + 86400 * 60) as $e) {
+        $alarm = libcalendaring::get_next_alarm($e);
+
+        // overwrite alarm time with snooze value (or null if dismissed)
+        if ($dismissed = $cache->get($e['id']))
+          $alarm['time'] = $dismissed['notifyat'];
+
+        // add to list if alarm is set
+        if ($alarm && $alarm['time'] && $alarm['time'] <= $time) {
+          $e['id'] = 'cal:bday:' . $e['id'];
+          $e['notifyat'] = $alarm['time'];
+          $p['alarms'][] = $e;
+        }
+      }
+    }
+
+    return $p;
+  }
+
+  /**
+   * Handler for alarm dismiss hook triggered by libcalendaring
+   */
+  public function dismiss_alarms($p)
+  {
+      $this->load_driver();
+      foreach ((array)$p['ids'] as $id) {
+          if (strpos($id, 'cal:bday:') === 0) {
+              $p['success'] |= $this->driver->dismiss_birthday_alarm(substr($id, 9), $p['snooze']);
+          }
+          else if (strpos($id, 'cal:') === 0) {
+              $p['success'] |= $this->driver->dismiss_alarm(substr($id, 4), $p['snooze']);
+          }
+      }
+
+      return $p;
+  }
+
+  /**
+   * Handler for check-recent requests which are accidentally sent to calendar
+   */
+  function check_recent()
+  {
+    // NOP
+    $this->rc->output->send();
+  }
+
+  /**
+   * Hook triggered when a contact is saved
+   */
+  function contact_update($p)
+  {
+    // clear birthdays calendar cache
+    if (!empty($p['record']['birthday'])) {
+      $cache = $this->rc->get_cache('calendar.birthdays', 'db');
+      $cache->remove();
+    }
+  }
+
+  /**
+   *
+   */
+  function import_events()
+  {
+    // Upload progress update
+    if (!empty($_GET['_progress'])) {
+      $this->rc->upload_progress();
+    }
+
+    @set_time_limit(0);
+
+    // process uploaded file if there is no error
+    $err = $_FILES['_data']['error'];
+
+    if (!$err && $_FILES['_data']['tmp_name']) {
+      $calendar   = rcube_utils::get_input_value('calendar', rcube_utils::INPUT_GPC);
+      $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0;
+
+      // extract zip file
+      if ($_FILES['_data']['type'] == 'application/zip') {
+        $count = 0;
+        if (class_exists('ZipArchive', false)) {
+          $zip = new ZipArchive();
+          if ($zip->open($_FILES['_data']['tmp_name'])) {
+            $randname = uniqid('zip-' . session_id(), true);
+            $tmpdir = slashify($this->rc->config->get('temp_dir', sys_get_temp_dir())) . $randname;
+            mkdir($tmpdir, 0700);
+
+            // extract each ical file from the archive and import it
+            for ($i = 0; $i < $zip->numFiles; $i++) { 
+              $filename = $zip->getNameIndex($i);
+              if (preg_match('/\.ics$/i', $filename)) {
+                $tmpfile = $tmpdir . '/' . basename($filename);
+                if (copy('zip://' . $_FILES['_data']['tmp_name'] . '#'.$filename, $tmpfile)) {
+                  $count += $this->import_from_file($tmpfile, $calendar, $rangestart, $errors);
+                  unlink($tmpfile);
+                }
+              }
+            }
+
+            rmdir($tmpdir);
+            $zip->close();
+          }
+          else {
+            $errors = 1;
+            $msg = 'Failed to open zip file.';
+          }
+        }
+        else {
+          $errors = 1;
+          $msg = 'Zip files are not supported for import.';
+        }
+      }
+      else {
+        // attempt to import teh uploaded file directly
+        $count = $this->import_from_file($_FILES['_data']['tmp_name'], $calendar, $rangestart, $errors);
+      }
+
+      if ($count) {
+        $this->rc->output->command('display_message', $this->gettext(array('name' => 'importsuccess', 'vars' => array('nr' => $count))), 'confirmation');
+        $this->rc->output->command('plugin.import_success', array('source' => $calendar, 'refetch' => true));
+      }
+      else if (!$errors) {
+        $this->rc->output->command('display_message', $this->gettext('importnone'), 'notice');
+        $this->rc->output->command('plugin.import_success', array('source' => $calendar));
+      }
+      else {
+        $this->rc->output->command('plugin.import_error', array('message' => $this->gettext('importerror') . ($msg ? ': ' . $msg : '')));
+      }
+    }
+    else {
+      if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
+        $msg = $this->rc->gettext(array('name' => 'filesizeerror', 'vars' => array(
+            'size' => $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize'))))));
+      }
+      else {
+        $msg = $this->rc->gettext('fileuploaderror');
+      }
+
+      $this->rc->output->command('plugin.import_error', array('message' => $msg));
+    }
+
+    $this->rc->output->send('iframe');
+  }
+
+  /**
+   * Helper function to parse and import a single .ics file
+   */
+  private function import_from_file($filepath, $calendar, $rangestart, &$errors)
+  {
+    $user_email = $this->rc->user->get_username();
+
+    $ical = $this->get_ical();
+    $errors = !$ical->fopen($filepath);
+    $count = $i = 0;
+    foreach ($ical as $event) {
+      // keep the browser connection alive on long import jobs
+      if (++$i > 100 && $i % 100 == 0) {
+          echo "<!-- -->";
+          ob_flush();
+      }
+
+      // TODO: correctly handle recurring events which start before $rangestart
+      if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart)))
+        continue;
+
+      $event['_owner'] = $user_email;
+      $event['calendar'] = $calendar;
+      if ($this->driver->new_event($event)) {
+        $count++;
+      }
+      else {
+        $errors++;
+      }
+    }
+
+    return $count;
+  }
+
+
+  /**
+   * Construct the ics file for exporting events to iCalendar format;
+   */
+  function export_events($terminate = true)
+  {
+    $start = rcube_utils::get_input_value('start', rcube_utils::INPUT_GET);
+    $end   = rcube_utils::get_input_value('end', rcube_utils::INPUT_GET);
+
+    if (!isset($start))
+      $start = 'today -1 year';
+    if (!is_numeric($start))
+      $start = strtotime($start . ' 00:00:00');
+    if (!$end)
+      $end = 'today +10 years';
+    if (!is_numeric($end))
+      $end = strtotime($end . ' 23:59:59');
+
+    $event_id    = rcube_utils::get_input_value('id', rcube_utils::INPUT_GET);
+    $attachments = rcube_utils::get_input_value('attachments', rcube_utils::INPUT_GET);
+    $calid = $filename = rcube_utils::get_input_value('source', rcube_utils::INPUT_GET);
+
+    $calendars = $this->driver->list_calendars();
+    $events = array();
+
+    if ($calendars[$calid]) {
+      $filename = $calendars[$calid]['name'] ? $calendars[$calid]['name'] : $calid;
+      $filename = asciiwords(html_entity_decode($filename));  // to 7bit ascii
+      if (!empty($event_id)) {
+        if ($event = $this->driver->get_event(array('calendar' => $calid, 'id' => $event_id), 0, true)) {
+          if ($event['recurrence_id']) {
+            $event = $this->driver->get_event(array('calendar' => $calid, 'id' => $event['recurrence_id']), 0, true);
+          }
+          $events = array($event);
+          $filename = asciiwords($event['title']);
+          if (empty($filename))
+            $filename = 'event';
+        }
+      }
+      else {
+         $events = $this->driver->load_events($start, $end, null, $calid, 0);
+         if (empty($filename))
+           $filename = $calid;
+      }
+    }
+
+    header("Content-Type: text/calendar");
+    header("Content-Disposition: inline; filename=".$filename.'.ics');
+
+    $this->get_ical()->export($events, '', true, $attachments ? array($this->driver, 'get_attachment_body') : null);
+
+    if ($terminate)
+      exit;
+  }
+
+
+  /**
+   * Handler for iCal feed requests
+   */
+  function ical_feed_export()
+  {
+    $session_exists = !empty($_SESSION['user_id']);
+
+    // process HTTP auth info
+    if (!empty($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW'])) {
+      $_POST['_user'] = $_SERVER['PHP_AUTH_USER']; // used for rcmail::autoselect_host()
+      $auth = $this->rc->plugins->exec_hook('authenticate', array(
+        'host' => $this->rc->autoselect_host(),
+        'user' => trim($_SERVER['PHP_AUTH_USER']),
+        'pass' => $_SERVER['PHP_AUTH_PW'],
+        'cookiecheck' => true,
+        'valid' => true,
+      ));
+      if ($auth['valid'] && !$auth['abort'])
+        $this->rc->login($auth['user'], $auth['pass'], $auth['host']);
+    }
+
+    // require HTTP auth
+    if (empty($_SESSION['user_id'])) {
+      header('WWW-Authenticate: Basic realm="Roundcube Calendar"');
+      header('HTTP/1.0 401 Unauthorized');
+      exit;
+    }
+
+    // decode calendar feed hash
+    $format = 'ics';
+    $calhash = rcube_utils::get_input_value('_cal', rcube_utils::INPUT_GET);
+    if (preg_match(($suff_regex = '/\.([a-z0-9]{3,5})$/i'), $calhash, $m)) {
+      $format = strtolower($m[1]);
+      $calhash = preg_replace($suff_regex, '', $calhash);
+    }
+
+    if (!strpos($calhash, ':'))
+      $calhash = base64_decode($calhash);
+
+    list($user, $_GET['source']) = explode(':', $calhash, 2);
+
+    // sanity check user
+    if ($this->rc->user->get_username() == $user) {
+      $this->setup();
+      $this->load_driver();
+      $this->export_events(false);
+    }
+    else {
+      header('HTTP/1.0 404 Not Found');
+    }
+
+    // don't save session data
+    if (!$session_exists)
+      session_destroy();
+    exit;
+  }
+
+  /**
+   *
+   */
+  function load_settings()
+  {
+    $this->lib->load_settings();
+    $this->defaults += $this->lib->defaults;
+
+    $settings = array();
+
+    // configuration
+    $settings['default_calendar'] = $this->rc->config->get('calendar_default_calendar');
+    $settings['default_view'] = (string)$this->rc->config->get('calendar_default_view', $this->defaults['calendar_default_view']);
+    $settings['date_agenda'] = (string)$this->rc->config->get('calendar_date_agenda', $this->defaults['calendar_date_agenda']);
+
+    $settings['timeslots'] = (int)$this->rc->config->get('calendar_timeslots', $this->defaults['calendar_timeslots']);
+    $settings['first_day'] = (int)$this->rc->config->get('calendar_first_day', $this->defaults['calendar_first_day']);
+    $settings['first_hour'] = (int)$this->rc->config->get('calendar_first_hour', $this->defaults['calendar_first_hour']);
+    $settings['work_start'] = (int)$this->rc->config->get('calendar_work_start', $this->defaults['calendar_work_start']);
+    $settings['work_end'] = (int)$this->rc->config->get('calendar_work_end', $this->defaults['calendar_work_end']);
+    $settings['agenda_range'] = (int)$this->rc->config->get('calendar_agenda_range', $this->defaults['calendar_agenda_range']);
+    $settings['agenda_sections'] = $this->rc->config->get('calendar_agenda_sections', $this->defaults['calendar_agenda_sections']);
+    $settings['event_coloring'] = (int)$this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring']);
+    $settings['time_indicator'] = (int)$this->rc->config->get('calendar_time_indicator', $this->defaults['calendar_time_indicator']);
+    $settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', $this->defaults['calendar_allow_invite_shared']);
+    $settings['invitation_calendars'] = (bool)$this->rc->config->get('kolab_invitation_calendars', false);
+    $settings['itip_notify'] = (int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']);
+
+    // get user identity to create default attendee
+    if ($this->ui->screen == 'calendar') {
+      foreach ($this->rc->user->list_emails() as $rec) {
+        if (!$identity)
+          $identity = $rec;
+        $identity['emails'][] = $rec['email'];
+        $settings['identities'][$rec['identity_id']] = $rec['email'];
+      }
+      $identity['emails'][] = $this->rc->user->get_username();
+      $settings['identity'] = array('name' => $identity['name'], 'email' => strtolower($identity['email']), 'emails' => ';' . strtolower(join(';', $identity['emails'])));
+    }
+
+    return $settings;
+  }
+
+  /**
+   * Encode events as JSON
+   *
+   * @param  array  Events as array
+   * @param  boolean Add CSS class names according to calendar and categories
+   * @return string JSON encoded events
+   */
+  function encode($events, $addcss = false)
+  {
+    $json = array();
+    foreach ($events as $event) {
+      $json[] = $this->_client_event($event, $addcss);
+    }
+    return rcube_output::json_serialize($json);
+  }
+
+  /**
+   * Convert an event object to be used on the client
+   */
+  private function _client_event($event, $addcss = false)
+  {
+    // compose a human readable strings for alarms_text and recurrence_text
+    if ($event['valarms']) {
+      $event['alarms_text'] = libcalendaring::alarms_text($event['valarms']);
+      $event['valarms'] = libcalendaring::to_client_alarms($event['valarms']);
+    }
+    if ($event['recurrence']) {
+      $event['recurrence_text'] = $this->lib->recurrence_text($event['recurrence']);
+      $event['recurrence'] = $this->lib->to_client_recurrence($event['recurrence'], $event['allday']);
+      unset($event['recurrence_date']);
+    }
+
+    foreach ((array)$event['attachments'] as $k => $attachment) {
+      $event['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
+
+      unset($event['attachments'][$k]['data'], $event['attachments'][$k]['content']);
+
+      if (!$attachment['id']) {
+        $event['attachments'][$k]['id'] = $k;
+      }
+    }
+
+    // convert link URIs references into structs
+    if (array_key_exists('links', $event)) {
+        foreach ((array) $event['links'] as $i => $link) {
+            if (strpos($link, 'imap://') === 0 && ($msgref = $this->driver->get_message_reference($link))) {
+                $event['links'][$i] = $msgref;
+            }
+        }
+    }
+
+    // check for organizer in attendees list
+    $organizer = null;
+    foreach ((array)$event['attendees'] as $i => $attendee) {
+      if ($attendee['role'] == 'ORGANIZER') {
+        $organizer = $attendee;
+      }
+      if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] == false) {
+        $event['attendees'][$i]['noreply'] = true;
+      }
+      else {
+        unset($event['attendees'][$i]['noreply']);
+      }
+    }
+
+    if ($organizer === null && !empty($event['organizer'])) {
+      $organizer = $event['organizer'];
+      $organizer['role'] = 'ORGANIZER';
+      if (!is_array($event['attendees']))
+        $event['attendees'] = array();
+      array_unshift($event['attendees'], $organizer);
+    }
+
+    // Convert HTML description into plain text
+    if ($this->is_html($event)) {
+      $h2t = new rcube_html2text($event['description'], false, true, 0);
+      $event['description'] = trim($h2t->get_text());
+    }
+
+    // mapping url => vurl because of the fullcalendar client script
+    $event['vurl'] = $event['url'];
+    unset($event['url']);
+
+    return array(
+      '_id'   => $event['calendar'] . ':' . $event['id'],  // unique identifier for fullcalendar
+      'start' => $this->lib->adjust_timezone($event['start'], $event['allday'])->format('c'),
+      'end'   => $this->lib->adjust_timezone($event['end'], $event['allday'])->format('c'),
+      // 'changed' might be empty for event recurrences (Bug #2185)
+      'changed' => $event['changed'] ? $this->lib->adjust_timezone($event['changed'])->format('c') : null,
+      'created' => $event['created'] ? $this->lib->adjust_timezone($event['created'])->format('c') : null,
+      'title'       => strval($event['title']),
+      'description' => strval($event['description']),
+      'location'    => strval($event['location']),
+      'className'   => ($addcss ? 'fc-event-cal-'.asciiwords($event['calendar'], true).' ' : '') .
+          'fc-event-cat-' . asciiwords(strtolower(join('-', (array)$event['categories'])), true) .
+          rtrim(' ' . $event['className']),
+      'allDay'      => ($event['allday'] == 1),
+    ) + $event;
+  }
+
+
+  /**
+   * Generate a unique identifier for an event
+   */
+  public function generate_uid()
+  {
+    return strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16));
+  }
+
+
+  /**
+   * TEMPORARY: generate random event data for testing
+   * Create events by opening http://<roundcubeurl>/?_task=calendar&_action=randomdata&_num=500&_date=2014-08-01&_dev=120
+   */
+  public function generate_randomdata()
+  {
+    @set_time_limit(0);
+
+    $num   = $_REQUEST['_num'] ? intval($_REQUEST['_num']) : 100;
+    $date  = $_REQUEST['_date'] ?: 'now';
+    $dev   = $_REQUEST['_dev'] ?: 30;
+    $cats  = array_keys($this->driver->list_categories());
+    $cals  = $this->driver->list_calendars(calendar_driver::FILTER_ACTIVE);
+    $count = 0;
+
+    while ($count++ < $num) {
+      $spread = intval($dev) * 86400; // days
+      $refdate = strtotime($date);
+      $start = round(($refdate + rand(-$spread, $spread)) / 600) * 600;
+      $duration = round(rand(30, 360) / 30) * 30 * 60;
+      $allday = rand(0,20) > 18;
+      $alarm = rand(-30,12) * 5;
+      $fb = rand(0,2);
+      
+      if (date('G', $start) > 23)
+        $start -= 3600;
+      
+      if ($allday) {
+        $start = strtotime(date('Y-m-d 00:00:00', $start));
+        $duration = 86399;
+      }
+      
+      $title = '';
+      $len = rand(2, 12);
+      $words = explode(" ", "The Hough transform is named after Paul Hough who patented the method in 1962. It is a technique which can be used to isolate features of a particular shape within an image. Because it requires that the desired features be specified in some parametric form, the classical Hough transform is most commonly used for the de- tection of regular curves such as lines, circles, ellipses, etc. A generalized Hough transform can be employed in applications where a simple analytic description of a feature(s) is not possible. Due to the computational complexity of the generalized Hough algorithm, we restrict the main focus of this discussion to the classical Hough transform. Despite its domain restrictions, the classical Hough transform (hereafter referred to without the classical prefix ) retains many applications, as most manufac- tured parts (and many anatomical parts investigated in medical imagery) contain feature boundaries which can be described by regular curves. The main advantage of the Hough transform technique is that it is tolerant of gaps in feature boundary descriptions and is relatively unaffected by image noise.");
+//      $chars = "!# abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890";
+      for ($i = 0; $i < $len; $i++)
+        $title .= $words[rand(0,count($words)-1)] . " ";
+      
+      $this->driver->new_event(array(
+        'uid' => $this->generate_uid(),
+        'start' => new DateTime('@'.$start),
+        'end' => new DateTime('@'.($start + $duration)),
+        'allday' => $allday,
+        'title' => rtrim($title),
+        'free_busy' => $fb == 2 ? 'outofoffice' : ($fb ? 'busy' : 'free'),
+        'categories' => $cats[array_rand($cats)],
+        'calendar' => array_rand($cals),
+        'alarms' => $alarm > 0 ? "-{$alarm}M:DISPLAY" : '',
+        'priority' => rand(0,9),
+      ));
+    }
+    
+    $this->rc->output->redirect('');
+  }
+
+  /**
+   * Handler for attachments upload
+   */
+  public function attachment_upload()
+  {
+    $this->lib->attachment_upload(self::SESSION_KEY, 'cal-');
+  }
+
+  /**
+   * Handler for attachments download/displaying
+   */
+  public function attachment_get()
+  {
+    // show loading page
+    if (!empty($_GET['_preload'])) {
+        return $this->lib->attachment_loading_page();
+    }
+
+    $event_id = rcube_utils::get_input_value('_event', rcube_utils::INPUT_GPC);
+    $calendar = rcube_utils::get_input_value('_cal', rcube_utils::INPUT_GPC);
+    $id       = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
+    $rev      = rcube_utils::get_input_value('_rev', rcube_utils::INPUT_GPC);
+
+    $event = array('id' => $event_id, 'calendar' => $calendar, 'rev' => $rev);
+
+    if ($calendar == '--invitation--itip') {
+        $uid  = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_GPC);
+        $part = rcube_utils::get_input_value('_part', rcube_utils::INPUT_GPC);
+        $mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_GPC);
+
+        $event      = $this->lib->mail_get_itip_object($mbox, $uid, $part, 'event');
+        $attachment = $event['attachments'][$id];
+        $attachment['body'] = &$attachment['data'];
+    }
+    else {
+        $attachment = $this->driver->get_attachment($id, $event);
+    }
+
+    // show part page
+    if (!empty($_GET['_frame'])) {
+        $this->lib->attachment = $attachment;
+        $this->register_handler('plugin.attachmentframe', array($this->lib, 'attachment_frame'));
+        $this->register_handler('plugin.attachmentcontrols', array($this->lib, 'attachment_header'));
+        $this->rc->output->send('calendar.attachment');
+    }
+    // deliver attachment content
+    else if ($attachment) {
+        if ($calendar != '--invitation--itip') {
+            $attachment['body'] = $this->driver->get_attachment_body($id, $event);
+        }
+
+        $this->lib->attachment_get($attachment);
+    }
+
+    // if we arrive here, the requested part was not found
+    header('HTTP/1.1 404 Not Found');
+    exit;
+  }
+
+  /**
+   * Determine whether the given event description is HTML formatted
+   */
+  private function is_html($event)
+  {
+      // check for opening and closing <html> or <body> tags
+      return (preg_match('/<(html|body)(\s+[a-z]|>)/', $event['description'], $m) && strpos($event['description'], '</'.$m[1].'>') > 0);
+  }
+
+  /**
+   * Prepares new/edited event properties before save
+   */
+  private function write_preprocess(&$event, $action)
+  {
+    // convert dates into DateTime objects in user's current timezone
+    $event['start']  = new DateTime($event['start'], $this->timezone);
+    $event['end']    = new DateTime($event['end'], $this->timezone);
+    $event['allday'] = (bool)$event['allday'];
+
+    // start/end is all we need for 'move' action (#1480)
+    if ($action == 'move') {
+      return;
+    }
+
+    // convert the submitted recurrence settings
+    if (is_array($event['recurrence'])) {
+      $event['recurrence'] = $this->lib->from_client_recurrence($event['recurrence'], $event['start']);
+    }
+
+    // convert the submitted alarm values
+    if ($event['valarms']) {
+      $event['valarms'] = libcalendaring::from_client_alarms($event['valarms']);
+    }
+
+    $attachments = array();
+    $eventid     = 'cal-'.$event['id'];
+
+    if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $eventid) {
+      if (!empty($_SESSION[self::SESSION_KEY]['attachments'])) {
+        foreach ($_SESSION[self::SESSION_KEY]['attachments'] as $id => $attachment) {
+          if (is_array($event['attachments']) && in_array($id, $event['attachments'])) {
+            $attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment);
+          }
+        }
+      }
+    }
+
+    $event['attachments'] = $attachments;
+
+    // convert link references into simple URIs
+    if (array_key_exists('links', $event)) {
+      $event['links'] = array_map(function($link) {
+          return is_array($link) ? $link['uri'] : strval($link);
+        }, (array)$event['links']);
+    }
+
+    // check for organizer in attendees
+    if ($action == 'new' || $action == 'edit') {
+      if (!$event['attendees'])
+        $event['attendees'] = array();
+
+      $emails = $this->get_user_emails();
+      $organizer = $owner = false;
+      foreach ((array)$event['attendees'] as $i => $attendee) {
+        if ($attendee['role'] == 'ORGANIZER')
+          $organizer = $i;
+        if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails))
+          $owner = $i;
+        if (!isset($attendee['rsvp']))
+          $event['attendees'][$i]['rsvp'] = true;
+        else if (is_string($attendee['rsvp']))
+          $event['attendees'][$i]['rsvp'] = $attendee['rsvp'] == 'true' || $attendee['rsvp'] == '1';
+      }
+
+      if (!empty($event['_identity'])) {
+        $identity = $this->rc->user->get_identity($event['_identity']);
+      }
+
+      // set new organizer identity
+      if ($organizer !== false && $identity) {
+        $event['attendees'][$organizer]['name'] = $identity['name'];
+        $event['attendees'][$organizer]['email'] = $identity['email'];
+      }
+      // set owner as organizer if yet missing
+      else if ($organizer === false && $owner !== false) {
+        $event['attendees'][$owner]['role'] = 'ORGANIZER';
+        unset($event['attendees'][$owner]['rsvp']);
+      }
+      // fallback to the selected identity
+      else if ($organizer === false && $identity) {
+        $event['attendees'][] = array(
+          'role'  => 'ORGANIZER',
+          'name'  => $identity['name'],
+          'email' => $identity['email'],
+        );
+      }
+    }
+
+    // mapping url => vurl because of the fullcalendar client script
+    if (array_key_exists('vurl', $event)) {
+      $event['url'] = $event['vurl'];
+      unset($event['vurl']);
+    }
+  }
+
+  /**
+   * Releases some resources after successful event save
+   */
+  private function cleanup_event(&$event)
+  {
+    // remove temp. attachment files
+    if (!empty($_SESSION[self::SESSION_KEY]) && ($eventid = $_SESSION[self::SESSION_KEY]['id'])) {
+      $this->rc->plugins->exec_hook('attachments_cleanup', array('group' => $eventid));
+      $this->rc->session->remove(self::SESSION_KEY);
+    }
+  }
+
+  /**
+   * Send out an invitation/notification to all event attendees
+   */
+  private function notify_attendees($event, $old, $action = 'edit', $comment = null, $rsvp = null)
+  {
+    if ($action == 'remove' || ($event['status'] == 'CANCELLED' && $old['status'] != $event['status'])) {
+      $event['cancelled'] = true;
+      $is_cancelled = true;
+    }
+
+    if ($rsvp === null)
+      $rsvp = !$old || $event['sequence'] > $old['sequence'];
+
+    $itip = $this->load_itip();
+    $emails = $this->get_user_emails();
+    $itip_notify = (int)$this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']);
+
+    // add comment to the iTip attachment
+    $event['comment'] = $comment;
+
+    // set a valid recurrence-id if this is a recurrence instance
+    libcalendaring::identify_recurrence_instance($event);
+
+    // compose multipart message using PEAR:Mail_Mime
+    $method = $action == 'remove' ? 'CANCEL' : 'REQUEST';
+    $message = $itip->compose_itip_message($event, $method, $rsvp);
+
+    // list existing attendees from $old event
+    $old_attendees = array();
+    foreach ((array)$old['attendees'] as $attendee) {
+      $old_attendees[] = $attendee['email'];
+    }
+
+    // send to every attendee
+    $sent = 0; $current = array();
+    foreach ((array)$event['attendees'] as $attendee) {
+      $current[] = strtolower($attendee['email']);
+      
+      // skip myself for obvious reasons
+      if (!$attendee['email'] || in_array(strtolower($attendee['email']), $emails))
+        continue;
+
+      // skip if notification is disabled for this attendee
+      if ($attendee['noreply'] && $itip_notify & 2)
+        continue;
+
+      // skip if this attendee has delegated and set RSVP=FALSE
+      if ($attendee['status'] == 'DELEGATED' && $attendee['rsvp'] === false)
+        continue;
+
+      // which template to use for mail text
+      $is_new = !in_array($attendee['email'], $old_attendees);
+      $is_rsvp = $is_new || $event['sequence'] > $old['sequence'];
+      $bodytext = $is_cancelled ? 'eventcancelmailbody' : ($is_new ? 'invitationmailbody' : 'eventupdatemailbody');
+      $subject  = $is_cancelled ? 'eventcancelsubject'  : ($is_new ? 'invitationsubject' : ($event['title'] ? 'eventupdatesubject':'eventupdatesubjectempty'));
+
+      $event['comment'] = $comment;
+
+      // finally send the message
+      if ($itip->send_itip_message($event, $method, $attendee, $subject, $bodytext, $message, $is_rsvp))
+        $sent++;
+      else
+        $sent = -100;
+    }
+
+    // TODO: on change of a recurring (main) event, also send updates to differing attendess of recurrence exceptions
+
+    // send CANCEL message to removed attendees
+    foreach ((array)$old['attendees'] as $attendee) {
+      if ($attendee['role'] == 'ORGANIZER' || !$attendee['email'] || in_array(strtolower($attendee['email']), $current))
+        continue;
+
+      $vevent = $old;
+      $vevent['cancelled'] = $is_cancelled;
+      $vevent['attendees'] = array($attendee);
+      $vevent['comment']   = $comment;
+      if ($itip->send_itip_message($vevent, 'CANCEL', $attendee, 'eventcancelsubject', 'eventcancelmailbody'))
+        $sent++;
+      else
+        $sent = -100;
+    }
+
+    return $sent;
+  }
+
+  /**
+   * Echo simple free/busy status text for the given user and time range
+   */
+  public function freebusy_status()
+  {
+    $email = rcube_utils::get_input_value('email', rcube_utils::INPUT_GPC);
+    $start = rcube_utils::get_input_value('start', rcube_utils::INPUT_GPC);
+    $end   = rcube_utils::get_input_value('end', rcube_utils::INPUT_GPC);
+
+    // convert dates into unix timestamps
+    if (!empty($start) && !is_numeric($start)) {
+      $dts = new DateTime($start, $this->timezone);
+      $start = $dts->format('U');
+    }
+    if (!empty($end) && !is_numeric($end)) {
+      $dte = new DateTime($end, $this->timezone);
+      $end = $dte->format('U');
+    }
+    
+    if (!$start) $start = time();
+    if (!$end) $end = $start + 3600;
+    
+    $fbtypemap = array(calendar::FREEBUSY_UNKNOWN => 'UNKNOWN', calendar::FREEBUSY_FREE => 'FREE', calendar::FREEBUSY_BUSY => 'BUSY', calendar::FREEBUSY_TENTATIVE => 'TENTATIVE', calendar::FREEBUSY_OOF => 'OUT-OF-OFFICE');
+    $status = 'UNKNOWN';
+    
+    // if the backend has free-busy information
+    $fblist = $this->driver->get_freebusy_list($email, $start, $end);
+
+    if (is_array($fblist)) {
+      $status = 'FREE';
+      
+      foreach ($fblist as $slot) {
+        list($from, $to, $type) = $slot;
+        if ($from < $end && $to > $start) {
+          $status = isset($type) && $fbtypemap[$type] ? $fbtypemap[$type] : 'BUSY';
+          break;
+        }
+      }
+    }
+    
+    // let this information be cached for 5min
+    $this->rc->output->future_expire_header(300);
+    
+    echo $status;
+    exit;
+  }
+  
+  /**
+   * Return a list of free/busy time slots within the given period
+   * Echo data in JSON encoding
+   */
+  public function freebusy_times()
+  {
+    $email = rcube_utils::get_input_value('email', rcube_utils::INPUT_GPC);
+    $start = rcube_utils::get_input_value('start', rcube_utils::INPUT_GPC);
+    $end   = rcube_utils::get_input_value('end', rcube_utils::INPUT_GPC);
+    $interval  = intval(rcube_utils::get_input_value('interval', rcube_utils::INPUT_GPC));
+    $strformat = $interval > 60 ? 'Ymd' : 'YmdHis';
+
+    // convert dates into unix timestamps
+    if (!empty($start) && !is_numeric($start)) {
+      $dts = rcube_utils::anytodatetime($start, $this->timezone);
+      $start = $dts ? $dts->format('U') : null;
+    }
+    if (!empty($end) && !is_numeric($end)) {
+      $dte = rcube_utils::anytodatetime($end, $this->timezone);
+      $end = $dte ? $dte->format('U') : null;
+    }
+
+    if (!$start) $start = time();
+    if (!$end)   $end = $start + 86400 * 30;
+    if (!$interval) $interval = 60;  // 1 hour
+    
+    if (!$dte) {
+      $dts = new DateTime('@'.$start);
+      $dts->setTimezone($this->timezone);
+    }
+
+    $fblist = $this->driver->get_freebusy_list($email, $start, $end);
+    $slots  = '';
+
+    // prepare freebusy list before use (for better performance)
+    if (is_array($fblist)) {
+      foreach ($fblist as $idx => $slot) {
+        list($from, $to, ) = $slot;
+
+        // check for possible all-day times
+        if (gmdate('His', $from) == '000000' && gmdate('His', $to) == '235959') {
+          // shift into the user's timezone for sane matching
+          $fblist[$idx][0] -= $this->gmt_offset;
+          $fblist[$idx][1] -= $this->gmt_offset;
+        }
+      }
+    }
+
+    // build a list from $start till $end with blocks representing the fb-status
+    for ($s = 0, $t = $start; $t <= $end; $s++) {
+      $t_end = $t + $interval * 60;
+      $dt = new DateTime('@'.$t);
+      $dt->setTimezone($this->timezone);
+
+      // determine attendee's status
+      if (is_array($fblist)) {
+        $status = self::FREEBUSY_FREE;
+
+        foreach ($fblist as $slot) {
+          list($from, $to, $type) = $slot;
+
+          if ($from < $t_end && $to > $t) {
+            $status = isset($type) ? $type : self::FREEBUSY_BUSY;
+            if ($status == self::FREEBUSY_BUSY)  // can't get any worse :-)
+              break;
+          }
+        }
+      }
+      else {
+        $status = self::FREEBUSY_UNKNOWN;
+      }
+
+      // use most compact format, assume $status is one digit/character
+      $slots .= $status;
+      $t = $t_end;
+    }
+    
+    $dte = new DateTime('@'.$t_end);
+    $dte->setTimezone($this->timezone);
+    
+    // let this information be cached for 5min
+    $this->rc->output->future_expire_header(300);
+    
+    echo rcube_output::json_serialize(array(
+      'email' => $email,
+      'start' => $dts->format('c'),
+      'end'   => $dte->format('c'),
+      'interval' => $interval,
+      'slots' => $slots,
+    ));
+    exit;
+  }
+
+  /**
+   * Handler for printing calendars
+   */
+  public function print_view()
+  {
+    $title = $this->gettext('print');
+
+    $view = rcube_utils::get_input_value('view', rcube_utils::INPUT_GPC);
+    if (!in_array($view, array('agendaWeek', 'agendaDay', 'month', 'table')))
+      $view = 'agendaDay';
+
+    $this->rc->output->set_env('view',$view);
+
+    if ($date = rcube_utils::get_input_value('date', rcube_utils::INPUT_GPC))
+      $this->rc->output->set_env('date', $date);
+
+    if ($range = rcube_utils::get_input_value('range', rcube_utils::INPUT_GPC))
+      $this->rc->output->set_env('listRange', intval($range));
+
+    if (isset($_REQUEST['sections']))
+      $this->rc->output->set_env('listSections', rcube_utils::get_input_value('sections', rcube_utils::INPUT_GPC));
+
+    if ($search = rcube_utils::get_input_value('search', rcube_utils::INPUT_GPC)) {
+      $this->rc->output->set_env('search', $search);
+      $title .= ' "' . $search . '"';
+    }
+
+    // Add CSS stylesheets to the page header
+    $skin_path = $this->local_skin_path();
+    $this->include_stylesheet($skin_path . '/fullcalendar.css');
+    $this->include_stylesheet($skin_path . '/print.css');
+    
+    // Add JS files to the page header
+    $this->include_script('print.js');
+    $this->include_script('lib/js/fullcalendar.js');
+    
+    $this->register_handler('plugin.calendar_css', array($this->ui, 'calendar_css'));
+    $this->register_handler('plugin.calendar_list', array($this->ui, 'calendar_list'));
+    
+    $this->rc->output->set_pagetitle($title);
+    $this->rc->output->send("calendar.print");
+  }
+
+  /**
+   *
+   */
+  public function get_inline_ui()
+  {
+    foreach (array('save','cancel','savingdata') as $label)
+      $texts['calendar.'.$label] = $this->gettext($label);
+    
+    $texts['calendar.new_event'] = $this->gettext('createfrommail');
+    
+    $this->ui->init_templates();
+    $this->ui->calendar_list();  # set env['calendars']
+    echo $this->api->output->parse('calendar.eventedit', false, false);
+    echo html::tag('script', array('type' => 'text/javascript'),
+      "rcmail.set_env('calendars', " . rcube_output::json_serialize($this->api->output->env['calendars']) . ");\n".
+      "rcmail.set_env('deleteicon', '" . $this->api->output->env['deleteicon'] . "');\n".
+      "rcmail.set_env('cancelicon', '" . $this->api->output->env['cancelicon'] . "');\n".
+      "rcmail.set_env('loadingicon', '" . $this->api->output->env['loadingicon'] . "');\n".
+      "rcmail.gui_object('attachmentlist', '"  . $this->ui->attachmentlist_id . "');\n".
+      "rcmail.add_label(" . rcube_output::json_serialize($texts) . ");\n"
+    );
+    exit;
+  }
+
+  /**
+   * Compare two event objects and return differing properties
+   *
+   * @param array Event A
+   * @param array Event B
+   * @return array List of differing event properties
+   */
+  public static function event_diff($a, $b)
+  {
+    $diff = array();
+    $ignore = array('changed' => 1, 'attachments' => 1);
+    foreach (array_unique(array_merge(array_keys($a), array_keys($b))) as $key) {
+      if (!$ignore[$key] && $key[0] != '_' && $a[$key] != $b[$key])
+        $diff[] = $key;
+    }
+    
+    // only compare number of attachments
+    if (count($a['attachments']) != count($b['attachments']))
+      $diff[] = 'attachments';
+    
+    return $diff;
+  }
+
+  /**
+   * Update attendee properties on the given event object
+   *
+   * @param array The event object to be altered
+   * @param array List of hash arrays each represeting an updated/added attendee
+   */
+  public static function merge_attendee_data(&$event, $attendees, $removed = null)
+  {
+    if (!empty($attendees) && !is_array($attendees[0])) {
+      $attendees = array($attendees);
+    }
+
+    foreach ($attendees as $attendee) {
+      $found = false;
+
+      foreach ($event['attendees'] as $i => $candidate) {
+        if ($candidate['email'] == $attendee['email']) {
+          $event['attendees'][$i] = $attendee;
+          $found = true;
+          break;
+        }
+      }
+
+      if (!$found) {
+        $event['attendees'][] = $attendee;
+      }
+    }
+
+    // filter out removed attendees
+    if (!empty($removed)) {
+      $event['attendees'] = array_filter($event['attendees'], function($attendee) use ($removed) {
+        return !in_array($attendee['email'], $removed);
+      });
+    }
+  }
+
+
+  /****  Resource management functions  ****/
+
+  /**
+   * Getter for the configured implementation of the resource directory interface
+   */
+  private function resources_directory()
+  {
+    if (is_object($this->resources_dir)) {
+      return $this->resources_dir;
+    }
+
+    if ($driver_name = $this->rc->config->get('calendar_resources_driver')) {
+      $driver_class = 'resources_driver_' . $driver_name;
+
+      require_once($this->home . '/drivers/resources_driver.php');
+      require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
+
+      $this->resources_dir = new $driver_class($this);
+    }
+
+    return $this->resources_dir;
+  }
+
+  /**
+   * Handler for resoruce autocompletion requests
+   */
+  public function resources_autocomplete()
+  {
+    $search = rcube_utils::get_input_value('_search', rcube_utils::INPUT_GPC, true);
+    $sid    = rcube_utils::get_input_value('_reqid', rcube_utils::INPUT_GPC);
+    $maxnum = (int)$this->rc->config->get('autocomplete_max', 15);
+    $results = array();
+
+    if ($directory = $this->resources_directory()) {
+      foreach ($directory->load_resources($search, $maxnum) as $rec) {
+        $results[]  = array(
+            'name'  => $rec['name'],
+            'email' => $rec['email'],
+            'type'  => $rec['_type'],
+        );
+      }
+    }
+
+    $this->rc->output->command('ksearch_query_results', $results, $search, $sid);
+    $this->rc->output->send();
+  }
+
+  /**
+   * Handler for load-requests for resource data
+   */
+  function resources_list()
+  {
+    $data = array();
+
+    if ($directory = $this->resources_directory()) {
+      foreach ($directory->load_resources() as $rec) {
+        $data[] = $rec;
+      }
+    }
+
+    $this->rc->output->command('plugin.resource_data', $data);
+    $this->rc->output->send();
+  }
+
+  /**
+   * Handler for requests loading resource owner information
+   */
+  function resources_owner()
+  {
+    if ($directory = $this->resources_directory()) {
+      $id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
+      $data = $directory->get_resource_owner($id);
+    }
+
+    $this->rc->output->command('plugin.resource_owner', $data);
+    $this->rc->output->send();
+  }
+
+  /**
+   * Deliver event data for a resource's calendar
+   */
+  function resources_calendar()
+  {
+    $events = array();
+
+    if ($directory = $this->resources_directory()) {
+      $events = $directory->get_resource_calendar(
+        rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC),
+        rcube_utils::get_input_value('start', rcube_utils::INPUT_GET),
+        rcube_utils::get_input_value('end', rcube_utils::INPUT_GET));
+    }
+
+    echo $this->encode($events);
+    exit;
+  }
+
+
+  /****  Event invitation plugin hooks ****/
+
+  /**
+   * Find an event in user calendars
+   */
+  protected function find_event($event, &$mode)
+  {
+    $this->load_driver();
+
+    // We search for writeable calendars in personal namespace by default
+    $mode   = calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL;
+    $result = $this->driver->get_event($event, $mode);
+    // ... now check shared folders if not found
+    if (!$result) {
+      $result = $this->driver->get_event($event, calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_SHARED);
+      if ($result) {
+        $mode |= calendar_driver::FILTER_SHARED;
+      }
+    }
+
+    return $result;
+  }
+
+  /**
+   * Handler for calendar/itip-status requests
+   */
+  function event_itip_status()
+  {
+    $data = rcube_utils::get_input_value('data', rcube_utils::INPUT_POST, true);
+
+    $this->load_driver();
+
+    // find local copy of the referenced event (in personal namespace)
+    $existing  = $this->find_event($data, $mode);
+    $is_shared = $mode & calendar_driver::FILTER_SHARED;
+    $itip      = $this->load_itip();
+    $response  = $itip->get_itip_status($data, $existing);
+
+    // get a list of writeable calendars to save new events to
+    if ((!$existing || $is_shared)
+      && !$data['nosave']
+      && ($response['action'] == 'rsvp' || $response['action'] == 'import')
+    ) {
+      $calendars = $this->driver->list_calendars($mode);
+      $calendar_select = new html_select(array('name' => 'calendar', 'id' => 'itip-saveto', 'is_escaped' => true));
+      $calendar_select->add('--', '');
+      $numcals = 0;
+      foreach ($calendars as $calendar) {
+        if ($calendar['editable']) {
+          $calendar_select->add($calendar['name'], $calendar['id']);
+          $numcals++;
+        }
+      }
+      if ($numcals < 1)
+        $calendar_select = null;
+    }
+
+    if ($calendar_select) {
+      $default_calendar = $this->get_default_calendar($data['sensitivity'], $calendars);
+      $response['select'] = html::span('folder-select', $this->gettext('saveincalendar') . '&nbsp;' .
+        $calendar_select->show($is_shared ? $existing['calendar'] : $default_calendar['id']));
+    }
+    else if ($data['nosave']) {
+      $response['select'] = html::tag('input', array('type' => 'hidden', 'name' => 'calendar', 'id' => 'itip-saveto', 'value' => ''));
+    }
+
+    // render small agenda view for the respective day
+    if ($data['method'] == 'REQUEST' && !empty($data['date']) && $response['action'] == 'rsvp') {
+      $event_start = rcube_utils::anytodatetime($data['date']);
+      $day_start = new Datetime(gmdate('Y-m-d 00:00', $data['date']), $this->lib->timezone);
+      $day_end = new Datetime(gmdate('Y-m-d 23:59', $data['date']), $this->lib->timezone);
+
+      // get events on that day from the user's personal calendars
+      $calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL);
+      $events = $this->driver->load_events($day_start->format('U'), $day_end->format('U'), null, array_keys($calendars));
+      usort($events, function($a, $b) { return $a['start'] > $b['start'] ? 1 : -1; });
+
+      $before = $after = array();
+      foreach ($events as $event) {
+        // TODO: skip events with free_busy == 'free' ?
+        if ($event['uid'] == $data['uid'] || $event['end'] < $day_start || $event['start'] > $day_end)
+          continue;
+        else if ($event['start'] < $event_start)
+          $before[] = $this->mail_agenda_event_row($event);
+        else
+          $after[] = $this->mail_agenda_event_row($event);
+      }
+
+      $response['append'] = array(
+        'selector' => '.calendar-agenda-preview',
+        'replacements' => array(
+          '%before%' => !empty($before) ? join("\n", array_slice($before,  -3)) : html::div('event-row no-event', $this->gettext('noearlierevents')),
+          '%after%'  => !empty($after)  ? join("\n", array_slice($after, 0, 3)) : html::div('event-row no-event', $this->gettext('nolaterevents')),
+        ),
+      );
+    }
+
+    $this->rc->output->command('plugin.update_itip_object_status', $response);
+  }
+
+  /**
+   * Handler for calendar/itip-remove requests
+   */
+  function event_itip_remove()
+  {
+    $success  = false;
+    $uid      = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST);
+    $instance = rcube_utils::get_input_value('_instance', rcube_utils::INPUT_POST);
+    $savemode = rcube_utils::get_input_value('_savemode', rcube_utils::INPUT_POST);
+    $listmode = calendar_driver::FILTER_WRITEABLE | calendar_driver::FILTER_PERSONAL;
+
+    // search for event if only UID is given
+    if ($event = $this->driver->get_event(array('uid' => $uid, '_instance' => $instance), $listmode)) {
+      $event['_savemode'] = $savemode;
+      $success = $this->driver->remove_event($event, true);
+    }
+
+    if ($success) {
+      $this->rc->output->show_message('calendar.successremoval', 'confirmation');
+    }
+    else {
+      $this->rc->output->show_message('calendar.errorsaving', 'error');
+    }
+  }
+
+  /**
+   * Handler for URLs that allow an invitee to respond on his invitation mail
+   */
+  public function itip_attend_response($p)
+  {
+    $this->setup();
+
+    if ($p['action'] == 'attend') {
+      $this->ui->init();
+
+      $this->rc->output->set_env('task', 'calendar');  // override some env vars
+      $this->rc->output->set_env('refresh_interval', 0);
+      $this->rc->output->set_pagetitle($this->gettext('calendar'));
+
+      $itip  = $this->load_itip();
+      $token = rcube_utils::get_input_value('_t', rcube_utils::INPUT_GPC);
+
+      // read event info stored under the given token
+      if ($invitation = $itip->get_invitation($token)) {
+        $this->token = $token;
+        $this->event = $invitation['event'];
+
+        // show message about cancellation
+        if ($invitation['cancelled']) {
+          $this->invitestatus = html::div('rsvp-status declined', $itip->gettext('eventcancelled'));
+        }
+        // save submitted RSVP status
+        else if (!empty($_POST['rsvp'])) {
+          $status = null;
+          foreach (array('accepted','tentative','declined') as $method) {
+            if ($_POST['rsvp'] == $itip->gettext('itip' . $method)) {
+              $status = $method;
+              break;
+            }
+          }
+
+          // send itip reply to organizer
+          $invitation['event']['comment'] = rcube_utils::get_input_value('_comment', rcube_utils::INPUT_POST);
+          if ($status && $itip->update_invitation($invitation, $invitation['attendee'], strtoupper($status))) {
+            $this->invitestatus = html::div('rsvp-status ' . strtolower($status), $itip->gettext('youhave'.strtolower($status)));
+          }
+          else
+            $this->rc->output->command('display_message', $this->gettext('errorsaving'), 'error', -1);
+
+          // if user is logged in...
+          // FIXME: we should really consider removing this functionality
+          //        it's confusing that it creates/updates an event only for logged-in user
+          //        what if the logged-in user is not the same as the attendee?
+          if ($this->rc->user->ID) {
+            $this->load_driver();
+
+            $invitation = $itip->get_invitation($token);
+            $existing   = $this->driver->get_event($this->event);
+
+            // save the event to his/her default calendar if not yet present
+            if (!$existing && ($calendar = $this->get_default_calendar($invitation['event']['sensitivity']))) {
+              $invitation['event']['calendar'] = $calendar['id'];
+              if ($this->driver->new_event($invitation['event']))
+                $this->rc->output->command('display_message', $this->gettext(array('name' => 'importedsuccessfully', 'vars' => array('calendar' => $calendar['name']))), 'confirmation');
+              else
+                $this->rc->output->command('display_message', $this->gettext('errorimportingevent'), 'error');
+            }
+            else if ($existing
+              && ($this->event['sequence'] >= $existing['sequence'] || $this->event['changed'] >= $existing['changed'])
+              && ($calendar = $this->driver->get_calendar($existing['calendar']))
+            ) {
+              $this->event       = $invitation['event'];
+              $this->event['id'] = $existing['id'];
+
+              unset($this->event['comment']);
+
+              // merge attendees status
+              // e.g. preserve my participant status for regular updates
+              $this->lib->merge_attendees($this->event, $existing, $status);
+
+              // update attachments list
+              $event['deleted_attachments'] = true;
+
+              // show me as free when declined (#1670)
+              if ($status == 'declined')
+                $this->event['free_busy'] = 'free';
+
+              if ($this->driver->edit_event($this->event))
+                $this->rc->output->command('display_message', $this->gettext(array('name' => 'updatedsuccessfully', 'vars' => array('calendar' => $calendar->get_name()))), 'confirmation');
+              else
+                $this->rc->output->command('display_message', $this->gettext('errorimportingevent'), 'error');
+            }
+          }
+        }
+        
+        $this->register_handler('plugin.event_inviteform', array($this, 'itip_event_inviteform'));
+        $this->register_handler('plugin.event_invitebox', array($this->ui, 'event_invitebox'));
+        
+        if (!$this->invitestatus) {
+          $this->itip->set_rsvp_actions(array('accepted','tentative','declined'));
+          $this->register_handler('plugin.event_rsvp_buttons', array($this->ui, 'event_rsvp_buttons'));
+        }
+        
+        $this->rc->output->set_pagetitle($itip->gettext('itipinvitation') . ' ' . $this->event['title']);
+      }
+      else
+        $this->rc->output->command('display_message', $this->gettext('itipinvalidrequest'), 'error', -1);
+      
+      $this->rc->output->send('calendar.itipattend');
+    }
+  }
+  
+  /**
+   *
+   */
+  public function itip_event_inviteform($attrib)
+  {
+    $hidden = new html_hiddenfield(array('name' => "_t", 'value' => $this->token));
+    return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'attend')), 'method' => 'post', 'noclose' => true) + $attrib) . $hidden->show();
+  }
+
+  /**
+   * 
+   */
+  private function mail_agenda_event_row($event, $class = '')
+  {
+    $time = $event['allday'] ? $this->gettext('all-day') :
+      $this->rc->format_date($event['start'], $this->rc->config->get('time_format')) . ' - ' .
+        $this->rc->format_date($event['end'], $this->rc->config->get('time_format'));
+
+    return html::div(rtrim('event-row ' . $class),
+      html::span('event-date', $time) .
+      html::span('event-title', rcube::Q($event['title']))
+    );
+  }
+  
+  /**
+   * 
+   */
+  public function mail_messages_list($p)
+  {
+    if (in_array('attachment', (array)$p['cols']) && !empty($p['messages'])) {
+      foreach ($p['messages'] as $header) {
+        $part = new StdClass;
+        $part->mimetype = $header->ctype;
+        if (libcalendaring::part_is_vcalendar($part)) {
+          $header->list_flags['attachmentClass'] = 'ical';
+        }
+        else if (in_array($header->ctype, array('multipart/alternative', 'multipart/mixed'))) {
+          // TODO: fetch bodystructure and search for ical parts. Maybe too expensive?
+
+          if (!empty($header->structure) && is_array($header->structure->parts)) {
+            foreach ($header->structure->parts as $part) {
+              if (libcalendaring::part_is_vcalendar($part) && !empty($part->ctype_parameters['method'])) {
+                $header->list_flags['attachmentClass'] = 'ical';
+                break;
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Add UI element to copy event invitations or updates to the calendar
+   */
+  public function mail_messagebody_html($p)
+  {
+    // load iCalendar functions (if necessary)
+    if (!empty($this->lib->ical_parts)) {
+      $this->get_ical();
+      $this->load_itip();
+    }
+
+    $html = '';
+    $has_events = false;
+    $ical_objects = $this->lib->get_mail_ical_objects();
+
+    // show a box for every event in the file
+    foreach ($ical_objects as $idx => $event) {
+      if ($event['_type'] != 'event')  // skip non-event objects (#2928)
+        continue;
+
+      $has_events = true;
+
+      // get prepared inline UI for this event object
+      if ($ical_objects->method) {
+        $append = '';
+
+        // prepare a small agenda preview to be filled with actual event data on async request
+        if ($ical_objects->method == 'REQUEST') {
+          $append = html::div('calendar-agenda-preview',
+            html::tag('h3', 'preview-title', $this->gettext('agenda') . ' ' .
+              html::span('date', $this->rc->format_date($event['start'], $this->rc->config->get('date_format')))
+            ) . '%before%' . $this->mail_agenda_event_row($event, 'current') . '%after%');
+        }
+
+        $html .= html::div('calendar-invitebox',
+          $this->itip->mail_itip_inline_ui(
+            $event,
+            $ical_objects->method,
+            $ical_objects->mime_id . ':' . $idx,
+            'calendar',
+            rcube_utils::anytodatetime($ical_objects->message_date),
+            $this->rc->url(array('task' => 'calendar')) . '&view=agendaDay&date=' . $event['start']->format('U')
+          ) . $append
+        );
+      }
+
+      // limit listing
+      if ($idx >= 3)
+        break;
+    }
+
+    // prepend event boxes to message body
+    if ($html) {
+      $this->ui->init();
+      $p['content'] = $html . $p['content'];
+      $this->rc->output->add_label('calendar.savingdata','calendar.deleteventconfirm','calendar.declinedeleteconfirm');
+    }
+
+    // add "Save to calendar" button into attachment menu
+    if ($has_events) {
+      $this->add_button(array(
+        'id'         => 'attachmentsavecal',
+        'name'       => 'attachmentsavecal',
+        'type'       => 'link',
+        'wrapper'    => 'li',
+        'command'    => 'attachment-save-calendar',
+        'class'      => 'icon calendarlink',
+        'classact'   => 'icon calendarlink active',
+        'innerclass' => 'icon calendar',
+        'label'      => 'calendar.savetocalendar',
+        ), 'attachmentmenu');
+    }
+
+    return $p;
+  }
+
+
+  /**
+   * Handler for POST request to import an event attached to a mail message
+   */
+  public function mail_import_itip()
+  {
+    $itip_sending = $this->rc->config->get('calendar_itip_send_option', $this->defaults['calendar_itip_send_option']);
+
+    $uid     = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
+    $mbox    = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
+    $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
+    $status  = rcube_utils::get_input_value('_status', rcube_utils::INPUT_POST);
+    $delete  = intval(rcube_utils::get_input_value('_del', rcube_utils::INPUT_POST));
+    $noreply = intval(rcube_utils::get_input_value('_noreply', rcube_utils::INPUT_POST));
+    $noreply = $noreply || $status == 'needs-action' || $itip_sending === 0;
+    $instance = rcube_utils::get_input_value('_instance', rcube_utils::INPUT_POST);
+    $savemode = rcube_utils::get_input_value('_savemode', rcube_utils::INPUT_POST);
+    $comment  = rcube_utils::get_input_value('_comment', rcube_utils::INPUT_POST);
+
+    $error_msg = $this->gettext('errorimportingevent');
+    $success   = false;
+
+    if ($status == 'delegated') {
+      $delegates = rcube_mime::decode_address_list(rcube_utils::get_input_value('_to', rcube_utils::INPUT_POST, true), 1, false);
+      $delegate  = reset($delegates);
+
+      if (empty($delegate) || empty($delegate['mailto'])) {
+        $this->rc->output->command('display_message', $this->rc->gettext('libcalendaring.delegateinvalidaddress'), 'error');
+        return;
+      }
+    }
+
+    // successfully parsed events?
+    if ($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) {
+      // forward iTip request to delegatee
+      if ($delegate) {
+        $rsvpme = rcube_utils::get_input_value('_rsvp', rcube_utils::INPUT_POST);
+        $itip   = $this->load_itip();
+
+        $event['comment'] = $comment;
+
+        if ($itip->delegate_to($event, $delegate, !empty($rsvpme))) {
+          $this->rc->output->show_message('calendar.itipsendsuccess', 'confirmation');
+        }
+        else {
+          $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+        }
+
+        unset($event['comment']);
+
+        // the delegator is set to non-participant, thus save as non-blocking
+        $event['free_busy'] = 'free';
+      }
+
+      $mode = calendar_driver::FILTER_PERSONAL
+        | calendar_driver::FILTER_SHARED
+        | calendar_driver::FILTER_WRITEABLE;
+
+      // find writeable calendar to store event
+      $cal_id    = rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST);
+      $dontsave  = $cal_id === '' && $event['_method'] == 'REQUEST';
+      $calendars = $this->driver->list_calendars($mode);
+      $calendar  = $calendars[$cal_id];
+
+      // select default calendar except user explicitly selected 'none'
+      if (!$calendar && !$dontsave)
+         $calendar = $this->get_default_calendar($event['sensitivity'], $calendars);
+
+      $metadata = array(
+        'uid' => $event['uid'],
+        '_instance' => $event['_instance'],
+        'changed' => is_object($event['changed']) ? $event['changed']->format('U') : 0,
+        'sequence' => intval($event['sequence']),
+        'fallback' => strtoupper($status),
+        'method' => $event['_method'],
+        'task' => 'calendar',
+      );
+
+      // update my attendee status according to submitted method
+      if (!empty($status)) {
+        $organizer = null;
+        $emails = $this->get_user_emails();
+        foreach ($event['attendees'] as $i => $attendee) {
+          if ($attendee['role'] == 'ORGANIZER') {
+            $organizer = $attendee;
+          }
+          else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+            $event['attendees'][$i]['status'] = strtoupper($status);
+            if (!in_array($event['attendees'][$i]['status'], array('NEEDS-ACTION','DELEGATED')))
+              $event['attendees'][$i]['rsvp'] = false;  // unset RSVP attribute
+
+            $metadata['attendee'] = $attendee['email'];
+            $metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT';
+            $reply_sender = $attendee['email'];
+            $event_attendee = $attendee;
+          }
+        }
+
+        // add attendee with this user's default identity if not listed
+        if (!$reply_sender) {
+          $sender_identity = $this->rc->user->list_emails(true);
+          $event['attendees'][] = array(
+            'name' => $sender_identity['name'],
+            'email' => $sender_identity['email'],
+            'role' => 'OPT-PARTICIPANT',
+            'status' => strtoupper($status),
+          );
+          $metadata['attendee'] = $sender_identity['email'];
+        }
+      }
+      
+      // save to calendar
+      if ($calendar && $calendar['editable']) {
+        // check for existing event with the same UID
+        $existing = $this->find_event($event, $mode);
+
+        // we'll create a new copy if user decided to change the calendar
+        if ($existing && $cal_id && $calendar && $calendar['id'] != $existing['calendar']) {
+          $existing = null;
+        }
+
+        if ($existing) {
+          $calendar = $calendars[$existing['calendar']];
+
+          // forward savemode for correct updates of recurring events
+          $existing['_savemode'] = $savemode ?: $event['_savemode'];
+
+          // only update attendee status
+          if ($event['_method'] == 'REPLY') {
+            // try to identify the attendee using the email sender address
+            $existing_attendee = -1;
+            $existing_attendee_emails = array();
+            foreach ($existing['attendees'] as $i => $attendee) {
+              $existing_attendee_emails[] = $attendee['email'];
+              if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) {
+                $existing_attendee = $i;
+              }
+            }
+            $event_attendee = null;
+            $update_attendees = array();
+            foreach ($event['attendees'] as $attendee) {
+              if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) {
+                $event_attendee = $attendee;
+                $update_attendees[] = $attendee;
+                $metadata['fallback'] = $attendee['status'];
+                $metadata['attendee'] = $attendee['email'];
+                $metadata['rsvp'] = $attendee['rsvp'] || $attendee['role'] != 'NON-PARTICIPANT';
+                if ($attendee['status'] != 'DELEGATED') {
+                  break;
+                }
+              }
+              // also copy delegate attendee
+              else if (!empty($attendee['delegated-from'])
+                && $this->itip->compare_email($attendee['delegated-from'], $event['_sender'], $event['_sender_utf'])
+              ) {
+                $update_attendees[] = $attendee;
+                if (!in_array_nocase($attendee['email'], $existing_attendee_emails)) {
+                  $existing['attendees'][] = $attendee;
+                }
+              }
+            }
+
+            // if delegatee has declined, set delegator's RSVP=True
+            if ($event_attendee && $event_attendee['status'] == 'DECLINED' && $event_attendee['delegated-from']) {
+              foreach ($existing['attendees'] as $i => $attendee) {
+                if ($attendee['email'] == $event_attendee['delegated-from']) {
+                  $existing['attendees'][$i]['rsvp'] = true;
+                  break;
+                }
+              }
+            }
+
+            // found matching attendee entry in both existing and new events
+            if ($existing_attendee >= 0 && $event_attendee) {
+              $existing['attendees'][$existing_attendee] = $event_attendee;
+              $success = $this->driver->update_attendees($existing, $update_attendees);
+            }
+            // update the entire attendees block
+            else if (($event['sequence'] >= $existing['sequence'] || $event['changed'] >= $existing['changed']) && $event_attendee) {
+              $existing['attendees'][] = $event_attendee;
+              $success = $this->driver->update_attendees($existing, $update_attendees);
+            }
+            else {
+              $error_msg = $this->gettext('newerversionexists');
+            }
+          }
+          // delete the event when declined (#1670)
+          else if ($status == 'declined' && $delete) {
+             $deleted = $this->driver->remove_event($existing, true);
+             $success = true;
+          }
+          // import the (newer) event
+          else if ($event['sequence'] >= $existing['sequence'] || $event['changed'] >= $existing['changed']) {
+            $event['id'] = $existing['id'];
+            $event['calendar'] = $existing['calendar'];
+
+            // merge attendees status
+            // e.g. preserve my participant status for regular updates
+            $this->lib->merge_attendees($event, $existing, $status);
+
+            // set status=CANCELLED on CANCEL messages
+            if ($event['_method'] == 'CANCEL')
+              $event['status'] = 'CANCELLED';
+
+            // update attachments list, allow attachments update only on REQUEST (#5342)
+            if ($event['_method'] == 'REQUEST')
+              $event['deleted_attachments'] = true;
+            else
+              unset($event['attachments']);
+
+            // show me as free when declined (#1670)
+            if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT')
+              $event['free_busy'] = 'free';
+
+            $success = $this->driver->edit_event($event);
+          }
+          else if (!empty($status)) {
+            $existing['attendees'] = $event['attendees'];
+            if ($status == 'declined' || $event_attendee['role'] == 'NON-PARTICIPANT')  // show me as free when declined (#1670)
+              $existing['free_busy'] = 'free';
+            $success = $this->driver->edit_event($existing);
+          }
+          else
+            $error_msg = $this->gettext('newerversionexists');
+        }
+        else if (!$existing && ($status != 'declined' || $this->rc->config->get('kolab_invitation_calendars'))) {
+          if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT') {
+            $event['free_busy'] = 'free';
+          }
+
+          // if the RSVP reply only refers to a single instance:
+          // store unmodified master event with current instance as exception
+          if (!empty($instance) && !empty($savemode) && $savemode != 'all') {
+            $master = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event');
+            if ($master['recurrence'] && !$master['_instance']) {
+              // compute recurring events until this instance's date
+              if ($recurrence_date = rcube_utils::anytodatetime($instance, $master['start']->getTimezone())) {
+                $recurrence_date->setTime(23,59,59);
+
+                foreach ($this->driver->get_recurring_events($master, $master['start'], $recurrence_date) as $recurring) {
+                  if ($recurring['_instance'] == $instance) {
+                    // copy attendees block with my partstat to exception
+                    $recurring['attendees'] = $event['attendees'];
+                    $master['recurrence']['EXCEPTIONS'][] = $recurring;
+                    $event = $recurring;  // set reference for iTip reply
+                    break;
+                  }
+                }
+
+                $master['calendar'] = $event['calendar'] = $calendar['id'];
+                $success = $this->driver->new_event($master);
+              }
+              else {
+                $master = null;
+              }
+            }
+            else {
+              $master = null;
+            }
+          }
+
+          // save to the selected/default calendar
+          if (!$master) {
+            $event['calendar'] = $calendar['id'];
+            $success = $this->driver->new_event($event);
+          }
+        }
+        else if ($status == 'declined')
+          $error_msg = null;
+      }
+      else if ($status == 'declined' || $dontsave)
+        $error_msg = null;
+      else
+        $error_msg = $this->gettext('nowritecalendarfound');
+    }
+
+    if ($success) {
+      $message = $event['_method'] == 'REPLY' ? 'attendeupdateesuccess' : ($deleted ? 'successremoval' : ($existing ? 'updatedsuccessfully' : 'importedsuccessfully'));
+      $this->rc->output->command('display_message', $this->gettext(array('name' => $message, 'vars' => array('calendar' => $calendar['name']))), 'confirmation');
+    }
+
+    if ($success || $dontsave) {
+      $metadata['calendar'] = $event['calendar'];
+      $metadata['nosave'] = $dontsave;
+      $metadata['rsvp'] = intval($metadata['rsvp']);
+      $metadata['after_action'] = $this->rc->config->get('calendar_itip_after_action', $this->defaults['calendar_itip_after_action']);
+      $this->rc->output->command('plugin.itip_message_processed', $metadata);
+      $error_msg = null;
+    }
+    else if ($error_msg) {
+      $this->rc->output->command('display_message', $error_msg, 'error');
+    }
+
+    // send iTip reply
+    if ($event['_method'] == 'REQUEST' && $organizer && !$noreply && !in_array(strtolower($organizer['email']), $emails) && !$error_msg) {
+      $event['comment'] = $comment;
+      $itip = $this->load_itip();
+      $itip->set_sender_email($reply_sender);
+      if ($itip->send_itip_message($event, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
+        $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
+      else
+        $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+    }
+
+    $this->rc->output->send();
+  }
+
+  /**
+   * Handler for calendar/itip-remove requests
+   */
+  function mail_itip_decline_reply()
+  {
+    $uid     = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
+    $mbox    = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
+    $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
+
+    if (($event = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event')) && $event['_method'] == 'REPLY') {
+      $event['comment'] = rcube_utils::get_input_value('_comment', rcube_utils::INPUT_POST);
+
+      foreach ($event['attendees'] as $_attendee) {
+        if ($_attendee['role'] != 'ORGANIZER') {
+          $attendee = $_attendee;
+          break;
+        }
+      }
+
+      $itip = $this->load_itip();
+      if ($itip->send_itip_message($event, 'CANCEL', $attendee, 'itipsubjectcancel', 'itipmailbodycancel'))
+        $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $attendee['name'] ? $attendee['name'] : $attendee['email']))), 'confirmation');
+      else
+        $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+    }
+    else {
+      $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
+    }
+  }
+
+  /**
+   * Handler for calendar/itip-delegate requests
+   */
+  function mail_itip_delegate()
+  {
+    // forward request to mail_import_itip() with the right status
+    $_POST['_status'] = $_REQUEST['_status'] = 'delegated';
+    $this->mail_import_itip();
+  }
+
+  /**
+   * Import the full payload from a mail message attachment
+   */
+  public function mail_import_attachment()
+  {
+    $uid     = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
+    $mbox    = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
+    $mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
+    $charset = RCUBE_CHARSET;
+
+    // establish imap connection
+    $imap = $this->rc->get_storage();
+    $imap->set_folder($mbox);
+
+    if ($uid && $mime_id) {
+      $part = $imap->get_message_part($uid, $mime_id);
+      if ($part->ctype_parameters['charset'])
+        $charset = $part->ctype_parameters['charset'];
+//      $headers = $imap->get_message_headers($uid);
+
+      if ($part) {
+        $events = $this->get_ical()->import($part, $charset);
+      }
+    }
+
+    $success = $existing = 0;
+    if (!empty($events)) {
+      // find writeable calendar to store event
+      $cal_id = !empty($_REQUEST['_calendar']) ? rcube_utils::get_input_value('_calendar', rcube_utils::INPUT_POST) : null;
+      $calendars = $this->driver->list_calendars(calendar_driver::FILTER_PERSONAL);
+
+      foreach ($events as $event) {
+        // save to calendar
+        $calendar = $calendars[$cal_id] ?: $this->get_default_calendar($event['sensitivity']);
+        if ($calendar && $calendar['editable'] && $event['_type'] == 'event') {
+          $event['calendar'] = $calendar['id'];
+
+          if (!$this->driver->get_event($event['uid'], calendar_driver::FILTER_WRITEABLE)) {
+            $success += (bool)$this->driver->new_event($event);
+          }
+          else {
+            $existing++;
+          }
+        }
+      }
+    }
+
+    if ($success) {
+      $this->rc->output->command('display_message', $this->gettext(array(
+        'name' => 'importsuccess',
+        'vars' => array('nr' => $success),
+      )), 'confirmation');
+    }
+    else if ($existing) {
+      $this->rc->output->command('display_message', $this->gettext('importwarningexists'), 'warning');
+    }
+    else {
+      $this->rc->output->command('display_message', $this->gettext('errorimportingevent'), 'error');
+    }
+  }
+
+  /**
+   * Read email message and return contents for a new event based on that message
+   */
+  public function mail_message2event()
+  {
+    $uid   = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
+    $mbox  = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
+    $event = array();
+
+    // establish imap connection
+    $imap = $this->rc->get_storage();
+    $imap->set_folder($mbox);
+    $message = new rcube_message($uid);
+
+    if ($message->headers) {
+      $event['title'] = trim($message->subject);
+      $event['description'] = trim($message->first_text_part());
+
+      $this->load_driver();
+
+      // add a reference to the email message
+      if ($msgref = $this->driver->get_message_reference($message->headers, $mbox)) {
+        $event['links'] = array($msgref);
+      }
+      else {
+	// hack around missing database_driver implementation of that method
+	$event['links'] = array(
+				array('mailurl' => "https://hppllc.org/roundcube/?_task=mail&_mbox=Bookings%2FBigHouse%2FPending&_uid=$uid",
+				      'subject' => 'link to original email'));
+      }
+      // copy mail attachments to event
+      if ($message->attachments) {
+        $eventid = 'cal-';
+        if (!is_array($_SESSION[self::SESSION_KEY]) || $_SESSION[self::SESSION_KEY]['id'] != $eventid) {
+          $_SESSION[self::SESSION_KEY] = array();
+          $_SESSION[self::SESSION_KEY]['id'] = $eventid;
+          $_SESSION[self::SESSION_KEY]['attachments'] = array();
+        }
+
+        foreach ((array)$message->attachments as $part) {
+          $attachment = array(
+            'data' => $imap->get_message_part($uid, $part->mime_id, $part),
+            'size' => $part->size,
+            'name' => $part->filename,
+            'mimetype' => $part->mimetype,
+            'group' => $eventid,
+          );
+
+          $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment);
+
+          if ($attachment['status'] && !$attachment['abort']) {
+            $id = $attachment['id'];
+            $attachment['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
+
+            // store new attachment in session
+            unset($attachment['status'], $attachment['abort'], $attachment['data']);
+            $_SESSION[self::SESSION_KEY]['attachments'][$id] = $attachment;
+
+            $attachment['id'] = 'rcmfile' . $attachment['id'];  // add prefix to consider it 'new'
+            $event['attachments'][] = $attachment;
+          }
+        }
+      }
+      
+      $this->rc->output->command('plugin.mail2event_dialog', $event);
+    }
+    else {
+      $this->rc->output->command('display_message', $this->gettext('messageopenerror'), 'error');
+    }
+    
+    $this->rc->output->send();
+  }
+
+  /**
+   * Handler for the 'message_compose' plugin hook. This will check for
+   * a compose parameter 'calendar_event' and create an attachment with the
+   * referenced event in iCal format
+   */
+  public function mail_message_compose($args)
+  {
+    // set the submitted event ID as attachment
+    if (!empty($args['param']['calendar_event'])) {
+      $this->load_driver();
+
+      list($cal, $id) = explode(':', $args['param']['calendar_event'], 2);
+      if ($event = $this->driver->get_event(array('id' => $id, 'calendar' => $cal))) {
+        $filename = asciiwords($event['title']);
+        if (empty($filename))
+          $filename = 'event';
+
+        // save ics to a temp file and register as attachment
+        $tmp_path = tempnam($this->rc->config->get('temp_dir'), 'rcmAttmntCal');
+        file_put_contents($tmp_path, $this->get_ical()->export(array($event), '', false, array($this->driver, 'get_attachment_body')));
+
+        $args['attachments'][] = array(
+          'path'     => $tmp_path,
+          'name'     => $filename . '.ics',
+          'mimetype' => 'text/calendar',
+          'size'     => filesize($tmp_path),
+        );
+        $args['param']['subject'] = $event['title'];
+      }
+    }
+
+    return $args;
+  }
+
+
+  /**
+   * Get a list of email addresses of the current user (from login and identities)
+   */
+  public function get_user_emails()
+  {
+    return $this->lib->get_user_emails();
+  }
+
+
+  /**
+   * Build an absolute URL with the given parameters
+   */
+  public function get_url($param = array())
+  {
+    $param += array('task' => 'calendar');
+    return $this->rc->url($param, true, true);
+  }
+
+
+  public function ical_feed_hash($source)
+  {
+    return base64_encode($this->rc->user->get_username() . ':' . $source);
+  }
+
+  /**
+   * Handler for user_delete plugin hook
+   */
+  public function user_delete($args)
+  {
+     // delete itipinvitations entries related to this user
+     $db = $this->rc->get_dbh();
+     $table_itipinvitations = $db->table_name('itipinvitations', true);
+     $db->query("DELETE FROM $table_itipinvitations WHERE `user_id` = ?", $args['user']->ID);
+
+     $this->setup();
+     $this->load_driver();
+     return $this->driver->user_delete($args);
+  }
+
+  /**
+   * Magic getter for public access to protected members
+   */
+  public function __get($name)
+  {
+    switch ($name) {
+      case 'ical':
+        return $this->get_ical();
+
+      case 'itip':
+        return $this->load_itip();
+
+      case 'driver':
+        $this->load_driver();
+        return $this->driver;
+    }
+
+    return null;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/calendar_base.js	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,138 @@
+/**
+ * Base Javascript class for the Calendar plugin
+ *
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * @licstart  The following is the entire license notice for the
+ * JavaScript code in this page.
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2013-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ *
+ * @licend  The above is the entire license notice
+ * for the JavaScript code in this page.
+ */
+
+// Basic setup for Roundcube calendar client class
+function rcube_calendar(settings)
+{
+    // extend base class
+    rcube_libcalendaring.call(this, settings);
+
+    // member vars
+    this.ui_loaded = false;
+    this.selected_attachment = null;
+
+    // private vars
+    var me = this;
+
+    // create new event from current mail message
+    this.create_from_mail = function(uid)
+    {
+      if (uid || (uid = rcmail.get_single_uid())) {
+        // load calendar UI (scripts and edit dialog template)
+        if (!this.ui_loaded) {
+          $.when(
+            $.getScript(rcmail.assets_path('plugins/calendar/calendar_ui.js')),
+            $.getScript(rcmail.assets_path('plugins/calendar/lib/js/fullcalendar.js')),
+            $.get(rcmail.url('calendar/inlineui'), function(html) { $(document.body).append(html); }, 'html')
+          ).then(function() {
+            // disable attendees feature (autocompletion and stuff is not initialized)
+            for (var c in rcmail.env.calendars)
+              rcmail.env.calendars[c].attendees = rcmail.env.calendars[c].resources = false;
+
+            me.ui_loaded = true;
+            me.ui = new rcube_calendar_ui(me.settings);
+            me.create_from_mail(uid);  // start over
+          });
+
+          return;
+        }
+
+        // get message contents for event dialog
+        var lock = rcmail.set_busy(true, 'loading');
+        rcmail.http_post('calendar/mailtoevent', {
+            '_mbox': rcmail.env.mailbox,
+            '_uid': uid
+          }, lock);
+      }
+    };
+
+    // callback function triggered from server with contents for the new event
+    this.mail2event_dialog = function(event)
+    {
+      if (event.title) {
+        this.ui.add_event(event);
+        if (rcmail.message_list)
+          rcmail.message_list.blur();
+      }
+    };
+
+    // handler for attachment-save-calendar commands
+    this.save_to_calendar = function(p)
+    {
+      // TODO: show dialog to select the calendar for importing
+      if (this.selected_attachment && window.rcube_libcalendaring) {
+        rcmail.http_post('calendar/mailimportattach', {
+            _uid: rcmail.env.uid,
+            _mbox: rcmail.env.mailbox,
+            _part: this.selected_attachment
+            // _calendar: $('#calendar-attachment-saveto').val(),
+          }, rcmail.set_busy(true, 'itip.savingdata'));
+      }
+    }
+}
+
+
+/* calendar plugin initialization (for non-calendar tasks) */
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+  if (rcmail.task != 'calendar') {
+    var cal = new rcube_calendar($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
+
+    // register create-from-mail command to message_commands array
+    if (rcmail.env.task == 'mail') {
+      rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail(); });
+      rcmail.register_command('attachment-save-calendar', function() { cal.save_to_calendar(); });
+      rcmail.addEventListener('plugin.mail2event_dialog', function(p) { cal.mail2event_dialog(p); });
+      rcmail.addEventListener('plugin.unlock_saving', function(p) { cal.ui && cal.ui.unlock_saving(); });
+
+      if (rcmail.env.action != 'show') {
+        rcmail.env.message_commands.push('calendar-create-from-mail');
+        rcmail.add_element($('<a>'));
+      }
+      else {
+        rcmail.enable_command('calendar-create-from-mail', true);
+      }
+
+      rcmail.addEventListener('beforemenu-open', function(p) {
+        if (p.menu == 'attachmentmenu') {
+          cal.selected_attachment = p.id;
+          var mimetype = rcmail.env.attachments[p.id];
+          rcmail.enable_command('attachment-save-calendar', mimetype == 'text/calendar' || mimetype == 'text/x-vcalendar' || mimetype == 'application/ics');
+        }
+      });
+    }
+  }
+
+  rcmail.register_command('plugin.calendar', function() { rcmail.switch_task('calendar'); }, true);
+
+  rcmail.addEventListener('plugin.ping_url', function(p) {
+    var action = p.action;
+    p.action = p.event = null;
+    new Image().src = rcmail.url(action, p);
+  });
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/calendar_ui.js	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,4216 @@
+/**
+ * Client UI Javascript for the Calendar plugin
+ *
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * @licstart  The following is the entire license notice for the
+ * JavaScript code in this file.
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ *
+ * @licend  The above is the entire license notice
+ * for the JavaScript code in this file.
+ */
+
+// Roundcube calendar UI client class
+function rcube_calendar_ui(settings)
+{
+    // extend base class
+    rcube_calendar.call(this, settings);
+    
+    /***  member vars  ***/
+    this.is_loading = false;
+    this.selected_event = null;
+    this.selected_calendar = null;
+    this.search_request = null;
+    this.saving_lock = null;
+    this.calendars = {};
+    this.quickview_sources = [];
+
+
+    /***  private vars  ***/
+    var DAY_MS = 86400000;
+    var HOUR_MS = 3600000;
+    var me = this;
+    var day_clicked = day_clicked_ts = 0;
+    var ignore_click = false;
+    var event_defaults = { free_busy:'busy', alarms:'' };
+    var event_attendees = [];
+    var calendars_list;
+    var calenders_search_list;
+    var calenders_search_container;
+    var search_calendars = {};
+    var attendees_list;
+    var resources_list;
+    var resources_treelist;
+    var resources_data = {};
+    var resources_index = [];
+    var resource_owners = {};
+    var resources_events_source = { url:null, editable:false };
+    var freebusy_ui = { workinhoursonly:false, needsupdate:false };
+    var freebusy_data = {};
+    var current_view = null;
+    var count_sources = [];
+    var exec_deferred = bw.ie6 ? 5 : 1;
+    var sensitivitylabels = { 'public':rcmail.gettext('public','calendar'), 'private':rcmail.gettext('private','calendar'), 'confidential':rcmail.gettext('confidential','calendar') };
+    var ui_loading = rcmail.set_busy(true, 'loading');
+
+    // general datepicker settings
+    var datepicker_settings = {
+      // translate from fullcalendar format to datepicker format
+      dateFormat: settings['date_format'].replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
+      firstDay : settings['first_day'],
+      dayNamesMin: settings['days_short'],
+      monthNames: settings['months'],
+      monthNamesShort: settings['months'],
+      changeMonth: false,
+      showOtherMonths: true,
+      selectOtherMonths: true
+    };
+
+    // global fullcalendar settings
+    var fullcalendar_defaults = {
+      aspectRatio: 1,
+      ignoreTimezone: true,  // will treat the given date strings as in local (browser's) timezone
+      monthNames : settings.months,
+      monthNamesShort : settings.months_short,
+      dayNames : settings.days,
+      dayNamesShort : settings.days_short,
+      firstDay : settings.first_day,
+      firstHour : settings.first_hour,
+      slotMinutes : 60/settings.timeslots,
+      timeFormat: {
+        '': settings.time_format,
+        agenda: settings.time_format + '{ - ' + settings.time_format + '}',
+        list: settings.time_format + '{ - ' + settings.time_format + '}',
+        table: settings.time_format + '{ - ' + settings.time_format + '}'
+      },
+      axisFormat : settings.time_format,
+      columnFormat: {
+        month: 'ddd', // Mon
+        week: 'ddd ' + settings.date_short, // Mon 9/7
+        day: 'dddd ' + settings.date_short,  // Monday 9/7
+        table: settings.date_agenda
+      },
+      titleFormat: {
+        month: 'MMMM yyyy',
+        week: settings.dates_long,
+        day: 'dddd ' + settings['date_long'],
+        table: settings.dates_long
+      },
+      listPage: 7,  // advance one week in agenda view
+      listRange: settings.agenda_range,
+      listSections: settings.agenda_sections,
+      tableCols: ['handle', 'date', 'time', 'title', 'location'],
+      defaultView: rcmail.env.view || settings.default_view,
+      allDayText: rcmail.gettext('all-day', 'calendar'),
+      buttonText: {
+        prev: '&nbsp;&#9668;&nbsp;',
+        next: '&nbsp;&#9658;&nbsp;',
+        today: settings['today'],
+        day: rcmail.gettext('day', 'calendar'),
+        week: rcmail.gettext('week', 'calendar'),
+        month: rcmail.gettext('month', 'calendar'),
+        table: rcmail.gettext('agenda', 'calendar')
+      },
+      listTexts: {
+        until: rcmail.gettext('until', 'calendar'),
+        past: rcmail.gettext('pastevents', 'calendar'),
+        today: rcmail.gettext('today', 'calendar'),
+        tomorrow: rcmail.gettext('tomorrow', 'calendar'),
+        thisWeek: rcmail.gettext('thisweek', 'calendar'),
+        nextWeek: rcmail.gettext('nextweek', 'calendar'),
+        thisMonth: rcmail.gettext('thismonth', 'calendar'),
+        nextMonth: rcmail.gettext('nextmonth', 'calendar'),
+        future: rcmail.gettext('futureevents', 'calendar'),
+        week: rcmail.gettext('weekofyear', 'calendar')
+      },
+      currentTimeIndicator: settings.time_indicator,
+      // event rendering
+      eventRender: function(event, element, view) {
+        if (view.name != 'list' && view.name != 'table') {
+          var prefix = event.sensitivity && event.sensitivity != 'public' ? String(sensitivitylabels[event.sensitivity]).toUpperCase()+': ' : '';
+          element.attr('title', prefix + event.title);
+        }
+        if (view.name != 'month') {
+          if (event.location) {
+            element.find('div.fc-event-title').after('<div class="fc-event-location">@&nbsp;' + Q(event.location) + '</div>');
+          }
+          if (event.sensitivity && event.sensitivity != 'public')
+            element.find('div.fc-event-time').append('<i class="fc-icon-sensitive"></i>');
+          if (event.recurrence)
+            element.find('div.fc-event-time').append('<i class="fc-icon-recurring"></i>');
+          if (event.alarms || (event.valarms && event.valarms.length))
+            element.find('div.fc-event-time').append('<i class="fc-icon-alarms"></i>');
+        }
+        if (event.status) {
+          element.addClass('cal-event-status-' + String(event.status).toLowerCase());
+        }
+
+        element.attr('aria-label', event.title + ', ' + me.event_date_text(event, true));
+      },
+      // render element indicating more (invisible) events
+      overflowRender: function(data, element) {
+        element.html(rcmail.gettext('andnmore', 'calendar').replace('$nr', data.count))
+          .click(function(e){ me.fisheye_view(data.date); });
+      },
+      // callback when a specific event is clicked
+      eventClick: function(event, ev, view) {
+        if (!event.temp && String(event.className).indexOf('fc-type-freebusy') < 0)
+          event_show_dialog(event, ev);
+      }
+    };
+
+    /***  imports  ***/
+    var Q = this.quote_html;
+    var text2html = this.text2html;
+    var event_date_text = this.event_date_text;
+    var parse_datetime = this.parse_datetime;
+    var date2unixtime = this.date2unixtime;
+    var fromunixtime = this.fromunixtime;
+    var parseISO8601 = this.parseISO8601;
+    var date2servertime = this.date2ISO8601;
+    var render_message_links = this.render_message_links;
+
+
+    /***  private methods  ***/
+
+    // same as str.split(delimiter) but it ignores delimiters within quoted strings
+    var explode_quoted_string = function(str, delimiter)
+    {
+      var result = [],
+        strlen = str.length,
+        q, p, i, chr, last;
+
+      for (q = p = i = 0; i < strlen; i++) {
+        chr = str.charAt(i);
+        if (chr == '"' && last != '\\') {
+          q = !q;
+        }
+        else if (!q && chr == delimiter) {
+          result.push(str.substring(p, i));
+          p = i + 1;
+        }
+        last = chr;
+      }
+
+      result.push(str.substr(p));
+      return result;
+    };
+
+    // Change the first charcter to uppercase
+    var ucfirst = function(str)
+    {
+        return str.charAt(0).toUpperCase() + str.substr(1);
+    };
+
+    // clone the given date object and optionally adjust time
+    var clone_date = function(date, adjust)
+    {
+      var d = new Date(date.getTime());
+      
+      // set time to 00:00
+      if (adjust == 1) {
+        d.setHours(0);
+        d.setMinutes(0);
+      }
+      // set time to 23:59
+      else if (adjust == 2) {
+        d.setHours(23);
+        d.setMinutes(59);
+      }
+      
+      return d;
+    };
+
+    // fix date if jumped over a DST change
+    var fix_date = function(date)
+    {
+      if (date.getHours() == 23)
+        date.setTime(date.getTime() + HOUR_MS);
+      else if (date.getHours() > 0)
+        date.setHours(0);
+    };
+
+    var date2timestring = function(date, dateonly)
+    {
+      return date2servertime(date).replace(/[^0-9]/g, '').substr(0, (dateonly ? 8 : 14));
+    }
+
+    var format_datetime = function(date, mode, voice)
+    {
+      return me.format_datetime(date, mode, voice);
+    }
+
+    var render_link = function(url)
+    {
+      var islink = false, href = url;
+      if (url.match(/^[fhtpsmailo]+?:\/\//i)) {
+        islink = true;
+      }
+      else if (url.match(/^[a-z0-9.-:]+(\/|$)/i)) {
+        islink = true;
+        href = 'http://' + url;
+      }
+      return islink ? '<a href="' + Q(href) + '" target="_blank">' + Q(url) + '</a>' : Q(url);
+    }
+
+    // determine whether the given date is on a weekend
+    var is_weekend = function(date)
+    {
+      return date.getDay() == 0 || date.getDay() == 6;
+    };
+
+    var is_workinghour = function(date)
+    {
+      if (settings['work_start'] > settings['work_end'])
+        return date.getHours() >= settings['work_start'] || date.getHours() < settings['work_end'];
+      else
+        return date.getHours() >= settings['work_start'] && date.getHours() < settings['work_end'];
+    };
+
+    var load_attachment = function(event, att)
+    {
+      var query = { _id: att.id, _event: event.recurrence_id || event.id, _cal:event.calendar, _frame: 1 };
+      if (event.rev)
+        query._rev = event.rev;
+
+      if (event.calendar == "--invitation--itip")
+        $.extend(query, {_uid: event._uid, _part: event._part, _mbox: event._mbox});
+
+      // open attachment in frame if it's of a supported mimetype
+      if (id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
+        if (rcmail.open_window(rcmail.url('get-attachment', query), true, true)) {
+          return;
+        }
+      }
+
+      query._frame = null;
+      query._download = 1;
+      rcmail.goto_url('get-attachment', query, false);
+    };
+
+    // build event attachments list
+    var event_show_attachments = function(list, container, event, edit)
+    {
+      var i, id, len, img, content, li, elem,
+        ul = document.createElement('UL');
+      ul.className = 'attachmentslist';
+
+      for (i=0, len=list.length; i<len; i++) {
+        elem = list[i];
+        li = document.createElement('LI');
+        li.className = elem.classname;
+
+        if (edit) {
+          rcmail.env.attachments[elem.id] = elem;
+          // delete icon
+          content = $('<a href="#delete" />')
+            .attr('title', rcmail.gettext('delete'))
+            .attr('aria-label', rcmail.gettext('delete') + ' ' + Q(elem.name))
+            .addClass('delete')
+            .click({id: elem.id}, function(e) { remove_attachment(this, e.data.id); return false; });
+
+          if (!rcmail.env.deleteicon)
+            content.html(rcmail.gettext('delete'));
+          else {
+            img = document.createElement('IMG');
+            img.src = rcmail.env.deleteicon;
+            img.alt = rcmail.gettext('delete');
+            content.append(img);
+          }
+
+          content.appendTo(li);
+        }
+
+        // name/link
+        content = $('<a href="#load" />')
+          .html(Q(elem.name))
+          .addClass('file')
+          .click({event: event, att: elem}, function(e) {
+            load_attachment(e.data.event, e.data.att);
+            return false;
+          })
+          .appendTo(li);
+
+        ul.appendChild(li);
+      }
+
+      if (edit && rcmail.gui_objects.attachmentlist) {
+        ul.id = rcmail.gui_objects.attachmentlist.id;
+        rcmail.gui_objects.attachmentlist = ul;
+      }
+
+      container.empty().append(ul);
+    };
+
+    var remove_attachment = function(elem, id)
+    {
+      $(elem.parentNode).hide();
+      rcmail.env.deleted_attachments.push(id);
+      delete rcmail.env.attachments[id];
+    };
+
+    // event details dialog (show only)
+    var event_show_dialog = function(event, ev, temp)
+    {
+      var $dialog = $("#eventshow");
+      var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false, rights:'lrs' };
+
+      if (!temp)
+        me.selected_event = event;
+
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+
+      // remove status-* and sensitivity-* classes
+      $dialog.removeClass(function(i, oldclass) {
+          var oldies = String(oldclass).split(' ');
+          return $.grep(oldies, function(cls) { return cls.indexOf('status-') === 0 || cls.indexOf('sensitivity-') === 0 }).join(' ');
+      });
+
+      // convert start/end dates if not done yet by fullcalendar
+      if (typeof event.start == 'string')
+        event.start = parseISO8601(event.start);
+      if (typeof event.end == 'string')
+        event.end = parseISO8601(event.end);
+
+      // allow other plugins to do actions when event form is opened
+      rcmail.triggerEvent('calendar-event-init', {o: event});
+
+      $dialog.find('div.event-section, div.event-line').hide();
+      $('#event-title').html(Q(event.title)).show();
+      
+      if (event.location)
+        $('#event-location').html('@ ' + text2html(event.location)).show();
+      if (event.description)
+        $('#event-description').show().children('.event-text').html(text2html(event.description, 300, 6));
+      if (event.vurl)
+        $('#event-url').show().children('.event-text').html(render_link(event.vurl));
+      
+      // render from-to in a nice human-readable way
+      // -> now shown in dialog title
+      // $('#event-date').html(Q(me.event_date_text(event))).show();
+      
+      if (event.recurrence && event.recurrence_text)
+        $('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
+      
+      if (event.valarms && event.alarms_text)
+        $('#event-alarm').show().children('.event-text').html(Q(event.alarms_text).replace(',', ',<br>'));
+      
+      if (calendar.name)
+        $('#event-calendar').show().children('.event-text').html(Q(calendar.name)).attr('class', 'event-text cal-'+calendar.id).css('color', calendar.textColor || calendar.color || '');
+      if (event.categories)
+        $('#event-category').show().children('.event-text').html(Q(event.categories)).attr('class', 'event-text cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, ''));
+      if (event.free_busy)
+        $('#event-free-busy').show().children('.event-text').html(Q(rcmail.gettext(event.free_busy, 'calendar')));
+      if (event.priority > 0) {
+        var priolabels = [ '', rcmail.gettext('highest'), rcmail.gettext('high'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
+        $('#event-priority').show().children('.event-text').html(Q(event.priority+' '+priolabels[event.priority]));
+      }
+
+      if (event.status) {
+        var status_lc = String(event.status).toLowerCase();
+        $('#event-status').show().children('.event-text').text(rcmail.gettext('status-'+status_lc,'calendar'));
+        $('#event-status-badge > span').text(rcmail.gettext('status-'+status_lc,'calendar'));
+        $dialog.addClass('status-'+status_lc);
+      }
+      if (event.sensitivity && event.sensitivity != 'public') {
+        $('#event-sensitivity').show().children('.event-text').text(sensitivitylabels[event.sensitivity]);
+        $('#event-status-badge > span').text(sensitivitylabels[event.sensitivity]);
+        $dialog.addClass('sensitivity-'+event.sensitivity);
+      }
+      if (event.created || event.changed) {
+        var created = parseISO8601(event.created),
+          changed = parseISO8601(event.changed)
+        $('#event-created-changed .event-created').html(Q(created ? format_datetime(created) : rcmail.gettext('unknown','calendar')))
+        $('#event-created-changed .event-changed').html(Q(changed ? format_datetime(changed) : rcmail.gettext('unknown','calendar')))
+        $('#event-created-changed').show()
+      }
+
+      // create attachments list
+      if ($.isArray(event.attachments)) {
+        event_show_attachments(event.attachments, $('#event-attachments').children('.event-text'), event);
+        if (event.attachments.length > 0) {
+          $('#event-attachments').show();
+        }
+      }
+      else if (calendar.attachments) {
+        // fetch attachments, some drivers doesn't set 'attachments' prop of the event?
+      }
+
+      // build attachments list
+      $('#event-links').hide();
+      if ($.isArray(event.links) && event.links.length) {
+          render_message_links(event.links || [], $('#event-links').children('.event-text'), false, 'calendar');
+          $('#event-links').show();
+      }
+
+      // list event attendees
+      if (calendar.attendees && event.attendees) {
+        // sort resources to the end
+        event.attendees.sort(function(a,b) {
+          var j = a.cutype == 'RESOURCE' ? 1 : 0,
+              k = b.cutype == 'RESOURCE' ? 1 : 0;
+          return (j - k);
+        });
+
+        var data, mystatus = null, rsvp, line, morelink, html = '', overflow = '',
+          organizer = me.is_organizer(event);
+
+        for (var j=0; j < event.attendees.length; j++) {
+          data = event.attendees[j];
+          if (data.email) {
+            if (data.role != 'ORGANIZER' && settings.identity.emails.indexOf(';'+data.email) >= 0) {
+              mystatus = data.status.toLowerCase();
+              if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
+                rsvp = mystatus;
+            }
+          }
+
+          line = rcube_libcalendaring.attendee_html(data);
+
+          if (morelink)
+            overflow += ' ' + line;
+          else
+            html += ' ' + line;
+
+          // stop listing attendees
+          if (j == 7 && event.attendees.length >= 7) {
+            morelink = $('<a href="#more" class="morelink"></a>').html(rcmail.gettext('andnmore', 'calendar').replace('$nr', event.attendees.length - j - 1));
+          }
+        }
+
+        if (html && (event.attendees.length > 1 || !organizer)) {
+          $('#event-attendees').show()
+            .children('.event-text')
+            .html(html)
+            .find('a.mailtolink').click(event_attendee_click);
+
+          // display all attendees in a popup when clicking the "more" link
+          if (morelink) {
+            $('#event-attendees .event-text').append(morelink);
+            morelink.click(function(e){
+              rcmail.show_popup_dialog(
+                '<div id="all-event-attendees" class="event-attendees">' + html + overflow + '</div>',
+                rcmail.gettext('tabattendees','calendar'),
+                null,
+                { width:450, modal:false });
+              $('#all-event-attendees a.mailtolink').click(event_attendee_click);
+              return false;
+            })
+          }
+        }
+
+        if (mystatus && !rsvp) {
+          $('#event-partstat').show().children('.changersvp')
+            .removeClass('accepted tentative declined delegated needs-action')
+            .addClass(mystatus)
+            .children('.event-text')
+            .text(rcmail.gettext('status' + mystatus, 'libcalendaring'));
+        }
+
+        var show_rsvp = rsvp && !organizer && event.status != 'CANCELLED' && me.has_permission(calendar, 'v');
+        $('#event-rsvp')[(show_rsvp ? 'show' : 'hide')]();
+        $('#event-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+mystatus+']').prop('disabled', true);
+
+        if (show_rsvp && event.comment)
+          $('#event-rsvp-comment').show().children('.event-text').html(Q(event.comment));
+
+        $('#event-rsvp a.reply-comment-toggle').show();
+        $('#event-rsvp .itip-reply-comment textarea').hide().val('');
+
+        if (event.recurrence && event.id) {
+          var sel = event._savemode || (event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all'));
+          $('#event-rsvp .rsvp-buttons').addClass('recurring');
+        }
+        else {
+          $('#event-rsvp .rsvp-buttons').removeClass('recurring');
+        }
+      }
+
+      var buttons = [];
+      if (!temp && calendar.editable && event.editable !== false) {
+        buttons.push({
+          text: rcmail.gettext('edit', 'calendar'),
+          click: function() {
+            event_edit_dialog('edit', event);
+          }
+        });
+      }
+      if (!temp && me.has_permission(calendar, 'td') && event.editable !== false) {
+        buttons.push({
+          text: rcmail.gettext('delete', 'calendar'),
+          'class': 'delete',
+          click: function() {
+            me.delete_event(event);
+            $dialog.dialog('close');
+          }
+        });
+      }
+
+      if (!buttons.length) {
+        buttons.push({
+          text: rcmail.gettext('close', 'calendar'),
+          click: function(){
+            $dialog.dialog('close');
+          }
+        });
+      }
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: false,
+        resizable: !bw.ie6,
+        closeOnEscape: (!bw.ie6 && !bw.ie7),  // disable for performance reasons
+        title: me.event_date_text(event),
+        open: function() {
+          $dialog.attr('aria-hidden', 'false');
+          setTimeout(function(){
+            $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
+          }, 5);
+        },
+        close: function() {
+          $dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
+        },
+        dragStart: function() {
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
+        },
+        resizeStart: function() {
+          rcmail.command('menu-close','eventoptionsmenu');
+          $('.libcal-rsvp-replymode').hide();
+        },
+        buttons: buttons,
+        minWidth: 320,
+        width: 420
+      }).show();
+
+      // remember opener element (to be focused on close)
+      $dialog.data('opener', ev && rcube_event.is_keyboard(ev) ? ev.target : null);
+
+      // set voice title on dialog widget
+      $dialog.dialog('widget').removeAttr('aria-labelledby')
+        .attr('aria-label', me.event_date_text(event, true) + ', ', event.title);
+
+      // set dialog size according to content
+      me.dialog_resize($dialog.get(0), $dialog.height(), 420);
+
+      // add link for "more options" drop-down
+      if (!temp && !event.temporary && event.calendar != '_resource') {
+        $('<a>')
+          .attr('href', '#')
+          .html(rcmail.gettext('eventoptions','calendar'))
+          .addClass('dropdown-link')
+          .click(function(e) {
+            return rcmail.command('menu-open','eventoptionsmenu', this, e)
+          })
+          .appendTo($dialog.parent().find('.ui-dialog-buttonset'));
+      }
+
+      rcmail.enable_command('event-history', calendar.history)
+
+      rcmail.triggerEvent('calendar-event-dialog', {dialog: $dialog});
+    };
+
+    // event handler for clicks on an attendee link
+    var event_attendee_click = function(e)
+    {
+      var cutype = $(this).attr('data-cutype'),
+        mailto = this.href.substr(7);
+      if (rcmail.env.calendar_resources && cutype == 'RESOURCE') {
+        event_resources_dialog(mailto);
+      }
+      else {
+        rcmail.command('compose', mailto, e ? e.target : null, e);
+      }
+      return false;
+    };
+
+    // bring up the event dialog (jquery-ui popup)
+    var event_edit_dialog = function(action, event)
+    {
+      // copy opener element from show dialog
+      var op_elem = $("#eventshow:ui-dialog").data('opener');
+
+      // close show dialog first
+      $("#eventshow:ui-dialog").data('opener', null).dialog('close');
+
+      var $dialog = $('<div>');
+      var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:true, rights: action=='new' ? 'lrwitd' : 'lrs' };
+      me.selected_event = $.extend($.extend({}, event_defaults), event);  // clone event object (with defaults)
+      event = me.selected_event; // change reference to clone
+      freebusy_ui.needsupdate = false;
+
+      // reset dialog first
+      $('#eventtabs').get(0).reset();
+      $('#event-panel-recurrence input, #event-panel-recurrence select, #event-panel-attachments input').prop('disabled', false);
+      $('#event-panel-recurrence, #event-panel-attachments').removeClass('disabled');
+
+      // allow other plugins to do actions when event form is opened
+      rcmail.triggerEvent('calendar-event-init', {o: event});
+
+      // event details
+      var title = $('#edit-title').val(event.title || '');
+      var location = $('#edit-location').val(event.location || '');
+      var description = $('#edit-description').text(event.description || '');
+      var vurl = $('#edit-url').val(event.vurl || '');
+      var categories = $('#edit-categories').val(event.categories);
+      var calendars = $('#edit-calendar').val(event.calendar);
+      var eventstatus = $('#edit-event-status').val(event.status);
+      var freebusy = $('#edit-free-busy').val(event.free_busy);
+      var priority = $('#edit-priority').val(event.priority);
+      var sensitivity = $('#edit-sensitivity').val(event.sensitivity);
+      
+      var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
+      var startdate = $('#edit-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
+      var starttime = $('#edit-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
+      var enddate = $('#edit-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
+      var endtime = $('#edit-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
+      var allday = $('#edit-allday').get(0);
+      var notify = $('#edit-attendees-donotify').get(0);
+      var invite = $('#edit-attendees-invite').get(0);
+      var comment = $('#edit-attendees-comment');
+
+      // make sure any calendar is selected
+      if (!calendars.val())
+        calendars.val($('option:first', calendars).attr('value'));
+
+      invite.checked = settings.itip_notify & 1 > 0;
+      notify.checked = me.has_attendees(event) && invite.checked;
+
+      if (event.allDay) {
+        starttime.val("12:00").hide();
+        endtime.val("13:00").hide();
+        allday.checked = true;
+      }
+      else {
+        allday.checked = false;
+      }
+
+      // set calendar selection according to permissions
+      calendars.find('option').each(function(i, opt) {
+        var cal = me.calendars[opt.value] || {};
+        $(opt).prop('disabled', !(cal.editable || (action == 'new' && me.has_permission(cal, 'i'))))
+      });
+
+      // set alarm(s)
+      me.set_alarms_edit('#edit-alarms', action != 'new' && event.valarms && calendar.alarms ? event.valarms : []);
+
+      // enable/disable alarm property according to backend support
+      $('#edit-alarms')[(calendar.alarms ? 'show' : 'hide')]();
+
+      // check categories drop-down: add value if not exists
+      if (event.categories && !categories.find("option[value='"+event.categories+"']").length) {
+        $('<option>').attr('value', event.categories).text(event.categories).appendTo(categories).prop('selected', true);
+      }
+
+      if ($.isArray(event.links) && event.links.length) {
+          render_message_links(event.links, $('#edit-event-links .event-text'), true, 'calendar');
+          $('#edit-event-links').show();
+      }
+      else {
+          $('#edit-event-links').hide();
+      }
+
+      // show warning if editing a recurring event
+      if (event.id && event.recurrence) {
+        var sel = event._savemode || (event.thisandfuture ? 'future' : (event.isexception ? 'current' : 'all'));
+        $('#edit-recurring-warning').show();
+        $('input.edit-recurring-savemode[value="'+sel+'"]').prop('checked', true).change();
+      }
+      else
+        $('#edit-recurring-warning').hide();
+
+      // init attendees tab
+      var organizer = !event.attendees || me.is_organizer(event),
+        allow_invitations = organizer || (calendar.owner && calendar.owner == 'anonymous') || settings.invite_shared;
+      event_attendees = [];
+      attendees_list = $('#edit-attendees-table > tbody').html('');
+      resources_list = $('#edit-resources-table > tbody').html('');
+      $('#edit-attendees-notify')[(action != 'new' && allow_invitations && me.has_attendees(event) && (settings.itip_notify & 2) ? 'show' : 'hide')]();
+      $('#edit-localchanges-warning')[(action != 'new' && me.has_attendees(event) && !(allow_invitations || (calendar.owner && me.is_organizer(event, calendar.owner))) ? 'show' : 'hide')]();
+
+      var load_attendees_tab = function()
+      {
+        var j, data, organizer_attendee, reply_selected = 0;
+        if (event.attendees) {
+          for (j=0; j < event.attendees.length; j++) {
+            data = event.attendees[j];
+            // reset attendee status
+            if (event._savemode == 'new' && data.role != 'ORGANIZER') {
+              data.status = 'NEEDS-ACTION';
+              delete data.noreply;
+            }
+            add_attendee(data, !allow_invitations);
+            if (allow_invitations && data.role != 'ORGANIZER' && !data.noreply)
+              reply_selected++;
+
+            if (data.role == 'ORGANIZER')
+              organizer_attendee = data;
+          }
+        }
+
+        // make sure comment box is visible if at least one attendee has reply enabled
+        // or global "send invitations" checkbox is checked
+        $('#eventedit .attendees-commentbox')[(reply_selected || invite.checked ? 'show' : 'hide')]();
+
+        // select the correct organizer identity
+        var identity_id = 0;
+        $.each(settings.identities, function(i,v){
+          if (organizer && typeof organizer == 'object' && v == organizer.email) {
+            identity_id = i;
+            return false;
+          }
+        });
+
+        // In case the user is not the (shared) event organizer we'll add the organizer to the selection list
+        if (!identity_id && !organizer && organizer_attendee) {
+          var organizer_name = organizer_attendee.email;
+          if (organizer_attendee.name)
+            organizer_name = '"' + organizer_attendee.name + '" <' + organizer_name + '>';
+          $('#edit-identities-list').append($('<option value="0">').text(organizer_name));
+        }
+
+        $('#edit-identities-list').val(identity_id);
+        $('#edit-attendees-form')[(allow_invitations?'show':'hide')]();
+        $('#edit-attendee-schedule')[(calendar.freebusy?'show':'hide')]();
+      };
+
+      // attachments
+      var load_attachments_tab = function()
+      {
+        rcmail.enable_command('remove-attachment', calendar.editable && !event.recurrence_id);
+        rcmail.env.deleted_attachments = [];
+        // we're sharing some code for uploads handling with app.js
+        rcmail.env.attachments = [];
+        rcmail.env.compose_id = event.id; // for rcmail.async_upload_form()
+
+        if ($.isArray(event.attachments)) {
+          event_show_attachments(event.attachments, $('#edit-attachments'), event, true);
+        }
+        else {
+          $('#edit-attachments > ul').empty();
+          // fetch attachments, some drivers doesn't set 'attachments' array for event?
+        }
+      };
+      
+      // init dialog buttons
+      var buttons = [];
+      
+      // save action
+      buttons.push({
+        text: rcmail.gettext('save', 'calendar'),
+        'class': 'mainaction',
+        click: function() {
+        var start = parse_datetime(allday.checked ? '12:00' : starttime.val(), startdate.val());
+        var end   = parse_datetime(allday.checked ? '13:00' : endtime.val(), enddate.val());
+        
+        // basic input validatetion
+        if (start.getTime() > end.getTime()) {
+          alert(rcmail.gettext('invalideventdates', 'calendar'));
+          return false;
+        }
+        
+        // post data to server
+        var data = {
+          calendar: event.calendar,
+          start: date2servertime(start),
+          end: date2servertime(end),
+          allday: allday.checked?1:0,
+          title: title.val(),
+          description: description.val(),
+          location: location.val(),
+          categories: categories.val(),
+          vurl: vurl.val(),
+          free_busy: freebusy.val(),
+          priority: priority.val(),
+          sensitivity: sensitivity.val(),
+          status: eventstatus.val(),
+          recurrence: me.serialize_recurrence(endtime.val()),
+          valarms: me.serialize_alarms('#edit-alarms'),
+          attendees: event_attendees,
+          links: me.selected_event.links,
+          deleted_attachments: rcmail.env.deleted_attachments,
+          attachments: []
+        };
+
+        // uploaded attachments list
+        for (var i in rcmail.env.attachments)
+          if (i.match(/^rcmfile(.+)/))
+            data.attachments.push(RegExp.$1);
+
+        // read attendee roles
+        $('select.edit-attendee-role').each(function(i, elem){
+          if (data.attendees[i])
+            data.attendees[i].role = $(elem).val();
+        });
+
+        if (organizer)
+          data._identity = $('#edit-identities-list option:selected').val();
+
+        // per-attendee notification suppression
+        var need_invitation = false;
+        if (allow_invitations) {
+          $.each(data.attendees, function (i, v) {
+            if (v.role != 'ORGANIZER') {
+              if ($('input.edit-attendee-reply[value="' + v.email + '"]').prop('checked') || v.cutype == 'RESOURCE') {
+                need_invitation = true;
+                delete data.attendees[i]['noreply'];
+              }
+              else if (settings.itip_notify > 0) {
+                data.attendees[i].noreply = 1;
+              }
+            }
+          });
+        }
+
+        // tell server to send notifications
+        if ((data.attendees.length || (event.id && event.attendees.length)) && allow_invitations && (notify.checked || invite.checked || need_invitation)) {
+          data._notify = settings.itip_notify;
+          data._comment = comment.val();
+        }
+
+        data.calendar = calendars.val();
+
+        if (event.id) {
+          data.id = event.id;
+          if (event.recurrence)
+            data._savemode = $('input.edit-recurring-savemode:checked').val();
+          if (data.calendar && data.calendar != event.calendar)
+            data._fromcalendar = event.calendar;
+        }
+
+        update_event(action, data);
+        $dialog.dialog("close");
+      }  // end click:
+      });
+
+      if (event.id) {
+        buttons.push({
+          text: rcmail.gettext('delete', 'calendar'),
+          'class': 'delete',
+          click: function() {
+            me.delete_event(event);
+            $dialog.dialog('close');
+          }
+        });
+      }
+
+      buttons.push({
+        text: rcmail.gettext('cancel', 'calendar'),
+        click: function() {
+          $dialog.dialog("close");
+        }
+      });
+
+      // show/hide tabs according to calendar's feature support
+      $('#edit-tab-attendees')[(calendar.attendees?'show':'hide')]();
+      $('#edit-tab-resources')[(rcmail.env.calendar_resources?'show':'hide')]();
+      $('#edit-tab-attachments')[(calendar.attachments?'show':'hide')]();
+
+      // activate the first tab
+      $('#eventtabs').tabs('option', 'active', 0);
+
+      // hack: set task to 'calendar' to make all dialog actions work correctly
+      var comm_path_before = rcmail.env.comm_path;
+      rcmail.env.comm_path = comm_path_before.replace(/_task=[a-z]+/, '_task=calendar');
+
+      var editform = $("#eventedit");
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: true,
+        resizable: (!bw.ie6 && !bw.ie7),  // disable for performance reasons
+        closeOnEscape: false,
+        title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
+        open: function() {
+          editform.attr('aria-hidden', 'false');
+        },
+        close: function() {
+          editform.hide().attr('aria-hidden', 'true').appendTo(document.body);
+          $dialog.dialog("destroy").remove();
+          rcmail.ksearch_blur();
+          freebusy_data = {};
+          rcmail.env.comm_path = comm_path_before;  // restore comm_path
+          if (op_elem)
+            $(op_elem).focus();
+        },
+        buttons: buttons,
+        minWidth: 500,
+        width: 600
+      }).append(editform.show());  // adding form content AFTERWARDS massively speeds up opening on IE6
+
+      // set dialog size according to form content
+      me.dialog_resize($dialog.get(0), editform.height() + (bw.ie ? 20 : 0), 550);
+
+      title.select();
+
+      // init other tabs asynchronously
+      window.setTimeout(function(){ me.set_recurrence_edit(event); }, exec_deferred);
+      if (calendar.attendees)
+        window.setTimeout(load_attendees_tab, exec_deferred);
+      if (calendar.attachments)
+        window.setTimeout(load_attachments_tab, exec_deferred);
+
+      rcmail.triggerEvent('calendar-event-dialog', {dialog: $dialog});
+    };
+
+    // show event changelog in a dialog
+    var event_history_dialog = function(event)
+    {
+      if (!event.id || !window.libkolab_audittrail)
+        return false
+
+      // render dialog
+      var $dialog = libkolab_audittrail.object_history_dialog({
+        module: 'calendar',
+        container: '#eventhistory',
+        title: rcmail.gettext('objectchangelog','calendar') + ' - ' + event.title + ', ' + me.event_date_text(event),
+
+        // callback function for list actions
+        listfunc: function(action, rev) {
+          me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
+          rcmail.http_post('event', { action:action, e:{ id:event.id, calendar:event.calendar, rev: rev } }, me.loading_lock);
+        },
+
+        // callback function for comparing two object revisions
+        comparefunc: function(rev1, rev2) {
+          me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
+          rcmail.http_post('event', { action:'diff', e:{ id:event.id, calendar:event.calendar, rev1: rev1, rev2: rev2 } }, me.loading_lock);
+        }
+      });
+
+      $dialog.data('event', event);
+
+      // fetch changelog data
+      me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
+      rcmail.http_post('event', { action:'changelog', e:{ id:event.id, calendar:event.calendar } }, me.loading_lock);
+    };
+
+    // callback from server with changelog data
+    var render_event_changelog = function(data)
+    {
+      var $dialog = $('#eventhistory'),
+        event = $dialog.data('event');
+
+      if (data === false || !data.length || !event) {
+        // display 'unavailable' message
+        $('<div class="notfound-message event-dialog-message warning">' + rcmail.gettext('objectchangelognotavailable','calendar') + '</div>')
+          .insertBefore($dialog.find('.changelog-table').hide());
+        return;
+      }
+
+      data.module = 'calendar';
+      libkolab_audittrail.render_changelog(data, event, me.calendars[event.calendar]);
+
+      // set dialog size according to content
+      me.dialog_resize($dialog.get(0), $dialog.height(), 600);
+    };
+
+    // callback from server with event diff data
+    var event_show_diff = function(data)
+    {
+      var event = me.selected_event,
+        $dialog = $("#eventdiff");
+
+      $dialog.find('div.event-section, div.event-line, h1.event-title-new').hide().data('set', false).find('.index').html('');
+      $dialog.find('div.event-section.clone, div.event-line.clone').remove();
+
+      // always show event title and date
+      $('.event-title', $dialog).text(event.title).removeClass('event-text-old').show();
+      $('.event-date', $dialog).text(me.event_date_text(event)).show();
+
+      // show each property change
+      $.each(data.changes, function(i,change) {
+        var prop = change.property, r2, html = false,
+          row = $('div.event-' + prop, $dialog).first();
+
+          // special case: title
+          if (prop == 'title') {
+            $('.event-title', $dialog).addClass('event-text-old').text(change['old'] || '--');
+            $('.event-title-new', $dialog).text(change['new'] || '--').show();
+          }
+
+          // no display container for this property
+          if (!row.length) {
+            return true;
+          }
+
+          // clone row if already exists
+          if (row.data('set')) {
+            r2 = row.clone().addClass('clone').insertAfter(row);
+            row = r2;
+          }
+
+          // format dates
+          if (['start','end','changed'].indexOf(prop) >= 0) {
+            if (change['old']) change.old_ = me.format_datetime(parseISO8601(change['old']));
+            if (change['new']) change.new_ = me.format_datetime(parseISO8601(change['new']));
+          }
+          // render description text
+          else if (prop == 'description') {
+            // TODO: show real text diff
+            if (!change.diff_ && change['old']) change.old_ = text2html(change['old']);
+            if (!change.diff_ && change['new']) change.new_ = text2html(change['new']);
+            html = true;
+          }
+          // format attendees struct
+          else if (prop == 'attendees') {
+            if (change['old']) change.old_ = rcube_libcalendaring.attendee_html(change['old']);
+            if (change['new']) change.new_ = rcube_libcalendaring.attendee_html($.extend({}, change['old'] || {}, change['new']));
+            html = true;
+          }
+          // localize priority values
+          else if (prop == 'priority') {
+            var priolabels = [ '', rcmail.gettext('highest'), rcmail.gettext('high'), '', '', rcmail.gettext('normal'), '', '', rcmail.gettext('low'), rcmail.gettext('lowest') ];
+            if (change['old']) change.old_ = change['old'] + ' ' + (priolabels[change['old']] || '');
+            if (change['new']) change.new_ = change['new'] + ' ' + (priolabels[change['new']] || '');
+          }
+          // localize status
+          else if (prop == 'status') {
+            var status_lc = String(event.status).toLowerCase();
+            if (change['old']) change.old_ = rcmail.gettext(String(change['old']).toLowerCase(), 'calendar');
+            if (change['new']) change.new_ = rcmail.gettext(String(change['new']).toLowerCase(), 'calendar');
+          }
+
+          // format attachments struct
+          if (prop == 'attachments') {
+            if (change['old']) event_show_attachments([change['old']], row.children('.event-text-old'), event, false);
+            else            row.children('.event-text-old').text('--');
+            if (change['new']) event_show_attachments([$.extend({}, change['old'] || {}, change['new'])], row.children('.event-text-new'), event, false);
+            else            row.children('.event-text-new').text('--');
+            // remove click handler as we're currentyl not able to display the according attachment contents
+            $('.attachmentslist li a', row).unbind('click').removeAttr('href');
+          }
+          else if (change.diff_) {
+            row.children('.event-text-diff').html(change.diff_);
+            row.children('.event-text-old, .event-text-new').hide();
+          }
+          else {
+            if (!html) {
+              // escape HTML characters
+              change.old_ = Q(change.old_ || change['old'] || '--')
+              change.new_ = Q(change.new_ || change['new'] || '--')
+            }
+            row.children('.event-text-old').html(change.old_ || change['old'] || '--');
+            row.children('.event-text-new').html(change.new_ || change['new'] || '--');
+          }
+
+          // display index number
+          if (typeof change.index != 'undefined') {
+            row.find('.index').html('(' + change.index + ')');
+          }
+
+          row.show().data('set', true);
+
+          // hide event-date line
+          if (prop == 'start' || prop == 'end')
+            $('.event-date', $dialog).hide();
+      });
+
+      var buttons = {};
+      buttons[rcmail.gettext('close', 'calendar')] = function() {
+        $dialog.dialog('close');
+      };
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: false,
+        resizable: true,
+        closeOnEscape: true,
+        title: rcmail.gettext('objectdiff','calendar').replace('$rev1', data.rev1).replace('$rev2', data.rev2) + ' - ' + event.title,
+        open: function() {
+          $dialog.attr('aria-hidden', 'false');
+          setTimeout(function(){
+            $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
+          }, 5);
+        },
+        close: function() {
+          $dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
+        },
+        buttons: buttons,
+        minWidth: 320,
+        width: 450
+      }).show();
+
+      // set dialog size according to content
+      me.dialog_resize($dialog.get(0), $dialog.height(), 400);
+    };
+
+    // close the event history dialog
+    var close_history_dialog = function()
+    {
+      $('#eventhistory, #eventdiff').each(function(i, elem) {
+        var $dialog = $(elem);
+        if ($dialog.is(':ui-dialog'))
+          $dialog.dialog('close');
+      });
+    }
+
+    // exports
+    this.event_show_diff = event_show_diff;
+    this.event_show_dialog = event_show_dialog;
+    this.event_history_dialog = event_history_dialog;
+    this.render_event_changelog = render_event_changelog;
+    this.close_history_dialog = close_history_dialog;
+
+    // open a dialog to display detailed free-busy information and to find free slots
+    var event_freebusy_dialog = function()
+    {
+      var $dialog = $('#eventfreebusy'),
+        event = me.selected_event;
+      
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+      
+      if (!event_attendees.length)
+        return false;
+      
+      // set form elements
+      var allday = $('#edit-allday').get(0);
+      var duration = Math.round((event.end.getTime() - event.start.getTime()) / 1000);
+      freebusy_ui.startdate = $('#schedule-startdate').val($.fullCalendar.formatDate(event.start, settings['date_format'])).data('duration', duration);
+      freebusy_ui.starttime = $('#schedule-starttime').val($.fullCalendar.formatDate(event.start, settings['time_format'])).show();
+      freebusy_ui.enddate = $('#schedule-enddate').val($.fullCalendar.formatDate(event.end, settings['date_format']));
+      freebusy_ui.endtime = $('#schedule-endtime').val($.fullCalendar.formatDate(event.end, settings['time_format'])).show();
+      
+      if (allday.checked) {
+        freebusy_ui.starttime.val("12:00").hide();
+        freebusy_ui.endtime.val("13:00").hide();
+        event.allDay = true;
+      }
+      
+      // read attendee roles from drop-downs
+      $('select.edit-attendee-role').each(function(i, elem){
+        if (event_attendees[i])
+          event_attendees[i].role = $(elem).val();
+      });
+      
+      // render time slots
+      var now = new Date(), fb_start = new Date(), fb_end = new Date();
+      fb_start.setTime(event.start);
+      fb_start.setHours(0); fb_start.setMinutes(0); fb_start.setSeconds(0); fb_start.setMilliseconds(0);
+      fb_end.setTime(fb_start.getTime() + DAY_MS);
+      
+      freebusy_data = { required:{}, all:{} };
+      freebusy_ui.loading = 1;  // prevent render_freebusy_grid() to load data yet
+      freebusy_ui.numdays = Math.max(allday.checked ? 14 : 1, Math.ceil(duration * 2 / 86400));
+      freebusy_ui.interval = allday.checked ? 1440 : (60 / (settings.timeslots || 1));
+      freebusy_ui.start = fb_start;
+      freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays);
+      render_freebusy_grid(0);
+      
+      // render list of attendees
+      freebusy_ui.attendees = {};
+      var domid, dispname, data, role_html, list_html = '';
+      for (var i=0; i < event_attendees.length; i++) {
+        data = event_attendees[i];
+        dispname = Q(data.name || data.email);
+        domid = String(data.email).replace(rcmail.identifier_expr, '');
+        role_html = '<a class="attendee-role-toggle" id="rcmlia' + domid + '" title="' + Q(rcmail.gettext('togglerole', 'calendar')) + '">&nbsp;</a>';
+        list_html += '<div class="attendee ' + String(data.role).toLowerCase() + '" id="rcmli' + domid + '">' + role_html + dispname + '</div>';
+        
+        // clone attendees data for local modifications
+        freebusy_ui.attendees[i] = freebusy_ui.attendees[domid] = $.extend({}, data);
+      }
+      
+      // add total row
+      list_html += '<div class="attendee spacer">&nbsp;</div>';
+      list_html += '<div class="attendee total">' + rcmail.gettext('reqallattendees','calendar') + '</div>';
+      
+      $('#schedule-attendees-list').html(list_html)
+        .unbind('click.roleicons')
+        .bind('click.roleicons', function(e){
+          // toggle attendee status upon click on icon
+          if (e.target.id && e.target.id.match(/rcmlia(.+)/)) {
+            var attendee, domid = RegExp.$1,
+                roles = [ 'REQ-PARTICIPANT', 'OPT-PARTICIPANT', 'NON-PARTICIPANT', 'CHAIR' ];
+            if ((attendee = freebusy_ui.attendees[domid]) && attendee.role != 'ORGANIZER') {
+              var req = attendee.role != 'OPT-PARTICIPANT' && attendee.role != 'NON-PARTICIPANT';
+              var j = $.inArray(attendee.role, roles);
+              j = (j+1) % roles.length;
+              attendee.role = roles[j];
+              $(e.target).parent().attr('class', 'attendee '+String(attendee.role).toLowerCase());
+              
+              // update total display if required-status changed
+              if (req != (roles[j] != 'OPT-PARTICIPANT' && roles[j] != 'NON-PARTICIPANT')) {
+                compute_freebusy_totals();
+                update_freebusy_display(attendee.email);
+              }
+            }
+          }
+          
+          return false;
+        });
+      
+      // enable/disable buttons
+      $('#shedule-find-prev').button('option', 'disabled', (fb_start.getTime() < now.getTime()));
+      
+      // dialog buttons
+      var buttons = {};
+      
+      buttons[rcmail.gettext('select', 'calendar')] = function() {
+        $('#edit-startdate').val(freebusy_ui.startdate.val());
+        $('#edit-starttime').val(freebusy_ui.starttime.val());
+        $('#edit-enddate').val(freebusy_ui.enddate.val());
+        $('#edit-endtime').val(freebusy_ui.endtime.val());
+        
+        // write role changes back to main dialog
+        $('select.edit-attendee-role').each(function(i, elem){
+          if (event_attendees[i] && freebusy_ui.attendees[i]) {
+            event_attendees[i].role = freebusy_ui.attendees[i].role;
+            $(elem).val(event_attendees[i].role);
+          }
+        });
+        
+        if (freebusy_ui.needsupdate)
+          update_freebusy_status(me.selected_event);
+        freebusy_ui.needsupdate = false;
+        $dialog.dialog("close");
+      };
+      
+      buttons[rcmail.gettext('cancel', 'calendar')] = function() {
+        $dialog.dialog("close");
+      };
+      
+      $dialog.dialog({
+        modal: true,
+        resizable: true,
+        closeOnEscape: (!bw.ie6 && !bw.ie7),
+        title: rcmail.gettext('scheduletime', 'calendar'),
+        open: function() {
+          rcmail.ksearch_blur();
+          $dialog.attr('aria-hidden', 'false').find('#shedule-find-next, #shedule-find-prev').not(':disabled').first().focus();
+        },
+        close: function() {
+          if (bw.ie6)
+            $("#edit-attendees-table").css('visibility','visible');
+          $dialog.dialog("destroy").attr('aria-hidden', 'true').hide();
+          // TODO: focus opener button
+        },
+        resizeStop: function() {
+          render_freebusy_overlay();
+        },
+        buttons: buttons,
+        minWidth: 640,
+        width: 850
+      }).show();
+      
+      // hide edit dialog on IE6 because of drop-down elements
+      if (bw.ie6)
+        $("#edit-attendees-table").css('visibility','hidden');
+      
+      // adjust dialog size to fit grid without scrolling
+      var gridw = $('#schedule-freebusy-times').width();
+      var overflow = gridw - $('#attendees-freebusy-table td.times').width();
+      me.dialog_resize($dialog.get(0), $dialog.height() + (bw.ie ? 20 : 0), 800 + Math.max(0, overflow));
+      
+      // fetch data from server
+      freebusy_ui.loading = 0;
+      load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
+    };
+
+    // render an HTML table showing free-busy status for all the event attendees
+    var render_freebusy_grid = function(delta)
+    {
+      if (delta) {
+        freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
+        fix_date(freebusy_ui.start);
+        
+        // skip weekends if in workinhoursonly-mode
+        if (Math.abs(delta) == 1 && freebusy_ui.workinhoursonly) {
+          while (is_weekend(freebusy_ui.start))
+            freebusy_ui.start.setTime(freebusy_ui.start.getTime() + DAY_MS * delta);
+          fix_date(freebusy_ui.start);
+        }
+        
+        freebusy_ui.end = new Date(freebusy_ui.start.getTime() + DAY_MS * freebusy_ui.numdays);
+      }
+      
+      var dayslots = Math.floor(1440 / freebusy_ui.interval);
+      var date_format = 'ddd '+ (dayslots <= 2 ? settings.date_short : settings.date_format);
+      var lastdate, datestr, css,
+        curdate = new Date(),
+        allday = (freebusy_ui.interval == 1440),
+        interval = allday ? 1440 : (freebusy_ui.interval * (settings.timeslots || 1));
+        times_css = (allday ? 'allday ' : ''),
+        dates_row = '<tr class="dates">',
+        times_row = '<tr class="times">',
+        slots_row = '';
+
+      for (var s = 0, t = freebusy_ui.start.getTime(); t < freebusy_ui.end.getTime(); s++) {
+        curdate.setTime(t);
+        datestr = fc.fullCalendar('formatDate', curdate, date_format);
+        if (datestr != lastdate) {
+          if (lastdate && !allday) break;
+          dates_row += '<th colspan="' + dayslots + '" class="boxtitle date' + $.fullCalendar.formatDate(curdate, 'ddMMyyyy') + '">' + Q(datestr) + '</th>';
+          lastdate = datestr;
+        }
+        
+        // set css class according to working hours
+        css = is_weekend(curdate) || (freebusy_ui.interval <= 60 && !is_workinghour(curdate)) ? 'offhours' : 'workinghours';
+        times_row += '<td class="' + times_css + css + '" id="t-' + Math.floor(t/1000) + '">' + Q(allday ? rcmail.gettext('all-day','calendar') : $.fullCalendar.formatDate(curdate, settings['time_format'])) + '</td>';
+        slots_row += '<td class="' + css + '">&nbsp;</td>';
+        
+        t += interval * 60000;
+      }
+      dates_row += '</tr>';
+      times_row += '</tr>';
+      
+      // render list of attendees
+      var domid, data, list_html = '', times_html = '';
+      for (var i=0; i < event_attendees.length; i++) {
+        data = event_attendees[i];
+        domid = String(data.email).replace(rcmail.identifier_expr, '');
+        times_html += '<tr id="fbrow' + domid + '">' + slots_row + '</tr>';
+      }
+      
+      // add line for all/required attendees
+      times_html += '<tr class="spacer"><td colspan="' + (dayslots * freebusy_ui.numdays) + '"></td>';
+      times_html += '<tr id="fbrowall">' + slots_row + '</tr>';
+      
+      var table = $('#schedule-freebusy-times');
+      table.children('thead').html(dates_row + times_row);
+      table.children('tbody').html(times_html);
+      
+      // initialize event handlers on grid
+      if (!freebusy_ui.grid_events) {
+        freebusy_ui.grid_events = true;
+        table.children('thead').click(function(e){
+          // move event to the clicked date/time
+          if (e.target.id && e.target.id.match(/t-(\d+)/)) {
+            var newstart = new Date(RegExp.$1 * 1000);
+            // set time to 00:00
+            if (me.selected_event.allDay) {
+              newstart.setMinutes(0);
+              newstart.setHours(0);
+            }
+            update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000));
+            render_freebusy_overlay();
+          }
+        });
+      }
+      
+      // if we have loaded free-busy data, show it
+      if (!freebusy_ui.loading) {
+        if (freebusy_ui.start < freebusy_data.start || freebusy_ui.end > freebusy_data.end || freebusy_ui.interval != freebusy_data.interval) {
+          load_freebusy_data(freebusy_ui.start, freebusy_ui.interval);
+        }
+        else {
+          for (var email, i=0; i < event_attendees.length; i++) {
+            if ((email = event_attendees[i].email))
+              update_freebusy_display(email);
+          }
+        }
+      }
+      
+      // render current event date/time selection over grid table
+      // use timeout to let the dom attributes (width/height/offset) be set first
+      window.setTimeout(function(){ render_freebusy_overlay(); }, 10);
+    };
+    
+    // render overlay element over the grid to visiualize the current event date/time
+    var render_freebusy_overlay = function()
+    {
+      var overlay = $('#schedule-event-time');
+      if (me.selected_event.end.getTime() <= freebusy_ui.start.getTime() || me.selected_event.start.getTime() >= freebusy_ui.end.getTime()) {
+        overlay.hide();
+        if (overlay.data('isdraggable'))
+          overlay.draggable('disable');
+      }
+      else {
+        var i, n, table = $('#schedule-freebusy-times'),
+          width = 0,
+          pos = { top:table.children('thead').height(), left:0 },
+          eventstart = date2unixtime(clone_date(me.selected_event.start, me.selected_event.allDay?1:0)),
+          eventend = date2unixtime(clone_date(me.selected_event.end, me.selected_event.allDay?2:0)) - 60,
+          slotstart = date2unixtime(freebusy_ui.start),
+          slotsize = freebusy_ui.interval * 60,
+          slotnum = freebusy_ui.interval > 60 ? 1 : (60 / freebusy_ui.interval),
+          cells = table.children('thead').find('td'),
+          cell_width = cells.first().get(0).offsetWidth,
+          slotend;
+
+        // iterate through slots to determine position and size of the overlay
+        for (i=0; i < cells.length; i++) {
+          for (n=0; n < slotnum; n++) {
+            slotend = slotstart + slotsize - 1;
+            // event starts in this slot: compute left
+            if (eventstart >= slotstart && eventstart <= slotend) {
+              pos.left = Math.round(i * cell_width + (cell_width / slotnum) * n);
+            }
+            // event ends in this slot: compute width
+            if (eventend >= slotstart && eventend <= slotend) {
+              width = Math.round(i * cell_width + (cell_width / slotnum) * (n + 1)) - pos.left;
+            }
+            slotstart += slotsize;
+          }
+        }
+
+        if (!width)
+          width = table.width() - pos.left;
+
+        // overlay is visible
+        if (width > 0) {
+          overlay.css({ width: (width-4)+'px', height:(table.children('tbody').height() - 4)+'px', left:pos.left+'px', top:pos.top+'px' }).show();
+          
+          // configure draggable
+          if (!overlay.data('isdraggable')) {
+            overlay.draggable({
+              axis: 'x',
+              scroll: true,
+              stop: function(e, ui){
+                // convert pixels to time
+                var px = ui.position.left;
+                var range_p = $('#schedule-freebusy-times').width();
+                var range_t = freebusy_ui.end.getTime() - freebusy_ui.start.getTime();
+                var newstart = new Date(freebusy_ui.start.getTime() + px * (range_t / range_p));
+                newstart.setSeconds(0); newstart.setMilliseconds(0);
+                // snap to day boundaries
+                if (me.selected_event.allDay) {
+                  if (newstart.getHours() >= 12)  // snap to next day
+                    newstart.setTime(newstart.getTime() + DAY_MS);
+                  newstart.setMinutes(0);
+                  newstart.setHours(0);
+                }
+                else {
+                  // round to 5 minutes
+                  // @TODO: round to timeslots?
+                  var round = newstart.getMinutes() % 5;
+                  if (round > 2.5) newstart.setTime(newstart.getTime() + (5 - round) * 60000);
+                  else if (round > 0) newstart.setTime(newstart.getTime() - round * 60000);
+                }
+                // update event times and display
+                update_freebusy_dates(newstart, new Date(newstart.getTime() + freebusy_ui.startdate.data('duration') * 1000));
+                if (me.selected_event.allDay)
+                  render_freebusy_overlay();
+              }
+            }).data('isdraggable', true);
+          }
+          else
+            overlay.draggable('enable');
+        }
+        else
+          overlay.draggable('disable').hide();
+      }
+    };
+
+    // fetch free-busy information for each attendee from server
+    var load_freebusy_data = function(from, interval)
+    {
+      var start = new Date(from.getTime() - DAY_MS * 2);  // start 2 days before event
+      fix_date(start);
+      var end = new Date(start.getTime() + DAY_MS * Math.max(14, freebusy_ui.numdays + 7));   // load min. 14 days
+      freebusy_ui.numrequired = 0;
+      freebusy_data.all = [];
+      freebusy_data.required = [];
+
+      // load free-busy information for every attendee
+      var domid, email;
+      for (var i=0; i < event_attendees.length; i++) {
+        if ((email = event_attendees[i].email)) {
+          domid = String(email).replace(rcmail.identifier_expr, '');
+          $('#rcmli' + domid).addClass('loading');
+          freebusy_ui.loading++;
+          
+          $.ajax({
+            type: 'GET',
+            dataType: 'json',
+            url: rcmail.url('freebusy-times'),
+            data: { email:email, start:date2servertime(clone_date(start, 1)), end:date2servertime(clone_date(end, 2)), interval:interval, _remote:1 },
+            success: function(data) {
+              freebusy_ui.loading--;
+              
+              // find attendee
+              var i, attendee = null;
+              for (i=0; i < event_attendees.length; i++) {
+                if (freebusy_ui.attendees[i].email == data.email) {
+                  attendee = freebusy_ui.attendees[i];
+                  break;
+                }
+              }
+              
+              // copy data to member var
+              var ts, status,
+                req = attendee.role != 'OPT-PARTICIPANT',
+                start = parseISO8601(data.start);
+
+              freebusy_data.start = new Date(start);
+              freebusy_data.end = parseISO8601(data.end);
+              freebusy_data.interval = data.interval;
+              freebusy_data[data.email] = {};
+
+              for (i=0; i < data.slots.length; i++) {
+                ts = date2timestring(start, data.interval > 60);
+                status = data.slots.charAt(i);
+                freebusy_data[data.email][ts] = status
+                start = new Date(start.getTime() + data.interval * 60000);
+                
+                // set totals
+                if (!freebusy_data.required[ts])
+                  freebusy_data.required[ts] = [0,0,0,0];
+                if (req)
+                  freebusy_data.required[ts][status]++;
+                
+                if (!freebusy_data.all[ts])
+                  freebusy_data.all[ts] = [0,0,0,0];
+                freebusy_data.all[ts][status]++;
+              }
+
+              // hide loading indicator
+              var domid = String(data.email).replace(rcmail.identifier_expr, '');
+              $('#rcmli' + domid).removeClass('loading');
+              
+              // update display
+              update_freebusy_display(data.email);
+            }
+          });
+          
+          // count required attendees
+          if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT')
+            freebusy_ui.numrequired++;
+        }
+      }
+    };
+    
+    // re-calculate total status after role change
+    var compute_freebusy_totals = function()
+    {
+      freebusy_ui.numrequired = 0;
+      freebusy_data.all = [];
+      freebusy_data.required = [];
+      
+      var email, req, status;
+      for (var i=0; i < event_attendees.length; i++) {
+        if (!(email = event_attendees[i].email))
+          continue;
+        
+        req = freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT';
+        if (req)
+          freebusy_ui.numrequired++;
+        
+        for (var ts in freebusy_data[email]) {
+          if (!freebusy_data.required[ts])
+            freebusy_data.required[ts] = [0,0,0,0];
+          if (!freebusy_data.all[ts])
+            freebusy_data.all[ts] = [0,0,0,0];
+          
+          status = freebusy_data[email][ts];
+          freebusy_data.all[ts][status]++;
+          
+          if (req)
+            freebusy_data.required[ts][status]++;
+        }
+      }
+    };
+
+    // update free-busy grid with status loaded from server
+    var update_freebusy_display = function(email)
+    {
+      var status_classes = ['unknown','free','busy','tentative','out-of-office'];
+      var domid = String(email).replace(rcmail.identifier_expr, '');
+      var row = $('#fbrow' + domid);
+      var rowall = $('#fbrowall').children();
+      var dateonly = freebusy_ui.interval > 60,
+        t, ts = date2timestring(freebusy_ui.start, dateonly),
+        curdate = new Date(),
+        fbdata = freebusy_data[email];
+
+      if (fbdata && fbdata[ts] !== undefined && row.length) {
+        t = freebusy_ui.start.getTime();
+        row.children().each(function(i, cell) {
+          var j, n, attr, last, all_slots = [], slots = [],
+            all_cell = rowall.get(i),
+            cnt = dateonly ? 1 : (60 / freebusy_ui.interval),
+            percent = (100 / cnt);
+
+          for (n=0; n < cnt; n++) {
+            curdate.setTime(t);
+            ts = date2timestring(curdate, dateonly);
+            attr = {
+              'style': 'float:left; width:' + percent.toFixed(2) + '%',
+              'class': fbdata[ts] ? status_classes[fbdata[ts]] : 'unknown'
+            };
+
+            slots.push($('<div>').attr(attr));
+
+            // also update total row if all data was loaded
+            if (!freebusy_ui.loading && freebusy_data.all[ts] && all_cell) {
+              var all_status = freebusy_data.all[ts][2] ? 'busy' : 'unknown',
+                req_status = freebusy_data.required[ts][2] ? 'busy' : 'free';
+
+              for (j=1; j < status_classes.length; j++) {
+                if (freebusy_ui.numrequired && freebusy_data.required[ts][j] >= freebusy_ui.numrequired)
+                  req_status = status_classes[j];
+                if (freebusy_data.all[ts][j] == event_attendees.length)
+                  all_status = status_classes[j];
+              }
+
+              attr['class'] = req_status + ' all-' + all_status;
+
+              // these elements use some specific styling, so we want to minimize their number
+              if (last && last.attr('class') == attr['class'])
+                last.css('width', (percent + parseFloat(last.css('width').replace('%', ''))).toFixed(2) + '%');
+              else {
+                last = $('<div>').attr(attr);
+                all_slots.push(last);
+              }
+            }
+
+            t += freebusy_ui.interval * 60000;
+          }
+
+          $(cell).html('').append(slots);
+          if (all_slots.length)
+            $(all_cell).html('').append(all_slots);
+        });
+      }
+    };
+    
+    // write changed event date/times back to form fields
+    var update_freebusy_dates = function(start, end)
+    {
+      // fix all-day evebt times
+      if (me.selected_event.allDay) {
+        var numdays = Math.floor((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / DAY_MS);
+        start.setHours(12);
+        start.setMinutes(0);
+        end.setTime(start.getTime() + numdays * DAY_MS);
+        end.setHours(13);
+        end.setMinutes(0);
+      }
+      me.selected_event.start = start;
+      me.selected_event.end = end;
+      freebusy_ui.startdate.val($.fullCalendar.formatDate(start, settings['date_format']));
+      freebusy_ui.starttime.val($.fullCalendar.formatDate(start, settings['time_format']));
+      freebusy_ui.enddate.val($.fullCalendar.formatDate(end, settings['date_format']));
+      freebusy_ui.endtime.val($.fullCalendar.formatDate(end, settings['time_format']));
+      freebusy_ui.needsupdate = true;
+    };
+
+    // attempt to find a time slot where all attemdees are available
+    var freebusy_find_slot = function(dir)
+    {
+      // exit if free-busy data isn't available yet
+      if (!freebusy_data || !freebusy_data.start)
+        return false;
+
+      var event = me.selected_event,
+        eventstart = clone_date(event.start, event.allDay ? 1 : 0).getTime(),  // calculate with integers
+        eventend = clone_date(event.end, event.allDay ? 2 : 0).getTime(),
+        duration = eventend - eventstart - (event.allDay ? HOUR_MS : 0),  /* make sure we don't cross day borders on DST change */
+        sinterval = freebusy_data.interval * 60000,
+        intvlslots = 1,
+        numslots = Math.ceil(duration / sinterval),
+        fb_start = freebusy_data.start.getTime(),
+        fb_end = freebusy_data.end.getTime(),
+        checkdate, slotend, email, ts, slot, slotdate = new Date(),
+        candidatecount = 0, candidatestart = false, success = false;
+
+      // shift event times to next possible slot
+      eventstart += sinterval * intvlslots * dir;
+      eventend += sinterval * intvlslots * dir;
+
+      // iterate through free-busy slots and find candidates
+      for (slot = dir > 0 ? fb_start : fb_end - sinterval;
+            (dir > 0 && slot < fb_end) || (dir < 0 && slot >= fb_start);
+            slot += sinterval * dir
+      ) {
+        slotdate.setTime(slot);
+        // fix slot if just crossed a DST change
+        if (event.allDay) {
+          fix_date(slotdate);
+          slot = slotdate.getTime();
+        }
+        slotend = slot + sinterval;
+
+        if ((dir > 0 && slotend <= eventstart) || (dir < 0 && slot >= eventend))  // skip
+          continue;
+
+        // respect workinghours setting
+        if (freebusy_ui.workinhoursonly) {
+          if (is_weekend(slotdate) || (freebusy_data.interval <= 60 && !is_workinghour(slotdate))) {  // skip off-hours
+            candidatestart = false;
+            candidatecount = 0;
+            continue;
+          }
+        }
+
+        if (!candidatestart)
+          candidatestart = slot;
+
+        ts = date2timestring(slotdate, freebusy_data.interval > 60);
+
+        // check freebusy data for all attendees
+        for (var i=0; i < event_attendees.length; i++) {
+          if (freebusy_ui.attendees[i].role != 'OPT-PARTICIPANT' && (email = freebusy_ui.attendees[i].email) && freebusy_data[email] && freebusy_data[email][ts] > 1) {
+            candidatestart = false;
+            break;
+          }
+        }
+        
+        // occupied slot
+        if (!candidatestart) {
+          slot += Math.max(0, intvlslots - candidatecount - 1) * sinterval * dir;
+          candidatecount = 0;
+          continue;
+        }
+        else if (dir < 0)
+          candidatestart = slot;
+        
+        candidatecount++;
+        
+        // if candidate is big enough, this is it!
+        if (candidatecount == numslots) {
+          event.start.setTime(candidatestart);
+          event.end.setTime(candidatestart + duration);
+          success = true;
+          break;
+        }
+      }
+      
+      // update event date/time display
+      if (success) {
+        update_freebusy_dates(event.start, event.end);
+        
+        // move freebusy grid if necessary
+        var offset = Math.ceil((event.start.getTime() - freebusy_ui.end.getTime()) / DAY_MS);
+        if (event.start.getTime() >= freebusy_ui.end.getTime())
+          render_freebusy_grid(Math.max(1, offset));
+        else if (event.end.getTime() <= freebusy_ui.start.getTime())
+          render_freebusy_grid(Math.min(-1, offset));
+        else
+          render_freebusy_overlay();
+        
+        var now = new Date();
+        $('#shedule-find-prev').button('option', 'disabled', (event.start.getTime() < now.getTime()));
+        
+        // speak new selection
+        rcmail.display_message(rcmail.gettext('suggestedslot', 'calendar') + ': ' + me.event_date_text(event, true), 'voice');
+      }
+      else {
+        alert(rcmail.gettext('noslotfound','calendar'));
+      }
+    };
+
+    // update event properties and attendees availability if event times have changed
+    var event_times_changed = function()
+    {
+      if (me.selected_event) {
+        var allday = $('#edit-allday').get(0);
+        me.selected_event.allDay = allday.checked;
+        me.selected_event.start = parse_datetime(allday.checked ? '12:00' : $('#edit-starttime').val(), $('#edit-startdate').val());
+        me.selected_event.end   = parse_datetime(allday.checked ? '13:00' : $('#edit-endtime').val(), $('#edit-enddate').val());
+        if (event_attendees)
+          freebusy_ui.needsupdate = true;
+        $('#edit-startdate').data('duration', Math.round((me.selected_event.end.getTime() - me.selected_event.start.getTime()) / 1000));
+      }
+    };
+
+    // add the given list of participants
+    var add_attendees = function(names, params)
+    {
+      names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
+
+      // parse name/email pairs
+      var item, email, name, success = false;
+      for (var i=0; i < names.length; i++) {
+        email = name = '';
+        item = $.trim(names[i]);
+        
+        if (!item.length) {
+          continue;
+        } // address in brackets without name (do nothing)
+        else if (item.match(/^<[^@]+@[^>]+>$/)) {
+          email = item.replace(/[<>]/g, '');
+        } // address without brackets and without name (add brackets)
+        else if (rcube_check_email(item)) {
+          email = item;
+        } // address with name
+        else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
+          email = RegExp.$1;
+          name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
+        }
+        if (email) {
+          add_attendee($.extend({ email:email, name:name }, params));
+          success = true;
+        }
+        else {
+          alert(rcmail.gettext('noemailwarning'));
+        }
+      }
+      
+      return success;
+    };
+
+    // add the given attendee to the list
+    var add_attendee = function(data, readonly, before)
+    {
+      if (!me.selected_event)
+        return false;
+
+      // check for dupes...
+      var exists = false;
+      $.each(event_attendees, function(i, v){ exists |= (v.email == data.email); });
+      if (exists)
+        return false;
+      
+      var calendar = me.selected_event && me.calendars[me.selected_event.calendar] ? me.calendars[me.selected_event.calendar] : me.calendars[me.selected_calendar];
+
+      var dispname = Q(data.name || data.email);
+      if (data.email)
+        dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
+      
+      // role selection
+      var organizer = data.role == 'ORGANIZER';
+      var opts = {};
+      if (organizer)
+        opts.ORGANIZER = rcmail.gettext('calendar.roleorganizer');
+      opts['REQ-PARTICIPANT'] = rcmail.gettext('calendar.rolerequired');
+      opts['OPT-PARTICIPANT'] = rcmail.gettext('calendar.roleoptional');
+      opts['NON-PARTICIPANT'] = rcmail.gettext('calendar.rolenonparticipant');
+
+      if (data.cutype != 'RESOURCE')
+        opts['CHAIR'] =  rcmail.gettext('calendar.rolechair');
+
+      if (organizer && !readonly)
+          dispname = rcmail.env['identities-selector'];
+      
+      var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','calendar') + '">';
+      for (var r in opts)
+        select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
+      select += '</select>';
+      
+      // availability
+      var avail = data.email ? 'loading' : 'unknown';
+
+      // delete icon
+      var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
+      var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
+      var tooltip = '', status = (data.status || '').toLowerCase(),
+        status_label = rcmail.gettext('status' + status, 'libcalendaring');
+
+      // send invitation checkbox
+      var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('calendar.sendinvitations')) + '" '
+        + (!data.noreply && settings.itip_notify & 1 ? 'checked="checked" ' : '') + '/>';
+
+      if (data['delegated-to'])
+        tooltip = rcmail.gettext('libcalendaring.delegatedto') + ' ' + data['delegated-to'];
+      else if (data['delegated-from'])
+        tooltip = rcmail.gettext('libcalendaring.delegatedfrom') + ' ' + data['delegated-from'];
+      else if (status)
+        tooltip = status_label;
+
+      // add expand button for groups
+      if (data.cutype == 'GROUP') {
+        dispname += ' <a href="#expand" data-email="' + Q(data.email) + '" class="iconbutton add expandlink" title="' + rcmail.gettext('expandattendeegroup','libcalendaring') + '">' +
+          rcmail.gettext('expandattendeegroup','libcalendaring') + '</a>';
+      }
+
+      var img_src = rcmail.assets_path('program/resources/blank.gif');
+      var html = '<td class="role">' + select + '</td>' +
+        '<td class="name"><span class="attendee-name">' + dispname + '</span></td>' +
+        '<td class="availability"><img src="' + img_src + '" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
+        '<td class="confirmstate"><span class="' + status + '" title="' + Q(tooltip) + '">' + Q(status ? status_label : '') + '</span></td>' +
+        (data.cutype != 'RESOURCE' ? '<td class="invite">' + (organizer || readonly || !invbox ? '' : invbox) + '</td>' : '') +
+        '<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
+
+      var table = rcmail.env.calendar_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
+      var tr = $('<tr>')
+        .addClass(String(data.role).toLowerCase())
+        .html(html);
+
+      if (before)
+        tr.insertBefore(before)
+      else
+        tr.appendTo(table);
+
+      tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
+      tr.find('a.mailtolink').click(event_attendee_click);
+      tr.find('a.expandlink').click(data, function(e) { me.expand_attendee_group(e, add_attendee, remove_attendee); return false; });
+      tr.find('input.edit-attendee-reply').click(function() {
+        var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
+        $('#eventedit .attendees-commentbox')[enabled ? 'show' : 'hide']();
+      });
+
+      // select organizer identity
+      if (data.identity_id)
+        $('#edit-identities-list').val(data.identity_id);
+      
+      // check free-busy status
+      if (avail == 'loading') {
+        check_freebusy_status(tr.find('img.availabilityicon'), data.email, me.selected_event);
+      }
+      
+      event_attendees.push(data);
+      return true;
+    };
+    
+    // iterate over all attendees and update their free-busy status display
+    var update_freebusy_status = function(event)
+    {
+      attendees_list.find('img.availabilityicon').each(function(i,v) {
+        var email, icon = $(this);
+        if (email = icon.attr('data-email'))
+          check_freebusy_status(icon, email, event);
+      });
+      
+      freebusy_ui.needsupdate = false;
+    };
+    
+    // load free-busy status from server and update icon accordingly
+    var check_freebusy_status = function(icon, email, event)
+    {
+      var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { freebusy:false };
+      if (!calendar.freebusy) {
+        $(icon).attr('class', 'availabilityicon unknown');
+        return;
+      }
+      
+      icon = $(icon).attr('class', 'availabilityicon loading');
+      
+      $.ajax({
+        type: 'GET',
+        dataType: 'html',
+        url: rcmail.url('freebusy-status'),
+        data: { email:email, start:date2servertime(clone_date(event.start, event.allDay?1:0)), end:date2servertime(clone_date(event.end, event.allDay?2:0)), _remote: 1 },
+        success: function(status){
+          var avail = String(status).toLowerCase();
+          icon.removeClass('loading').addClass(avail).attr('alt', rcmail.gettext('avail' + avail, 'calendar'));
+        },
+        error: function(){
+          icon.removeClass('loading').addClass('unknown').attr('alt', rcmail.gettext('availunknown', 'calendar'));
+        }
+      });
+    };
+    
+    // remove an attendee from the list
+    var remove_attendee = function(elem, id)
+    {
+      $(elem).closest('tr').remove();
+      event_attendees = $.grep(event_attendees, function(data){ return (data.name != id && data.email != id) });
+    };
+
+    // open a dialog to display detailed free-busy information and to find free slots
+    var event_resources_dialog = function(search)
+    {
+      var $dialog = $('#eventresourcesdialog');
+
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+  
+      // dialog buttons
+      var buttons = {};
+  
+      buttons[rcmail.gettext('addresource', 'calendar')] = function() {
+        rcmail.command('add-resource');
+      };
+  
+      buttons[rcmail.gettext('close')] = function() {
+        $dialog.dialog("close");
+      };
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: true,
+        resizable: true,
+        closeOnEscape: true,
+        title: rcmail.gettext('findresources', 'calendar'),
+        open: function() {
+          rcmail.ksearch_blur();
+          $dialog.attr('aria-hidden', 'false');
+        },
+        close: function() {
+          $dialog.dialog('destroy').attr('aria-hidden', 'true').hide();
+        },
+        resize: function(e) {
+          var container = $(rcmail.gui_objects.resourceinfocalendar);
+          container.fullCalendar('option', 'height', container.height() + 4);
+        },
+        buttons: buttons,
+        width: 900,
+        height: 500
+      }).show();
+
+      // define add-button as main action
+      $('.ui-dialog-buttonset .ui-button', $dialog.parent()).first().addClass('mainaction').attr('id', 'rcmbtncalresadd');
+
+      me.dialog_resize($dialog.get(0), 540, Math.min(1000, $(window).width() - 50));
+
+      // set search query
+      $('#resourcesearchbox').val(search || '');
+
+      // initialize the treelist widget
+      if (!resources_treelist) {
+        resources_treelist = new rcube_treelist_widget(rcmail.gui_objects.resourceslist, {
+          id_prefix: 'rcres',
+          id_encode: rcmail.html_identifier_encode,
+          id_decode: rcmail.html_identifier_decode,
+          selectable: true,
+          save_state: true
+        });
+        resources_treelist.addEventListener('select', function(node) {
+          if (resources_data[node.id]) {
+            resource_showinfo(resources_data[node.id]);
+            rcmail.enable_command('add-resource', me.selected_event && $("#eventedit").is(':visible') ? true : false);
+          }
+          else {
+            rcmail.enable_command('add-resource', false);
+            $(rcmail.gui_objects.resourceinfo).hide();
+            $(rcmail.gui_objects.resourceownerinfo).hide();
+            $(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
+          }
+        });
+
+        // fetch (all) resource data from server
+        me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
+        rcmail.http_request('resources-list', {}, me.loading_lock);
+
+        // register button
+        rcmail.register_button('add-resource', 'rcmbtncalresadd', 'uibutton');
+
+        // initialize resource calendar display
+        var resource_cal = $(rcmail.gui_objects.resourceinfocalendar);
+        resource_cal.fullCalendar($.extend({}, fullcalendar_defaults, {
+          header: { left: '', center: '', right: '' },
+          height: resource_cal.height() + 4,
+          defaultView: 'agendaWeek',
+          eventSources: [],
+          slotMinutes: 60,
+          allDaySlot: false,
+          eventRender: function(event, element, view) {
+            var title = rcmail.get_label(event.status, 'calendar');
+            element.addClass('status-' + event.status);
+            element.find('.fc-event-head').hide();
+            element.find('.fc-event-title').text(title);
+            element.attr('aria-label', me.event_date_text(event, true) + ': ' + title);
+          }
+        }));
+
+        $('#resource-calendar-prev').click(function(){
+          resource_cal.fullCalendar('prev');
+          return false;
+        });
+        $('#resource-calendar-next').click(function(){
+          resource_cal.fullCalendar('next');
+          return false;
+        });
+      }
+      else if (search) {
+        resource_search();
+      }
+      else {
+        resource_render_list(resources_index);
+      }
+
+      if (me.selected_event && me.selected_event.start) {
+        $(rcmail.gui_objects.resourceinfocalendar).fullCalendar('gotoDate', me.selected_event.start);
+      }
+    };
+
+    // render the resource details UI box
+    var resource_showinfo = function(resource)
+    {
+      // inline function to render a resource attribute
+      function render_attrib(value) {
+        if (typeof value == 'boolean') {
+          return value ? rcmail.get_label('yes') : rcmail.get_label('no');
+        }
+
+        return value;
+      }
+
+      if (rcmail.gui_objects.resourceinfo) {
+        var tr, table = $(rcmail.gui_objects.resourceinfo).show().find('tbody').html(''),
+          attribs = $.extend({ name:resource.name }, resource.attributes||{})
+          attribs.description = resource.description;
+
+        for (var k in attribs) {
+          if (typeof attribs[k] == 'undefined')
+            continue;
+          table.append($('<tr>').addClass(k)
+            .append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
+            .append('<td class="value">' + text2html(render_attrib(attribs[k])) + '</td>')
+          );
+        }
+
+        $(rcmail.gui_objects.resourceownerinfo).hide();
+        $(rcmail.gui_objects.resourceinfocalendar).fullCalendar('removeEventSource', resources_events_source);
+
+        if (resource.owner) {
+          // display cached data
+          if (resource_owners[resource.owner]) {
+            resource_owner_load(resource_owners[resource.owner]);
+          }
+          else {
+            // fetch owner data from server
+            me.loading_lock = rcmail.set_busy(true, 'loading', me.loading_lock);
+            rcmail.http_request('resources-owner', { _id: resource.owner }, me.loading_lock);
+          }
+        }
+
+        // load resource calendar
+        resources_events_source.url = "./?_task=calendar&_action=resources-calendar&_id="+urlencode(resource.ID);
+        $(rcmail.gui_objects.resourceinfocalendar).fullCalendar('addEventSource', resources_events_source);
+      }
+    };
+
+    // callback from server for resource listing
+    var resource_data_load = function(data)
+    {
+      var resources_tree = {};
+
+      // store data by ID
+      $.each(data, function(i, rec) {
+        resources_data[rec.ID] = rec;
+
+        // assign parent-relations
+        if (rec.members) {
+          $.each(rec.members, function(j, m){
+            resources_tree[m] = rec.ID;
+          });
+        }
+      });
+
+      // walk the parent-child tree to determine the depth of each node
+      $.each(data, function(i, rec) {
+        rec._depth = 0;
+        if (resources_tree[rec.ID])
+          rec.parent_id = resources_tree[rec.ID];
+
+        var parent_id = resources_tree[rec.ID];
+        while (parent_id) {
+          rec._depth++;
+          parent_id = resources_tree[parent_id];
+        }
+      });
+
+      // sort by depth, collection and name
+      data.sort(function(a,b) {
+        var j = a._type == 'collection' ? 1 : 0,
+            k = b._type == 'collection' ? 1 : 0,
+            d = a._depth - b._depth;
+        if (!d) d = (k - j);
+        if (!d) d = b.name < a.name ? 1 : -1;
+        return d;
+      });
+
+      $.each(data, function(i, rec) {
+        resources_index.push(rec.ID);
+      });
+
+      // apply search filter...
+      if ($('#resourcesearchbox').val() != '')
+        resource_search();
+      else  // ...or render full list
+        resource_render_list(resources_index);
+
+      rcmail.set_busy(false, null, me.loading_lock);
+    };
+
+    // renders the given list of resource records into the treelist
+    var resource_render_list = function(index) {
+      var rec, link;
+
+      resources_treelist.reset();
+
+      $.each(index, function(i, dn) {
+        if (rec = resources_data[dn]) {
+          link = $('<a>').attr('href', '#')
+            .attr('rel', rec.ID)
+            .html(Q(rec.name));
+
+          resources_treelist.insert({ id:rec.ID, html:link, classes:[rec._type], collapsed:true }, rec.parent_id, false);
+        }
+      });
+    };
+
+    // callback from server for owner information display
+    var resource_owner_load = function(data)
+    {
+      if (data) {
+        // cache this!
+        resource_owners[data.ID] = data;
+
+        var table = $(rcmail.gui_objects.resourceownerinfo).find('tbody').html('');
+
+        for (var k in data) {
+          if (k == 'event' || k == 'ID')
+            continue;
+
+          table.append($('<tr>').addClass(k)
+            .append('<td class="title">' + Q(ucfirst(rcmail.get_label(k, 'calendar'))) + '</td>')
+            .append('<td class="value">' + text2html(data[k]) + '</td>')
+          );
+        }
+
+        table.parent().show();
+      }
+    }
+
+    // quick-filter the loaded resource data
+    var resource_search = function()
+    {
+      var dn, rec, dataset = [],
+        q = $('#resourcesearchbox').val().toLowerCase();
+
+      if (q.length && resources_data) {
+        // search by iterating over all resource records
+        for (dn in resources_data) {
+          rec = resources_data[dn];
+          if ((rec.name && String(rec.name).toLowerCase().indexOf(q) >= 0)
+            || (rec.email && String(rec.email).toLowerCase().indexOf(q) >= 0)
+            || (rec.description && String(rec.description).toLowerCase().indexOf(q) >= 0)
+          ) {
+            dataset.push(rec.ID);
+          }
+        }
+
+        resource_render_list(dataset);
+
+        // select single match
+        if (dataset.length == 1) {
+          resources_treelist.select(dataset[0]);
+        }
+      }
+      else {
+        $('#resourcesearchbox').val('');
+      }
+    };
+
+    // 
+    var reset_resource_search = function()
+    {
+      $('#resourcesearchbox').val('').focus();
+      resource_render_list(resources_index);
+    };
+
+    // 
+    var add_resource2event = function()
+    {
+      var resource = resources_data[resources_treelist.get_selection()];
+      if (resource) {
+        if (add_attendee($.extend({ role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }, resource)))
+          rcmail.display_message(rcmail.get_label('resourceadded', 'calendar'), 'confirmation');
+      }
+    }
+
+    // when the user accepts or declines an event invitation
+    var event_rsvp = function(response, delegate, replymode)
+    {
+      var btn;
+      if (typeof response == 'object') {
+        btn = $(response);
+        response = btn.attr('rel')
+      }
+      else {
+        btn = $('#event-rsvp input.button[rel='+response+']');
+      }
+
+      // show menu to select rsvp reply mode (current or all)
+      if (me.selected_event && me.selected_event.recurrence && !replymode) {
+        rcube_libcalendaring.itip_rsvp_recurring(btn, function(resp, mode) {
+          event_rsvp(resp, null, mode);
+        });
+        return;
+      }
+
+      if (me.selected_event && me.selected_event.attendees && response) {
+        // bring up delegation dialog
+        if (response == 'delegated' && !delegate) {
+          rcube_libcalendaring.itip_delegate_dialog(function(data) {
+            data.rsvp = data.rsvp ? 1 : '';
+            event_rsvp('delegated', data, replymode);
+          });
+          return;
+        }
+
+        // update attendee status
+        attendees = [];
+        for (var data, i=0; i < me.selected_event.attendees.length; i++) {
+          data = me.selected_event.attendees[i];
+          if (settings.identity.emails.indexOf(';'+String(data.email).toLowerCase()) >= 0) {
+            data.status = response.toUpperCase();
+            data.rsvp = 0;  // unset RSVP flag
+
+            if (data.status == 'DELEGATED') {
+              data['delegated-to'] = delegate.to;
+              data.rsvp = delegate.rsvp
+            }
+            else {
+              if (data['delegated-to']) {
+                delete data['delegated-to'];
+                if (data.role == 'NON-PARTICIPANT' && data.status != 'DECLINED')
+                  data.role = 'REQ-PARTICIPANT';
+              }
+            }
+
+            attendees.push(i)
+          }
+          else if (response != 'DELEGATED' && data['delegated-from'] &&
+              settings.identity.emails.indexOf(';'+String(data['delegated-from']).toLowerCase()) >= 0) {
+            delete data['delegated-from'];
+          }
+
+          // set free_busy status to transparent if declined (#4425)
+          if (data.status == 'DECLINED' || data.role == 'NON-PARTICIPANT') {
+            me.selected_event.free_busy = 'free';
+          }
+          else {
+            me.selected_event.free_busy = 'busy';
+          }
+        }
+
+        // submit status change to server
+        var submit_data = $.extend({}, me.selected_event, { source:null, comment:$('#reply-comment-event-rsvp').val(), _savemode: replymode || 'all' }, (delegate || {})),
+          noreply = $('#noreply-event-rsvp:checked').length ? 1 : 0;
+
+        // import event from mail (temporary iTip event)
+        if (submit_data._mbox && submit_data._uid) {
+          me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
+          rcmail.http_post('mailimportitip', {
+            _mbox: submit_data._mbox,
+            _uid:  submit_data._uid,
+            _part: submit_data._part,
+            _status:  response,
+            _to: (delegate ? delegate.to : null),
+            _rsvp: (delegate && delegate.rsvp) ? 1 : 0,
+            _noreply: noreply,
+            _comment: submit_data.comment,
+            _instance: submit_data._instance,
+            _savemode: submit_data._savemode
+          });
+        }
+        else if (settings.invitation_calendars) {
+          update_event('rsvp', submit_data, { status:response, noreply:noreply, attendees:attendees });
+        }
+        else {
+          me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
+          rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response, attendees:attendees, noreply:noreply });
+        }
+
+        event_show_dialog(me.selected_event);
+      }
+    };
+    
+    // add the given date to the RDATE list
+    var add_rdate = function(date)
+    {
+      var li = $('<li>')
+        .attr('data-value', date2servertime(date))
+        .html('<span>' + Q($.fullCalendar.formatDate(date, settings['date_format'])) + '</span>')
+        .appendTo('#edit-recurrence-rdates');
+
+      $('<a>').attr('href', '#del')
+        .addClass('iconbutton delete')
+        .html(rcmail.get_label('delete', 'calendar'))
+        .attr('title', rcmail.get_label('delete', 'calendar'))
+        .appendTo(li);
+    };
+
+    // re-sort the list items by their 'data-value' attribute
+    var sort_rdates = function()
+    {
+      var mylist = $('#edit-recurrence-rdates'),
+        listitems = mylist.children('li').get();
+      listitems.sort(function(a, b) {
+         var compA = $(a).attr('data-value');
+         var compB = $(b).attr('data-value');
+         return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
+      })
+      $.each(listitems, function(idx, item) { mylist.append(item); });
+    }
+
+    // remove the link reference matching the given uri
+    function remove_link(elem)
+    {
+      var $elem = $(elem), uri = $elem.attr('data-uri');
+
+      me.selected_event.links = $.grep(me.selected_event.links, function(link) { return link.uri != uri; });
+
+      // remove UI list item
+      $elem.hide().closest('li').addClass('deleted');
+    }
+
+    // post the given event data to server
+    var update_event = function(action, data, add)
+    {
+      me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
+      rcmail.http_post('calendar/event', $.extend({ action:action, e:data }, (add || {})));
+      
+      // render event temporarily into the calendar
+      if ((data.start && data.end) || data.id) {
+        var event = data.id ? $.extend(fc.fullCalendar('clientEvents', function(e){ return e.id == data.id; })[0], data) : data;
+        if (data.start)
+          event.start = data.start;
+        if (data.end)
+          event.end = data.end;
+        if (data.allday !== undefined)
+          event.allDay = data.allday;
+        event.editable = false;
+        event.temp = true;
+        event.className = 'fc-event-cal-'+data.calendar+' fc-event-temp';
+        fc.fullCalendar(data.id ? 'updateEvent' : 'renderEvent', event);
+
+        // mark all recurring instances as temp
+        if (event.recurrence || event.recurrence_id) {
+          var base_id = event.recurrence_id ? event.recurrence_id : String(event.id).replace(/-\d+(T\d{6})?$/, '');
+          $.each(fc.fullCalendar('clientEvents', function(e){ return e.id == base_id || e.recurrence_id == base_id; }), function(i,ev) {
+            ev.temp = true;
+            ev.editable = false;
+            event.className += ' fc-event-temp';
+            fc.fullCalendar('updateEvent', ev);
+          });
+        }
+      }
+    };
+
+    // mouse-click handler to check if the show dialog is still open and prevent default action
+    var dialog_check = function(e)
+    {
+      var showd = $("#eventshow");
+      if (showd.is(':visible') && !$(e.target).closest('.ui-dialog').length && !$(e.target).closest('.popupmenu').length) {
+        showd.dialog('close');
+        e.stopImmediatePropagation();
+        ignore_click = true;
+        return false;
+      }
+      else if (ignore_click) {
+        window.setTimeout(function(){ ignore_click = false; }, 20);
+        return false;
+      }
+      return true;
+    };
+
+    // display confirm dialog when modifying/deleting an event
+    var update_event_confirm = function(action, event, data)
+    {
+      // Allow other plugins to do actions here
+      // E.g. when you move/resize the event init wasn't called
+      // but we need it as some plugins may modify user identities
+      // we depend on here (kolab_delegation)
+      rcmail.triggerEvent('calendar-event-init', {o: event});
+
+      if (!data) data = event;
+      var decline = false, notify = false, html = '', cal = me.calendars[event.calendar],
+        _has_attendees = me.has_attendees(event),
+        _is_attendee = _has_attendees && me.is_attendee(event),
+        _is_organizer = me.is_organizer(event);
+
+      // event has attendees, ask whether to notify them
+      if (_has_attendees) {
+        var checked = (settings.itip_notify & 1 ? ' checked="checked"' : '');
+
+        if (action == 'remove' && cal.group != 'shared' && !_is_organizer && _is_attendee) {
+          decline = true;
+          checked = event.status != 'CANCELLED' ? checked : '';
+          html += '<div class="message">' +
+            '<label><input class="confirm-attendees-decline" type="checkbox"' + checked + ' value="1" name="decline" />&nbsp;' +
+            rcmail.gettext('itipdeclineevent', 'calendar') + 
+            '</label></div>';
+        }
+        else if (_is_organizer) {
+          notify = true;
+          if (settings.itip_notify & 2) {
+            html += '<div class="message">' +
+              '<label><input class="confirm-attendees-donotify" type="checkbox"' + checked + ' value="1" name="notify" />&nbsp;' +
+                rcmail.gettext((action == 'remove' ? 'sendcancellation' : 'sendnotifications'), 'calendar') +
+              '</label></div>';
+          }
+          else {
+            data._notify = settings.itip_notify;
+          }
+        }
+      }
+
+      // recurring event: user needs to select the savemode
+      if (event.recurrence) {
+        var future_disabled = '', message_label = (action == 'remove' ? 'removerecurringeventwarning' : 'changerecurringeventwarning');
+
+        // disable the 'future' savemode if I'm an attendee
+        // reason: no calendaring system supports the thisandfuture range parameter in iTip REPLY
+        if (action == 'remove' && !_is_organizer && _is_attendee) {
+          future_disabled = ' disabled';
+        }
+
+        html += '<div class="message"><span class="ui-icon ui-icon-alert"></span>' +
+          rcmail.gettext(message_label, 'calendar') + '</div>' +
+          '<div class="savemode">' +
+            '<a href="#current" class="button">' + rcmail.gettext('currentevent', 'calendar') + '</a>' +
+            '<a href="#future" class="button' + future_disabled + '">' + rcmail.gettext('futurevents', 'calendar') + '</a>' +
+            '<a href="#all" class="button">' + rcmail.gettext('allevents', 'calendar') + '</a>' +
+            (action != 'remove' ? '<a href="#new" class="button">' + rcmail.gettext('saveasnew', 'calendar') + '</a>' : '') +
+          '</div>';
+      }
+      
+      // show dialog
+      if (html) {
+        var $dialog = $('<div>').html(html);
+      
+        $dialog.find('a.button').button().filter(':not(.disabled)').click(function(e) {
+          data._savemode = String(this.href).replace(/.+#/, '');
+          data._notify = settings.itip_notify;
+
+          // open event edit dialog when saving as new
+          if (data._savemode == 'new') {
+            event._savemode = 'new';
+            event_edit_dialog('edit', event);
+            fc.fullCalendar('refetchEvents');
+          }
+          else {
+            if ($dialog.find('input.confirm-attendees-donotify').length)
+              data._notify = $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0;
+            if (decline) {
+              data._decline = $dialog.find('input.confirm-attendees-decline:checked').length;
+              data._notify = 0;
+            }
+            update_event(action, data);
+          }
+
+          $dialog.dialog("close");
+          return false;
+        });
+        
+        var buttons = [];
+
+        if (!event.recurrence) {
+          buttons.push({
+            text: rcmail.gettext((action == 'remove' ? 'delete' : 'save'), 'calendar'),
+            click: function() {
+              data._notify = notify && $dialog.find('input.confirm-attendees-donotify:checked').length ? 1 : 0;
+              data._decline = decline && $dialog.find('input.confirm-attendees-decline:checked').length ? 1 : 0;
+              update_event(action, data);
+              $(this).dialog("close");
+            }
+          });
+        }
+
+        buttons.push({
+          text: rcmail.gettext('cancel', 'calendar'),
+          click: function() {
+            $(this).dialog("close");
+          }
+        });
+
+        $dialog.dialog({
+          modal: true,
+          width: 460,
+          dialogClass: 'warning',
+          title: rcmail.gettext((action == 'remove' ? 'removeeventconfirm' : 'changeeventconfirm'), 'calendar'),
+          buttons: buttons,
+          open: function() {
+            setTimeout(function(){
+              $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
+            }, 5);
+          },
+          close: function(){
+            $dialog.dialog("destroy").remove();
+            if (!rcmail.busy)
+              fc.fullCalendar('refetchEvents');
+          }
+        }).addClass('event-update-confirm').show();
+        
+        return false;
+      }
+      // show regular confirm box when deleting
+      else if (action == 'remove' && !cal.undelete) {
+        if (!confirm(rcmail.gettext('deleteventconfirm', 'calendar')))
+          return false;
+      }
+
+      // do update
+      update_event(action, data);
+      
+      return true;
+    };
+
+    var update_agenda_toolbar = function()
+    {
+      $('#agenda-listrange').val(fc.fullCalendar('option', 'listRange'));
+      $('#agenda-listsections').val(fc.fullCalendar('option', 'listSections'));
+    }
+
+
+    /*** public methods ***/
+
+    /**
+     * Remove saving lock and free the UI for new input
+     */
+    this.unlock_saving = function()
+    {
+        if (me.saving_lock)
+            rcmail.set_busy(false, null, me.saving_lock);
+    };
+
+    // opens calendar day-view in a popup
+    this.fisheye_view = function(date)
+    {
+      $('#fish-eye-view:ui-dialog').dialog('close');
+      
+      // create list of active event sources
+      var src, cals = {}, sources = [];
+      for (var id in this.calendars) {
+        src = $.extend({}, this.calendars[id]);
+        src.editable = false;
+        src.url = null;
+        src.events = [];
+
+        if (src.active) {
+          cals[id] = src;
+          sources.push(src);
+        }
+      }
+      
+      // copy events already loaded
+      var events = fc.fullCalendar('clientEvents');
+      for (var event, i=0; i< events.length; i++) {
+        event = events[i];
+        if (event.source && (src = cals[event.source.id])) {
+          src.events.push(event);
+        }
+      }
+      
+      var h = $(window).height() - 50;
+      var dialog = $('<div>')
+        .attr('id', 'fish-eye-view')
+        .dialog({
+          modal: true,
+          width: 680,
+          height: h,
+          title: $.fullCalendar.formatDate(date, 'dddd ' + settings['date_long']),
+          close: function(){
+            dialog.dialog("destroy");
+            me.fisheye_date = null;
+          }
+        })
+        .fullCalendar($.extend({}, fullcalendar_defaults, {
+          defaultView: 'agendaDay',
+          header: { left: '', center: '', right: '' },
+          height: h - 50,
+          date: date.getDate(),
+          month: date.getMonth(),
+          year: date.getFullYear(),
+          eventSources: sources
+        }));
+        
+        this.fisheye_date = date;
+    };
+
+    // opens the given calendar in a popup dialog
+    this.quickview = function(id, shift)
+    {
+      var src, in_quickview = false;
+      $.each(this.quickview_sources, function(i,cal) {
+        if (cal.id == id) {
+          in_quickview = true;
+          src = cal;
+        }
+      });
+
+      // remove source from quickview
+      if (in_quickview && shift) {
+        this.quickview_sources = $.grep(this.quickview_sources, function(src) { return src.id != id; });
+      }
+      else {
+        if (!shift) {
+          // remove all current quickview event sources
+          if (this.quickview_active) {
+            fc.fullCalendar('removeEventSources');
+          }
+
+          this.quickview_sources = [];
+
+          // uncheck all active quickview icons
+          calendars_list.container.find('div.focusview')
+            .add('#calendars .searchresults div.focusview')
+            .removeClass('focusview')
+              .find('a.quickview').attr('aria-checked', 'false');
+        }
+
+        if (!in_quickview) {
+          // clone and modify calendar properties
+          src = $.extend({}, this.calendars[id]);
+          src.url += '&_quickview=1';
+          this.quickview_sources.push(src);
+        }
+      }
+
+      // disable quickview
+      if (this.quickview_active && !this.quickview_sources.length) {
+        // register regular calendar event sources
+        $.each(this.calendars, function(k, cal) {
+          if (cal.active)
+            fc.fullCalendar('addEventSource', cal);
+        });
+
+        this.quickview_active = false;
+        $('body').removeClass('quickview-active');
+
+        // uncheck all active quickview icons
+        calendars_list.container.find('div.focusview')
+          .add('#calendars .searchresults div.focusview')
+          .removeClass('focusview')
+            .find('a.quickview').attr('aria-checked', 'false');
+      }
+      // activate quickview
+      else if (!this.quickview_active) {
+        // remove regular calendar event sources
+        fc.fullCalendar('removeEventSources');
+
+        // register quickview event sources
+        $.each(this.quickview_sources, function(i, src) {
+          fc.fullCalendar('addEventSource', src);
+        });
+
+        this.quickview_active = true;
+        $('body').addClass('quickview-active');
+      }
+      // update quickview sources
+      else if (in_quickview) {
+        fc.fullCalendar('removeEventSource', src);
+      }
+      else if (src) {
+        fc.fullCalendar('addEventSource', src);
+      }
+
+      // activate quickview icon
+      if (this.quickview_active) {
+        $(calendars_list.get_item(id)).find('.calendar').first()
+          .add('#calendars .searchresults .cal-' + id)
+          [in_quickview ? 'removeClass' : 'addClass']('focusview')
+            .find('a.quickview').attr('aria-checked', in_quickview ? 'false' : 'true');
+      }
+    };
+
+    // disable quickview mode
+    function reset_quickview()
+    {
+      // remove all current quickview event sources
+      if (me.quickview_active) {
+        fc.fullCalendar('removeEventSources');
+        me.quickview_sources = [];
+      }
+
+      // register regular calendar event sources
+      $.each(me.calendars, function(k, cal) {
+        if (cal.active)
+          fc.fullCalendar('addEventSource', cal);
+      });
+
+      // uncheck all active quickview icons
+      calendars_list.container.find('div.focusview')
+        .add('#calendars .searchresults div.focusview')
+        .removeClass('focusview')
+          .find('a.quickview').attr('aria-checked', 'false');
+
+      me.quickview_active = false;
+      $('body').removeClass('quickview-active');
+    };
+
+    //public method to show the print dialog.
+    this.print_calendars = function(view)
+    {
+      if (!view) view = fc.fullCalendar('getView').name;
+      var date = fc.fullCalendar('getDate') || new Date();
+      var range = fc.fullCalendar('option', 'listRange');
+      var sections = fc.fullCalendar('option', 'listSections');
+      rcmail.open_window(rcmail.url('print', { view: view, date: date2unixtime(date), range: range, sections: sections, search: this.search_query }), true, true);
+    };
+
+    // public method to bring up the new event dialog
+    this.add_event = function(templ) {
+      if (this.selected_calendar) {
+        var now = new Date();
+        var date = fc.fullCalendar('getDate');
+        if (typeof date != 'Date')
+          date = now;
+        date.setHours(now.getHours()+1);
+        date.setMinutes(0);
+        var end = new Date(date.getTime());
+        end.setHours(date.getHours()+1);
+        event_edit_dialog('new', $.extend({ start:date, end:end, allDay:false, calendar:this.selected_calendar }, templ || {}));
+      }
+    };
+
+    // delete the given event after showing a confirmation dialog
+    this.delete_event = function(event) {
+      // show confirm dialog for recurring events, use jquery UI dialog
+      return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees });
+    };
+    
+    // opens a jquery UI dialog with event properties (or empty for creating a new calendar)
+    this.calendar_edit_dialog = function(calendar)
+    {
+      // close show dialog first
+      var $dialog = $("#calendarform");
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+      
+      if (!calendar)
+        calendar = { name:'', color:'cc0000', editable:true, showalarms:true };
+      
+      var form, name, color, alarms;
+      
+      $dialog.html(rcmail.get_label('loading'));
+      $.ajax({
+        type: 'GET',
+        dataType: 'html',
+        url: rcmail.url('calendar'),
+        data: { action:(calendar.id ? 'form-edit' : 'form-new'), c:{ id:calendar.id } },
+        success: function(data) {
+          $dialog.html(data);
+          // resize and reposition dialog window
+          form = $('#calendarpropform');
+          me.dialog_resize('#calendarform', form.height(), form.width());
+          name = $('#calendar-name').prop('disabled', !calendar.editable).val(calendar.editname || calendar.name);
+          color = $('#calendar-color').val(calendar.color).miniColors({ value: calendar.color, colorValues:rcmail.env.mscolors });
+          alarms = $('#calendar-showalarms').prop('checked', calendar.showalarms).get(0);
+          name.select();
+        }
+      });
+
+      // dialog buttons
+      var buttons = {};
+      
+      buttons[rcmail.gettext('save', 'calendar')] = function() {
+        // form is not loaded
+        if (!form || !form.length)
+          return;
+          
+        // TODO: do some input validation
+        if (!name.val() || name.val().length < 2) {
+          alert(rcmail.gettext('invalidcalendarproperties', 'calendar'));
+          name.select();
+          return;
+        }
+        
+        // post data to server
+        var data = form.serializeJSON();
+        if (data.color)
+          data.color = data.color.replace(/^#/, '');
+        if (calendar.id)
+          data.id = calendar.id;
+        if (alarms)
+          data.showalarms = alarms.checked ? 1 : 0;
+
+        me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
+        rcmail.http_post('calendar', { action:(calendar.id ? 'edit' : 'new'), c:data });
+        $dialog.dialog("close");
+      };
+
+      buttons[rcmail.gettext('cancel', 'calendar')] = function() {
+        $dialog.dialog("close");
+      };
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: true,
+        resizable: true,
+        closeOnEscape: false,
+        title: rcmail.gettext((calendar.id ? 'editcalendar' : 'createcalendar'), 'calendar'),
+        open: function() {
+          $dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
+        },
+        close: function() {
+          $dialog.html('').dialog("destroy").hide();
+        },
+        buttons: buttons,
+        minWidth: 400,
+        width: 420
+      }).show();
+
+    };
+
+    this.calendar_remove = function(calendar)
+    {
+      this.calendar_destroy_source(calendar.id);
+      rcmail.http_post('calendar', { action:'subscribe', c:{ id:calendar.id, active:0, permanent:0, recursive:1 } });
+      return true;
+    };
+
+    this.calendar_delete = function(calendar)
+    {
+      if (confirm(rcmail.gettext(calendar.children ? 'deletecalendarconfirmrecursive' : 'deletecalendarconfirm', 'calendar'))) {
+        rcmail.http_post('calendar', { action:'delete', c:{ id:calendar.id } });
+        return true;
+      }
+      return false;
+    };
+
+    this.calendar_refresh_source = function(id)
+    {
+      // got race-conditions fc.currentFetchID when using refetchEvents,
+      // so we remove and add the source instead
+      // fc.fullCalendar('refetchEvents', me.calendars[id]);
+      fc.fullCalendar('removeEventSource', me.calendars[id]);
+      fc.fullCalendar('addEventSource', me.calendars[id]);
+    };
+
+    this.calendar_destroy_source = function(id)
+    {
+      var delete_ids = [];
+
+      if (this.calendars[id]) {
+        // find sub-calendars
+        if (this.calendars[id].children) {
+          for (var child_id in this.calendars) {
+            if (String(child_id).indexOf(id) == 0)
+              delete_ids.push(child_id);
+          }
+        }
+        else {
+          delete_ids.push(id);
+        }
+      }
+
+      // delete all calendars in the list
+      for (var i=0; i < delete_ids.length; i++) {
+        id = delete_ids[i];
+        calendars_list.remove(id);
+        fc.fullCalendar('removeEventSource', this.calendars[id]);
+        $('#edit-calendar option[value="'+id+'"]').remove();
+        delete this.calendars[id];
+      }
+    };
+
+    // open a dialog to upload an .ics file with events to be imported
+    this.import_events = function(calendar)
+    {
+      // close show dialog first
+      var $dialog = $("#eventsimport"),
+        form = rcmail.gui_objects.importform;
+      
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+      
+      if (calendar)
+        $('#event-import-calendar').val(calendar.id);
+      
+      var buttons = {};
+      buttons[rcmail.gettext('import', 'calendar')] = function() {
+        if (form && form.elements._data.value) {
+          rcmail.async_upload_form(form, 'import_events', function(e) {
+            rcmail.set_busy(false, null, me.saving_lock);
+            $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
+
+            // display error message if no sophisticated response from server arrived (e.g. iframe load error)
+            if (me.import_succeeded === null)
+              rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error');
+          });
+
+          // display upload indicator (with extended timeout)
+          var timeout = rcmail.env.request_timeout;
+          rcmail.env.request_timeout = 600;
+          me.import_succeeded = null;
+          me.saving_lock = rcmail.set_busy(true, 'uploading');
+          $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable');
+
+          // restore settings
+          rcmail.env.request_timeout = timeout;
+        }
+      };
+
+      buttons[rcmail.gettext('cancel', 'calendar')] = function() {
+        $dialog.dialog("close");
+      };
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: true,
+        resizable: false,
+        closeOnEscape: false,
+        title: rcmail.gettext('importevents', 'calendar'),
+        open: function() {
+          $dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
+        },
+        close: function() {
+          $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
+          $dialog.dialog("destroy").hide();
+        },
+        buttons: buttons,
+        width: 520
+      }).show();
+    };
+
+    // callback from server if import succeeded
+    this.import_success = function(p)
+    {
+      this.import_succeeded = true;
+      $("#eventsimport:ui-dialog").dialog('close');
+      rcmail.set_busy(false, null, me.saving_lock);
+      rcmail.gui_objects.importform.reset();
+
+      if (p.refetch)
+        this.refresh(p);
+    };
+
+    // callback from server to report errors on import
+    this.import_error = function(p)
+    {
+      this.import_succeeded = false;
+      rcmail.set_busy(false, null, me.saving_lock);
+      rcmail.display_message(p.message || rcmail.get_label('importerror', 'calendar'), 'error');
+    }
+
+    // open a dialog to select calendars for export
+    this.export_events = function(calendar)
+    {
+      // close show dialog first
+      var $dialog = $("#eventsexport"),
+        form = rcmail.gui_objects.exportform;
+
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+
+      if (calendar)
+        $('#event-export-calendar').val(calendar.id);
+
+      $('#event-export-range').change(function(e){
+        var custom = $('option:selected', this).val() == 'custom',
+          input = $('#event-export-startdate')
+        input.parent()[(custom?'show':'hide')]();
+        if (custom)
+          input.select();
+      })
+
+      var buttons = {};
+      buttons[rcmail.gettext('export', 'calendar')] = function() {
+        if (form) {
+          var start = 0, range = $('#event-export-range option:selected', this).val(),
+            source = $('#event-export-calendar option:selected').val(),
+            attachmt = $('#event-export-attachments').get(0).checked;
+
+          if (range == 'custom')
+            start = date2unixtime(parse_datetime('00:00', $('#event-export-startdate').val()));
+          else if (range > 0)
+            start = 'today -' + range + ' months';
+
+          rcmail.goto_url('export_events', { source:source, start:start, attachments:attachmt?1:0 });
+        }
+        $dialog.dialog("close");
+      };
+
+      buttons[rcmail.gettext('cancel', 'calendar')] = function() {
+        $dialog.dialog("close");
+      };
+
+      // open jquery UI dialog
+      $dialog.dialog({
+        modal: true,
+        resizable: false,
+        closeOnEscape: false,
+        title: rcmail.gettext('exporttitle', 'calendar'),
+        open: function() {
+          $dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
+        },
+        close: function() {
+          $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable');
+          $dialog.dialog("destroy").hide();
+        },
+        buttons: buttons,
+        width: 520
+      }).show();
+    };
+
+    // download the selected event as iCal
+    this.event_download = function(event)
+    {
+      if (event && event.id) {
+        rcmail.goto_url('export_events', { source:event.calendar, id:event.id, attachments:1 });
+      }
+    };
+
+    // open the message compose step with a calendar_event parameter referencing the selected event.
+    // the server-side plugin hook will pick that up and attach the event to the message.
+    this.event_sendbymail = function(event, e)
+    {
+      if (event && event.id) {
+        rcmail.command('compose', { _calendar_event:event._id }, e ? e.target : null, e);
+      }
+    };
+
+    // display the edit dialog, request 'new' action and pass the selected event
+    this.event_copy = function(event) {
+      if (event && event.id) {
+        var copy = $.extend(true, {}, event);
+
+        delete copy.id;
+        delete copy._id;
+        delete copy.created;
+        delete copy.changed;
+        delete copy.recurrence_id;
+        delete copy.attachments; // @TODO
+
+        $.each(copy.attendees, function (k, v) {
+          if (v.role != 'ORGANIZER') {
+            v.status = 'NEEDS-ACTION';
+          }
+        })
+
+        event_edit_dialog('new', copy);
+      }
+    };
+
+    // show URL of the given calendar in a dialog box
+    this.showurl = function(calendar)
+    {
+      var $dialog = $('#calendarurlbox');
+
+      if ($dialog.is(':ui-dialog'))
+        $dialog.dialog('close');
+
+      if (calendar.feedurl) {
+        if (calendar.caldavurl) {
+          $('#caldavurl').val(calendar.caldavurl);
+          $('#calendarcaldavurl').show();
+        }
+        else {
+          $('#calendarcaldavurl').hide();
+        }
+
+        $dialog.dialog({
+          resizable: true,
+          closeOnEscape: true,
+          title: rcmail.gettext('showurl', 'calendar'),
+          close: function() {
+            $dialog.dialog("destroy").hide();
+          },
+          width: 520
+        }).show();
+
+        $('#calfeedurl').val(calendar.feedurl).select();
+      }
+    };
+
+    // refresh the calendar view after saving event data
+    this.refresh = function(p)
+    {
+      var source = me.calendars[p.source];
+
+      // helper function to update the given fullcalendar view
+      function update_view(view, event, source) {
+        var existing = view.fullCalendar('clientEvents', event._id);
+        if (existing.length) {
+          $.extend(existing[0], event);
+          view.fullCalendar('updateEvent', existing[0]);
+          // remove old recurrence instances
+          if (event.recurrence && !event.recurrence_id)
+            view.fullCalendar('removeEvents', function(e){ return e._id.indexOf(event._id+'-') == 0; });
+        }
+        else {
+          event.source = source;  // link with source
+          view.fullCalendar('renderEvent', event);
+        }
+      }
+
+      // remove temp events
+      fc.fullCalendar('removeEvents', function(e){ return e.temp; });
+
+      if (source && (p.refetch || (p.update && !source.active))) {
+        // activate event source if new event was added to an invisible calendar
+        if (this.quickview_active) {
+          // map source to the quickview_sources equivalent
+          $.each(this.quickview_sources, function(src) {
+            if (src.id == source.id) {
+              source = src;
+              return false;
+            }
+          });
+          fc.fullCalendar('refetchEvents', source, true);
+        }
+        else if (!source.active) {
+          source.active = true;
+          fc.fullCalendar('addEventSource', source);
+          $('#rcmlical' + source.id + ' input').prop('checked', true);
+        }
+        else
+          fc.fullCalendar('refetchEvents', source, true);
+
+        fetch_counts();
+      }
+      // add/update single event object
+      else if (source && p.update) {
+        var event = p.update;
+        event.temp = false;
+        event.editable = 0;
+
+          // update fish-eye view
+        if (this.fisheye_date)
+          update_view($('#fish-eye-view'), event, source);
+
+        // update main view
+        event.editable = source.editable;
+        update_view(fc, event, source);
+
+        // update the currently displayed event dialog
+        if ($('#eventshow').is(':visible') && me.selected_event && me.selected_event.id == event.id)
+          event_show_dialog(event)
+      }
+      // refetch all calendars
+      else if (p.refetch) {
+        fc.fullCalendar('refetchEvents', undefined, true);
+        fetch_counts();
+      }
+    };
+
+    // modify query parameters for refresh requests
+    this.before_refresh = function(query)
+    {
+      var view = fc.fullCalendar('getView');
+
+      query.start = date2unixtime(view.visStart);
+      query.end = date2unixtime(view.visEnd);
+
+      if (this.search_query)
+        query.q = this.search_query;
+
+      return query;
+    };
+
+    // callback from server providing event counts
+    this.update_counts = function(p)
+    {
+      $.each(p.counts, function(cal, count) {
+        var li = calendars_list.get_item(cal),
+          bubble = $(li).children('.calendar').find('span.count');
+
+        if (!bubble.length && count > 0) {
+          bubble = $('<span>')
+            .addClass('count')
+            .appendTo($(li).children('.calendar').first())
+        }
+
+        if (count > 0) {
+          bubble.text(count).show();
+        }
+        else {
+          bubble.text('').hide();
+        }
+      });
+    };
+
+    // callback after an iTip message event was imported
+    this.itip_message_processed = function(data)
+    {
+      // remove temporary iTip source
+      fc.fullCalendar('removeEventSource', this.calendars['--invitation--itip']);
+
+      $('#eventshow:ui-dialog').dialog('close');
+      this.selected_event = null;
+
+      // refresh destination calendar source
+      this.refresh({ source:data.calendar, refetch:true });
+
+      this.unlock_saving();
+
+      // process 'after_action' in mail task
+      if (window.opener && window.opener.rcube_libcalendaring)
+        window.opener.rcube_libcalendaring.itip_message_processed(data);
+    };
+
+    // reload the calendar view by keeping the current date/view selection
+    this.reload_view = function()
+    {
+      var query = { view: fc.fullCalendar('getView').name },
+        date = fc.fullCalendar('getDate');
+      if (date)
+        query.date = date2unixtime(date);
+      rcmail.redirect(rcmail.url('', query));
+    }
+
+    // update browser location to remember current view
+    this.update_state = function()
+    {
+      var query = { view: current_view },
+        date = fc.fullCalendar('getDate');
+      if (date)
+        query.date = date2unixtime(date);
+
+      if (window.history.replaceState)
+        window.history.replaceState({}, document.title, rcmail.url('', query).replace('&_action=', ''));
+    };
+
+    this.resource_search = resource_search;
+    this.reset_resource_search = reset_resource_search;
+    this.add_resource2event = add_resource2event;
+    this.resource_data_load = resource_data_load;
+    this.resource_owner_load = resource_owner_load;
+
+
+    /***  event searching  ***/
+
+    // execute search
+    this.quicksearch = function()
+    {
+      if (rcmail.gui_objects.qsearchbox) {
+        var q = rcmail.gui_objects.qsearchbox.value;
+        if (q != '') {
+          var id = 'search-'+q;
+          var sources = [];
+          
+          if (me.quickview_active)
+            reset_quickview();
+          
+          if (this._search_message)
+            rcmail.hide_message(this._search_message);
+          
+          for (var sid in this.calendars) {
+            if (this.calendars[sid]) {
+              this.calendars[sid].url = this.calendars[sid].url.replace(/&q=.+/, '') + '&q=' + urlencode(q);
+              sources.push(sid);
+            }
+          }
+          id += '@'+sources.join(',');
+          
+          // ignore if query didn't change
+          if (this.search_request == id) {
+            return;
+          }
+          // remember current view
+          else if (!this.search_request) {
+            this.default_view = fc.fullCalendar('getView').name;
+          }
+          
+          this.search_request = id;
+          this.search_query = q;
+          
+          // change to list view
+          fc.fullCalendar('option', 'listSections', 'month')
+            .fullCalendar('option', 'listRange', Math.max(60, settings['agenda_range']))
+            .fullCalendar('changeView', 'table');
+          
+          update_agenda_toolbar();
+          
+          // refetch events with new url (if not already triggered by changeView)
+          if (!this.is_loading)
+            fc.fullCalendar('refetchEvents');
+        }
+        else  // empty search input equals reset
+          this.reset_quicksearch();
+      }
+    };
+
+    // reset search and get back to normal event listing
+    this.reset_quicksearch = function()
+    {
+      $(rcmail.gui_objects.qsearchbox).val('');
+      
+      if (this._search_message)
+        rcmail.hide_message(this._search_message);
+      
+      if (this.search_request) {
+        // hide bottom links of agenda view
+        fc.find('.fc-list-content > .fc-listappend').hide();
+        
+        // restore original event sources and view mode from fullcalendar
+        fc.fullCalendar('option', 'listSections', settings['agenda_sections'])
+          .fullCalendar('option', 'listRange', settings['agenda_range']);
+        
+        update_agenda_toolbar();
+        
+        for (var sid in this.calendars) {
+          if (this.calendars[sid])
+            this.calendars[sid].url = this.calendars[sid].url.replace(/&q=.+/, '');
+        }
+        if (this.default_view)
+          fc.fullCalendar('changeView', this.default_view);
+        
+        if (!this.is_loading)
+          fc.fullCalendar('refetchEvents');
+        
+        this.search_request = this.search_query = null;
+      }
+    };
+
+    // callback if all sources have been fetched from server
+    this.events_loaded = function(count)
+    {
+      var addlinks, append = '';
+      
+      // enhance list view when searching
+      if (this.search_request) {
+        if (!count) {
+          this._search_message = rcmail.display_message(rcmail.gettext('searchnoresults', 'calendar'), 'notice');
+          append = '<div class="message">' + rcmail.gettext('searchnoresults', 'calendar') + '</div>';
+        }
+        append += '<div class="fc-bottomlinks formlinks"></div>';
+        addlinks = true;
+      }
+      
+      if (fc.fullCalendar('getView').name == 'table') {
+        var container = fc.find('.fc-list-content > .fc-listappend');
+        if (append) {
+          if (!container.length)
+            container = $('<div class="fc-listappend"></div>').appendTo(fc.find('.fc-list-content'));
+          container.html(append).show();
+        }
+        else if (container.length)
+          container.hide();
+        
+        // add links to adjust search date range
+        if (addlinks) {
+          var lc = container.find('.fc-bottomlinks');
+          $('<a>').attr('href', '#').html(rcmail.gettext('searchearlierdates', 'calendar')).appendTo(lc).click(function(){
+            fc.fullCalendar('incrementDate', 0, -1, 0);
+          });
+          lc.append(" ");
+          $('<a>').attr('href', '#').html(rcmail.gettext('searchlaterdates', 'calendar')).appendTo(lc).click(function(){
+            var range = fc.fullCalendar('option', 'listRange');
+            if (range < 90) {
+              fc.fullCalendar('option', 'listRange', fc.fullCalendar('option', 'listRange') + 30).fullCalendar('render');
+              update_agenda_toolbar();
+            }
+            else
+              fc.fullCalendar('incrementDate', 0, 1, 0);
+          });
+        }
+      }
+      
+      if (this.fisheye_date)
+        this.fisheye_view(this.fisheye_date);
+    };
+
+    // adjust calendar view size
+    this.view_resize = function()
+    {
+      var footer = fc.fullCalendar('getView').name == 'table' ? $('#agendaoptions').outerHeight() : 0;
+      fc.fullCalendar('option', 'height', $('#calendar').height() - footer);
+    };
+
+    // mark the given calendar folder as selected
+    this.select_calendar = function(id, nolistupdate)
+    {
+      if (!nolistupdate)
+        calendars_list.select(id);
+
+      // trigger event hook
+      rcmail.triggerEvent('selectfolder', { folder:id, prefix:'rcmlical' });
+
+      this.selected_calendar = id;
+
+      rcmail.update_state({source: id});
+    };
+
+    // register the given calendar to the current view
+    var add_calendar_source = function(cal)
+    {
+      var color, brightness, select, id = cal.id;
+
+      me.calendars[id] = $.extend({
+        url: rcmail.url('calendar/load_events', { source: id }),
+        className: 'fc-event-cal-'+id,
+        id: id
+      }, cal);
+
+      // choose black text color when background is bright, white otherwise
+      if (color = settings.event_coloring % 2  ? '' : '#' + cal.color) {
+        if (/^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.test(color)) {
+          // use information about brightness calculation found at
+          // http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/
+          brightness = (parseInt(RegExp.$1, 16) * 299 + parseInt(RegExp.$2, 16) * 587 + parseInt(RegExp.$3, 16) * 114) / 1000;
+          if (brightness > 125)
+            me.calendars[id].textColor = 'black';
+        }
+
+        me.calendars[id].color = color;
+      }
+
+      if (fc && (cal.active || cal.subscribed)) {
+        if (cal.active)
+          fc.fullCalendar('addEventSource', me.calendars[id]);
+
+        var submit = { id: id, active: cal.active ? 1 : 0 };
+        if (cal.subscribed !== undefined)
+            submit.permanent = cal.subscribed ? 1 : 0;
+        rcmail.http_post('calendar', { action:'subscribe', c:submit });
+      }
+
+      // insert to #calendar-select options if writeable
+      select = $('#edit-calendar');
+      if (fc && me.has_permission(cal, 'i') && select.length && !select.find('option[value="'+id+'"]').length) {
+        $('<option>').attr('value', id).html(cal.name).appendTo(select);
+      }
+    }
+
+    // fetch counts for some calendars from the server
+    var fetch_counts = function()
+    {
+      if (count_sources.length) {
+        setTimeout(function() {
+          rcmail.http_request('calendar/count', { source:count_sources });
+        }, 500);
+      }
+    };
+
+
+    /***  startup code  ***/
+
+    // create list of event sources AKA calendars
+    var id, cal, active, event_sources = [];
+    for (id in rcmail.env.calendars) {
+      cal = rcmail.env.calendars[id];
+      active = cal.active || false;
+      add_calendar_source(cal);
+
+      // check active calendars
+      $('#rcmlical'+id+' > .calendar input').prop('checked', active);
+
+      if (active) {
+        event_sources.push(this.calendars[id]);
+      }
+      if (cal.counts) {
+        count_sources.push(id);
+      }
+
+      if (cal.editable && !this.selected_calendar) {
+        this.selected_calendar = id;
+        rcmail.enable_command('addevent', true);
+      }
+    }
+
+    // initialize treelist widget that controls the calendars list
+    var widget_class = window.kolab_folderlist || rcube_treelist_widget;
+    calendars_list = new widget_class(rcmail.gui_objects.calendarslist, {
+      id_prefix: 'rcmlical',
+      selectable: true,
+      save_state: true,
+      keyboard: false,
+      searchbox: '#calendarlistsearch',
+      search_action: 'calendar/calendar',
+      search_sources: [ 'folders', 'users' ],
+      search_title: rcmail.gettext('calsearchresults','calendar')
+    });
+    calendars_list.addEventListener('select', function(node) {
+      if (node && node.id && me.calendars[node.id]) {
+        me.select_calendar(node.id, true);
+        rcmail.enable_command('calendar-edit', 'calendar-showurl', true);
+        rcmail.enable_command('calendar-delete', me.calendars[node.id].editable);
+        rcmail.enable_command('calendar-remove', me.calendars[node.id] && me.calendars[node.id].removable);
+      }
+    });
+    calendars_list.addEventListener('insert-item', function(p) {
+      var cal = p.data;
+      if (cal && cal.id) {
+        add_calendar_source(cal);
+
+        // add css classes related to this calendar to document
+        if (cal.css) {
+          $('<style type="text/css"></style>')
+            .html(cal.css)
+            .appendTo('head');
+        }
+      }
+    });
+    calendars_list.addEventListener('subscribe', function(p) {
+      var cal;
+      if ((cal = me.calendars[p.id])) {
+        cal.subscribed = p.subscribed || false;
+        rcmail.http_post('calendar', { action:'subscribe', c:{ id:p.id, active:cal.active?1:0, permanent:cal.subscribed?1:0 } });
+      }
+    });
+    calendars_list.addEventListener('remove', function(p) {
+      if (me.calendars[p.id] && me.calendars[p.id].removable) {
+        me.calendar_remove(me.calendars[p.id]);
+      }
+    });
+    calendars_list.addEventListener('search-complete', function(data) {
+      if (data.length)
+        rcmail.display_message(rcmail.gettext('nrcalendarsfound','calendar').replace('$nr', data.length), 'voice');
+      else
+        rcmail.display_message(rcmail.gettext('nocalendarsfound','calendar'), 'info');
+    });
+    calendars_list.addEventListener('click-item', function(event) {
+      // handle clicks on quickview icon: temprarily add this source and open in quickview
+      if ($(event.target).hasClass('quickview') && event.data) {
+        if (!me.calendars[event.data.id]) {
+          event.data.readonly = true;
+          event.data.active = false;
+          event.data.subscribed = false;
+          add_calendar_source(event.data);
+        }
+        me.quickview(event.data.id, event.shiftKey || event.metaKey || event.ctrlKey);
+        return false;
+      }
+    });
+
+    // init (delegate) event handler on calendar list checkboxes
+    $(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e) {
+      e.stopPropagation();
+
+      if (me.quickview_active) {
+        this.checked = !this.checked;
+        return false;
+      }
+
+      var id = this.value;
+      if (me.calendars[id]) {  // add or remove event source on click
+        var action;
+        if (this.checked) {
+          action = 'addEventSource';
+          me.calendars[id].active = true;
+        }
+        else {
+          action = 'removeEventSource';
+          me.calendars[id].active = false;
+        }
+
+        // adjust checked state of original list item
+        if (calendars_list.is_search()) {
+          calendars_list.container.find('input[value="'+id+'"]').prop('checked', this.checked);
+        }
+
+        // add/remove event source
+        fc.fullCalendar(action, me.calendars[id]);
+        rcmail.http_post('calendar', { action:'subscribe', c:{ id:id, active:me.calendars[id].active?1:0 } });
+      }
+    })
+    .on('keypress', 'input[type=checkbox]', function(e) {
+        // select calendar on <Enter>
+        if (e.keyCode == 13) {
+            calendars_list.select(this.value);
+            return rcube_event.cancel(e);
+        }
+    })
+    // init (delegate) event handler on quickview links
+    .on('click', 'a.quickview', function(e) {
+      var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
+
+      if (calendars_list.is_search())
+        id = id.replace(/--xsR$/, '');
+
+      if (me.calendars[id])
+        me.quickview(id, e.shiftKey || e.metaKey || e.ctrlKey);
+
+      if (!rcube_event.is_keyboard(e) && this.blur)
+        this.blur();
+
+      e.stopPropagation();
+      return false;
+    });
+
+    // register dbl-click handler to open calendar edit dialog
+    $(rcmail.gui_objects.calendarslist).on('dblclick', ':not(.virtual) > .calname', function(e){
+      var id = $(this).closest('li').attr('id').replace(/^rcmlical/, '');
+      me.calendar_edit_dialog(me.calendars[id]);
+    });
+
+    // select default calendar
+    if (rcmail.env.source && this.calendars[rcmail.env.source])
+      this.selected_calendar = rcmail.env.source;
+    else if (settings.default_calendar && this.calendars[settings.default_calendar] && this.calendars[settings.default_calendar].editable)
+      this.selected_calendar = settings.default_calendar;
+    
+    if (this.selected_calendar)
+      this.select_calendar(this.selected_calendar);
+    
+    var viewdate = new Date();
+    if (rcmail.env.date)
+      viewdate.setTime(fromunixtime(rcmail.env.date));
+
+    // add source with iTip event data for rendering
+    if (rcmail.env.itip_events && rcmail.env.itip_events.length) {
+      me.calendars['--invitation--itip'] = {
+        events: rcmail.env.itip_events,
+        className: 'fc-event-cal---invitation--itip',
+        color: '#fff',
+        textColor: '#333',
+        editable: false,
+        rights: 'lrs',
+        attendees: true
+      };
+      event_sources.push(me.calendars['--invitation--itip']);
+    }
+
+    // initalize the fullCalendar plugin
+    var fc = $('#calendar').fullCalendar($.extend({}, fullcalendar_defaults, {
+      header: {
+        right: 'prev,next today',
+        center: 'title',
+        left: 'agendaDay,agendaWeek,month,table'
+      },
+      date: viewdate.getDate(),
+      month: viewdate.getMonth(),
+      year: viewdate.getFullYear(),
+      height: $('#calendar').height(),
+      eventSources: event_sources,
+      selectable: true,
+      selectHelper: false,
+      loading: function(isLoading) {
+        me.is_loading = isLoading;
+        this._rc_loading = rcmail.set_busy(isLoading, 'loading', this._rc_loading);
+        // trigger callback
+        if (!isLoading)
+          me.events_loaded($(this).fullCalendar('clientEvents').length);
+      },
+      // callback for date range selection
+      select: function(start, end, allDay, e, view) {
+        var range_select = (!allDay || start.getDate() != end.getDate())
+        if (dialog_check(e) && range_select)
+          event_edit_dialog('new', { start:start, end:end, allDay:allDay, calendar:me.selected_calendar });
+        if (range_select || ignore_click)
+          view.calendar.unselect();
+      },
+      // callback for clicks in all-day box
+      dayClick: function(date, allDay, e, view) {
+        var now = new Date().getTime();
+        if (now - day_clicked_ts < 400 && day_clicked == date.getTime()) {  // emulate double-click on day
+          var enddate = new Date(); enddate.setTime(date.getTime() + DAY_MS - 60000);
+          return event_edit_dialog('new', { start:date, end:enddate, allDay:allDay, calendar:me.selected_calendar });
+        }
+        
+        if (!ignore_click) {
+          view.calendar.gotoDate(date);
+          if (day_clicked && new Date(day_clicked).getMonth() != date.getMonth())
+            view.calendar.select(date, date, allDay);
+        }
+        day_clicked = date.getTime();
+        day_clicked_ts = now;
+      },
+      // callback when an event was dragged and finally dropped
+      eventDrop: function(event, dayDelta, minuteDelta, allDay, revertFunc) {
+        if (event.end == null || event.end.getTime() < event.start.getTime()) {
+          event.end = new Date(event.start.getTime() + (allDay ? DAY_MS : HOUR_MS));
+        }
+        // moved to all-day section: set times to 12:00 - 13:00
+        if (allDay && !event.allDay) {
+          event.start.setHours(12);
+          event.start.setMinutes(0);
+          event.start.setSeconds(0);
+          event.end.setHours(13);
+          event.end.setMinutes(0);
+          event.end.setSeconds(0);
+        }
+        // moved from all-day section: set times to working hours
+        else if (event.allDay && !allDay) {
+          var newstart = event.start.getTime();
+          revertFunc();  // revert to get original duration
+          var numdays = Math.max(1, Math.round((event.end.getTime() - event.start.getTime()) / DAY_MS)) - 1;
+          event.start = new Date(newstart);
+          event.end = new Date(newstart + numdays * DAY_MS);
+          event.end.setHours(settings['work_end'] || 18);
+          event.end.setMinutes(0);
+          
+          if (event.end.getTime() < event.start.getTime())
+            event.end = new Date(newstart + HOUR_MS);
+        }
+        
+        // send move request to server
+        var data = {
+          id: event.id,
+          calendar: event.calendar,
+          start: date2servertime(event.start),
+          end: date2servertime(event.end),
+          allday: allDay?1:0
+        };
+        update_event_confirm('move', event, data);
+      },
+      // callback for event resizing
+      eventResize: function(event, delta) {
+        // sanitize event dates
+        if (event.allDay)
+          event.start.setHours(12);
+        if (!event.end || event.end.getTime() < event.start.getTime())
+          event.end = new Date(event.start.getTime() + HOUR_MS);
+
+        // send resize request to server
+        var data = {
+          id: event.id,
+          calendar: event.calendar,
+          start: date2servertime(event.start),
+          end: date2servertime(event.end)
+        };
+        update_event_confirm('resize', event, data);
+      },
+      viewDisplay: function(view) {
+        $('#agendaoptions')[view.name == 'table' ? 'show' : 'hide']();
+        if (minical) {
+          window.setTimeout(function(){ minical.datepicker('setDate', fc.fullCalendar('getDate')); }, exec_deferred);
+          if (view.name != current_view)
+            me.view_resize();
+          current_view = view.name;
+          me.update_state();
+        }
+      },
+      viewRender: function(view) {
+        if (fc && view.name == 'month')
+          fc.fullCalendar('option', 'maxHeight', Math.floor((view.element.parent().height()-18) / 6) - 35);
+      }
+    }));
+
+    // if start date is changed, shift end date according to initial duration
+    var shift_enddate = function(dateText) {
+      var newstart = parse_datetime('0', dateText);
+      var newend = new Date(newstart.getTime() + $('#edit-startdate').data('duration') * 1000);
+      $('#edit-enddate').val($.fullCalendar.formatDate(newend, me.settings['date_format']));
+      event_times_changed();
+    };
+
+    // Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+    // Uses the default $.datepicker.iso8601Week() function but takes firstDay setting into account.
+    // This is a temporary fix until http://bugs.jqueryui.com/ticket/8420 is resolved.
+    var iso8601Week = datepicker_settings.calculateWeek = function(date) {
+      var mondayOffset = Math.abs(1 - datepicker_settings.firstDay);
+      return $.datepicker.iso8601Week(new Date(date.getTime() + mondayOffset * 86400000));
+    };
+
+    var minical;
+    var init_calendar_ui = function()
+    {
+      // initialize small calendar widget using jQuery UI datepicker
+      minical = $('#datepicker').datepicker($.extend(datepicker_settings, {
+        inline: true,
+        showWeek: true,
+        changeMonth: true,
+        changeYear: true,
+        onSelect: function(dateText, inst) {
+          ignore_click = true;
+          var d = minical.datepicker('getDate'); //parse_datetime('0:0', dateText);
+          fc.fullCalendar('gotoDate', d).fullCalendar('select', d, d, true);
+        },
+        onChangeMonthYear: function(year, month, inst) {
+          minical.data('year', year).data('month', month);
+        },
+        beforeShowDay: function(date) {
+          var view = fc.fullCalendar('getView');
+          var active = view.visStart && date.getTime() >= view.visStart.getTime() && date.getTime() < view.visEnd.getTime();
+          return [ true, (active ? 'ui-datepicker-activerange ui-datepicker-active-' + view.name : ''), ''];
+        }
+      })) // set event handler for clicks on calendar week cell of the datepicker widget
+        .on('click', 'td.ui-datepicker-week-col', function(e) {
+          var cell = $(e.target);
+          if (e.target.tagName == 'TD') {
+            var base_date = minical.datepicker('getDate');
+            if (minical.data('month'))
+              base_date.setMonth(minical.data('month')-1);
+            if (minical.data('year'))
+              base_date.setYear(minical.data('year'));
+            base_date.setHours(12);
+            base_date.setDate(base_date.getDate() - ((base_date.getDay() + 6) % 7) + datepicker_settings.firstDay);
+            var base_kw = iso8601Week(base_date),
+              target_kw = parseInt(cell.html()),
+              wdiff = target_kw - base_kw;
+            if (wdiff > 10)  // year jump
+              base_date.setYear(base_date.getFullYear() - 1);
+            else if (wdiff < -10)
+              base_date.setYear(base_date.getFullYear() + 1);
+            // select monday of the chosen calendar week
+            var day_off = base_date.getDay() - datepicker_settings.firstDay,
+              date = new Date(base_date.getTime() - day_off * DAY_MS + wdiff * 7 * DAY_MS);
+            fc.fullCalendar('gotoDate', date).fullCalendar('setDate', date).fullCalendar('changeView', 'agendaWeek');
+            minical.datepicker('setDate', date);
+          }
+        });
+
+      minical.find('.ui-datepicker-inline').attr('aria-labelledby', 'aria-label-minical');
+
+      if (rcmail.env.date) {
+        var viewdate = new Date();
+        viewdate.setTime(fromunixtime(rcmail.env.date));
+        minical.datepicker('setDate', viewdate);
+      }
+
+      // init event dialog
+      $('#eventtabs').tabs({
+        activate: function(event, ui) {
+          // newPanel.selector for jQuery-UI 1.10, newPanel.attr('id') for jQuery-UI 1.12
+          var tab = String(ui.newPanel.selector || ui.newPanel.attr('id'))
+              .replace(/^#?event-panel-/, '').replace(/s$/, '');
+
+          var has_real_attendee = function(attendees) {
+              for (var i=0; i < (attendees ? attendees.length : 0); i++) {
+                if (attendees[i].cutype != 'RESOURCE')
+                  return true;
+              }
+            };
+
+          if (tab == 'attendee' || tab == 'resource') {
+            if (!rcube_event.is_keyboard(event))
+              $('#edit-'+tab+'-name').select();
+            // update free-busy status if needed
+            if (freebusy_ui.needsupdate && me.selected_event)
+              update_freebusy_status(me.selected_event);
+            // add current user as organizer if non added yet
+            if (tab == 'attendee' && !has_real_attendee(event_attendees)) {
+              add_attendee($.extend({ role:'ORGANIZER' }, settings.identity));
+              $('#edit-attendees-form .attendees-invitebox').show();
+            }
+          }
+          // reset autocompletion on tab change (#3389)
+          rcmail.ksearch_blur();
+        }
+      });
+      $('#edit-enddate').datepicker(datepicker_settings);
+      $('#edit-startdate').datepicker(datepicker_settings).datepicker('option', 'onSelect', shift_enddate).change(function(){ shift_enddate(this.value); });
+      $('#edit-enddate').datepicker('option', 'onSelect', event_times_changed).change(event_times_changed);
+      $('#edit-allday').click(function(){ $('#edit-starttime, #edit-endtime')[(this.checked?'hide':'show')](); event_times_changed(); });
+
+      // configure drop-down menu on time input fields based on jquery UI autocomplete
+      $('#edit-starttime, #edit-endtime, #eventedit input.edit-alarm-time').each(function() {
+        me.init_time_autocomplete(this, {
+          container: '#eventedit',
+          change: event_times_changed
+        });
+      });
+
+      // adjust end time when changing start
+      $('#edit-starttime').change(function(e) {
+        var dstart = $('#edit-startdate'),
+          newstart = parse_datetime(this.value, dstart.val()),
+          newend = new Date(newstart.getTime() + dstart.data('duration') * 1000);
+        $('#edit-endtime').val($.fullCalendar.formatDate(newend, me.settings['time_format']));
+        $('#edit-enddate').val($.fullCalendar.formatDate(newend, me.settings['date_format']));
+        event_times_changed();
+      });
+
+      // register events on alarms and recurrence fields
+      me.init_alarms_edit('#edit-alarms');
+      me.init_recurrence_edit('#eventedit');
+
+      // reload free-busy status when changing the organizer identity
+      $('#eventedit').on('change', '#edit-identities-list', function(e) {
+        var email = settings.identities[$(this).val()],
+          icon = $(this).closest('tr').find('img.availabilityicon');
+
+        if (email && icon.length) {
+          icon.attr('data-email', email);
+          check_freebusy_status(icon, email, me.selected_event);
+        }
+      });
+
+      $('#event-export-startdate').datepicker(datepicker_settings);
+
+      // init attendees autocompletion
+      var ac_props;
+      // parallel autocompletion
+      if (rcmail.env.autocomplete_threads > 0) {
+        ac_props = {
+          threads: rcmail.env.autocomplete_threads,
+          sources: rcmail.env.autocomplete_sources
+        };
+      }
+      rcmail.init_address_input_events($('#edit-attendee-name'), ac_props);
+      rcmail.addEventListener('autocomplete_insert', function(e) {
+        var success = false;
+        if (e.field.name == 'participant') {
+          success = add_attendees(e.insert, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:(e.data && e.data.type == 'group' ? 'GROUP' : 'INDIVIDUAL') });
+        }
+        else if (e.field.name == 'resource' && e.data && e.data.email) {
+          success = add_attendee($.extend(e.data, { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' }));
+        }
+        if (e.field && success) {
+          e.field.value = '';
+        }
+      });
+
+      $('#edit-attendee-add').click(function(){
+        var input = $('#edit-attendee-name');
+        rcmail.ksearch_blur();
+        if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'INDIVIDUAL' })) {
+          input.val('');
+        }
+      });
+
+      rcmail.init_address_input_events($('#edit-resource-name'), { action:'calendar/resources-autocomplete' });
+
+      $('#edit-resource-add').click(function(){
+        var input = $('#edit-resource-name');
+        rcmail.ksearch_blur();
+        if (add_attendees(input.val(), { role:'REQ-PARTICIPANT', status:'NEEDS-ACTION', cutype:'RESOURCE' })) {
+          input.val('');
+        }
+      });
+      
+      $('#edit-resource-find').click(function(){
+        event_resources_dialog();
+        return false;
+      });
+
+      // handle change of "send invitations" checkbox
+      $('#edit-attendees-invite').change(function() {
+        $('#edit-attendees-donotify,input.edit-attendee-reply').prop('checked', this.checked);
+        // hide/show comment field
+        $('#eventedit .attendees-commentbox')[this.checked ? 'show' : 'hide']();
+      });
+
+      // delegate change event to "send invitations" checkbox
+      $('#edit-attendees-donotify').change(function() {
+        $('#edit-attendees-invite').click();
+        return false;
+      });
+
+      $('#edit-attendee-schedule').click(function(){
+        event_freebusy_dialog();
+      });
+
+      $('#shedule-freebusy-prev').html(bw.ie6 ? '&lt;&lt;' : '&#9668;').button().click(function(){ render_freebusy_grid(-1); });
+      $('#shedule-freebusy-next').html(bw.ie6 ? '&gt;&gt;' : '&#9658;').button().click(function(){ render_freebusy_grid(1); }).parent().buttonset();
+
+      $('#shedule-find-prev').button().click(function(){ freebusy_find_slot(-1); });
+      $('#shedule-find-next').button().click(function(){ freebusy_find_slot(1); });
+
+      $('#schedule-freebusy-workinghours').click(function(){
+        freebusy_ui.workinhoursonly = this.checked;
+        $('#workinghourscss').remove();
+        if (this.checked)
+          $('<style type="text/css" id="workinghourscss"> td.offhours { opacity:0.3; filter:alpha(opacity=30) } </style>').appendTo('head');
+      });
+
+      $('#event-rsvp input.button').click(function(e) {
+        event_rsvp(this)
+      });
+
+      $('#eventedit input.edit-recurring-savemode').change(function(e) {
+        var sel = $('input.edit-recurring-savemode:checked').val(),
+          disabled = sel == 'current' || sel == 'future';
+        $('#event-panel-recurrence input, #event-panel-recurrence select, #event-panel-attachments input').prop('disabled', disabled);
+        $('#event-panel-recurrence, #event-panel-attachments')[(disabled?'addClass':'removeClass')]('disabled');
+      })
+
+      $('#eventshow .changersvp').click(function(e) {
+        var d = $('#eventshow'),
+          h = -$(this).closest('.event-line').toggle().height();
+        $('#event-rsvp').slideDown(300, function() {
+          h += $(this).height();
+          me.dialog_resize(d.get(0), d.height() + h, d.outerWidth() - 50);
+        });
+        return false;
+      })
+
+      // register click handler for message links
+      $('#edit-event-links, #event-links').on('click', 'li a.messagelink', function(e) {
+        rcmail.open_window(this.href);
+        if (!rcube_event.is_keyboard(e) && this.blur)
+          this.blur();
+        return false;
+      });
+
+      // register click handler for message delete buttons
+      $('#edit-event-links').on('click', 'li a.delete', function(e) {
+          remove_link(e.target);
+          return false;
+      });
+
+      $('#agenda-listrange').change(function(e){
+        settings['agenda_range'] = parseInt($(this).val());
+        fc.fullCalendar('option', 'listRange', settings['agenda_range']).fullCalendar('render');
+        // TODO: save new settings in prefs
+      }).val(settings['agenda_range']);
+
+      $('#agenda-listsections').change(function(e){
+        settings['agenda_sections'] = $(this).val();
+        fc.fullCalendar('option', 'listSections', settings['agenda_sections']).fullCalendar('render');
+        // TODO: save new settings in prefs
+      }).val(fc.fullCalendar('option', 'listSections'));
+
+      // hide event dialog when clicking somewhere into document
+      $(document).bind('mousedown', dialog_check);
+
+      rcmail.set_busy(false, 'loading', ui_loading);
+    }
+
+    // initialize more UI elements (deferred)
+    window.setTimeout(init_calendar_ui, exec_deferred);
+
+    // fetch counts for some calendars
+    fetch_counts();
+
+    // add proprietary css styles if not IE
+    if (!bw.ie)
+      $('div.fc-content').addClass('rcube-fc-content');
+
+    // IE supresses 2nd click event when double-clicking
+    if (bw.ie && bw.vendver < 9) {
+      $('div.fc-content').bind('dblclick', function(e){
+        if (!$(this).hasClass('fc-widget-header') && fc.fullCalendar('getView').name != 'table') {
+          var date = fc.fullCalendar('getDate');
+          var enddate = new Date(); enddate.setTime(date.getTime() + DAY_MS - 60000);
+          event_edit_dialog('new', { start:date, end:enddate, allDay:true, calendar:me.selected_calendar });
+        }
+      });
+    }
+} // end rcube_calendar class
+
+
+/* calendar plugin initialization */
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+  // configure toolbar buttons
+  rcmail.register_command('addevent', function(){ cal.add_event(); }, true);
+  rcmail.register_command('print', function(){ cal.print_calendars(); }, true);
+
+  // configure list operations
+  rcmail.register_command('calendar-create', function(){ cal.calendar_edit_dialog(null); }, true);
+  rcmail.register_command('calendar-edit', function(){ cal.calendar_edit_dialog(cal.calendars[cal.selected_calendar]); }, false);
+  rcmail.register_command('calendar-remove', function(){ cal.calendar_remove(cal.calendars[cal.selected_calendar]); }, false);
+  rcmail.register_command('calendar-delete', function(){ cal.calendar_delete(cal.calendars[cal.selected_calendar]); }, false);
+  rcmail.register_command('events-import', function(){ cal.import_events(cal.calendars[cal.selected_calendar]); }, true);
+  rcmail.register_command('calendar-showurl', function(){ cal.showurl(cal.calendars[cal.selected_calendar]); }, false);
+  rcmail.register_command('event-download', function(){ cal.event_download(cal.selected_event); }, true);
+  rcmail.register_command('event-sendbymail', function(p, obj, e){ cal.event_sendbymail(cal.selected_event, e); }, true);
+  rcmail.register_command('event-copy', function(){ cal.event_copy(cal.selected_event); }, true);
+  rcmail.register_command('event-history', function(p, obj, e){ cal.event_history_dialog(cal.selected_event); }, false);
+
+  // search and export events
+  rcmail.register_command('export', function(){ cal.export_events(cal.calendars[cal.selected_calendar]); }, true);
+  rcmail.register_command('search', function(){ cal.quicksearch(); }, true);
+  rcmail.register_command('reset-search', function(){ cal.reset_quicksearch(); }, true);
+
+  // resource invitation dialog
+  rcmail.register_command('search-resource', function(){ cal.resource_search(); }, true);
+  rcmail.register_command('reset-resource-search', function(){ cal.reset_resource_search(); }, true);
+  rcmail.register_command('add-resource', function(){ cal.add_resource2event(); }, false);
+
+  // register callback commands
+  rcmail.addEventListener('plugin.refresh_source', function(data) { cal.calendar_refresh_source(data); });
+  rcmail.addEventListener('plugin.destroy_source', function(p){ cal.calendar_destroy_source(p.id); });
+  rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); });
+  rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); });
+  rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); });
+  rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); });
+  rcmail.addEventListener('plugin.update_counts', function(p){ cal.update_counts(p); });
+  rcmail.addEventListener('plugin.reload_view', function(p){ cal.reload_view(p); });
+  rcmail.addEventListener('plugin.resource_data', function(p){ cal.resource_data_load(p); });
+  rcmail.addEventListener('plugin.resource_owner', function(p){ cal.resource_owner_load(p); });
+  rcmail.addEventListener('plugin.render_event_changelog', function(data){ cal.render_event_changelog(data); });
+  rcmail.addEventListener('plugin.event_show_diff', function(data){ cal.event_show_diff(data); });
+  rcmail.addEventListener('plugin.close_history_dialog', function(data){ cal.close_history_dialog(); });
+  rcmail.addEventListener('plugin.event_show_revision', function(data){ cal.event_show_dialog(data, null, true); });
+  rcmail.addEventListener('plugin.itip_message_processed', function(data){ cal.itip_message_processed(data); });
+  rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); });
+
+  // let's go
+  var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings));
+
+  $(window).resize(function(e) {
+    // check target due to bugs in jquery
+    // http://bugs.jqueryui.com/ticket/7514
+    // http://bugs.jquery.com/ticket/9841
+    if (e.target == window) {
+      cal.view_resize();
+    }
+  }).resize();
+
+  // show calendars list when ready
+  $('#calendars').css('visibility', 'inherit');
+
+  // show toolbar
+  $('#toolbar').show();
+
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/config.inc.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,169 @@
+<?php
+/*
+ +-------------------------------------------------------------------------+
+ | Configuration for the Calendar plugin                                   |
+ |                                                                         |
+ | Copyright (C) 2010, Lazlo Westerhof - Netherlands                       |
+ | Copyright (C) 2011-2014, Kolab Systems AG                               |
+ |                                                                         |
+ | 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/>.    |
+ |                                                                         |
+ +-------------------------------------------------------------------------+
+ | Author: Lazlo Westerhof <hello@lazlo.me>                                |
+ |         Thomas Bruederli <bruederli@kolabsys.com>                       |
+ +-------------------------------------------------------------------------+
+*/
+
+// backend type (database, google, kolab)
+$config['calendar_driver'] = "database";
+
+// default calendar view (agendaDay, agendaWeek, month)
+$config['calendar_default_view'] = "agendaWeek";
+
+// show a birthdays calendar from the user's address book(s)
+$config['calendar_contact_birthdays'] = false;
+
+// mapping of Roundcube date formats to calendar formats (long/short/agenda)
+// should be in sync with 'date_formats' in main config
+$config['calendar_date_format_sets'] = array(
+  'yyyy-MM-dd' => array('MMM d yyyy',   'M-d',  'ddd MM-dd'),
+  'dd-MM-yyyy' => array('d MMM yyyy',   'd-M',  'ddd dd-MM'),
+  'yyyy/MM/dd' => array('MMM d yyyy',   'M/d',  'ddd MM/dd'),
+  'MM/dd/yyyy' => array('MMM d yyyy',   'M/d',  'ddd MM/dd'),
+  'dd/MM/yyyy' => array('d MMM yyyy',   'd/M',  'ddd dd/MM'),
+  'dd.MM.yyyy' => array('dd. MMM yyyy', 'd.M',  'ddd dd.MM.'),
+  'd.M.yyyy'   => array('d. MMM yyyy',  'd.M',  'ddd d.MM.'),
+);
+
+// general date format (only set if different from default date format and not user configurable)
+// $config['calendar_date_format'] = "yyyy-MM-dd";
+
+// time format  (only set if different from default date format)
+// $config['calendar_time_format'] = "HH:mm";
+
+// short date format (used for column titles)
+// $config['calendar_date_short'] = 'M-d';
+
+// long date format (used for calendar title)
+// $config['calendar_date_long'] = 'MMM d yyyy';
+
+// date format used for agenda view
+// $config['calendar_date_agenda'] = 'ddd MM-dd';
+
+// timeslots per hour (1, 2, 3, 4, 6)
+$config['calendar_timeslots'] = 2;
+
+// show this number of days in agenda view
+$config['calendar_agenda_range'] = 60;
+
+// first day of the week (0-6)
+$config['calendar_first_day'] = 1;
+
+// first hour of the calendar (0-23)
+$config['calendar_first_hour'] = 6;
+
+// working hours begin
+$config['calendar_work_start'] = 6;
+
+// working hours end
+$config['calendar_work_end'] = 18;
+
+// show line at current time of the day
+$config['calendar_time_indicator'] = true;
+
+// default alarm settings for new events.
+// this is only a preset when a new event dialog opens
+// possible values are <empty>, DISPLAY, EMAIL
+$config['calendar_default_alarm_type'] = '';
+
+// default alarm offset for new events.
+// use ical-style offset values like "-1H" (one hour before) or "+30M" (30 minutes after)
+$config['calendar_default_alarm_offset'] = '-15M';
+
+// how to colorize events:
+// 0: according to calendar color
+// 1: according to category color
+// 2: calendar for outer, category for inner color
+// 3: category for outer, calendar for inner color
+$config['calendar_event_coloring'] = 0;
+
+// event categories
+$config['calendar_categories'] = array(
+  'Personal' => 'c0c0c0',
+      'Work' => 'ff0000',
+    'Family' => '00ff00',
+   'Holiday' => 'ff6600',
+);
+
+// enable users to invite/edit attendees for shared events organized by others
+$config['calendar_allow_invite_shared'] = false;
+
+// allow users to accecpt iTip invitations who are no explicitly listed as attendee.
+// this can be the case if invitations are sent to mailing lists or alias email addresses.
+$config['calendar_allow_itip_uninvited'] = true;
+
+// controls the visibility/default of the checkbox controlling the sending of iTip invitations
+// 0 = hidden  + disabled
+// 1 = hidden  + active
+// 2 = visible + unchecked
+// 3 = visible + active
+$config['calendar_itip_send_option'] = 3;
+
+// Action taken after iTip request is handled. Possible values:
+// 0 - no action
+// 1 - move to Trash
+// 2 - delete the message
+// 3 - flag as deleted
+// folder_name - move the message to the specified folder
+$config['calendar_itip_after_action'] = 0;
+
+// enable asynchronous free-busy triggering after data changed
+$config['calendar_freebusy_trigger'] = false;
+
+// free-busy information will be displayed for user calendars if available
+// 0 - no free-busy information
+// 1 - enabled in all views
+// 2 - only in quickview
+$config['calendar_include_freebusy_data'] = 1;
+
+// SMTP server host used to send (anonymous) itip messages.
+// Set to '' in order to use PHP's mail() function for email delivery.
+// To override the SMTP port or connection method, provide a full URL like 'tls://somehost:587'
+$config['calendar_itip_smtp_server'] = null;
+
+// SMTP username used to send (anonymous) itip messages
+$config['calendar_itip_smtp_user'] = 'smtpauth';
+
+// SMTP password used to send (anonymous) itip messages
+$config['calendar_itip_smtp_pass'] = '123456';
+
+// show virtual invitation calendars (Kolab driver only)
+$config['kolab_invitation_calendars'] = false;
+
+// Base URL to build fully qualified URIs to access calendars via CALDAV
+// The following replacement variables are supported:
+// %h - Current HTTP host
+// %u - Current webmail user name
+// %n - Calendar name
+// %i - Calendar UUID
+// $config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
+
+// Driver to provide a resource directory ('ldap' is the only implementation yet).
+// Leave empty or commented to disable resources support.
+// $config['calendar_resources_driver'] = 'ldap';
+
+// LDAP directory configuration to find avilable resources for events
+// $config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */);
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/config.inc.php.dist	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,169 @@
+<?php
+/*
+ +-------------------------------------------------------------------------+
+ | Configuration for the Calendar plugin                                   |
+ |                                                                         |
+ | Copyright (C) 2010, Lazlo Westerhof - Netherlands                       |
+ | Copyright (C) 2011-2014, Kolab Systems AG                               |
+ |                                                                         |
+ | 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/>.    |
+ |                                                                         |
+ +-------------------------------------------------------------------------+
+ | Author: Lazlo Westerhof <hello@lazlo.me>                                |
+ |         Thomas Bruederli <bruederli@kolabsys.com>                       |
+ +-------------------------------------------------------------------------+
+*/
+
+// backend type (database, google, kolab)
+$config['calendar_driver'] = "database";
+
+// default calendar view (agendaDay, agendaWeek, month)
+$config['calendar_default_view'] = "agendaWeek";
+
+// show a birthdays calendar from the user's address book(s)
+$config['calendar_contact_birthdays'] = false;
+
+// mapping of Roundcube date formats to calendar formats (long/short/agenda)
+// should be in sync with 'date_formats' in main config
+$config['calendar_date_format_sets'] = array(
+  'yyyy-MM-dd' => array('MMM d yyyy',   'M-d',  'ddd MM-dd'),
+  'dd-MM-yyyy' => array('d MMM yyyy',   'd-M',  'ddd dd-MM'),
+  'yyyy/MM/dd' => array('MMM d yyyy',   'M/d',  'ddd MM/dd'),
+  'MM/dd/yyyy' => array('MMM d yyyy',   'M/d',  'ddd MM/dd'),
+  'dd/MM/yyyy' => array('d MMM yyyy',   'd/M',  'ddd dd/MM'),
+  'dd.MM.yyyy' => array('dd. MMM yyyy', 'd.M',  'ddd dd.MM.'),
+  'd.M.yyyy'   => array('d. MMM yyyy',  'd.M',  'ddd d.MM.'),
+);
+
+// general date format (only set if different from default date format and not user configurable)
+// $config['calendar_date_format'] = "yyyy-MM-dd";
+
+// time format  (only set if different from default date format)
+// $config['calendar_time_format'] = "HH:mm";
+
+// short date format (used for column titles)
+// $config['calendar_date_short'] = 'M-d';
+
+// long date format (used for calendar title)
+// $config['calendar_date_long'] = 'MMM d yyyy';
+
+// date format used for agenda view
+// $config['calendar_date_agenda'] = 'ddd MM-dd';
+
+// timeslots per hour (1, 2, 3, 4, 6)
+$config['calendar_timeslots'] = 2;
+
+// show this number of days in agenda view
+$config['calendar_agenda_range'] = 60;
+
+// first day of the week (0-6)
+$config['calendar_first_day'] = 1;
+
+// first hour of the calendar (0-23)
+$config['calendar_first_hour'] = 6;
+
+// working hours begin
+$config['calendar_work_start'] = 6;
+
+// working hours end
+$config['calendar_work_end'] = 18;
+
+// show line at current time of the day
+$config['calendar_time_indicator'] = true;
+
+// default alarm settings for new events.
+// this is only a preset when a new event dialog opens
+// possible values are <empty>, DISPLAY, EMAIL
+$config['calendar_default_alarm_type'] = '';
+
+// default alarm offset for new events.
+// use ical-style offset values like "-1H" (one hour before) or "+30M" (30 minutes after)
+$config['calendar_default_alarm_offset'] = '-15M';
+
+// how to colorize events:
+// 0: according to calendar color
+// 1: according to category color
+// 2: calendar for outer, category for inner color
+// 3: category for outer, calendar for inner color
+$config['calendar_event_coloring'] = 0;
+
+// event categories
+$config['calendar_categories'] = array(
+  'Personal' => 'c0c0c0',
+      'Work' => 'ff0000',
+    'Family' => '00ff00',
+   'Holiday' => 'ff6600',
+);
+
+// enable users to invite/edit attendees for shared events organized by others
+$config['calendar_allow_invite_shared'] = false;
+
+// allow users to accecpt iTip invitations who are no explicitly listed as attendee.
+// this can be the case if invitations are sent to mailing lists or alias email addresses.
+$config['calendar_allow_itip_uninvited'] = true;
+
+// controls the visibility/default of the checkbox controlling the sending of iTip invitations
+// 0 = hidden  + disabled
+// 1 = hidden  + active
+// 2 = visible + unchecked
+// 3 = visible + active
+$config['calendar_itip_send_option'] = 3;
+
+// Action taken after iTip request is handled. Possible values:
+// 0 - no action
+// 1 - move to Trash
+// 2 - delete the message
+// 3 - flag as deleted
+// folder_name - move the message to the specified folder
+$config['calendar_itip_after_action'] = 0;
+
+// enable asynchronous free-busy triggering after data changed
+$config['calendar_freebusy_trigger'] = false;
+
+// free-busy information will be displayed for user calendars if available
+// 0 - no free-busy information
+// 1 - enabled in all views
+// 2 - only in quickview
+$config['calendar_include_freebusy_data'] = 1;
+
+// SMTP server host used to send (anonymous) itip messages.
+// Set to '' in order to use PHP's mail() function for email delivery.
+// To override the SMTP port or connection method, provide a full URL like 'tls://somehost:587'
+$config['calendar_itip_smtp_server'] = null;
+
+// SMTP username used to send (anonymous) itip messages
+$config['calendar_itip_smtp_user'] = 'smtpauth';
+
+// SMTP password used to send (anonymous) itip messages
+$config['calendar_itip_smtp_pass'] = '123456';
+
+// show virtual invitation calendars (Kolab driver only)
+$config['kolab_invitation_calendars'] = false;
+
+// Base URL to build fully qualified URIs to access calendars via CALDAV
+// The following replacement variables are supported:
+// %h - Current HTTP host
+// %u - Current webmail user name
+// %n - Calendar name
+// %i - Calendar UUID
+// $config['calendar_caldav_url'] = 'http://%h/iRony/calendars/%u/%i';
+
+// Driver to provide a resource directory ('ldap' is the only implementation yet).
+// Leave empty or commented to disable resources support.
+// $config['calendar_resources_driver'] = 'ldap';
+
+// LDAP directory configuration to find avilable resources for events
+// $config['calendar_resources_directory'] = array(/* ldap_public-like address book configuration */);
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/calendar_driver.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,831 @@
+<?php
+
+/**
+ * Driver interface for the Calendar plugin
+ *
+ * @version @package_version@
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+
+/**
+ * Struct of an internal event object how it is passed from/to the driver classes:
+ *
+ *  $event = array(
+ *            'id' => 'Event ID used for editing',
+ *           'uid' => 'Unique identifier of this event',
+ *      'calendar' => 'Calendar identifier to add event to or where the event is stored',
+ *         'start' => DateTime,  // Event start date/time as DateTime object
+ *           'end' => DateTime,  // Event end date/time as DateTime object
+ *        'allday' => true|false,  // Boolean flag if this is an all-day event
+ *       'changed' => DateTime,    // Last modification date of event
+ *         'title' => 'Event title/summary',
+ *      'location' => 'Location string',
+ *   'description' => 'Event description',
+ *           'url' => 'URL to more information',
+ *    'recurrence' => array(   // Recurrence definition according to iCalendar (RFC 2445) specification as list of key-value pairs
+ *            'FREQ' => 'DAILY|WEEKLY|MONTHLY|YEARLY',
+ *        'INTERVAL' => 1...n,
+ *           'UNTIL' => DateTime,
+ *           'COUNT' => 1..n,   // number of times
+ *                      // + more properties (see http://www.kanzaki.com/docs/ical/recur.html)
+ *          'EXDATE' => array(),  // list of DateTime objects of exception Dates/Times
+ *      'EXCEPTIONS' => array(<event>),  list of event objects which denote exceptions in the recurrence chain
+ *    ),
+ * 'recurrence_id' => 'ID of the recurrence group',   // usually the ID of the starting event
+ *     '_instance' => 'ID of the recurring instance',   // identifies an instance within a recurrence chain
+ *    'categories' => 'Event category',
+ *     'free_busy' => 'free|busy|outofoffice|tentative',  // Show time as
+ *        'status' => 'TENTATIVE|CONFIRMED|CANCELLED',    // event status according to RFC 2445
+ *      'priority' => 0-9,     // Event priority (0=undefined, 1=highest, 9=lowest)
+ *   'sensitivity' => 'public|private|confidential',   // Event sensitivity
+ *        'alarms' => '-15M:DISPLAY',  // DEPRECATED Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before event)
+ *       'valarms' => array(           // List of reminders (new format), each represented as a hash array:
+ *                  array(
+ *                     'trigger' => '-PT90M',     // ISO 8601 period string prefixed with '+' or '-', or DateTime object
+ *                      'action' => 'DISPLAY|EMAIL|AUDIO',
+ *                    'duration' => 'PT15M',      // ISO 8601 period string
+ *                      'repeat' => 0,            // number of repetitions
+ *                 'description' => '',        // text to display for DISPLAY actions
+ *                     'summary' => '',        // message text for EMAIL actions
+ *                   'attendees' => array(),   // list of email addresses to receive alarm messages
+ *                  ),
+ *   ),
+ *   'attachments' => array(   // List of attachments
+ *            'name' => 'File name',
+ *        'mimetype' => 'Content type',
+ *            'size' => 1..n, // in bytes
+ *              'id' => 'Attachment identifier'
+ *   ),
+ * 'deleted_attachments' => array(), // array of attachment identifiers to delete when event is updated
+ *     'attendees' => array(   // List of event participants
+ *            'name' => 'Participant name',
+ *           'email' => 'Participant e-mail address',  // used as identifier
+ *            'role' => 'ORGANIZER|REQ-PARTICIPANT|OPT-PARTICIPANT|CHAIR',
+ *          'status' => 'NEEDS-ACTION|UNKNOWN|ACCEPTED|TENTATIVE|DECLINED'
+ *            'rsvp' => true|false,
+ *    ),
+ *
+ *     '_savemode' => 'all|future|current|new',   // How changes on recurring event should be handled
+ *       '_notify' => true|false,  // whether to notify event attendees about changes
+ * '_fromcalendar' => 'Calendar identifier where the event was stored before',
+ *  );
+ */
+
+/**
+ * Interface definition for calendar driver classes
+ */
+abstract class calendar_driver
+{
+  const FILTER_ALL           = 0;
+  const FILTER_WRITEABLE     = 1;
+  const FILTER_INSERTABLE    = 2;
+  const FILTER_ACTIVE        = 4;
+  const FILTER_PERSONAL      = 8;
+  const FILTER_PRIVATE       = 16;
+  const FILTER_CONFIDENTIAL  = 32;
+  const FILTER_SHARED        = 64;
+  const BIRTHDAY_CALENDAR_ID = '__bdays__';
+
+  // features supported by backend
+  public $alarms = false;
+  public $attendees = false;
+  public $freebusy = false;
+  public $attachments = false;
+  public $undelete = false;
+  public $history = false;
+  public $categoriesimmutable = false;
+  public $alarm_types = array('DISPLAY');
+  public $alarm_absolute = true;
+  public $last_error;
+
+  protected $default_categories = array(
+    'Personal' => 'c0c0c0',
+    'Work'     => 'ff0000',
+    'Family'   => '00ff00',
+    'Holiday'  => 'ff6600',
+  );
+
+  /**
+   * Get a list of available calendars from this source
+   *
+   * @param integer Bitmask defining filter criterias.
+   *          See FILTER_* constants for possible values.
+   * @return array List of calendars
+   */
+  abstract function list_calendars($filter = 0);
+
+  /**
+   * Create a new calendar assigned to the current user
+   *
+   * @param array Hash array with calendar properties
+   *        name: Calendar name
+   *       color: The color of the calendar
+   *  showalarms: True if alarms are enabled
+   * @return mixed ID of the calendar on success, False on error
+   */
+  abstract function create_calendar($prop);
+
+  /**
+   * Update properties of an existing calendar
+   *
+   * @param array Hash array with calendar properties
+   *          id: Calendar Identifier
+   *        name: Calendar name
+   *       color: The color of the calendar
+   *  showalarms: True if alarms are enabled (if supported)
+   * @return boolean True on success, Fales on failure
+   */
+  abstract function edit_calendar($prop);
+  
+  /**
+   * Set active/subscribed state of a calendar
+   *
+   * @param array Hash array with calendar properties
+   *          id: Calendar Identifier
+   *      active: True if calendar is active, false if not
+   * @return boolean True on success, Fales on failure
+   */
+  abstract function subscribe_calendar($prop);
+
+  /**
+   * Delete the given calendar with all its contents
+   *
+   * @param array Hash array with calendar properties
+   *      id: Calendar Identifier
+   * @return boolean True on success, Fales on failure
+   */
+  abstract function delete_calendar($prop);
+
+  /**
+   * Search for shared or otherwise not listed calendars the user has access
+   *
+   * @param string Search string
+   * @param string Section/source to search
+   * @return array List of calendars
+   */
+  abstract function search_calendars($query, $source);
+
+  /**
+   * Add a single event to the database
+   *
+   * @param array Hash array with event properties (see header of this file)
+   * @return mixed New event ID on success, False on error
+   */
+  abstract function new_event($event);
+
+  /**
+   * Update an event entry with the given data
+   *
+   * @param array Hash array with event properties (see header of this file)
+   * @return boolean True on success, False on error
+   */
+  abstract function edit_event($event);
+
+  /**
+   * Extended event editing with possible changes to the argument
+   *
+   * @param array  Hash array with event properties
+   * @param string New participant status
+   * @param array  List of hash arrays with updated attendees
+   * @return boolean True on success, False on error
+   */
+  public function edit_rsvp(&$event, $status, $attendees)
+  {
+    return $this->edit_event($event);
+  }
+
+  /**
+   * Update the participant status for the given attendee
+   *
+   * @param array  Hash array with event properties
+   * @param array  List of hash arrays each represeting an updated attendee
+   * @return boolean True on success, False on error
+   */
+  public function update_attendees(&$event, $attendees)
+  {
+    return $this->edit_event($event);
+  }
+
+  /**
+   * Move a single event
+   *
+   * @param array Hash array with event properties:
+   *      id: Event identifier
+   *   start: Event start date/time as DateTime object
+   *     end: Event end date/time as DateTime object
+   *  allday: Boolean flag if this is an all-day event
+   * @return boolean True on success, False on error
+   */
+  abstract function move_event($event);
+
+  /**
+   * Resize a single event
+   *
+   * @param array Hash array with event properties:
+   *      id: Event identifier
+   *   start: Event start date/time as DateTime object with timezone
+   *     end: Event end date/time as DateTime object with timezone
+   * @return boolean True on success, False on error
+   */
+  abstract function resize_event($event);
+
+  /**
+   * Remove a single event from the database
+   *
+   * @param array   Hash array with event properties:
+   *      id: Event identifier
+   * @param boolean Remove event irreversible (mark as deleted otherwise,
+   *                if supported by the backend)
+   *
+   * @return boolean True on success, False on error
+   */
+  abstract function remove_event($event, $force = true);
+
+  /**
+   * Restores a single deleted event (if supported)
+   *
+   * @param array Hash array with event properties:
+   *      id: Event identifier
+   *
+   * @return boolean True on success, False on error
+   */
+  public function restore_event($event)
+  {
+    return false;
+  }
+
+  /**
+   * Return data of a single event
+   *
+   * @param mixed  UID string or hash array with event properties:
+   *         id: Event identifier
+   *        uid: Event UID
+   *  _instance: Instance identifier in combination with uid (optional)
+   *   calendar: Calendar identifier (optional)
+   * @param integer Bitmask defining the scope to search events in.
+   *          See FILTER_* constants for possible values.
+   * @param boolean If true, recurrence exceptions shall be added
+   *
+   * @return array Event object as hash array
+   */
+  abstract function get_event($event, $scope = 0, $full = false);
+
+  /**
+   * Get events from source.
+   *
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  mixed   List of calendar IDs to load events from (either as array or comma-separated string)
+   * @param  boolean Include virtual/recurring events (optional)
+   * @param  integer Only list events modified since this time (unix timestamp)
+   * @return array A list of event objects (see header of this file for struct of an event)
+   */
+  abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null);
+
+  /**
+   * Get number of events in the given calendar
+   *
+   * @param  mixed   List of calendar IDs to count events (either as array or comma-separated string)
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @return array   Hash array with counts grouped by calendar ID
+   */
+  abstract function count_events($calendars, $start, $end = null);
+
+  /**
+   * Get a list of pending alarms to be displayed to the user
+   *
+   * @param  integer Current time (unix timestamp)
+   * @param  mixed   List of calendar IDs to show alarms for (either as array or comma-separated string)
+   * @return array A list of alarms, each encoded as hash array:
+   *         id: Event identifier
+   *        uid: Unique identifier of this event
+   *      start: Event start date/time as DateTime object
+   *        end: Event end date/time as DateTime object
+   *     allday: Boolean flag if this is an all-day event
+   *      title: Event title/summary
+   *   location: Location string
+   */
+  abstract function pending_alarms($time, $calendars = null);
+
+  /**
+   * (User) feedback after showing an alarm notification
+   * This should mark the alarm as 'shown' or snooze it for the given amount of time
+   *
+   * @param  string  Event identifier
+   * @param  integer Suspend the alarm for this number of seconds
+   */
+  abstract function dismiss_alarm($event_id, $snooze = 0);
+
+  /**
+   * Check the given event object for validity
+   *
+   * @param array Event object as hash array
+   * @return boolean True if valid, false if not
+   */
+  public function validate($event)
+  {
+    $valid = true;
+
+    if (!is_object($event['start']) || !is_a($event['start'], 'DateTime'))
+      $valid = false;
+    if (!is_object($event['end']) || !is_a($event['end'], 'DateTime'))
+      $valid = false;
+
+    return $valid;
+  }
+
+
+  /**
+   * Get list of event's attachments.
+   * Drivers can return list of attachments as event property.
+   * If they will do not do this list_attachments() method will be used.
+   *
+   * @param array $event Hash array with event properties:
+   *         id: Event identifier
+   *   calendar: Calendar identifier
+   *
+   * @return array List of attachments, each as hash array:
+   *         id: Attachment identifier
+   *       name: Attachment name
+   *   mimetype: MIME content type of the attachment
+   *       size: Attachment size
+   */
+  public function list_attachments($event) { }
+
+  /**
+   * Get attachment properties
+   *
+   * @param string $id    Attachment identifier
+   * @param array  $event Hash array with event properties:
+   *         id: Event identifier
+   *   calendar: Calendar identifier
+   *
+   * @return array Hash array with attachment properties:
+   *         id: Attachment identifier
+   *       name: Attachment name
+   *   mimetype: MIME content type of the attachment
+   *       size: Attachment size
+   */
+  public function get_attachment($id, $event) { }
+
+  /**
+   * Get attachment body
+   *
+   * @param string $id    Attachment identifier
+   * @param array  $event Hash array with event properties:
+   *         id: Event identifier
+   *   calendar: Calendar identifier
+   *
+   * @return string Attachment body
+   */
+  public function get_attachment_body($id, $event) { }
+
+  /**
+   * Build a struct representing the given message reference
+   *
+   * @param object|string $uri_or_headers rcube_message_header instance holding the message headers
+   *                         or an URI from a stored link referencing a mail message.
+   * @param string $folder  IMAP folder the message resides in
+   *
+   * @return array An struct referencing the given IMAP message
+   */
+  public function get_message_reference($uri_or_headers, $folder = null)
+  {
+      // to be implemented by the derived classes
+      return false;
+  }
+
+  /**
+   * List availabale categories
+   * The default implementation reads them from config/user prefs
+   */
+  public function list_categories()
+  {
+    $rcmail = rcube::get_instance();
+    return $rcmail->config->get('calendar_categories', $this->default_categories);
+  }
+
+  /**
+   * Create a new category
+   */
+  public function add_category($name, $color) { }
+
+  /**
+   * Remove the given category
+   */
+  public function remove_category($name) { }
+
+  /**
+   * Update/replace a category
+   */
+  public function replace_category($oldname, $name, $color) { }
+
+  /**
+   * Fetch free/busy information from a person within the given range
+   *
+   * @param string  E-mail address of attendee
+   * @param integer Requested period start date/time as unix timestamp
+   * @param integer Requested period end date/time as unix timestamp
+   *
+   * @return array  List of busy timeslots within the requested range
+   */
+  public function get_freebusy_list($email, $start, $end)
+  {
+    return false;
+  }
+
+  /**
+   * Create instances of a recurring event
+   *
+   * @param array  Hash array with event properties
+   * @param object DateTime Start date of the recurrence window
+   * @param object DateTime End date of the recurrence window
+   * @return array List of recurring event instances
+   */
+  public function get_recurring_events($event, $start, $end = null)
+  {
+    $events = array();
+
+    if ($event['recurrence']) {
+      // include library class
+      require_once(dirname(__FILE__) . '/../lib/calendar_recurrence.php');
+
+      $rcmail = rcmail::get_instance();
+      $recurrence = new calendar_recurrence($rcmail->plugins->get_plugin('calendar'), $event);
+      $recurrence_id_format = libcalendaring::recurrence_id_format($event);
+
+      // determine a reasonable end date if none given
+      if (!$end) {
+        switch ($event['recurrence']['FREQ']) {
+          case 'YEARLY':  $intvl = 'P100Y'; break;
+          case 'MONTHLY': $intvl = 'P20Y';  break;
+          default:        $intvl = 'P10Y';  break;
+        }
+
+        $end = clone $event['start'];
+        $end->add(new DateInterval($intvl));
+      }
+
+      $i = 0;
+      while ($next_event = $recurrence->next_instance()) {
+        // add to output if in range
+        if (($next_event['start'] <= $end && $next_event['end'] >= $start)) {
+          $next_event['_instance'] = $next_event['start']->format($recurrence_id_format);
+          $next_event['id'] = $next_event['uid'] . '-' . $exception['_instance'];
+          $next_event['recurrence_id'] = $event['uid'];
+          $events[] = $next_event;
+        }
+        else if ($next_event['start'] > $end) {  // stop loop if out of range
+          break;
+        }
+
+        // avoid endless recursion loops
+        if (++$i > 1000) {
+          break;
+        }
+      }
+    }
+
+    return $events;
+  }
+
+  /**
+   * Provide a list of revisions for the given event
+   *
+   * @param array  $event Hash array with event properties:
+   *         id: Event identifier
+   *   calendar: Calendar identifier
+   *
+   * @return array List of changes, each as a hash array:
+   *         rev: Revision number
+   *        type: Type of the change (create, update, move, delete)
+   *        date: Change date
+   *        user: The user who executed the change
+   *          ip: Client IP
+   * destination: Destination calendar for 'move' type
+   */
+  public function get_event_changelog($event)
+  {
+    return false;
+  }
+
+  /**
+   * Get a list of property changes beteen two revisions of an event
+   *
+   * @param array $event Hash array with event properties:
+   *         id: Event identifier
+   *   calendar: Calendar identifier
+   * @param mixed $rev1 Old Revision
+   * @param mixed $rev2 New Revision
+   *
+   * @return array List of property changes, each as a hash array:
+   *    property: Revision number
+   *         old: Old property value
+   *         new: Updated property value
+   */
+  public function get_event_diff($event, $rev1, $rev2)
+  {
+    return false;
+  }
+
+  /**
+   * Return full data of a specific revision of an event
+   *
+   * @param mixed  UID string or hash array with event properties:
+   *        id: Event identifier
+   *  calendar: Calendar identifier
+   * @param mixed  $rev Revision number
+   *
+   * @return array Event object as hash array
+   * @see self::get_event()
+   */
+  public function get_event_revison($event, $rev)
+  {
+    return false;
+  }
+
+  /**
+   * Command the backend to restore a certain revision of an event.
+   * This shall replace the current event with an older version.
+   *
+   * @param mixed  UID string or hash array with event properties:
+   *        id: Event identifier
+   *  calendar: Calendar identifier
+   * @param mixed  $rev Revision number
+   *
+   * @return boolean True on success, False on failure
+   */
+  public function restore_event_revision($event, $rev)
+  {
+    return false;
+  }
+
+
+  /**
+   * Callback function to produce driver-specific calendar create/edit form
+   *
+   * @param string Request action 'form-edit|form-new'
+   * @param array  Calendar properties (e.g. id, color)
+   * @param array  Edit form fields
+   *
+   * @return string HTML content of the form
+   */
+  public function calendar_form($action, $calendar, $formfields)
+  {
+    $html = '';
+    foreach ($formfields as $field) {
+      $html .= html::div('form-section',
+        html::label($field['id'], $field['label']) .
+        $field['value']);
+    }
+
+    return $html;
+  }
+
+  /**
+   * Compose a list of birthday events from the contact records in the user's address books.
+   *
+   * This is a default implementation using Roundcube's address book API.
+   * It can be overriden with a more optimized version by the individual drivers.
+   *
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  integer Only list events modified since this time (unix timestamp)
+   * @return array A list of event records
+   */
+  public function load_birthday_events($start, $end, $search = null, $modifiedsince = null)
+  {
+    // ignore update requests for simplicity reasons
+    if (!empty($modifiedsince)) {
+      return array();
+    }
+
+    // convert to DateTime for comparisons
+    $start  = new DateTime('@'.$start);
+    $end    = new DateTime('@'.$end);
+    // extract the current year
+    $year   = $start->format('Y');
+    $year2  = $end->format('Y');
+
+    $events = array();
+    $search = mb_strtolower($search);
+    $rcmail = rcmail::get_instance();
+    $cache  = $rcmail->get_cache('calendar.birthdays', 'db', 3600);
+    $cache->expunge();
+
+    $alarm_type   = $rcmail->config->get('calendar_birthdays_alarm_type', '');
+    $alarm_offset = $rcmail->config->get('calendar_birthdays_alarm_offset', '-1D');
+    $alarms       = $alarm_type ? $alarm_offset . ':' . $alarm_type : null;
+
+    // let the user select the address books to consider in prefs
+    $selected_sources = $rcmail->config->get('calendar_birthday_adressbooks');
+    $sources = $selected_sources ?: array_keys($rcmail->get_address_sources(false, true));
+    foreach ($sources as $source) {
+      $abook = $rcmail->get_address_book($source);
+
+      // skip LDAP address books unless selected by the user
+      if (!$abook || ($abook instanceof rcube_ldap && empty($selected_sources))) {
+        continue;
+      }
+
+      $abook->set_pagesize(10000);
+
+      // check for cached results
+      $cache_records = array();
+      $cached = $cache->get($source);
+
+      // iterate over (cached) contacts
+      foreach (($cached ?: $abook->search('*', '', 2, true, true, array('birthday'))) as $contact) {
+        $event = self::parse_contact($contact, $source);
+
+        if (empty($event)) {
+          continue;
+        }
+
+        // add stripped record to cache
+        if (empty($cached)) {
+          $cache_records[] = array(
+            'ID'       => $contact['ID'],
+            'name'     => $event['_displayname'],
+            'birthday' => $event['start']->format('Y-m-d'),
+          );
+        }
+
+        // filter by search term (only name is involved here)
+        if (!empty($search) && strpos(mb_strtolower($event['title']), $search) === false) {
+          continue;
+        }
+
+        $bday  = clone $event['start'];
+        $byear = $bday->format('Y');
+
+        // quick-and-dirty recurrence computation: just replace the year
+        $bday->setDate($year, $bday->format('n'), $bday->format('j'));
+        $bday->setTime(12, 0, 0);
+        $this_year = $year;
+
+        // date range reaches over multiple years: use end year if not in range
+        if (($bday > $end || $bday < $start) && $year2 != $year) {
+          $bday->setDate($year2, $bday->format('n'), $bday->format('j'));
+          $this_year = $year2;
+        }
+
+        // birthday is within requested range
+        if ($bday <= $end && $bday >= $start) {
+          unset($event['_displayname']);
+          $event['alarms'] = $alarms;
+
+          // if this is not the first occurence modify event details
+          // but not when this is "all birthdays feed" request
+          if ($year2 - $year < 10 && ($age = ($this_year - $byear))) {
+            $event['description'] = $rcmail->gettext(array('name' => 'birthdayage', 'vars' => array('age' => $age)), 'calendar');
+            $event['start']       = $bday;
+            $event['end']         = clone $bday;
+            unset($event['recurrence']);
+          }
+
+          // add the main instance
+          $events[] = $event;
+        }
+      }
+
+      // store collected contacts in cache
+      if (empty($cached)) {
+        $cache->write($source, $cache_records);
+      }
+    }
+
+    return $events;
+  }
+
+  /**
+   * Get a single birthday calendar event
+   */
+  public function get_birthday_event($id)
+  {
+    // decode $id
+    list(,$source,$contact_id,$year) = explode(':', rcube_ldap::dn_decode($id));
+
+    $rcmail = rcmail::get_instance();
+
+    if ($source && $contact_id && ($abook = $rcmail->get_address_book($source))) {
+      if ($contact = $abook->get_record($contact_id, true)) {
+        return self::parse_contact($contact, $source);
+      }
+    }
+  }
+
+  /**
+   * Parse contact and create an event for its birthday
+   *
+   * @param array  $contact Contact data
+   * @param string $source  Addressbook source ID
+   *
+   * @return array Birthday event data
+   */
+  public static function parse_contact($contact, $source)
+  {
+    if (!is_array($contact)) {
+      return;
+    }
+
+    if (is_array($contact['birthday'])) {
+      $contact['birthday'] = reset($contact['birthday']);
+    }
+
+    if (empty($contact['birthday'])) {
+      return;
+    }
+
+    try {
+      $bday = $contact['birthday'];
+      if (!$bday instanceof DateTime) {
+        $bday = new DateTime($bday, new DateTimezone('UTC'));
+      }
+      $bday->_dateonly = true;
+    }
+    catch (Exception $e) {
+      rcube::raise_error(array(
+          'code' => 600, 'type' => 'php',
+          'file' => __FILE__, 'line' => __LINE__,
+          'message' => 'BIRTHDAY PARSE ERROR: ' . $e->getMessage()),
+        true, false);
+      return;
+    }
+
+    $rcmail       = rcmail::get_instance();
+    $birthyear    = $bday->format('Y');
+    $display_name = rcube_addressbook::compose_display_name($contact);
+    $label        = array('name' => 'birthdayeventtitle', 'vars' => array('name' => $display_name));
+    $event_title  = $rcmail->gettext($label, 'calendar');
+    $uid          = rcube_ldap::dn_encode('bday:' . $source . ':' . $contact['ID'] . ':' . $birthyear);
+
+    $event = array(
+      'id'           => $uid,
+      'uid'          => $uid,
+      'calendar'     => self::BIRTHDAY_CALENDAR_ID,
+      'title'        => $event_title,
+      'description'  => '',
+      'allday'       => true,
+      'start'        => $bday,
+      'end'          => clone $bday,
+      'recurrence'   => array('FREQ' => 'YEARLY', 'INTERVAL' => 1),
+      'free_busy'    => 'free',
+      '_displayname' => $display_name,
+    );
+
+    return $event;
+  }
+
+  /**
+   * Store alarm dismissal for birtual birthay events
+   *
+   * @param  string  Event identifier
+   * @param  integer Suspend the alarm for this number of seconds
+   */
+  public function dismiss_birthday_alarm($event_id, $snooze = 0)
+  {
+    $rcmail = rcmail::get_instance();
+    $cache  = $rcmail->get_cache('calendar.birthdayalarms', 'db', 86400 * 30);
+    $cache->remove($event_id);
+
+    // compute new notification time or disable if not snoozed
+    $notifyat = $snooze > 0 ? time() + $snooze : null;
+    $cache->set($event_id, array('snooze' => $snooze, 'notifyat' => $notifyat));
+
+    return true;
+  }
+
+  /**
+   * Handler for user_delete plugin hook
+   *
+   * @param array Hash array with hook arguments
+   * @return array Return arguments for plugin hooks
+   */
+  public function user_delete($args)
+  {
+    // TO BE OVERRIDDEN
+    return $args;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,85 @@
+/**
+ * Roundcube Calendar
+ *
+ * Plugin to add a calendar to Roundcube.
+ *
+ * @author Lazlo Westerhof
+ * @author Thomas Bruederli
+ * @licence GNU AGPL
+ * @copyright (c) 2010 Lazlo Westerhof - Netherlands
+ * @copyright (c) 2014 Kolab Systems AG
+ *
+ **/
+
+CREATE TABLE IF NOT EXISTS `calendars` (
+  `calendar_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+  `name` varchar(255) NOT NULL,
+  `color` varchar(8) NOT NULL,
+  `showalarms` tinyint(1) NOT NULL DEFAULT '1',
+  PRIMARY KEY(`calendar_id`),
+  INDEX `user_name_idx` (`user_id`, `name`),
+  CONSTRAINT `fk_calendars_user_id` FOREIGN KEY (`user_id`)
+    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE IF NOT EXISTS `events` (
+  `event_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `calendar_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
+  `recurrence_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
+  `uid` varchar(255) NOT NULL DEFAULT '',
+  `instance` varchar(16) NOT NULL DEFAULT '',
+  `isexception` tinyint(1) NOT NULL DEFAULT '0',
+  `created` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
+  `changed` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
+  `sequence` int(1) UNSIGNED NOT NULL DEFAULT '0',
+  `start` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
+  `end` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
+  `recurrence` varchar(255) DEFAULT NULL,
+  `title` varchar(255) NOT NULL,
+  `description` text NOT NULL,
+  `location` varchar(255) NOT NULL DEFAULT '',
+  `categories` varchar(255) NOT NULL DEFAULT '',
+  `url` varchar(255) NOT NULL DEFAULT '',
+  `all_day` tinyint(1) NOT NULL DEFAULT '0',
+  `free_busy` tinyint(1) NOT NULL DEFAULT '0',
+  `priority` tinyint(1) NOT NULL DEFAULT '0',
+  `sensitivity` tinyint(1) NOT NULL DEFAULT '0',
+  `status` varchar(32) NOT NULL DEFAULT '',
+  `alarms` text DEFAULT NULL,
+  `attendees` text DEFAULT NULL,
+  `notifyat` datetime DEFAULT NULL,
+  PRIMARY KEY(`event_id`),
+  INDEX `uid_idx` (`uid`),
+  INDEX `recurrence_idx` (`recurrence_id`),
+  INDEX `calendar_notify_idx` (`calendar_id`,`notifyat`),
+  CONSTRAINT `fk_events_calendar_id` FOREIGN KEY (`calendar_id`)
+    REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE IF NOT EXISTS `attachments` (
+  `attachment_id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
+  `event_id` int(11) UNSIGNED NOT NULL DEFAULT '0',
+  `filename` varchar(255) NOT NULL DEFAULT '',
+  `mimetype` varchar(255) NOT NULL DEFAULT '',
+  `size` int(11) NOT NULL DEFAULT '0',
+  `data` longtext NOT NULL,
+  PRIMARY KEY(`attachment_id`),
+  CONSTRAINT `fk_attachments_event_id` FOREIGN KEY (`event_id`)
+    REFERENCES `events`(`event_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+CREATE TABLE IF NOT EXISTS `itipinvitations` (
+  `token` VARCHAR(64) NOT NULL,
+  `event_uid` VARCHAR(255) NOT NULL,
+  `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+  `event` TEXT NOT NULL,
+  `expires` DATETIME DEFAULT NULL,
+  `cancelled` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+  PRIMARY KEY(`token`),
+  INDEX `uid_idx` (`user_id`,`event_uid`),
+  CONSTRAINT `fk_itipinvitations_user_id` FOREIGN KEY (`user_id`)
+    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+REPLACE INTO system (name, value) VALUES ('calendar-database-version', '2015022700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2012080600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- MySQL database updates since version 0.7/0.8
+
+ALTER TABLE `events` ADD `sequence` int(1) UNSIGNED NOT NULL DEFAULT '0' AFTER `changed`;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2013011000.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2013042700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2013051600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- MySQL database updates since version 0.9-beta
+
+ALTER TABLE `events` ADD `url` VARCHAR(255) NOT NULL AFTER `categories`;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2013071800.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- MySQL database updates since version 0.9.1
+
+ALTER TABLE `events` ADD `custom` TEXT NULL AFTER `attendees`;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2014040900.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- MySQL database updates since version 1.0
+
+ALTER TABLE `events` ADD `status` VARCHAR(32) NOT NULL AFTER `sensitivity`;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/mysql/2015022700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,15 @@
+-- add identifier for recurring instances and exceptions
+
+ALTER TABLE `events` ADD `instance` varchar(16) NOT NULL DEFAULT '' AFTER `uid`;
+ALTER TABLE `events` ADD `isexception` tinyint(1) NOT NULL DEFAULT '0' AFTER `instance`;
+
+UPDATE `events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%d')
+  WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 1;
+
+UPDATE `events` SET `instance` = DATE_FORMAT(`start`, '%Y%m%dT%k%i%s')
+  WHERE `recurrence_id` != 0 AND `instance` = '' AND `all_day` = 0;
+
+-- extend alarms columns for multiple values
+
+ALTER TABLE `events` CHANGE `alarms` `alarms` TEXT NULL DEFAULT NULL;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,109 @@
+/**
+ * RoundCube Calendar
+ *
+ * Plugin to add a calendar to RoundCube.
+ *
+ * @author Lazlo Westerhof
+ * @author Albert Lee
+ * @author Aleksander Machniak <machniak@kolabsys.com>
+ * @licence GNU AGPL
+ * @copyright (c) 2010 Lazlo Westerhof - Netherlands
+ * @copyright (c) 2014 Kolab Systems AG
+ *
+ **/
+
+
+CREATE SEQUENCE calendars_seq
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+CREATE TABLE calendars (
+    calendar_id integer DEFAULT nextval('calendars_seq'::regclass) NOT NULL,
+    user_id integer NOT NULL
+        REFERENCES users (user_id) ON UPDATE CASCADE ON DELETE CASCADE,
+    name varchar(255) NOT NULL,
+    color varchar(8) NOT NULL,
+    showalarms smallint NOT NULL DEFAULT 1,
+    PRIMARY KEY (calendar_id)
+);
+
+CREATE INDEX calendars_user_id_idx ON calendars (user_id, name);
+
+
+CREATE SEQUENCE events_seq
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+CREATE TABLE events (
+    event_id integer DEFAULT nextval('events_seq'::regclass) NOT NULL,
+    calendar_id integer NOT NULL
+        REFERENCES calendars (calendar_id) ON UPDATE CASCADE ON DELETE CASCADE,
+    recurrence_id integer NOT NULL DEFAULT 0,
+    uid varchar(255) NOT NULL DEFAULT '',
+    instance varchar(16) NOT NULL DEFAULT '',
+    isexception smallint NOT NULL DEFAULT '0',
+    created timestamp without time zone DEFAULT now() NOT NULL,
+    changed timestamp without time zone DEFAULT now(),
+    sequence integer NOT NULL DEFAULT 0,
+    "start" timestamp without time zone DEFAULT now() NOT NULL,
+    "end" timestamp without time zone DEFAULT now() NOT NULL,
+    recurrence varchar(255) DEFAULT NULL,
+    title character varying(255) NOT NULL DEFAULT '',
+    description text NOT NULL DEFAULT '',
+    location character varying(255) NOT NULL DEFAULT '',
+    categories character varying(255) NOT NULL DEFAULT '',
+    url character varying(255) NOT NULL DEFAULT '',
+    all_day smallint NOT NULL DEFAULT 0,
+    free_busy smallint NOT NULL DEFAULT 0,
+    priority smallint NOT NULL DEFAULT 0,
+    sensitivity smallint NOT NULL DEFAULT 0,
+    status character varying(32) NOT NULL DEFAULT '',
+    alarms text DEFAULT NULL,
+    attendees text DEFAULT NULL,
+    notifyat timestamp without time zone DEFAULT NULL,
+    PRIMARY KEY (event_id)
+);
+
+CREATE INDEX events_calendar_id_notifyat_idx ON events (calendar_id, notifyat);
+CREATE INDEX events_uid_idx ON events (uid);
+CREATE INDEX events_recurrence_id_idx ON events (recurrence_id);
+
+
+CREATE SEQUENCE attachments_seq
+    INCREMENT BY 1
+    NO MAXVALUE
+    NO MINVALUE
+    CACHE 1;
+
+CREATE TABLE attachments (
+    attachment_id integer DEFAULT nextval('attachments_seq'::regclass) NOT NULL,
+    event_id integer NOT NULL
+        REFERENCES events (event_id) ON DELETE CASCADE ON UPDATE CASCADE,
+    filename varchar(255) NOT NULL DEFAULT '',
+    mimetype varchar(255) NOT NULL DEFAULT '',
+    size integer NOT NULL DEFAULT 0,
+    data text NOT NULL DEFAULT '',
+    PRIMARY KEY (attachment_id)
+);
+
+CREATE INDEX attachments_user_id_idx ON attachments (event_id);
+
+
+CREATE TABLE itipinvitations (
+    token varchar(64) NOT NULL,
+    event_uid varchar(255) NOT NULL,
+    user_id integer NOT NULL
+        REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
+    event TEXT NOT NULL,
+    expires timestamp without time zone DEFAULT NULL,
+    cancelled smallint NOT NULL DEFAULT 0,
+    PRIMARY KEY (token)
+);
+
+CREATE INDEX itipinvitations_user_id_event_uid_idx ON itipinvitations (user_id, event_uid);
+
+INSERT INTO system (name, value) VALUES ('calendar-database-version', '2015022700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2012080600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- Postgres database updates since version 0.7/0.8
+
+ALTER TABLE events ADD sequence integer NOT NULL DEFAULT 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2013011000.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2013042700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,8 @@
+ALTER SEQUENCE calendar_ids RENAME TO calendars_seq;
+ALTER TABLE calendars ALTER COLUMN calendar_id SET DEFAULT nextval('calendars_seq'::text);
+
+ALTER SEQUENCE event_ids RENAME TO events_seq;
+ALTER TABLE events ALTER COLUMN event_id SET DEFAULT nextval('events_seq'::text);
+
+ALTER SEQUENCE attachment_ids RENAME TO attachments_seq;
+ALTER TABLE attachments ALTER COLUMN attachment_id SET DEFAULT nextval('attachments_seq'::text);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2013051600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- Postgres database updates since version 0.9-beta
+
+ALTER TABLE events ADD url character varying(255) NOT NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2013071800.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- Postgres database updates since version 0.9.1
+
+ALTER TABLE events ADD custom text DEFAULT NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2014040900.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,3 @@
+-- Postgres database updates since version 1.0
+
+ALTER TABLE events ADD status character varying(32) NOT NULL;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/postgres/2015022700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+-- add identifier for recurring instances and exceptions
+
+ALTER TABLE events ADD instance character varying(16) NOT NULL;
+ALTER TABLE events ADD isexception smallint NOT NULL DEFAULT '0';
+
+-- extend alarms columns for multiple values
+
+ALTER TABLE events ALTER COLUMN alarms TYPE text;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,79 @@
+/**
+ * Roundcube Calendar
+ *
+ * Plugin to add a calendar to Roundcube.
+ *
+ * @author Lazlo Westerhof
+ * @author Thomas Bruederli
+ * @author Albert Lee
+ * @licence GNU AGPL
+ * @copyright (c) 2010 Lazlo Westerhof - Netherlands
+ * @copyright (c) 2014 Kolab Systems AG
+ *
+ **/
+
+CREATE TABLE calendars (
+  calendar_id integer NOT NULL PRIMARY KEY,
+  user_id integer NOT NULL default '0',
+  name varchar(255) NOT NULL default '',
+  color varchar(255) NOT NULL default '',
+  showalarms tinyint(1) NOT NULL default '1',
+  CONSTRAINT fk_calendars_user_id FOREIGN KEY (user_id)
+    REFERENCES users(user_id)
+);
+
+CREATE TABLE events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  instance varchar(16) NOT NULL default '',
+  isexception tinyint(1) NOT NULL default '0',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  status varchar(32) NOT NULL default '',
+  alarms text default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL,
+  CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
+    REFERENCES calendars(calendar_id)
+);
+
+CREATE TABLE attachments (
+  attachment_id integer NOT NULL PRIMARY KEY,
+  event_id integer NOT NULL default '0',
+  filename varchar(255) NOT NULL default '',
+  mimetype varchar(255) NOT NULL default '',
+  size integer NOT NULL default '0',
+  data text NOT NULL default '',
+  CONSTRAINT fk_attachment_event_id FOREIGN KEY (event_id)
+    REFERENCES events(event_id)
+);
+
+CREATE TABLE itipinvitations (
+  token varchar(64) NOT NULL PRIMARY KEY,
+  event_uid varchar(255) NOT NULL,
+  user_id integer NOT NULL default '0',
+  event text NOT NULL,
+  expires datetime NOT NULL default '1000-01-01 00:00:00',
+  cancelled tinyint(1) NOT NULL default '0',
+  CONSTRAINT fk_itipinvitations_user_id FOREIGN KEY (user_id)
+    REFERENCES users(user_id)
+);
+
+CREATE INDEX ix_itipinvitations_uid ON itipinvitations(user_id, event_uid);
+
+INSERT INTO system (name, value) VALUES ('calendar-database-version', '2015022700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2013011000.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2013042700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2013051600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,63 @@
+-- SQLite database updates since version 0.9-beta
+
+-- ALTER TABLE events ADD url varchar(255) NOT NULL AFTER categories;
+
+CREATE TABLE temp_events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL
+);
+
+INSERT INTO temp_events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+    SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat FROM events;
+
+DROP TABLE events;
+
+CREATE TABLE events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL,
+  CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
+    REFERENCES calendars(calendar_id)
+);
+
+INSERT INTO events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+    SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat FROM temp_events;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2013071800.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,64 @@
+-- SQLite database updates since version 0.9.1
+
+-- ALTER TABLE events ADD custom text DEFAULT NULL AFTER attendees;
+
+CREATE TABLE temp_events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL
+);
+
+INSERT INTO temp_events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+    SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat FROM events;
+
+DROP TABLE events;
+
+CREATE TABLE events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  custom text default NULL,
+  notifyat datetime default NULL,
+  CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
+    REFERENCES calendars(calendar_id)
+);
+
+INSERT INTO events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+    SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat FROM temp_events;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2014040900.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,67 @@
+-- SQLite database updates since version 0.9-beta
+
+-- ALTER TABLE events ADD url varchar(255) NOT NULL AFTER categories;
+
+CREATE TABLE temp_events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL
+);
+
+INSERT INTO temp_events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+                  SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat
+                  FROM events;
+
+DROP TABLE events;
+
+CREATE TABLE events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  status varchar(32) NOT NULL default '',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL,
+  CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
+    REFERENCES calendars(calendar_id)
+);
+
+INSERT INTO events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+             SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat
+             FROM temp_events;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/SQL/sqlite/2015022700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,79 @@
+-- ALTER TABLE `events` ADD `instance` varchar(16) NOT NULL DEFAULT '' AFTER `uid`;
+-- ALTER TABLE `events` ADD `isexception` tinyint(3) NOT NULL DEFAULT '0' AFTER `instance`;
+-- ALTER TABLE `events` CHANGE `alarms` `alarms` TEXT NULL DEFAULT NULL;
+
+CREATE TABLE temp_events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  status varchar(32) NOT NULL default '',
+  alarms varchar(255) default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL
+);
+
+INSERT INTO temp_events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+                  SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat
+                  FROM events;
+
+DROP TABLE events;
+
+CREATE TABLE events (
+  event_id integer NOT NULL PRIMARY KEY,
+  calendar_id integer NOT NULL default '0',
+  recurrence_id integer NOT NULL default '0',
+  uid varchar(255) NOT NULL default '',
+  instance varchar(16) NOT NULL default '',
+  isexception tinyint(1) NOT NULL default '0',
+  created datetime NOT NULL default '1000-01-01 00:00:00',
+  changed datetime NOT NULL default '1000-01-01 00:00:00',
+  sequence integer NOT NULL default '0',
+  start datetime NOT NULL default '1000-01-01 00:00:00',
+  end datetime NOT NULL default '1000-01-01 00:00:00',
+  recurrence varchar(255) default NULL,
+  title varchar(255) NOT NULL,
+  description text NOT NULL,
+  location varchar(255) NOT NULL default '',
+  categories varchar(255) NOT NULL default '',
+  url varchar(255) NOT NULL default '',
+  all_day tinyint(1) NOT NULL default '0',
+  free_busy tinyint(1) NOT NULL default '0',
+  priority tinyint(1) NOT NULL default '0',
+  sensitivity tinyint(1) NOT NULL default '0',
+  status varchar(32) NOT NULL default '',
+  alarms text default NULL,
+  attendees text default NULL,
+  notifyat datetime default NULL,
+  CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id)
+    REFERENCES calendars(calendar_id)
+);
+
+INSERT INTO events (event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat)
+             SELECT event_id, calendar_id, recurrence_id, uid, created, changed, sequence, start, end, recurrence, title, description, location, categories, url, all_day, free_busy, priority, sensitivity, alarms, attendees, notifyat
+             FROM temp_events;
+
+DROP TABLE temp_events;
+
+-- Derrive instance columns from start date/time
+
+UPDATE events SET instance = strftime('%Y%m%d', start)
+ WHERE recurrence_id != 0 AND instance = '' AND all_day = 1;
+
+UPDATE events SET instance = strftime('%Y%m%dT%H%M%S', start)
+ WHERE recurrence_id != 0 AND instance = '' AND all_day = 0;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/database/database_driver.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1496 @@
+<?php
+
+/**
+ * Database driver for the Calendar plugin
+ *
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+
+class database_driver extends calendar_driver
+{
+  const DB_DATE_FORMAT = 'Y-m-d H:i:s';
+
+  public static $scheduling_properties = array('start', 'end', 'allday', 'recurrence', 'location', 'cancelled');
+
+  // features this backend supports
+  public $alarms = true;
+  public $attendees = true;
+  public $freebusy = false;
+  public $attachments = true;
+  public $alarm_types = array('DISPLAY');
+
+  private $rc;
+  private $cal;
+  private $cache = array();
+  private $calendars = array();
+  private $calendar_ids = '';
+  private $free_busy_map = array('free' => 0, 'busy' => 1, 'out-of-office' => 2, 'outofoffice' => 2, 'tentative' => 3);
+  private $sensitivity_map = array('public' => 0, 'private' => 1, 'confidential' => 2);
+  private $server_timezone;
+  
+  private $db_events = 'events';
+  private $db_calendars = 'calendars';
+  private $db_attachments = 'attachments';
+
+
+  /**
+   * Default constructor
+   */
+  public function __construct($cal)
+  {
+    $this->cal = $cal;
+    $this->rc = $cal->rc;
+    $this->server_timezone = new DateTimeZone(date_default_timezone_get());
+    
+    // read database config
+    $db = $this->rc->get_dbh();
+    $this->db_events = $this->rc->config->get('db_table_events', $db->table_name($this->db_events));
+    $this->db_calendars = $this->rc->config->get('db_table_calendars', $db->table_name($this->db_calendars));
+    $this->db_attachments = $this->rc->config->get('db_table_attachments', $db->table_name($this->db_attachments));
+    
+    $this->_read_calendars();
+  }
+
+  /**
+   * Read available calendars for the current user and store them internally
+   */
+  private function _read_calendars()
+  {
+    $hidden = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
+    
+    if (!empty($this->rc->user->ID)) {
+      $calendar_ids = array();
+      $result = $this->rc->db->query(
+        "SELECT *, calendar_id AS id FROM " . $this->db_calendars . "
+         WHERE user_id=?
+         ORDER BY name",
+         $this->rc->user->ID
+      );
+      while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
+        $arr['showalarms'] = intval($arr['showalarms']);
+        $arr['active']     = !in_array($arr['id'], $hidden);
+        $arr['name']       = html::quote($arr['name']);
+        $arr['listname']   = html::quote($arr['name']);
+        $arr['rights']     = 'lrswikxteav';
+        $arr['editable']  = true;
+        $this->calendars[$arr['calendar_id']] = $arr;
+        $calendar_ids[] = $this->rc->db->quote($arr['calendar_id']);
+      }
+      $this->calendar_ids = join(',', $calendar_ids);
+    }
+  }
+
+  /**
+   * Get a list of available calendars from this source
+   *
+   * @param integer Bitmask defining filter criterias
+   *
+   * @return array List of calendars
+   */
+  public function list_calendars($filter = 0)
+  {
+    // attempt to create a default calendar for this user
+    if (empty($this->calendars)) {
+      if ($this->create_calendar(array('name' => 'Default', 'color' => 'cc0000', 'showalarms' => true)))
+        $this->_read_calendars();
+    }
+
+    $calendars = $this->calendars;
+
+    // filter active calendars
+    if ($filter & self::FILTER_ACTIVE) {
+      foreach ($calendars as $idx => $cal) {
+        if (!$cal['active']) {
+          unset($calendars[$idx]);
+        }
+      }
+    }
+
+    // 'personal' is unsupported in this driver
+
+    // append the virtual birthdays calendar
+    if ($this->rc->config->get('calendar_contact_birthdays', false)) {
+      $prefs = $this->rc->config->get('birthday_calendar', array('color' => '87CEFA'));
+      $hidden = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
+
+      $id = self::BIRTHDAY_CALENDAR_ID;
+      if (!$active || !in_array($id, $hidden)) {
+        $calendars[$id] = array(
+          'id'         => $id,
+          'name'       => $this->cal->gettext('birthdays'),
+          'listname'   => $this->cal->gettext('birthdays'),
+          'color'      => $prefs['color'],
+          'showalarms' => (bool)$this->rc->config->get('calendar_birthdays_alarm_type'),
+          'active'     => !in_array($id, $hidden),
+          'group'      => 'x-birthdays',
+          'editable'  => false,
+          'default'    => false,
+          'children'   => false,
+        );
+      }
+    }
+
+    return $calendars;
+  }
+
+  /**
+   * Create a new calendar assigned to the current user
+   *
+   * @param array Hash array with calendar properties
+   *    name: Calendar name
+   *   color: The color of the calendar
+   * @return mixed ID of the calendar on success, False on error
+   */
+  public function create_calendar($prop)
+  {
+    $result = $this->rc->db->query(
+      "INSERT INTO " . $this->db_calendars . "
+       (user_id, name, color, showalarms)
+       VALUES (?, ?, ?, ?)",
+       $this->rc->user->ID,
+       $prop['name'],
+       $prop['color'],
+       $prop['showalarms']?1:0
+    );
+    
+    if ($result)
+      return $this->rc->db->insert_id($this->db_calendars);
+    
+    return false;
+  }
+
+  /**
+   * Update properties of an existing calendar
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function edit_calendar($prop)
+  {
+    // birthday calendar properties are saved in user prefs
+    if ($prop['id'] == self::BIRTHDAY_CALENDAR_ID) {
+      $prefs['birthday_calendar'] = $this->rc->config->get('birthday_calendar', array('color' => '87CEFA'));
+      if (isset($prop['color']))
+        $prefs['birthday_calendar']['color'] = $prop['color'];
+      if (isset($prop['showalarms']))
+        $prefs['calendar_birthdays_alarm_type'] = $prop['showalarms'] ? $this->alarm_types[0] : '';
+      $this->rc->user->save_prefs($prefs);
+      return true;
+    }
+
+    $query = $this->rc->db->query(
+      "UPDATE " . $this->db_calendars . "
+       SET   name=?, color=?, showalarms=?
+       WHERE calendar_id=?
+       AND   user_id=?",
+      $prop['name'],
+      $prop['color'],
+      $prop['showalarms']?1:0,
+      $prop['id'],
+      $this->rc->user->ID
+    );
+    
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Set active/subscribed state of a calendar
+   * Save a list of hidden calendars in user prefs
+   *
+   * @see calendar_driver::subscribe_calendar()
+   */
+  public function subscribe_calendar($prop)
+  {
+    $hidden = array_flip(explode(',', $this->rc->config->get('hidden_calendars', '')));
+    
+    if ($prop['active'])
+      unset($hidden[$prop['id']]);
+    else
+      $hidden[$prop['id']] = 1;
+    
+    return $this->rc->user->save_prefs(array('hidden_calendars' => join(',', array_keys($hidden))));
+  }
+
+  /**
+   * Delete the given calendar with all its contents
+   *
+   * @see calendar_driver::delete_calendar()
+   */
+  public function delete_calendar($prop)
+  {
+    if (!$this->calendars[$prop['id']])
+      return false;
+
+    // events and attachments will be deleted by foreign key cascade
+
+    $query = $this->rc->db->query(
+      "DELETE FROM " . $this->db_calendars . "
+       WHERE calendar_id=?",
+       $prop['id']
+    );
+
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Search for shared or otherwise not listed calendars the user has access
+   *
+   * @param string Search string
+   * @param string Section/source to search
+   * @return array List of calendars
+   */
+  public function search_calendars($query, $source)
+  {
+    // not implemented
+    return array();
+  }
+
+  /**
+   * Add a single event to the database
+   *
+   * @param array Hash array with event properties
+   * @see calendar_driver::new_event()
+   */
+  public function new_event($event)
+  {
+    if (!$this->validate($event))
+      return false;
+
+    if (!empty($this->calendars)) {
+      if ($event['calendar'] && !$this->calendars[$event['calendar']])
+        return false;
+      if (!$event['calendar'])
+        $event['calendar'] = reset(array_keys($this->calendars));
+
+      if ($event_id = $this->_insert_event($event)) {
+        $this->_update_recurring($event);
+      }
+
+      return $event_id;
+    }
+    
+    return false;
+  }
+
+  /**
+   *
+   */
+  private function _insert_event(&$event)
+  {
+    $event = $this->_save_preprocess($event);
+
+    $this->rc->db->query(sprintf(
+      "INSERT INTO " . $this->db_events . "
+       (calendar_id, created, changed, uid, recurrence_id, instance, isexception, %s, %s, all_day, recurrence,
+          title, description, location, categories, url, free_busy, priority, sensitivity, status, attendees, alarms, notifyat)
+       VALUES (?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+        $this->rc->db->quote_identifier('start'),
+        $this->rc->db->quote_identifier('end'),
+        $this->rc->db->now(),
+        $this->rc->db->now()
+      ),
+      $event['calendar'],
+      strval($event['uid']),
+      intval($event['recurrence_id']),
+      strval($event['_instance']),
+      intval($event['isexception']),
+      $event['start']->format(self::DB_DATE_FORMAT),
+      $event['end']->format(self::DB_DATE_FORMAT),
+      intval($event['all_day']),
+      $event['_recurrence'],
+      strval($event['title']),
+      strval($event['description']),
+      strval($event['location']),
+      join(',', (array)$event['categories']),
+      strval($event['url']),
+      intval($event['free_busy']),
+      intval($event['priority']),
+      intval($event['sensitivity']),
+      strval($event['status']),
+      $event['attendees'],
+      $event['alarms'],
+      $event['notifyat']
+    );
+
+    $event_id = $this->rc->db->insert_id($this->db_events);
+
+    if ($event_id) {
+      $event['id'] = $event_id;
+
+      // add attachments
+      if (!empty($event['attachments'])) {
+        foreach ($event['attachments'] as $attachment) {
+          $this->add_attachment($attachment, $event_id);
+          unset($attachment);
+        }
+      }
+
+      return $event_id;
+    }
+
+    return false;
+  }
+
+  /**
+   * Update an event entry with the given data
+   *
+   * @param array Hash array with event properties
+   * @see calendar_driver::edit_event()
+   */
+  public function edit_event($event)
+  {
+    if (!empty($this->calendars)) {
+      $update_master = false;
+      $update_recurring = true;
+      $old = $this->get_event($event);
+      $ret = true;
+
+      // check if update affects scheduling and update attendee status accordingly
+      $reschedule = $this->_check_scheduling($event, $old, true);
+
+      // increment sequence number
+      if (empty($event['sequence']) && $reschedule)
+        $event['sequence'] = max($event['sequence'], $old['sequence']) + 1;
+      
+      // modify a recurring event, check submitted savemode to do the right things
+      if ($old['recurrence'] || $old['recurrence_id']) {
+        $master = $old['recurrence_id'] ? $this->get_event(array('id' => $old['recurrence_id'])) : $old;
+        
+        // keep saved exceptions (not submitted by the client)
+        if ($old['recurrence']['EXDATE'])
+          $event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
+        
+        switch ($event['_savemode']) {
+          case 'new':
+            $event['uid'] = $this->cal->generate_uid();
+            return $this->new_event($event);
+          
+          case 'current':
+            // save as exception
+            $event['isexception'] = 1;
+            $update_recurring = false;
+
+            // set exception to first instance (= master)
+            if ($event['id'] == $master['id']) {
+              $event += $old;
+              $event['recurrence_id'] = $master['id'];
+              $event['_instance'] = libcalendaring::recurrence_instance_identifier($old, $master['allday']);
+              $event['isexception'] = 1;
+              $event_id = $this->_insert_event($event);
+              return $event_id;
+            }
+            break;
+          
+          case 'future':
+            if ($master['id'] != $event['id']) {
+              // set until-date on master event, then save this instance as new recurring event
+              $master['recurrence']['UNTIL'] = clone $event['start'];
+              $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
+              unset($master['recurrence']['COUNT']);
+              $update_master = true;
+              
+              // if recurrence COUNT, update value to the correct number of future occurences
+              if ($event['recurrence']['COUNT']) {
+                $fromdate = clone $event['start'];
+                $fromdate->setTimezone($this->server_timezone);
+                $sqlresult = $this->rc->db->query(sprintf(
+                  "SELECT event_id FROM " . $this->db_events . "
+                   WHERE calendar_id IN (%s)
+                   AND %s >= ?
+                   AND recurrence_id=?",
+                  $this->calendar_ids,
+                  $this->rc->db->quote_identifier('start')
+                  ),
+                  $fromdate->format(self::DB_DATE_FORMAT),
+                  $master['id']);
+                if ($count = $this->rc->db->num_rows($sqlresult))
+                  $event['recurrence']['COUNT'] = $count;
+              }
+            
+              $update_recurring = true;
+              $event['recurrence_id'] = 0;
+              $event['isexception'] = 0;
+              $event['_instance'] = '';
+              break;
+            }
+            // else: 'future' == 'all' if modifying the master event
+          
+          default:  // 'all' is default
+            $event['id'] = $master['id'];
+            $event['recurrence_id'] = 0;
+            
+            // use start date from master but try to be smart on time or duration changes
+            $old_start_date = $old['start']->format('Y-m-d');
+            $old_start_time = $old['allday'] ? '' : $old['start']->format('H:i');
+            $old_duration = $old['end']->format('U') - $old['start']->format('U');
+            
+            $new_start_date = $event['start']->format('Y-m-d');
+            $new_start_time = $event['allday'] ? '' : $event['start']->format('H:i');
+            $new_duration = $event['end']->format('U') - $event['start']->format('U');
+            
+            $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
+            $date_shift = $old['start']->diff($event['start']);
+            
+            // shifted or resized
+            if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
+              $event['start'] = $master['start']->add($old['start']->diff($event['start']));
+              $event['end'] = clone $event['start'];
+              $event['end']->add(new DateInterval('PT'.$new_duration.'S'));
+            }
+            // dates did not change, use the ones from master
+            else if ($new_start_date . $new_start_time == $old_start_date . $old_start_time) {
+              $event['start'] = $master['start'];
+              $event['end'] = $master['end'];
+            }
+            
+            // adjust recurrence-id when start changed and therefore the entire recurrence chain changes
+            if (is_array($event['recurrence']) && ($old_start_date != $new_start_date || $old_start_time != $new_start_time)
+                && ($exceptions = $this->_load_exceptions($old))) {
+              $recurrence_id_format = libcalendaring::recurrence_id_format($event);
+              foreach ($exceptions as $exception) {
+                $recurrence_id = rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone());
+                if (is_a($recurrence_id, 'DateTime')) {
+                  $recurrence_id->add($date_shift);
+                  $exception['_instance'] = $recurrence_id->format($recurrence_id_format);
+                  $this->_update_event($exception, false);
+                }
+              }
+            }
+            
+            $ret = $event['id'];  // return master ID
+            break;
+        }
+      }
+      
+      $success = $this->_update_event($event, $update_recurring);
+      
+      if ($success && $update_master)
+        $this->_update_event($master, true);
+      
+      return $success ? $ret : false;
+    }
+    
+    return false;
+  }
+
+  /**
+   * Extended event editing with possible changes to the argument
+   *
+   * @param array  Hash array with event properties
+   * @param string New participant status
+   * @param array  List of hash arrays with updated attendees
+   * @return boolean True on success, False on error
+   */
+  public function edit_rsvp(&$event, $status, $attendees)
+  {
+    $update_event = $event;
+
+    // apply changes to master (and all exceptions)
+    if ($event['_savemode'] == 'all' && $event['recurrence_id']) {
+      $update_event = $this->get_event(array('id' => $event['recurrence_id']));
+      $update_event['_savemode'] = $event['_savemode'];
+      calendar::merge_attendee_data($update_event, $attendees);
+    }
+
+    if ($ret = $this->update_attendees($update_event, $attendees)) {
+      // replace $event with effectively updated event (for iTip reply)
+      if ($ret !== true && $ret != $update_event['id'] && ($new_event = $this->get_event(array('id' => $ret)))) {
+        $event = $new_event;
+      }
+      else {
+        $event = $update_event;
+      }
+    }
+
+    return $ret;
+  }
+
+  /**
+   * Update the participant status for the given attendees
+   *
+   * @see calendar_driver::update_attendees()
+   */
+  public function update_attendees(&$event, $attendees)
+  {
+    $success = $this->edit_event($event, true);
+
+    // apply attendee updates to recurrence exceptions too
+    if ($success && $event['_savemode'] == 'all' && !empty($event['recurrence']) && empty($event['recurrence_id']) && ($exceptions = $this->_load_exceptions($event))) {
+      foreach ($exceptions as $exception) {
+        calendar::merge_attendee_data($exception, $attendees);
+        $this->_update_event($exception, false);
+      }
+    }
+
+    return $success;
+  }
+
+  /**
+   * Determine whether the current change affects scheduling and reset attendee status accordingly
+   */
+  private function _check_scheduling(&$event, $old, $update = true)
+  {
+    // skip this check when importing iCal/iTip events
+    if (isset($event['sequence']) || !empty($event['_method'])) {
+      return false;
+    }
+
+    $reschedule = false;
+
+    // iterate through the list of properties considered 'significant' for scheduling
+    foreach (self::$scheduling_properties as $prop) {
+        $a = $old[$prop];
+        $b = $event[$prop];
+        if ($event['allday'] && ($prop == 'start' || $prop == 'end') && $a instanceof DateTime && $b instanceof DateTime) {
+            $a = $a->format('Y-m-d');
+            $b = $b->format('Y-m-d');
+        }
+        if ($prop == 'recurrence' && is_array($a) && is_array($b)) {
+            unset($a['EXCEPTIONS'], $b['EXCEPTIONS']);
+            $a = array_filter($a);
+            $b = array_filter($b);
+
+            // advanced rrule comparison: no rescheduling if series was shortened
+            if ($a['COUNT'] && $b['COUNT'] && $b['COUNT'] < $a['COUNT']) {
+              unset($a['COUNT'], $b['COUNT']);
+            }
+            else if ($a['UNTIL'] && $b['UNTIL'] && $b['UNTIL'] < $a['UNTIL']) {
+              unset($a['UNTIL'], $b['UNTIL']);
+            }
+        }
+        if ($a != $b) {
+            $reschedule = true;
+            break;
+        }
+    }
+
+    // reset all attendee status to needs-action (#4360)
+    if ($update && $reschedule && is_array($event['attendees'])) {
+      $is_organizer = false;
+      $emails = $this->cal->get_user_emails();
+      $attendees = $event['attendees'];
+      foreach ($attendees as $i => $attendee) {
+        if ($attendee['role'] == 'ORGANIZER' && $attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+          $is_organizer = true;
+        }
+        else if ($attendee['role'] != 'ORGANIZER' && $attendee['role'] != 'NON-PARTICIPANT' && $attendee['status'] != 'DELEGATED') {
+          $attendees[$i]['status'] = 'NEEDS-ACTION';
+          $attendees[$i]['rsvp'] = true;
+        }
+      }
+
+      // update attendees only if I'm the organizer
+      if ($is_organizer || ($event['organizer'] && in_array(strtolower($event['organizer']['email']), $emails))) {
+        $event['attendees'] = $attendees;
+      }
+    }
+
+    return $reschedule;
+  }
+
+  /**
+   * Convert save data to be used in SQL statements
+   */
+  private function _save_preprocess($event)
+  {
+    // shift dates to server's timezone (except for all-day events)
+    if (!$event['allday']) {
+      $event['start'] = clone $event['start'];
+      $event['start']->setTimezone($this->server_timezone);
+      $event['end'] = clone $event['end'];
+      $event['end']->setTimezone($this->server_timezone);
+    }
+    
+    // compose vcalendar-style recurrencue rule from structured data
+    $rrule = $event['recurrence'] ? libcalendaring::to_rrule($event['recurrence']) : '';
+    $event['_recurrence'] = rtrim($rrule, ';');
+    $event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]);
+    $event['sensitivity'] = intval($this->sensitivity_map[strtolower($event['sensitivity'])]);
+
+    if ($event['free_busy'] == 'tentative') {
+        $event['status'] = 'TENTATIVE';
+    }
+
+    if (isset($event['allday'])) {
+      $event['all_day'] = $event['allday'] ? 1 : 0;
+    }
+
+    // compute absolute time to notify the user
+    $event['notifyat'] = $this->_get_notification($event);
+
+    if (is_array($event['valarms'])) {
+        $event['alarms'] = $this->serialize_alarms($event['valarms']);
+    }
+
+    // process event attendees
+    if (!empty($event['attendees']))
+      $event['attendees'] = json_encode((array)$event['attendees']);
+    else
+      $event['attendees'] = '';
+
+    return $event;
+  }
+  
+  /**
+   * Compute absolute time to notify the user
+   */
+  private function _get_notification($event)
+  {
+    if ($event['valarms'] && $event['start'] > new DateTime()) {
+      $alarm = libcalendaring::get_next_alarm($event);
+
+      if ($alarm['time'] && in_array($alarm['action'], $this->alarm_types))
+        return date('Y-m-d H:i:s', $alarm['time']);
+    }
+
+    return null;
+  }
+
+  /**
+   * Save the given event record to database
+   *
+   * @param array Event data
+   * @param boolean True if recurring events instances should be updated, too
+   */
+  private function _update_event($event, $update_recurring = true)
+  {
+    $event = $this->_save_preprocess($event);
+    $sql_set = array();
+    $set_cols = array('start', 'end', 'all_day', 'recurrence_id', 'isexception', 'sequence', 'title', 'description', 'location', 'categories', 'url', 'free_busy', 'priority', 'sensitivity', 'status', 'attendees', 'alarms', 'notifyat');
+    foreach ($set_cols as $col) {
+      if (is_object($event[$col]) && is_a($event[$col], 'DateTime'))
+        $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]->format(self::DB_DATE_FORMAT));
+      else if (is_array($event[$col]))
+        $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote(join(',', $event[$col]));
+      else if (array_key_exists($col, $event))
+        $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]);
+    }
+    
+    if ($event['_recurrence'])
+      $sql_set[] = $this->rc->db->quote_identifier('recurrence') . '=' . $this->rc->db->quote($event['_recurrence']);
+    
+    if ($event['_instance'])
+      $sql_set[] = $this->rc->db->quote_identifier('instance') . '=' . $this->rc->db->quote($event['_instance']);
+    
+    if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar'])
+        $sql_set[] = 'calendar_id=' . $this->rc->db->quote($event['calendar']);
+    
+    $query = $this->rc->db->query(sprintf(
+      "UPDATE " . $this->db_events . "
+       SET   changed=%s %s
+       WHERE event_id=?
+       AND   calendar_id IN (" . $this->calendar_ids . ")",
+        $this->rc->db->now(),
+        ($sql_set ? ', ' . join(', ', $sql_set) : '')
+      ),
+      $event['id']
+    );
+
+    $success = $this->rc->db->affected_rows($query);
+
+    // add attachments
+    if ($success && !empty($event['attachments'])) {
+      foreach ($event['attachments'] as $attachment) {
+        $this->add_attachment($attachment, $event['id']);
+        unset($attachment);
+      }
+    }
+
+    // remove attachments
+    if ($success && !empty($event['deleted_attachments'])) {
+      foreach ($event['deleted_attachments'] as $attachment) {
+        $this->remove_attachment($attachment, $event['id']);
+      }
+    }
+
+    if ($success) {
+      unset($this->cache[$event['id']]);
+      if ($update_recurring)
+        $this->_update_recurring($event);
+    }
+
+    return $success;
+  }
+
+  /**
+   * Insert "fake" entries for recurring occurences of this event
+   */
+  private function _update_recurring($event)
+  {
+    if (empty($this->calendars))
+      return;
+
+    if (!empty($event['recurrence'])) {
+      $exdata = array();
+      $exceptions = $this->_load_exceptions($event);
+
+      foreach ($exceptions as $exception) {
+        $exdate = substr($exception['_instance'], 0, 8);
+        $exdata[$exdate] = $exception;
+      }
+    }
+
+    // clear existing recurrence copies
+    $this->rc->db->query(
+      "DELETE FROM " . $this->db_events . "
+       WHERE recurrence_id=?
+       AND isexception=0
+       AND calendar_id IN (" . $this->calendar_ids . ")",
+       $event['id']
+    );
+
+    // create new fake entries
+    if (!empty($event['recurrence'])) {
+      // include library class
+      require_once($this->cal->home . '/lib/calendar_recurrence.php');
+      
+      $recurrence = new calendar_recurrence($this->cal, $event);
+
+      $count = 0;
+      $event['allday'] = $event['all_day'];
+      $duration = $event['start']->diff($event['end']);
+      $recurrence_id_format = libcalendaring::recurrence_id_format($event);
+      while ($next_start = $recurrence->next_start()) {
+        $instance = $next_start->format($recurrence_id_format);
+        $datestr = substr($instance, 0, 8);
+
+        // skip exceptions
+        // TODO: merge updated data from master event
+        if ($exdata[$datestr]) {
+          continue;
+        }
+
+        $next_start->setTimezone($this->server_timezone);
+        $next_end = clone $next_start;
+        $next_end->add($duration);
+
+        $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_start, 'end' => $next_end, 'status' => $event['status']));
+        $query = $this->rc->db->query(sprintf(
+          "INSERT INTO " . $this->db_events . "
+           (calendar_id, recurrence_id, created, changed, uid, instance, %s, %s, all_day, sequence, recurrence, title, description, location, categories, url, free_busy, priority, sensitivity, status, alarms, attendees, notifyat)
+            SELECT calendar_id, ?, %s, %s, uid, ?, ?, ?, all_day, sequence, recurrence, title, description, location, categories, url, free_busy, priority, sensitivity, status, alarms, attendees, ?
+            FROM  " . $this->db_events . " WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")",
+            $this->rc->db->quote_identifier('start'),
+            $this->rc->db->quote_identifier('end'),
+            $this->rc->db->now(),
+            $this->rc->db->now()
+          ),
+          $event['id'],
+          $instance,
+          $next_start->format(self::DB_DATE_FORMAT),
+          $next_end->format(self::DB_DATE_FORMAT),
+          $notify_at,
+          $event['id']
+        );
+        
+        if (!$this->rc->db->affected_rows($query))
+          break;
+        
+        // stop adding events for inifinite recurrence after 20 years
+        if (++$count > 999 || (!$recurrence->recurEnd && !$recurrence->recurCount && $next_start->format('Y') > date('Y') + 20))
+          break;
+      }
+      
+      // remove all exceptions after recurrence end
+      if ($next_end && !empty($exceptions)) {
+          $this->rc->db->query(
+          "DELETE FROM " . $this->db_events . "
+           WHERE `recurrence_id`=?
+           AND `isexception`=1
+           AND `start` > ?
+           AND `calendar_id` IN (" . $this->calendar_ids . ")",
+           $event['id'],
+           $next_end->format(self::DB_DATE_FORMAT)
+        );
+      }
+    }
+  }
+  
+  /**
+   *
+   */
+  private function _load_exceptions($event, $instance_id = null)
+  {
+    $sql_add_where = '';
+    if (!empty($instance_id)) {
+      $sql_add_where = 'AND `instance`=?';
+    }
+
+    $result = $this->rc->db->query(
+      "SELECT * FROM " . $this->db_events . "
+       WHERE `recurrence_id`=?
+       AND `isexception`=1
+       AND `calendar_id` IN (" . $this->calendar_ids . ")
+       $sql_add_where
+       ORDER BY `instance`, `start`",
+       $event['id'],
+       $instance_id
+    );
+
+    $exceptions = array();
+    while ($result && ($sql_arr = $this->rc->db->fetch_assoc($result)) && $sql_arr['event_id']) {
+      $exception = $this->_read_postprocess($sql_arr);
+      $instance = $exception['_instance'] ?: $exception['start']->format($exception['allday'] ? 'Ymd' : 'Ymd\THis');
+      $exceptions[$instance] = $exception;
+    }
+
+    return $exceptions;
+  }
+
+  /**
+   * Move a single event
+   *
+   * @param array Hash array with event properties
+   * @see calendar_driver::move_event()
+   */
+  public function move_event($event)
+  {
+    // let edit_event() do all the magic
+    return $this->edit_event($event + (array)$this->get_event($event));
+  }
+
+  /**
+   * Resize a single event
+   *
+   * @param array Hash array with event properties
+   * @see calendar_driver::resize_event()
+   */
+  public function resize_event($event)
+  {
+    // let edit_event() do all the magic
+    return $this->edit_event($event + (array)$this->get_event($event));
+  }
+
+  /**
+   * Remove a single event from the database
+   *
+   * @param array   Hash array with event properties
+   * @param boolean Remove record irreversible (@TODO)
+   *
+   * @see calendar_driver::remove_event()
+   */
+  public function remove_event($event, $force = true)
+  {
+    if (!empty($this->calendars)) {
+      $event += (array)$this->get_event($event);
+      $master = $event;
+      $update_master = false;
+      $savemode = 'all';
+      $ret = true;
+
+      // read master if deleting a recurring event
+      if ($event['recurrence'] || $event['recurrence_id']) {
+        $master = $event['recurrence_id'] ? $this->get_event(array('id' => $event['recurrence_id'])) : $event;
+        $savemode = $event['_savemode'];
+      }
+
+      switch ($savemode) {
+        case 'current':
+          // add exception to master event
+          $master['recurrence']['EXDATE'][] = $event['start'];
+          $update_master = true;
+          
+          // just delete this single occurence
+          $query = $this->rc->db->query(
+            "DELETE FROM " . $this->db_events . "
+             WHERE calendar_id IN (" . $this->calendar_ids . ")
+             AND event_id=?",
+            $event['id']
+          );
+          break;
+
+        case 'future':
+          if ($master['id'] != $event['id']) {
+            // set until-date on master event
+            $master['recurrence']['UNTIL'] = clone $event['start'];
+            $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
+            unset($master['recurrence']['COUNT']);
+            $update_master = true;
+            
+            // delete this and all future instances
+            $fromdate = clone $event['start'];
+            $fromdate->setTimezone($this->server_timezone);
+            $query = $this->rc->db->query(
+              "DELETE FROM " . $this->db_events . "
+               WHERE calendar_id IN (" . $this->calendar_ids . ")
+               AND " . $this->rc->db->quote_identifier('start') . " >= ?
+               AND recurrence_id=?",
+              $fromdate->format(self::DB_DATE_FORMAT),
+              $master['id']
+            );
+            $ret = $master['id'];
+            break;
+          }
+          // else: future == all if modifying the master event
+
+        default:  // 'all' is default
+          $query = $this->rc->db->query(
+            "DELETE FROM " . $this->db_events . "
+             WHERE (event_id=? OR recurrence_id=?)
+             AND calendar_id IN (" . $this->calendar_ids . ")",
+             $master['id'],
+             $master['id']
+          );
+          break;
+      }
+
+      $success = $this->rc->db->affected_rows($query);
+      if ($success && $update_master)
+        $this->_update_event($master, true);
+
+      return $success ? $ret : false;
+    }
+    
+    return false;
+  }
+
+  /**
+   * Return data of a specific event
+   * @param mixed  Hash array with event properties or event UID
+   * @param integer Bitmask defining the scope to search events in
+   * @param boolean If true, recurrence exceptions shall be added
+   * @return array Hash array with event properties
+   */
+  public function get_event($event, $scope = 0, $full = false)
+  {
+    $id  = is_array($event) ? ($event['id'] ?: $event['uid']) : $event;
+    $cal = is_array($event) ? $event['calendar'] : null;
+    $col = is_array($event) && is_numeric($id) ? 'event_id' : 'uid';
+
+    $where_add = '';
+    if (is_array($event) && !$event['id'] && !empty($event['_instance'])) {
+      $where_add = 'AND instance=' . $this->rc->db->quote($event['_instance']);
+    }
+
+    if ($this->cache[$id])
+      return $this->cache[$id];
+
+    // get event from the address books birthday calendar
+    if ($cal == self::BIRTHDAY_CALENDAR_ID) {
+      return $this->get_birthday_event($id);
+    }
+
+    if ($scope & self::FILTER_ACTIVE) {
+      $calendars = $this->calendars;
+      foreach ($calendars as $idx => $cal) {
+        if (!$cal['active']) {
+          unset($calendars[$idx]);
+        }
+      }
+      $cals = join(',', $calendars);
+    }
+    else {
+      $cals = $this->calendar_ids;
+    }
+
+    $result = $this->rc->db->query(sprintf(
+      "SELECT e.*, (SELECT COUNT(attachment_id) FROM " . $this->db_attachments . " 
+         WHERE event_id = e.event_id OR event_id = e.recurrence_id) AS _attachments
+       FROM " . $this->db_events . " AS e
+       WHERE e.calendar_id IN (%s)
+       AND e.$col=?
+       %s",
+       $cals,
+       $where_add
+      ),
+      $id);
+
+    if ($result && ($sql_arr = $this->rc->db->fetch_assoc($result)) && $sql_arr['event_id']) {
+      $event = $this->_read_postprocess($sql_arr);
+
+      // also load recurrence exceptions
+      if (!empty($event['recurrence']) && $full) {
+        $event['recurrence']['EXCEPTIONS'] = array_values($this->_load_exceptions($event));
+      }
+
+      $this->cache[$id] = $event;
+      return $this->cache[$id];
+    }
+
+    return false;
+  }
+
+  /**
+   * Get event data
+   *
+   * @see calendar_driver::load_events()
+   */
+  public function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null)
+  {
+    if (empty($calendars))
+      $calendars = array_keys($this->calendars);
+    else if (!is_array($calendars))
+      $calendars = explode(',', strval($calendars));
+      
+    // only allow to select from calendars of this use
+    $calendar_ids = array_map(array($this->rc->db, 'quote'), array_intersect($calendars, array_keys($this->calendars)));
+    
+    // compose (slow) SQL query for searching
+    // FIXME: improve searching using a dedicated col and normalized values
+    if ($query) {
+      foreach (array('title','location','description','categories','attendees') as $col)
+        $sql_query[] = $this->rc->db->ilike($col, '%'.$query.'%');
+      $sql_add = 'AND (' . join(' OR ', $sql_query) . ')';
+    }
+    
+    if (!$virtual)
+      $sql_add .= ' AND e.recurrence_id = 0';
+    
+    if ($modifiedsince)
+      $sql_add .= ' AND e.changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $modifiedsince));
+    
+    $events = array();
+    if (!empty($calendar_ids)) {
+      $result = $this->rc->db->query(sprintf(
+        "SELECT e.*, (SELECT COUNT(attachment_id) FROM " . $this->db_attachments . "
+            WHERE event_id = e.event_id OR event_id = e.recurrence_id) AS _attachments
+         FROM " . $this->db_events . " e
+         WHERE e.calendar_id IN (%s)
+            AND e.start <= %s AND e.end >= %s
+            %s",
+         join(',', $calendar_ids),
+         $this->rc->db->fromunixtime($end),
+         $this->rc->db->fromunixtime($start),
+         $sql_add
+       ));
+
+      while ($result && ($sql_arr = $this->rc->db->fetch_assoc($result))) {
+        $event = $this->_read_postprocess($sql_arr);
+        $add = true;
+
+        if (!empty($event['recurrence']) && !$event['recurrence_id']) {
+          // load recurrence exceptions (i.e. for export)
+          if (!$virtual) {
+            $event['recurrence']['EXCEPTIONS'] = $this->_load_exceptions($event);
+          }
+          // check for exception on first instance
+          else {
+            $instance = libcalendaring::recurrence_instance_identifier($event);
+            $exceptions = $this->_load_exceptions($event, $instance);
+            if ($exceptions && is_array($exceptions[$instance])) {
+              $event = $exceptions[$instance];
+              $add = false;
+            }
+          }
+        }
+
+        if ($add)
+          $events[] = $event;
+      }
+    }
+
+    // add events from the address books birthday calendar
+    if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars) && empty($query)) {
+      $events = array_merge($events, $this->load_birthday_events($start, $end, $search, $modifiedsince));
+    }
+
+    return $events;
+  }
+
+  /**
+   * Get number of events in the given calendar
+   *
+   * @param  mixed   List of calendar IDs to count events (either as array or comma-separated string)
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @return array   Hash array with counts grouped by calendar ID
+   */
+  public function count_events($calendars, $start, $end = null)
+  {
+      // not implemented
+      return array();
+  }
+
+  /**
+   * Convert sql record into a rcube style event object
+   */
+  private function _read_postprocess($event)
+  {
+    $free_busy_map = array_flip($this->free_busy_map);
+    $sensitivity_map = array_flip($this->sensitivity_map);
+    
+    $event['id'] = $event['event_id'];
+    $event['start'] = new DateTime($event['start']);
+    $event['end'] = new DateTime($event['end']);
+    $event['allday'] = intval($event['all_day']);
+    $event['created'] = new DateTime($event['created']);
+    $event['changed'] = new DateTime($event['changed']);
+    $event['free_busy'] = $free_busy_map[$event['free_busy']];
+    $event['sensitivity'] = $sensitivity_map[$event['sensitivity']];
+    $event['calendar'] = $event['calendar_id'];
+    $event['recurrence_id'] = intval($event['recurrence_id']);
+    $event['isexception'] = intval($event['isexception']);
+    
+    // parse recurrence rule
+    if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) {
+      $event['recurrence'] = array();
+      foreach ($m as $rr) {
+        if (is_numeric($rr[2]))
+          $rr[2] = intval($rr[2]);
+        else if ($rr[1] == 'UNTIL')
+          $rr[2] = date_create($rr[2]);
+        else if ($rr[1] == 'RDATE')
+          $rr[2] = array_map('date_create', explode(',', $rr[2]));
+        else if ($rr[1] == 'EXDATE')
+          $rr[2] = array_map('date_create', explode(',', $rr[2]));
+        $event['recurrence'][$rr[1]] = $rr[2];
+      }
+    }
+    
+    if ($event['recurrence_id']) {
+      libcalendaring::identify_recurrence_instance($event);
+    }
+    
+    if (strlen($event['instance'])) {
+      $event['_instance'] = $event['instance'];
+
+      if (empty($event['recurrence_id'])) {
+        $event['recurrence_date'] = rcube_utils::anytodatetime($event['_instance'], $event['start']->getTimezone());
+      }
+    }
+    
+    if ($event['_attachments'] > 0) {
+      $event['attachments'] = (array)$this->list_attachments($event);
+    }
+    
+    // decode serialized event attendees
+    if (strlen($event['attendees'])) {
+      $event['attendees'] = $this->unserialize_attendees($event['attendees']);
+    }
+    else {
+      $event['attendees'] = array();
+    }
+    
+    // decode serialized alarms
+    if ($event['alarms']) {
+      $event['valarms'] = $this->unserialize_alarms($event['alarms']);
+    }
+    
+    unset($event['event_id'], $event['calendar_id'], $event['notifyat'], $event['all_day'], $event['instance'], $event['_attachments']);
+    return $event;
+  }
+
+  /**
+   * Get a list of pending alarms to be displayed to the user
+   *
+   * @see calendar_driver::pending_alarms()
+   */
+  public function pending_alarms($time, $calendars = null)
+  {
+    if (empty($calendars))
+      $calendars = array_keys($this->calendars);
+    else if (is_string($calendars))
+      $calendars = explode(',', $calendars);
+    
+    // only allow to select from calendars with activated alarms
+    $calendar_ids = array();
+    foreach ($calendars as $cid) {
+      if ($this->calendars[$cid] && $this->calendars[$cid]['showalarms'])
+        $calendar_ids[] = $cid;
+    }
+    $calendar_ids = array_map(array($this->rc->db, 'quote'), $calendar_ids);
+    
+    $alarms = array();
+    if (!empty($calendar_ids)) {
+      $result = $this->rc->db->query(sprintf(
+        "SELECT * FROM " . $this->db_events . "
+         WHERE calendar_id IN (%s)
+         AND notifyat <= %s AND %s > %s",
+         join(',', $calendar_ids),
+         $this->rc->db->fromunixtime($time),
+         $this->rc->db->quote_identifier('end'),
+         $this->rc->db->fromunixtime($time)
+       ));
+
+      while ($result && ($event = $this->rc->db->fetch_assoc($result)))
+        $alarms[] = $this->_read_postprocess($event);
+    }
+
+    return $alarms;
+  }
+
+  /**
+   * Feedback after showing/sending an alarm notification
+   *
+   * @see calendar_driver::dismiss_alarm()
+   */
+  public function dismiss_alarm($event_id, $snooze = 0)
+  {
+    // set new notifyat time or unset if not snoozed
+    $notify_at = $snooze > 0 ? date(self::DB_DATE_FORMAT, time() + $snooze) : null;
+    
+    $query = $this->rc->db->query(sprintf(
+      "UPDATE " . $this->db_events . "
+       SET   changed=%s, notifyat=?
+       WHERE event_id=?
+       AND calendar_id IN (" . $this->calendar_ids . ")",
+        $this->rc->db->now()),
+      $notify_at,
+      $event_id
+    );
+    
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Save an attachment related to the given event
+   */
+  private function add_attachment($attachment, $event_id)
+  {
+    $data = $attachment['data'] ? $attachment['data'] : file_get_contents($attachment['path']);
+    
+    $query = $this->rc->db->query(
+      "INSERT INTO " . $this->db_attachments .
+      " (event_id, filename, mimetype, size, data)" .
+      " VALUES (?, ?, ?, ?, ?)",
+      $event_id,
+      $attachment['name'],
+      $attachment['mimetype'],
+      strlen($data),
+      base64_encode($data)
+    );
+
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Remove a specific attachment from the given event
+   */
+  private function remove_attachment($attachment_id, $event_id)
+  {
+    $query = $this->rc->db->query(
+      "DELETE FROM " . $this->db_attachments .
+      " WHERE attachment_id = ?" .
+        " AND event_id IN (SELECT event_id FROM " . $this->db_events .
+          " WHERE event_id = ?"  .
+            " AND calendar_id IN (" . $this->calendar_ids . "))",
+      $attachment_id,
+      $event_id
+    );
+
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * List attachments of specified event
+   */
+  public function list_attachments($event)
+  {
+    $attachments = array();
+
+    if (!empty($this->calendar_ids)) {
+      $result = $this->rc->db->query(
+        "SELECT attachment_id AS id, filename AS name, mimetype, size " .
+        " FROM " . $this->db_attachments .
+        " WHERE event_id IN (SELECT event_id FROM " . $this->db_events .
+          " WHERE event_id=?"  .
+            " AND calendar_id IN (" . $this->calendar_ids . "))".
+        " ORDER BY filename",
+        $event['recurrence_id'] ? $event['recurrence_id'] : $event['event_id']
+      );
+
+      while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
+        $attachments[] = $arr;
+      }
+    }
+
+    return $attachments;
+  }
+
+  /**
+   * Get attachment properties
+   */
+  public function get_attachment($id, $event)
+  {
+    if (!empty($this->calendar_ids)) {
+      $result = $this->rc->db->query(
+        "SELECT attachment_id AS id, filename AS name, mimetype, size " .
+        " FROM " . $this->db_attachments .
+        " WHERE attachment_id=?".
+          " AND event_id=?",
+        $id,
+        $event['recurrence_id'] ? $event['recurrence_id'] : $event['id']
+      );
+
+      if ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
+        return $arr;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Get attachment body
+   */
+  public function get_attachment_body($id, $event)
+  {
+    if (!empty($this->calendar_ids)) {
+      $result = $this->rc->db->query(
+        "SELECT data " .
+        " FROM " . $this->db_attachments .
+        " WHERE attachment_id=?".
+          " AND event_id=?",
+        $id,
+        $event['id']
+      );
+
+      if ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
+        return base64_decode($arr['data']);
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Remove the given category
+   */
+  public function remove_category($name)
+  {
+    $query = $this->rc->db->query(
+      "UPDATE " . $this->db_events . "
+       SET   categories=''
+       WHERE categories=?
+       AND   calendar_id IN (" . $this->calendar_ids . ")",
+      $name
+    );
+    
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Update/replace a category
+   */
+  public function replace_category($oldname, $name, $color)
+  {
+    $query = $this->rc->db->query(
+      "UPDATE " . $this->db_events . "
+       SET   categories=?
+       WHERE categories=?
+       AND   calendar_id IN (" . $this->calendar_ids . ")",
+      $name,
+      $oldname
+    );
+    
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * Helper method to serialize the list of alarms into a string
+   */
+  private function serialize_alarms($valarms)
+  {
+      foreach ((array)$valarms as $i => $alarm) {
+          if ($alarm['trigger'] instanceof DateTime) {
+              $valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c');
+          }
+      }
+
+      return $valarms ? json_encode($valarms) : null;
+  }
+
+  /**
+   * Helper method to decode a serialized list of alarms
+   */
+  private function unserialize_alarms($alarms)
+  {
+      // decode json serialized alarms
+      if ($alarms && $alarms[0] == '[') {
+          $valarms = json_decode($alarms, true);
+          foreach ($valarms as $i => $alarm) {
+              if ($alarm['trigger'][0] == '@') {
+                  try {
+                      $valarms[$i]['trigger'] = new DateTime(substr($alarm['trigger'], 1));
+                  }
+                  catch (Exception $e) {
+                      unset($valarms[$i]);
+                  }
+              }
+          }
+      }
+      // convert legacy alarms data
+      else if (strlen($alarms)) {
+          list($trigger, $action) = explode(':', $alarms, 2);
+          if ($trigger = libcalendaring::parse_alarm_value($trigger)) {
+              $valarms = array(array('action' => $action, 'trigger' => $trigger[3] ?: $trigger[0]));
+          }
+      }
+
+      return $valarms;
+  }
+
+  /**
+   * Helper method to decode the attendees list from string
+   */
+  private function unserialize_attendees($s_attendees)
+  {
+    $attendees = array();
+
+    // decode json serialized string
+    if ($s_attendees[0] == '[') {
+      $attendees = json_decode($s_attendees, true);
+    }
+    // decode the old serialization format
+    else {
+      foreach (explode("\n", $s_attendees) as $line) {
+        $att = array();
+        foreach (rcube_utils::explode_quoted_string(';', $line) as $prop) {
+          list($key, $value) = explode("=", $prop);
+          $att[strtolower($key)] = stripslashes(trim($value, '""'));
+        }
+        $attendees[] = $att;
+      }
+    }
+
+    return $attendees;
+  }
+
+  /**
+   * Handler for user_delete plugin hook
+   */
+  public function user_delete($args)
+  {
+      $db = $this->rc->db;
+      $user = $args['user'];
+      $event_ids = array();
+
+      $events = $db->query(
+          "SELECT event_id FROM " . $this->db_events . " AS ev" .
+          " LEFT JOIN " . $this->db_calendars . " cal ON (ev.calendar_id = cal.calendar_id)".
+          " WHERE user_id=?",
+          $user->ID);
+
+      while ($row = $db->fetch_assoc($events)) {
+          $event_ids[] = $row['event_id'];
+      }
+
+      if (!empty($event_ids)) {
+          foreach (array($this->db_attachments, $this->db_events) as $table) {
+              $db->query(sprintf("DELETE FROM $table WHERE event_id IN (%s)", join(',', $event_ids)));
+          }
+      }
+
+      foreach (array($this->db_calendars, 'itipinvitations') as $table) {
+          $db->query("DELETE FROM $table WHERE user_id=?", $user->ID);
+      }
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/mysql.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,32 @@
+/**
+ * Roundcube Calendar Kolab backend
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli
+ * @licence GNU AGPL
+ **/
+
+CREATE TABLE IF NOT EXISTS `kolab_alarms` (
+  `alarm_id` VARCHAR(255) NOT NULL,
+  `user_id` int(10) UNSIGNED NOT NULL,
+  `notifyat` DATETIME DEFAULT NULL,
+  `dismissed` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+  PRIMARY KEY(`alarm_id`,`user_id`),
+  CONSTRAINT `fk_kolab_alarms_user_id` FOREIGN KEY (`user_id`)
+    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */;
+
+CREATE TABLE IF NOT EXISTS `itipinvitations` (
+  `token` VARCHAR(64) NOT NULL,
+  `event_uid` VARCHAR(255) NOT NULL,
+  `user_id` int(10) UNSIGNED NOT NULL DEFAULT '0',
+  `event` TEXT NOT NULL,
+  `expires` DATETIME DEFAULT NULL,
+  `cancelled` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+  PRIMARY KEY(`token`),
+  INDEX `uid_idx` (`event_uid`,`user_id`),
+  CONSTRAINT `fk_itipinvitations_user_id` FOREIGN KEY (`user_id`)
+    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
+REPLACE INTO system (name, value) VALUES ('calendar-kolab-version', '2014041700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/mysql/2012080600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,11 @@
+DROP TABLE IF EXISTS `kolab_alarms`;
+
+CREATE TABLE `kolab_alarms` (
+  `event_id` VARCHAR(255) NOT NULL,
+  `user_id` int(10) UNSIGNED NOT NULL,
+  `notifyat` DATETIME DEFAULT NULL,
+  `dismissed` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
+  PRIMARY KEY(`event_id`),
+  CONSTRAINT `fk_kolab_alarms_user_id` FOREIGN KEY (`user_id`)
+    REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/mysql/2013011000.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+-- empty
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/mysql/2014041700.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+ALTER TABLE `kolab_alarms` CHANGE `event_id` `alarm_id` VARCHAR(255) NOT NULL;
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/mysql/2014082600.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,2 @@
+ALTER TABLE `kolab_alarms` DROP PRIMARY KEY;
+ALTER TABLE `kolab_alarms` ADD PRIMARY KEY (`alarm_id`, `user_id`);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/oracle.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,31 @@
+/**
+ * Roundcube Calendar Kolab backend
+ *
+ * @author Aleksander Machniak
+ * @licence GNU AGPL
+ **/
+
+CREATE TABLE "kolab_alarms" (
+    "alarm_id" varchar(255) NOT NULL PRIMARY KEY,
+    "user_id" integer NOT NULL
+        REFERENCES "users" ("user_id") ON DELETE CASCADE,
+    "notifyat" timestamp DEFAULT NULL,
+    "dismissed" smallint DEFAULT 0 NOT NULL
+);
+
+CREATE INDEX "kolab_alarms_user_id_idx" ON "kolab_alarms" ("user_id");
+
+
+CREATE TABLE "itipinvitations" (
+    "token" varchar(64) NOT NULL PRIMARY KEY,
+    "event_uid" varchar(255) NOT NULL,
+    "user_id" integer NOT NULL
+        REFERENCES "users" ("user_id") ON DELETE CASCADE,
+    "event" long NOT NULL,
+    "expires" timestamp DEFAULT NULL,
+    "cancelled" smallint DEFAULT 0 NOT NULL
+);
+
+CREATE INDEX "itipinvitations_user_id_idx" ON "itipinvitations" ("user_id", "event_uid");
+
+INSERT INTO "system" ("name", "value") VALUES ('calendar-kolab-version', '2014041700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/SQL/postgres.initial.sql	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,32 @@
+/**
+ * Roundcube Calendar Kolab backend
+ *
+ * @author Sergey Sidlyarenko
+ * @licence GNU AGPL
+ **/
+
+CREATE TABLE IF NOT EXISTS kolab_alarms (
+  alarm_id character varying(255) NOT NULL,
+  user_id integer NOT NULL
+        REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
+  notifyat timestamp without time zone DEFAULT NULL,
+  dismissed smallint NOT NULL DEFAULT 0,
+  PRIMARY KEY(alarm_id)
+);
+
+CREATE INDEX kolab_alarms_user_id_idx ON kolab_alarms (user_id);
+
+CREATE TABLE IF NOT EXISTS itipinvitations (
+  token character varying(64) NOT NULL,
+  event_uid character varying(255) NOT NULL,
+  user_id integer NOT NULL
+        REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
+  event text NOT NULL,
+  expires timestamp without time zone DEFAULT NULL,
+  cancelled smallint NOT NULL DEFAULT 0,
+  PRIMARY KEY(token)
+);
+
+CREATE INDEX itipinvitations_user_id_event_uid_idx ON itipinvitations (user_id, event_uid);
+
+INSERT INTO system (name, value) VALUES ('calendar-kolab-version', '2014041700');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,869 @@
+<?php
+
+/**
+ * Kolab calendar storage class
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ * @author Aleksander Machniak <machniak@kolabsys.com>
+ *
+ * Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+
+class kolab_calendar extends kolab_storage_folder_api
+{
+  public $ready = false;
+  public $rights = 'lrs';
+  public $editable = false;
+  public $attachments = true;
+  public $alarms = false;
+  public $history = false;
+  public $subscriptions = true;
+  public $categories = array();
+  public $storage;
+
+  public $type = 'event';
+
+  protected $cal;
+  protected $events = array();
+  protected $search_fields = array('title', 'description', 'location', 'attendees');
+
+  /**
+   * Factory method to instantiate a kolab_calendar object
+   *
+   * @param string  Calendar ID (encoded IMAP folder name)
+   * @param object  calendar plugin object
+   * @return object kolab_calendar instance
+   */
+  public static function factory($id, $calendar)
+  {
+    $imap = $calendar->rc->get_storage();
+    $imap_folder = kolab_storage::id_decode($id);
+    $info = $imap->folder_info($imap_folder, true);
+    if (empty($info) || $info['noselect'] || strpos(kolab_storage::folder_type($imap_folder), 'event') !== 0) {
+      return new kolab_user_calendar($imap_folder, $calendar);
+    }
+    else {
+      return new kolab_calendar($imap_folder, $calendar);
+    }
+  }
+
+  /**
+   * Default constructor
+   */
+  public function __construct($imap_folder, $calendar)
+  {
+    $this->cal = $calendar;
+    $this->imap = $calendar->rc->get_storage();
+    $this->name = $imap_folder;
+
+    // ID is derrived from folder name
+    $this->id = kolab_storage::folder_id($this->name, true);
+    $old_id   = kolab_storage::folder_id($this->name, false);
+
+    // fetch objects from the given IMAP folder
+    $this->storage = kolab_storage::get_folder($this->name);
+    $this->ready = $this->storage && $this->storage->valid;
+
+    // Set writeable and alarms flags according to folder permissions
+    if ($this->ready) {
+      if ($this->storage->get_namespace() == 'personal') {
+        $this->editable = true;
+        $this->rights = 'lrswikxteav';
+        $this->alarms = true;
+      }
+      else {
+        $rights = $this->storage->get_myrights();
+        if ($rights && !PEAR::isError($rights)) {
+          $this->rights = $rights;
+          if (strpos($rights, 't') !== false || strpos($rights, 'd') !== false)
+            $this->editable = strpos($rights, 'i');;
+        }
+      }
+      
+      // user-specific alarms settings win
+      $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+      if (isset($prefs[$this->id]['showalarms']))
+        $this->alarms = $prefs[$this->id]['showalarms'];
+      else if (isset($prefs[$old_id]['showalarms']))
+        $this->alarms = $prefs[$old_id]['showalarms'];
+    }
+
+    $this->default = $this->storage->default;
+    $this->subtype = $this->storage->subtype;
+  }
+
+
+  /**
+   * Getter for the IMAP folder name
+   *
+   * @return string Name of the IMAP folder
+   */
+  public function get_realname()
+  {
+    return $this->name;
+  }
+
+  /**
+   *
+   */
+  public function get_title()
+  {
+    return null;
+  }
+
+
+  /**
+   * Return color to display this calendar
+   */
+  public function get_color($default = null)
+  {
+    // color is defined in folder METADATA
+    if ($color = $this->storage->get_color()) {
+      return $color;
+    }
+
+    // calendar color is stored in user prefs (temporary solution)
+    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+
+    if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
+      return $prefs[$this->id]['color'];
+
+    return $default ?: 'cc0000';
+  }
+
+  /**
+   * Compose an URL for CalDAV access to this calendar (if configured)
+   */
+  public function get_caldav_url()
+  {
+    if ($template = $this->cal->rc->config->get('calendar_caldav_url', null)) {
+      return strtr($template, array(
+        '%h' => $_SERVER['HTTP_HOST'],
+        '%u' => urlencode($this->cal->rc->get_user_name()),
+        '%i' => urlencode($this->storage->get_uid()),
+        '%n' => urlencode($this->name),
+      ));
+    }
+
+    return false;
+  }
+
+
+  /**
+   * Update properties of this calendar folder
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function update(&$prop)
+  {
+    $prop['oldname'] = $this->get_realname();
+    $newfolder = kolab_storage::folder_update($prop);
+
+    if ($newfolder === false) {
+      $this->cal->last_error = $this->cal->gettext(kolab_storage::$last_error);
+      return false;
+    }
+
+    // create ID
+    return kolab_storage::folder_id($newfolder);
+  }
+
+  /**
+   * Getter for a single event object
+   */
+  public function get_event($id)
+  {
+    // remove our occurrence identifier if it's there
+    $master_id = preg_replace('/-\d{8}(T\d{6})?$/', '', $id);
+
+    // directly access storage object
+    if (!$this->events[$id] && $master_id == $id && ($record = $this->storage->get_object($id))) {
+      $this->events[$id] = $this->_to_driver_event($record, true);
+    }
+
+    // maybe a recurring instance is requested
+    if (!$this->events[$id] && $master_id != $id) {
+      $instance_id = substr($id, strlen($master_id) + 1);
+
+      if ($record = $this->storage->get_object($master_id)) {
+        $master = $this->_to_driver_event($record);
+      }
+
+      if ($master) {
+        // check for match in top-level exceptions (aka loose single occurrences)
+        if ($master['_formatobj'] && ($instance = $master['_formatobj']->get_instance($instance_id))) {
+          $this->events[$id] = $this->_to_driver_event($instance, false, true, $master);
+        }
+        // check for match on the first instance already
+        else if ($master['_instance'] && $master['_instance'] == $instance_id) {
+          $this->events[$id] = $master;
+        }
+        else if (is_array($master['recurrence'])) {
+          $this->get_recurring_events($record, $master['start'], null, $id);
+        }
+      }
+    }
+
+    return $this->events[$id];
+  }
+
+  /**
+   * Get attachment body
+   * @see calendar_driver::get_attachment_body()
+   */
+  public function get_attachment_body($id, $event)
+  {
+    if (!$this->ready)
+        return false;
+
+    $data = $this->storage->get_attachment($event['id'], $id);
+
+    if ($data == null) {
+        // try again with master UID
+        $uid = preg_replace('/-\d+(T\d{6})?$/', '', $event['id']);
+        if ($uid != $event['id']) {
+            $data = $this->storage->get_attachment($uid, $id);
+        }
+    }
+
+    return $data;
+  }
+
+  /**
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  boolean Include virtual events (optional)
+   * @param  array   Additional parameters to query storage
+   * @param  array   Additional query to filter events
+   * @return array A list of event records
+   */
+  public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null)
+  {
+    // convert to DateTime for comparisons
+    // #5190: make the range a little bit wider
+    // to workaround possible timezone differences
+    try {
+      $start = new DateTime('@' . ($start - 12 * 3600));
+    }
+    catch (Exception $e) {
+      $start = new DateTime('@0');
+    }
+    try {
+      $end = new DateTime('@' . ($end + 12 * 3600));
+    }
+    catch (Exception $e) {
+      $end = new DateTime('today +10 years');
+    }
+
+    // get email addresses of the current user
+    $user_emails = $this->cal->get_user_emails();
+
+    // query Kolab storage
+    $query[] = array('dtstart', '<=', $end);
+    $query[] = array('dtend',   '>=', $start);
+
+    if (is_array($filter_query)) {
+      $query = array_merge($query, $filter_query);
+    }
+
+    if (!empty($search)) {
+        $search = mb_strtolower($search);
+        $words = rcube_utils::tokenize_string($search, 1);
+        foreach (rcube_utils::normalize_string($search, true) as $word) {
+            $query[] = array('words', 'LIKE', $word);
+        }
+    }
+    else {
+      $words = array();
+    }
+
+    // set partstat filter to skip pending and declined invitations
+    if (empty($filter_query) && $this->cal->rc->config->get('kolab_invitation_calendars')
+      && $this->get_namespace() != 'other'
+    ) {
+      $partstat_exclude = array('NEEDS-ACTION','DECLINED');
+    }
+    else {
+      $partstat_exclude = array();
+    }
+
+    $events = array();
+    foreach ($this->storage->select($query) as $record) {
+      $event = $this->_to_driver_event($record, !$virtual, false);
+
+      // remember seen categories
+      if ($event['categories']) {
+        $cat = is_array($event['categories']) ? $event['categories'][0] : $event['categories'];
+        $this->categories[$cat]++;
+      }
+
+      // list events in requested time window
+      if ($event['start'] <= $end && $event['end'] >= $start) {
+        unset($event['_attendees']);
+        $add = true;
+
+        // skip the first instance of a recurring event if listed in exdate
+        if ($virtual && !empty($event['recurrence']['EXDATE'])) {
+          $event_date = $event['start']->format('Ymd');
+          $exdates = (array)$event['recurrence']['EXDATE'];
+
+          foreach ($exdates as $exdate) {
+            if ($exdate->format('Ymd') == $event_date) {
+              $add = false;
+              break;
+            }
+          }
+        }
+
+        // find and merge exception for the first instance
+        if ($virtual && !empty($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS'])) {
+          foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
+            if ($event['_instance'] == $exception['_instance']) {
+              // clone date objects from main event before adjusting them with exception data
+              if (is_object($event['start'])) $event['start'] = clone $record['start'];
+              if (is_object($event['end']))   $event['end']   = clone $record['end'];
+              kolab_driver::merge_exception_data($event, $exception);
+            }
+          }
+        }
+
+        if ($add)
+          $events[] = $event;
+      }
+
+      // resolve recurring events
+      if ($record['recurrence'] && $virtual == 1) {
+        $events = array_merge($events, $this->get_recurring_events($record, $start, $end));
+      }
+      // add top-level exceptions (aka loose single occurrences)
+      else if (is_array($record['exceptions'])) {
+        foreach ($record['exceptions'] as $ex) {
+          $component = $this->_to_driver_event($ex, false, false, $record);
+          if ($component['start'] <= $end && $component['end'] >= $start) {
+            $events[] = $component;
+          }
+        }
+      }
+    }
+
+    // post-filter all events by fulltext search and partstat values
+    $me = $this;
+    $events = array_filter($events, function($event) use ($words, $partstat_exclude, $user_emails, $me) {
+      // fulltext search
+      if (count($words)) {
+        $hits = 0;
+        foreach ($words as $word) {
+          $hits += $me->fulltext_match($event, $word, false);
+        }
+        if ($hits < count($words)) {
+          return false;
+        }
+      }
+
+      // partstat filter
+      if (count($partstat_exclude) && is_array($event['attendees'])) {
+        foreach ($event['attendees'] as $attendee) {
+          if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $partstat_exclude)) {
+            return false;
+          }
+        }
+      }
+
+      return true;
+    });
+
+    // Apply event-to-mail relations
+    $config = kolab_storage_config::get_instance();
+    $config->apply_links($events);
+
+    // avoid session race conditions that will loose temporary subscriptions
+    $this->cal->rc->session->nowrite = true;
+
+    return $events;
+  }
+
+  /**
+   *
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @param  array   Additional query to filter events
+   * @return integer Count
+   */
+  public function count_events($start, $end = null, $filter_query = null)
+  {
+    // convert to DateTime for comparisons
+    try {
+      $start = new DateTime('@'.$start);
+    }
+    catch (Exception $e) {
+      $start = new DateTime('@0');
+    }
+    if ($end) {
+      try {
+        $end = new DateTime('@'.$end);
+      }
+      catch (Exception $e) {
+        $end = null;
+      }
+    }
+
+    // query Kolab storage
+    $query[] = array('dtend',   '>=', $start);
+    
+    if ($end)
+      $query[] = array('dtstart', '<=', $end);
+
+    // add query to exclude pending/declined invitations
+    if (empty($filter_query)) {
+      foreach ($this->cal->get_user_emails() as $email) {
+        $query[] = array('tags', '!=', 'x-partstat:' . $email . ':needs-action');
+        $query[] = array('tags', '!=', 'x-partstat:' . $email . ':declined');
+      }
+    }
+    else if (is_array($filter_query)) {
+      $query = array_merge($query, $filter_query);
+    }
+
+    // we rely the Kolab storage query (no post-filtering)
+    return $this->storage->count($query);
+  }
+
+  /**
+   * Create a new event record
+   *
+   * @see calendar_driver::new_event()
+   * 
+   * @return mixed The created record ID on success, False on error
+   */
+  public function insert_event($event)
+  {
+    if (!is_array($event))
+      return false;
+
+    // email links are stored separately
+    $links = $event['links'];
+    unset($event['links']);
+
+    //generate new event from RC input
+    $object = $this->_from_driver_event($event);
+    $saved  = $this->storage->save($object, 'event');
+
+    if (!$saved) {
+      rcube::raise_error(array(
+        'code' => 600, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => "Error saving event object to Kolab server"),
+        true, false);
+      $saved = false;
+    }
+    else {
+      // save links in configuration.relation object
+      if ($this->save_links($event['uid'], $links)) {
+        $object['links'] = $links;
+      }
+
+      $this->events = array($event['uid'] => $this->_to_driver_event($object, true));
+    }
+
+    return $saved;
+  }
+
+  /**
+   * Update a specific event record
+   *
+   * @see calendar_driver::new_event()
+   * @return boolean True on success, False on error
+   */
+
+  public function update_event($event, $exception_id = null)
+  {
+    $updated = false;
+    $old = $this->storage->get_object($event['uid'] ?: $event['id']);
+    if (!$old || PEAR::isError($old))
+      return false;
+
+    // email links are stored separately
+    $links = $event['links'];
+    unset($event['links']);
+
+    $object = $this->_from_driver_event($event, $old);
+    $saved  = $this->storage->save($object, 'event', $old['uid']);
+
+    if (!$saved) {
+      rcube::raise_error(array(
+        'code' => 600, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => "Error saving event object to Kolab server"),
+        true, false);
+    }
+    else {
+      // save links in configuration.relation object
+      if ($this->save_links($event['uid'], $links)) {
+        $object['links'] = $links;
+      }
+
+      $updated = true;
+      $this->events = array($event['uid'] => $this->_to_driver_event($object, true));
+
+      // refresh local cache with recurring instances
+      if ($exception_id) {
+        $this->get_recurring_events($object, $event['start'], $event['end'], $exception_id);
+      }
+    }
+
+    return $updated;
+  }
+
+  /**
+   * Delete an event record
+   *
+   * @see calendar_driver::remove_event()
+   * @return boolean True on success, False on error
+   */
+  public function delete_event($event, $force = true)
+  {
+    $deleted = $this->storage->delete($event['uid'] ?: $event['id'], $force);
+
+    if (!$deleted) {
+      rcube::raise_error(array(
+        'code' => 600, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => sprintf("Error deleting event object '%s' from Kolab server", $event['id'])),
+        true, false);
+    }
+
+    return $deleted;
+  }
+
+  /**
+   * Restore deleted event record
+   *
+   * @see calendar_driver::undelete_event()
+   * @return boolean True on success, False on error
+   */
+  public function restore_event($event)
+  {
+    if ($this->storage->undelete($event['id'])) {
+        return true;
+    }
+    else {
+        rcube::raise_error(array(
+          'code' => 600, 'type' => 'php',
+          'file' => __FILE__, 'line' => __LINE__,
+          'message' => "Error undeleting the event object $event[id] from the Kolab server"),
+        true, false);
+    }
+
+    return false;
+  }
+
+  /**
+   * Find messages linked with an event
+   */
+  protected function get_links($uid)
+  {
+    $storage = kolab_storage_config::get_instance();
+    return $storage->get_object_links($uid);
+  }
+
+  /**
+   *
+   */
+  protected function save_links($uid, $links)
+  {
+    $storage = kolab_storage_config::get_instance();
+    return $storage->save_object_links($uid, (array) $links);
+  }
+
+  /**
+   * Create instances of a recurring event
+   *
+   * @param array  Hash array with event properties
+   * @param object DateTime Start date of the recurrence window
+   * @param object DateTime End date of the recurrence window
+   * @param string ID of a specific recurring event instance
+   * @return array List of recurring event instances
+   */
+  public function get_recurring_events($event, $start, $end = null, $event_id = null)
+  {
+    $object = $event['_formatobj'];
+    if (!$object) {
+      $rec = $this->storage->get_object($event['id']);
+      $object = $rec['_formatobj'];
+    }
+    if (!is_object($object))
+      return array();
+
+    // determine a reasonable end date if none given
+    if (!$end) {
+      $end = clone $event['start'];
+      $end->add(new DateInterval('P100Y'));
+    }
+
+    // copy the recurrence rule from the master event (to be used in the UI)
+    $recurrence_rule = $event['recurrence'];
+    unset($recurrence_rule['EXCEPTIONS'], $recurrence_rule['EXDATE']);
+
+    // read recurrence exceptions first
+    $events = array();
+    $exdata = array();
+    $futuredata = array();
+    $recurrence_id_format = libcalendaring::recurrence_id_format($event);
+
+    if (is_array($event['recurrence']['EXCEPTIONS'])) {
+      foreach ($event['recurrence']['EXCEPTIONS'] as $exception) {
+        if (!$exception['_instance'])
+          $exception['_instance'] = libcalendaring::recurrence_instance_identifier($exception, $event['allday']);
+
+        $rec_event = $this->_to_driver_event($exception, false, false, $event);
+        $rec_event['id'] = $event['uid'] . '-' . $exception['_instance'];
+        $rec_event['isexception'] = 1;
+
+        // found the specifically requested instance: register exception (single occurrence wins)
+        if ($rec_event['id'] == $event_id && (!$this->events[$event_id] || $this->events[$event_id]['thisandfuture'])) {
+          $rec_event['recurrence'] = $recurrence_rule;
+          $rec_event['recurrence_id'] = $event['uid'];
+          $this->events[$rec_event['id']] = $rec_event;
+        }
+
+        // remember this exception's date
+        $exdate = substr($exception['_instance'], 0, 8);
+        if (!$exdata[$exdate] || $exdata[$exdate]['thisandfuture']) {
+          $exdata[$exdate] = $rec_event;
+        }
+        if ($rec_event['thisandfuture']) {
+          $futuredata[$exdate] = $rec_event;
+        }
+      }
+    }
+
+    // found the specifically requested instance, exiting...
+    if ($event_id && !empty($this->events[$event_id])) {
+      return array($this->events[$event_id]);
+    }
+
+    // use libkolab to compute recurring events
+    if (class_exists('kolabcalendaring')) {
+        $recurrence = new kolab_date_recurrence($object);
+    }
+    else {
+      // fallback to local recurrence implementation
+      require_once($this->cal->home . '/lib/calendar_recurrence.php');
+      $recurrence = new calendar_recurrence($this->cal, $event);
+    }
+
+    $i = 0;
+    while ($next_event = $recurrence->next_instance()) {
+      $datestr     = $next_event['start']->format('Ymd');
+      $instance_id = $next_event['start']->format($recurrence_id_format);
+
+      // use this event data for future recurring instances
+      if ($futuredata[$datestr])
+        $overlay_data = $futuredata[$datestr];
+
+      $rec_id      = $event['uid'] . '-' . $instance_id;
+      $exception   = $exdata[$datestr] ?: $overlay_data;
+      $event_start = $next_event['start'];
+      $event_end   = $next_event['end'];
+
+      // copy some event from exception to get proper start/end dates
+      if ($exception) {
+        $event_copy = $next_event;
+        kolab_driver::merge_exception_dates($event_copy, $exception);
+        $event_start = $event_copy['start'];
+        $event_end   = $event_copy['end'];
+      }
+
+      // add to output if in range
+      if (($event_start <= $end && $event_end >= $start) || ($event_id && $rec_id == $event_id)) {
+        $rec_event = $this->_to_driver_event($next_event, false, false, $event);
+        $rec_event['_instance'] = $instance_id;
+        $rec_event['_count'] = $i + 1;
+
+        if ($exception)  // copy data from exception
+          kolab_driver::merge_exception_data($rec_event, $exception);
+
+        $rec_event['id'] = $rec_id;
+        $rec_event['recurrence_id'] = $event['uid'];
+        $rec_event['recurrence'] = $recurrence_rule;
+        unset($rec_event['_attendees']);
+        $events[] = $rec_event;
+
+        if ($rec_id == $event_id) {
+          $this->events[$rec_id] = $rec_event;
+          break;
+        }
+      }
+      else if ($next_event['start'] > $end)  // stop loop if out of range
+        break;
+
+      // avoid endless recursion loops
+      if (++$i > 100000)
+          break;
+    }
+    
+    return $events;
+  }
+
+  /**
+   * Convert from Kolab_Format to internal representation
+   */
+  private function _to_driver_event($record, $noinst = false, $links = true, $master_event = null)
+  {
+    $record['calendar'] = $this->id;
+
+    if ($links && !array_key_exists('links', $record)) {
+      $record['links'] = $this->get_links($record['uid']);
+    }
+
+    if ($this->get_namespace() == 'other') {
+      $record['className'] = 'fc-event-ns-other';
+      $record = kolab_driver::add_partstat_class($record, array('NEEDS-ACTION','DECLINED'), $this->get_owner());
+    }
+
+    // add instance identifier to first occurrence (master event)
+    $recurrence_id_format = libcalendaring::recurrence_id_format($master_event ? $master_event : $record);
+    if (!$noinst && $record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) {
+      $record['_instance'] = $record['start']->format($recurrence_id_format);
+    }
+    else if (is_a($record['recurrence_date'], 'DateTime')) {
+      $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format);
+    }
+
+    // clean up exception data
+    if ($record['recurrence'] && is_array($record['recurrence']['EXCEPTIONS'])) {
+      array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']);
+      });
+    }
+
+    return $record;
+  }
+
+   /**
+   * Convert the given event record into a data structure that can be passed to Kolab_Storage backend for saving
+   * (opposite of self::_to_driver_event())
+   */
+  private function _from_driver_event($event, $old = array())
+  {
+    // set current user as ORGANIZER
+    if ($identity = $this->cal->rc->user->list_emails(true)) {
+      $event['attendees'] = (array) $event['attendees'];
+      $found = false;
+
+      // there can be only resources on attendees list (T1484)
+      // let's check the existence of an organizer
+      foreach ($event['attendees'] as $attendee) {
+        if ($attendee['role'] == 'ORGANIZER') {
+          $found = true;
+          break;
+        }
+      }
+
+      if (!$found) {
+        $event['attendees'][] = array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email']);
+      }
+
+      $event['_owner'] = $identity['email'];
+    }
+
+    // remove EXDATE values if RDATE is given
+    if (!empty($event['recurrence']['RDATE'])) {
+      $event['recurrence']['EXDATE'] = array();
+    }
+
+    // remove recurrence information (e.g. EXDATES and EXCEPTIONS) entirely
+    if ($event['recurrence'] && empty($event['recurrence']['FREQ']) && empty($event['recurrence']['RDATE'])) {
+      $event['recurrence'] = array();
+    }
+
+    // keep 'comment' from initial itip invitation
+    if (!empty($old['comment'])) {
+      $event['comment'] = $old['comment'];
+    }
+
+    // clean up exception data
+    if (is_array($event['exceptions'])) {
+      array_walk($event['exceptions'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments'],
+          $event['attachments'], $event['deleted_attachments'], $event['recurrence_id']);
+      });
+    }
+
+    // remove some internal properties which should not be saved
+    unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_folder_id'],
+      $event['recurrence_id'], $event['attachments'], $event['deleted_attachments'], $event['className']);
+
+    // copy meta data (starting with _) from old object
+    foreach ((array)$old as $key => $val) {
+      if (!isset($event[$key]) && $key[0] == '_')
+        $event[$key] = $val;
+    }
+
+    return $event;
+  }
+
+  /**
+   * Match the given word in the event contents
+   */
+  public function fulltext_match($event, $word, $recursive = true)
+  {
+    $hits = 0;
+    foreach ($this->search_fields as $col) {
+      $sval = is_array($event[$col]) ? self::_complex2string($event[$col]) : $event[$col];
+      if (empty($sval))
+        continue;
+
+      // do a simple substring matching (to be improved)
+      $val = mb_strtolower($sval);
+      if (strpos($val, $word) !== false) {
+        $hits++;
+        break;
+      }
+    }
+
+    return $hits;
+  }
+
+  /**
+   * Convert a complex event attribute to a string value
+   */
+  private static function _complex2string($prop)
+  {
+      static $ignorekeys = array('role','status','rsvp');
+
+      $out = '';
+      if (is_array($prop)) {
+          foreach ($prop as $key => $val) {
+              if (is_numeric($key)) {
+                  $out .= self::_complex2string($val);
+              }
+              else if (!in_array($key, $ignorekeys)) {
+                $out .= $val . ' ';
+            }
+          }
+      }
+      else if (is_string($prop) || is_numeric($prop)) {
+          $out .= $prop . ' ';
+      }
+
+      return rtrim($out);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,2538 @@
+<?php
+
+/**
+ * Kolab driver for the Calendar plugin
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ * @author Aleksander Machniak <machniak@kolabsys.com>
+ *
+ * Copyright (C) 2012-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+class kolab_driver extends calendar_driver
+{
+  const INVITATIONS_CALENDAR_PENDING  = '--invitation--pending';
+  const INVITATIONS_CALENDAR_DECLINED = '--invitation--declined';
+
+  // features this backend supports
+  public $alarms = true;
+  public $attendees = true;
+  public $freebusy = true;
+  public $attachments = true;
+  public $undelete = true;
+  public $alarm_types = array('DISPLAY','AUDIO');
+  public $categoriesimmutable = true;
+
+  private $rc;
+  private $cal;
+  private $calendars;
+  private $has_writeable = false;
+  private $freebusy_trigger = false;
+  private $bonnie_api = false;
+
+  /**
+   * Default constructor
+   */
+  public function __construct($cal)
+  {
+    $cal->require_plugin('libkolab');
+
+    // load helper classes *after* libkolab has been loaded (#3248)
+    require_once(dirname(__FILE__) . '/kolab_calendar.php');
+    require_once(dirname(__FILE__) . '/kolab_user_calendar.php');
+    require_once(dirname(__FILE__) . '/kolab_invitation_calendar.php');
+
+    $this->cal = $cal;
+    $this->rc  = $cal->rc;
+
+    $this->cal->register_action('push-freebusy', array($this, 'push_freebusy'));
+    $this->cal->register_action('calendar-acl', array($this, 'calendar_acl'));
+
+    $this->freebusy_trigger = $this->rc->config->get('calendar_freebusy_trigger', false);
+
+    if (kolab_storage::$version == '2.0') {
+        $this->alarm_types = array('DISPLAY');
+        $this->alarm_absolute = false;
+    }
+
+    // get configuration for the Bonnie API
+    $this->bonnie_api = libkolab::get_bonnie_api();
+
+    // calendar uses fully encoded identifiers
+    kolab_storage::$encode_ids = true;
+  }
+
+
+  /**
+   * Read available calendars from server
+   */
+  private function _read_calendars()
+  {
+    // already read sources
+    if (isset($this->calendars))
+      return $this->calendars;
+
+    // get all folders that have "event" type, sorted by namespace/name
+    $folders = kolab_storage::sort_folders(kolab_storage::get_folders('event') + kolab_storage::get_user_folders('event', true));
+
+    $this->calendars = array();
+    foreach ($folders as $folder) {
+      if ($folder instanceof kolab_storage_folder_user) {
+        $calendar = new kolab_user_calendar($folder, $this->cal);
+        $calendar->subscriptions = count($folder->children) > 0;
+      }
+      else {
+        $calendar = new kolab_calendar($folder->name, $this->cal);
+      }
+
+      if ($calendar->ready) {
+        $this->calendars[$calendar->id] = $calendar;
+        if ($calendar->editable)
+          $this->has_writeable = true;
+      }
+    }
+
+    return $this->calendars;
+  }
+
+  /**
+   * Get a list of available calendars from this source
+   *
+   * @param integer $filter Bitmask defining filter criterias
+   * @param object $tree   Reference to hierarchical folder tree object
+   *
+   * @return array List of calendars
+   */
+  public function list_calendars($filter = 0, &$tree = null)
+  {
+    $this->_read_calendars();
+
+    // attempt to create a default calendar for this user
+    if (!$this->has_writeable) {
+      if ($this->create_calendar(array('name' => 'Calendar', 'color' => 'cc0000'))) {
+        unset($this->calendars);
+        $this->_read_calendars();
+      }
+    }
+
+    $delim = $this->rc->get_storage()->get_hierarchy_delimiter();
+    $folders = $this->filter_calendars($filter);
+    $calendars = array();
+
+    // include virtual folders for a full folder tree
+    if (!is_null($tree))
+      $folders = kolab_storage::folder_hierarchy($folders, $tree);
+
+    foreach ($folders as $id => $cal) {
+      $fullname = $cal->get_name();
+      $listname = $cal->get_foldername();
+      $imap_path = explode($delim, $cal->name);
+
+      // find parent
+      do {
+        array_pop($imap_path);
+        $parent_id = kolab_storage::folder_id(join($delim, $imap_path));
+      }
+      while (count($imap_path) > 1 && !$this->calendars[$parent_id]);
+
+      // restore "real" parent ID
+      if ($parent_id && !$this->calendars[$parent_id]) {
+          $parent_id = kolab_storage::folder_id($cal->get_parent());
+      }
+
+      // turn a kolab_storage_folder object into a kolab_calendar
+      if ($cal instanceof kolab_storage_folder) {
+          $cal = new kolab_calendar($cal->name, $this->cal);
+          $this->calendars[$cal->id] = $cal;
+      }
+
+      // special handling for user or virtual folders
+      if ($cal instanceof kolab_storage_folder_user) {
+        $calendars[$cal->id] = array(
+          'id'       => $cal->id,
+          'name'     => $fullname,
+          'listname' => $listname,
+          'editname' => $cal->get_foldername(),
+          'color'    => $cal->get_color(),
+          'active'   => $cal->is_active(),
+          'title'    => $cal->get_owner(),
+          'owner'    => $cal->get_owner(),
+          'history'  => false,
+          'virtual'  => false,
+          'editable' => false,
+          'group'     => 'other',
+          'class'     => 'user',
+          'removable' => true,
+        );
+      }
+      else if ($cal->virtual) {
+        $calendars[$cal->id] = array(
+          'id' => $cal->id,
+          'name' => $fullname,
+          'listname' => $listname,
+          'editname' => $cal->get_foldername(),
+          'virtual'  => true,
+          'editable' => false,
+          'group'     => $cal->get_namespace(),
+          'class'     => 'folder',
+        );
+      }
+      else {
+        $calendars[$cal->id] = array(
+          'id'       => $cal->id,
+          'name'     => $fullname,
+          'listname' => $listname,
+          'editname' => $cal->get_foldername(),
+          'title'    => $cal->get_title(),
+          'color'    => $cal->get_color(),
+          'editable' => $cal->editable,
+          'rights'    => $cal->rights,
+          'showalarms' => $cal->alarms,
+          'history'  => !empty($this->bonnie_api),
+          'group'    => $cal->get_namespace(),
+          'default'  => $cal->default,
+          'active'   => $cal->is_active(),
+          'owner'    => $cal->get_owner(),
+          'children' => true,  // TODO: determine if that folder indeed has child folders
+          'parent'   => $parent_id,
+          'subtype'  => $cal->subtype,
+          'caldavurl' => $cal->get_caldav_url(),
+          'removable' => !$cal->default,
+        );
+      }
+
+      if ($cal->subscriptions) {
+        $calendars[$cal->id]['subscribed'] = $cal->is_subscribed();
+      }
+    }
+
+    // list virtual calendars showing invitations
+    if ($this->rc->config->get('kolab_invitation_calendars')) {
+      foreach (array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED) as $id) {
+        $cal = new kolab_invitation_calendar($id, $this->cal);
+        $this->calendars[$cal->id] = $cal;
+        if (!($filter & self::FILTER_ACTIVE) || $cal->is_active()) {
+          $calendars[$id] = array(
+            'id'       => $cal->id,
+            'name'     => $cal->get_name(),
+            'listname' => $cal->get_name(),
+            'editname' => $cal->get_foldername(),
+            'title'    => $cal->get_title(),
+            'color'    => $cal->get_color(),
+            'editable' => $cal->editable,
+            'rights'    => $cal->rights,
+            'showalarms' => $cal->alarms,
+            'history'  => !empty($this->bonnie_api),
+            'group'    => 'x-invitations',
+            'default'  => false,
+            'active'   => $cal->is_active(),
+            'owner'    => $cal->get_owner(),
+            'children' => false,
+          );
+
+          if ($id == self::INVITATIONS_CALENDAR_PENDING) {
+            $calendars[$id]['counts'] = true;
+          }
+
+          if (is_object($tree)) {
+            $tree->children[] = $cal;
+          }
+        }
+      }
+    }
+
+    // append the virtual birthdays calendar
+    if ($this->rc->config->get('calendar_contact_birthdays', false)) {
+      $id = self::BIRTHDAY_CALENDAR_ID;
+      $prefs = $this->rc->config->get('kolab_calendars', array());  // read local prefs
+      if (!($filter & self::FILTER_ACTIVE) || $prefs[$id]['active']) {
+        $calendars[$id] = array(
+          'id'         => $id,
+          'name'       => $this->cal->gettext('birthdays'),
+          'listname'   => $this->cal->gettext('birthdays'),
+          'color'      => $prefs[$id]['color'] ?: '87CEFA',
+          'active'     => (bool)$prefs[$id]['active'],
+          'showalarms' => (bool)$this->rc->config->get('calendar_birthdays_alarm_type'),
+          'group'      => 'x-birthdays',
+          'editable'  => false,
+          'default'    => false,
+          'children'   => false,
+          'history'    => false,
+        );
+      }
+    }
+
+    return $calendars;
+  }
+
+  /**
+   * Get list of calendars according to specified filters
+   *
+   * @param integer Bitmask defining restrictions. See FILTER_* constants for possible values.
+   *
+   * @return array List of calendars
+   */
+  protected function filter_calendars($filter)
+  {
+    $this->_read_calendars();
+
+    $calendars = array();
+
+    $plugin = $this->rc->plugins->exec_hook('calendar_list_filter', array(
+      'list'      => $this->calendars,
+      'calendars' => $calendars,
+      'filter'    => $filter,
+    ));
+
+    if ($plugin['abort']) {
+      return $plugin['calendars'];
+    }
+
+    $personal = $filter & self::FILTER_PERSONAL;
+    $shared   = $filter & self::FILTER_SHARED;
+
+    foreach ($this->calendars as $cal) {
+      if (!$cal->ready) {
+        continue;
+      }
+      if (($filter & self::FILTER_WRITEABLE) && !$cal->editable) {
+        continue;
+      }
+      if (($filter & self::FILTER_INSERTABLE) && !$cal->insert) {
+        continue;
+      }
+      if (($filter & self::FILTER_ACTIVE) && !$cal->is_active()) {
+        continue;
+      }
+      if (($filter & self::FILTER_PRIVATE) && $cal->subtype != 'private') {
+        continue;
+      }
+      if (($filter & self::FILTER_CONFIDENTIAL) && $cal->subtype != 'confidential') {
+        continue;
+      }
+      if ($personal || $shared) {
+        $ns = $cal->get_namespace();
+        if (!(($personal && $ns == 'personal') || ($shared && $ns == 'shared'))) {
+          continue;
+        }
+      }
+
+      $calendars[$cal->id] = $cal;
+    }
+
+    return $calendars;
+  }
+
+
+  /**
+   * Get the kolab_calendar instance for the given calendar ID
+   *
+   * @param string Calendar identifier (encoded imap folder name)
+   * @return object kolab_calendar Object nor null if calendar doesn't exist
+   */
+  public function get_calendar($id)
+  {
+    $this->_read_calendars();
+
+    // create calendar object if necesary
+    if (!$this->calendars[$id]) {
+      if (in_array($id, array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) {
+        $this->calendars[$id] = new kolab_invitation_calendar($id, $this->cal);
+      }
+      else if ($id !== self::BIRTHDAY_CALENDAR_ID) {
+        $calendar = kolab_calendar::factory($id, $this->cal);
+        if ($calendar->ready) {
+          $this->calendars[$calendar->id] = $calendar;
+        }
+      }
+    }
+
+    return $this->calendars[$id];
+  }
+
+  /**
+   * Create a new calendar assigned to the current user
+   *
+   * @param array Hash array with calendar properties
+   *    name: Calendar name
+   *   color: The color of the calendar
+   * @return mixed ID of the calendar on success, False on error
+   */
+  public function create_calendar($prop)
+  {
+    $prop['type'] = 'event';
+    $prop['active'] = true;
+    $prop['subscribed'] = true;
+    $folder = kolab_storage::folder_update($prop);
+
+    if ($folder === false) {
+      $this->last_error = $this->cal->gettext(kolab_storage::$last_error);
+      return false;
+    }
+
+    // create ID
+    $id = kolab_storage::folder_id($folder);
+
+    // save color in user prefs (temp. solution)
+    $prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
+
+    if (isset($prop['color']))
+      $prefs['kolab_calendars'][$id]['color'] = $prop['color'];
+    if (isset($prop['showalarms']))
+      $prefs['kolab_calendars'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
+
+    if ($prefs['kolab_calendars'][$id])
+      $this->rc->user->save_prefs($prefs);
+
+    return $id;
+  }
+
+
+  /**
+   * Update properties of an existing calendar
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function edit_calendar($prop)
+  {
+    if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
+      $id = $cal->update($prop);
+    }
+    else {
+      $id = $prop['id'];
+    }
+
+    // fallback to local prefs
+    $prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
+    unset($prefs['kolab_calendars'][$prop['id']]['color'], $prefs['kolab_calendars'][$prop['id']]['showalarms']);
+
+    if (isset($prop['color']))
+      $prefs['kolab_calendars'][$id]['color'] = $prop['color'];
+
+    if (isset($prop['showalarms']) && $id == self::BIRTHDAY_CALENDAR_ID)
+      $prefs['calendar_birthdays_alarm_type'] = $prop['showalarms'] ? $this->alarm_types[0] : '';
+    else if (isset($prop['showalarms']))
+      $prefs['kolab_calendars'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
+
+    if (!empty($prefs['kolab_calendars'][$id]))
+      $this->rc->user->save_prefs($prefs);
+
+    return true;
+  }
+
+
+  /**
+   * Set active/subscribed state of a calendar
+   *
+   * @see calendar_driver::subscribe_calendar()
+   */
+  public function subscribe_calendar($prop)
+  {
+    if ($prop['id'] && ($cal = $this->get_calendar($prop['id'])) && is_object($cal->storage)) {
+      $ret = false;
+      if (isset($prop['permanent']))
+        $ret |= $cal->storage->subscribe(intval($prop['permanent']));
+      if (isset($prop['active']))
+        $ret |= $cal->storage->activate(intval($prop['active']));
+
+      // apply to child folders, too
+      if ($prop['recursive']) {
+        foreach ((array)kolab_storage::list_folders($cal->storage->name, '*', 'event') as $subfolder) {
+          if (isset($prop['permanent']))
+            ($prop['permanent'] ? kolab_storage::folder_subscribe($subfolder) : kolab_storage::folder_unsubscribe($subfolder));
+          if (isset($prop['active']))
+            ($prop['active'] ? kolab_storage::folder_activate($subfolder) : kolab_storage::folder_deactivate($subfolder));
+        }
+      }
+      return $ret;
+    }
+    else {
+      // save state in local prefs
+      $prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
+      $prefs['kolab_calendars'][$prop['id']]['active'] = (bool)$prop['active'];
+      $this->rc->user->save_prefs($prefs);
+      return true;
+    }
+
+    return false;
+  }
+
+
+  /**
+   * Delete the given calendar with all its contents
+   *
+   * @see calendar_driver::delete_calendar()
+   */
+  public function delete_calendar($prop)
+  {
+    if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
+      $folder = $cal->get_realname();
+      // TODO: unsubscribe if no admin rights
+      if (kolab_storage::folder_delete($folder)) {
+        // remove color in user prefs (temp. solution)
+        $prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
+        unset($prefs['kolab_calendars'][$prop['id']]);
+
+        $this->rc->user->save_prefs($prefs);
+        return true;
+      }
+      else
+        $this->last_error = kolab_storage::$last_error;
+    }
+
+    return false;
+  }
+
+
+  /**
+   * Search for shared or otherwise not listed calendars the user has access
+   *
+   * @param string Search string
+   * @param string Section/source to search
+   * @return array List of calendars
+   */
+  public function search_calendars($query, $source)
+  {
+    if (!kolab_storage::setup())
+      return array();
+
+    $this->calendars = array();
+    $this->search_more_results = false;
+
+    // find unsubscribed IMAP folders that have "event" type
+    if ($source == 'folders') {
+      foreach ((array)kolab_storage::search_folders('event', $query, array('other')) as $folder) {
+        $calendar = new kolab_calendar($folder->name, $this->cal);
+        $this->calendars[$calendar->id] = $calendar;
+      }
+    }
+    // find other user's virtual calendars
+    else if ($source == 'users') {
+      $limit = $this->rc->config->get('autocomplete_max', 15) * 2;  // we have slightly more space, so display twice the number
+      foreach (kolab_storage::search_users($query, 0, array(), $limit, $count) as $user) {
+        $calendar = new kolab_user_calendar($user, $this->cal);
+        $this->calendars[$calendar->id] = $calendar;
+
+        // search for calendar folders shared by this user
+        foreach (kolab_storage::list_user_folders($user, 'event', false) as $foldername) {
+          $cal = new kolab_calendar($foldername, $this->cal);
+          $this->calendars[$cal->id] = $cal;
+          $calendar->subscriptions = true;
+        }
+      }
+
+      if ($count > $limit) {
+        $this->search_more_results = true;
+      }
+    }
+
+    // don't list the birthday calendar
+    $this->rc->config->set('calendar_contact_birthdays', false);
+    $this->rc->config->set('kolab_invitation_calendars', false);
+
+    return $this->list_calendars();
+  }
+
+
+  /**
+   * Fetch a single event
+   *
+   * @see calendar_driver::get_event()
+   * @return array Hash array with event properties, false if not found
+   */
+  public function get_event($event, $scope = 0, $full = false)
+  {
+    if (is_array($event)) {
+      $id = $event['id'] ?: $event['uid'];
+      $cal = $event['calendar'];
+
+      // we're looking for a recurring instance: expand the ID to our internal convention for recurring instances
+      if (!$event['id'] && $event['_instance']) {
+        $id .= '-' . $event['_instance'];
+      }
+    }
+    else {
+      $id = $event;
+    }
+
+    if ($cal) {
+      if ($storage = $this->get_calendar($cal)) {
+        $result = $storage->get_event($id);
+        return self::to_rcube_event($result);
+      }
+      // get event from the address books birthday calendar
+      else if ($cal == self::BIRTHDAY_CALENDAR_ID) {
+        return $this->get_birthday_event($id);
+      }
+    }
+    // iterate over all calendar folders and search for the event ID
+    else {
+      foreach ($this->filter_calendars($scope) as $calendar) {
+        if ($result = $calendar->get_event($id)) {
+          return self::to_rcube_event($result);
+        }
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Add a single event to the database
+   *
+   * @see calendar_driver::new_event()
+   */
+  public function new_event($event)
+  {
+    if (!$this->validate($event))
+      return false;
+
+    $event = self::from_rcube_event($event);
+
+    if (!$event['calendar']) {
+      $this->_read_calendars();
+      $event['calendar'] = reset(array_keys($this->calendars));
+    }
+
+    if ($storage = $this->get_calendar($event['calendar'])) {
+      // if this is a recurrence instance, append as exception to an already existing object for this UID
+      if (!empty($event['recurrence_date']) && ($master = $storage->get_event($event['uid']))) {
+        self::add_exception($master, $event);
+        $success = $storage->update_event($master);
+      }
+      else {
+        $success = $storage->insert_event($event);
+      }
+
+      if ($success && $this->freebusy_trigger) {
+        $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
+        $this->freebusy_trigger = false; // disable after first execution (#2355)
+      }
+      
+      return $success;
+    }
+
+    return false;
+  }
+
+  /**
+   * Update an event entry with the given data
+   *
+   * @see calendar_driver::new_event()
+   * @return boolean True on success, False on error
+   */
+  public function edit_event($event)
+  {
+     if (!($storage = $this->get_calendar($event['calendar'])))
+       return false;
+
+    return $this->update_event(self::from_rcube_event($event, $storage->get_event($event['id'])));
+  }
+
+  /**
+   * Extended event editing with possible changes to the argument
+   *
+   * @param array  Hash array with event properties
+   * @param string New participant status
+   * @param array  List of hash arrays with updated attendees
+   * @return boolean True on success, False on error
+   */
+  public function edit_rsvp(&$event, $status, $attendees)
+  {
+    $update_event = $event;
+
+    // apply changes to master (and all exceptions)
+    if ($event['_savemode'] == 'all' && $event['recurrence_id']) {
+      if ($storage = $this->get_calendar($event['calendar'])) {
+        $update_event = $storage->get_event($event['recurrence_id']);
+        $update_event['_savemode'] = $event['_savemode'];
+        $update_event['id'] = $update_event['uid'];
+        unset($update_event['recurrence_id']);
+        calendar::merge_attendee_data($update_event, $attendees);
+      }
+    }
+
+    if ($ret = $this->update_attendees($update_event, $attendees)) {
+      // replace with master event (for iTip reply)
+      $event = self::to_rcube_event($update_event);
+
+      // re-assign to the according (virtual) calendar
+      if ($this->rc->config->get('kolab_invitation_calendars')) {
+        if (strtoupper($status) == 'DECLINED')
+          $event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
+        else if (strtoupper($status) == 'NEEDS-ACTION')
+          $event['calendar'] = self::INVITATIONS_CALENDAR_PENDING;
+        else if ($event['_folder_id'])
+          $event['calendar'] = $event['_folder_id'];
+      }
+    }
+
+    return $ret;
+  }
+
+  /**
+   * Update the participant status for the given attendees
+   *
+   * @see calendar_driver::update_attendees()
+   */
+  public function update_attendees(&$event, $attendees)
+  {
+    // for this-and-future updates, merge the updated attendees onto all exceptions in range
+    if (($event['_savemode'] == 'future' && $event['recurrence_id']) || (!empty($event['recurrence']) && !$event['recurrence_id'])) {
+      if (!($storage = $this->get_calendar($event['calendar'])))
+        return false;
+
+      // load master event
+      $master = $event['recurrence_id'] ? $storage->get_event($event['recurrence_id']) : $event;
+
+      // apply attendee update to each existing exception
+      if ($master['recurrence'] && !empty($master['recurrence']['EXCEPTIONS'])) {
+        $saved = false;
+        foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
+          // merge the new event properties onto future exceptions
+          if ($exception['_instance'] >= strval($event['_instance'])) {
+            calendar::merge_attendee_data($master['recurrence']['EXCEPTIONS'][$i], $attendees);
+          }
+          // update a specific instance
+          if ($exception['_instance'] == $event['_instance'] && $exception['thisandfuture']) {
+            $saved = true;
+          }
+        }
+
+        // add the given event as new exception
+        if (!$saved && $event['id'] != $master['id']) {
+          $event['thisandfuture'] = true;
+          $master['recurrence']['EXCEPTIONS'][] = $event;
+        }
+
+        // set link to top-level exceptions
+        $master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
+
+        return $this->update_event($master);
+      }
+    }
+
+    // just update the given event (instance)
+    return $this->update_event($event);
+  }
+
+  /**
+   * Move a single event
+   *
+   * @see calendar_driver::move_event()
+   * @return boolean True on success, False on error
+   */
+  public function move_event($event)
+  {
+    if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
+      unset($ev['sequence']);
+      self::clear_attandee_noreply($ev);
+      return $this->update_event($event + $ev);
+    }
+
+    return false;
+  }
+
+  /**
+   * Resize a single event
+   *
+   * @see calendar_driver::resize_event()
+   * @return boolean True on success, False on error
+   */
+  public function resize_event($event)
+  {
+    if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
+      unset($ev['sequence']);
+      self::clear_attandee_noreply($ev);
+      return $this->update_event($event + $ev);
+    }
+
+    return false;
+  }
+
+  /**
+   * Remove a single event
+   *
+   * @param array   Hash array with event properties:
+   *      id: Event identifier
+   * @param boolean Remove record(s) irreversible (mark as deleted otherwise)
+   *
+   * @return boolean True on success, False on error
+   */
+  public function remove_event($event, $force = true)
+  {
+    $ret = true;
+    $success = false;
+    $savemode = $event['_savemode'];
+    $decline  = $event['_decline'];
+
+    if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) {
+      $event['_savemode'] = $savemode;
+      $savemode = 'all';
+      $master = $event;
+
+      $this->rc->session->remove('calendar_restore_event_data');
+
+      // read master if deleting a recurring event
+      if ($event['recurrence'] || $event['recurrence_id'] || $event['isexception']) {
+        $master = $storage->get_event($event['uid']);
+        $savemode = $event['_savemode'] ?: ($event['_instance'] || $event['isexception'] ? 'current' : 'all');
+
+        // force 'current' mode for single occurrences stored as exception
+        if (!$event['recurrence'] && !$event['recurrence_id'] && $event['isexception'])
+          $savemode = 'current';
+      }
+
+      // removing an exception instance
+      if (($event['recurrence_id'] || $event['isexception']) && is_array($master['exceptions'])) {
+        foreach ($master['exceptions'] as $i => $exception) {
+          if ($exception['_instance'] == $event['_instance']) {
+            unset($master['exceptions'][$i]);
+            // set event date back to the actual occurrence
+            if ($exception['recurrence_date'])
+              $event['start'] = $exception['recurrence_date'];
+          }
+        }
+
+        if (is_array($master['recurrence'])) {
+          $master['recurrence']['EXCEPTIONS'] = &$master['exceptions'];
+        }
+      }
+
+      switch ($savemode) {
+        case 'current':
+          $_SESSION['calendar_restore_event_data'] = $master;
+
+          // removing the first instance => just move to next occurence
+          if ($master['recurrence'] && $event['_instance'] == libcalendaring::recurrence_instance_identifier($master)) {
+            $recurring = reset($storage->get_recurring_events($event, $event['start'], null, $event['id'].'-1'));
+
+            // no future instances found: delete the master event (bug #1677)
+            if (!$recurring['start']) {
+              $success = $storage->delete_event($master, $force);
+              break;
+            }
+
+            $master['start'] = $recurring['start'];
+            $master['end'] = $recurring['end'];
+            if ($master['recurrence']['COUNT'])
+              $master['recurrence']['COUNT']--;
+          }
+          // remove the matching RDATE entry
+          else if ($master['recurrence']['RDATE']) {
+            foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
+              if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
+                unset($master['recurrence']['RDATE'][$j]);
+                break;
+              }
+            }
+          }
+          else {  // add exception to master event
+            $master['recurrence']['EXDATE'][] = $event['start'];
+          }
+          $success = $storage->update_event($master);
+          break;
+
+        case 'future':
+          $master['_instance'] = libcalendaring::recurrence_instance_identifier($master);
+          if ($master['_instance'] != $event['_instance']) {
+            $_SESSION['calendar_restore_event_data'] = $master;
+            
+            // set until-date on master event
+            $master['recurrence']['UNTIL'] = clone $event['start'];
+            $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
+            unset($master['recurrence']['COUNT']);
+
+            // if all future instances are deleted, remove recurrence rule entirely (bug #1677)
+            if ($master['recurrence']['UNTIL']->format('Ymd') == $master['start']->format('Ymd')) {
+              $master['recurrence'] = array();
+            }
+            // remove matching RDATE entries
+            else if ($master['recurrence']['RDATE']) {
+              foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
+                if ($rdate->format('Ymd') == $event['start']->format('Ymd')) {
+                  $master['recurrence']['RDATE'] = array_slice($master['recurrence']['RDATE'], 0, $j);
+                  break;
+                }
+              }
+            }
+
+            $success = $storage->update_event($master);
+            $ret = $master['uid'];
+            break;
+          }
+
+        default:  // 'all' is default
+          // removing the master event with loose exceptions (not recurring though)
+          if (!empty($event['recurrence_date']) && empty($master['recurrence']) && !empty($master['exceptions'])) {
+            // make the first exception the new master
+            $newmaster = array_shift($master['exceptions']);
+            $newmaster['exceptions'] = $master['exceptions'];
+            $newmaster['_attachments'] = $master['_attachments'];
+            $newmaster['_mailbox'] = $master['_mailbox'];
+            $newmaster['_msguid'] = $master['_msguid'];
+
+            $success = $storage->update_event($newmaster);
+          }
+          else if ($decline && $this->rc->config->get('kolab_invitation_calendars')) {
+            // don't delete but set PARTSTAT=DECLINED
+            if ($this->cal->lib->set_partstat($master, 'DECLINED')) {
+              $success = $storage->update_event($master);
+            }
+          }
+
+          if (!$success)
+            $success = $storage->delete_event($master, $force);
+          break;
+      }
+    }
+
+    if ($success && $this->freebusy_trigger)
+      $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
+
+    return $success ? $ret : false;
+  }
+
+  /**
+   * Restore a single deleted event
+   *
+   * @param array Hash array with event properties:
+   *      id: Event identifier
+   * @return boolean True on success, False on error
+   */
+  public function restore_event($event)
+  {
+    if ($storage = $this->get_calendar($event['calendar'])) {
+      if (!empty($_SESSION['calendar_restore_event_data']))
+        $success = $storage->update_event($_SESSION['calendar_restore_event_data']);
+      else
+        $success = $storage->restore_event($event);
+      
+      if ($success && $this->freebusy_trigger)
+        $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
+      
+      return $success;
+    }
+
+    return false;
+  }
+
+  /**
+   * Wrapper to update an event object depending on the given savemode
+   */
+  private function update_event($event)
+  {
+    if (!($storage = $this->get_calendar($event['calendar'])))
+      return false;
+
+    // move event to another folder/calendar
+    if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar']) {
+      if (!($fromcalendar = $this->get_calendar($event['_fromcalendar'])))
+        return false;
+
+      $old = $fromcalendar->get_event($event['id']);
+
+      if ($event['_savemode'] != 'new') {
+        if (!$fromcalendar->storage->move($old['uid'], $storage->storage)) {
+          return false;
+        }
+
+        $fromcalendar = $storage;
+      }
+    }
+    else
+      $fromcalendar = $storage;
+
+    $success = false;
+    $savemode = 'all';
+    $attachments = array();
+    $old = $master = $storage->get_event($event['id']);
+
+    if (!$old || !$old['start']) {
+      rcube::raise_error(array(
+        'code' => 600, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => "Failed to load event object to update: id=" . $event['id']),
+        true, false);
+      return false;
+    }
+
+    // modify a recurring event, check submitted savemode to do the right things
+    if ($old['recurrence'] || $old['recurrence_id'] || $old['isexception']) {
+      $master = $storage->get_event($old['uid']);
+      $savemode = $event['_savemode'] ?: ($old['recurrence_id'] || $old['isexception'] ? 'current' : 'all');
+
+      // this-and-future on the first instance equals to 'all'
+      if ($savemode == 'future' && $master['start'] && $old['_instance'] == libcalendaring::recurrence_instance_identifier($master))
+        $savemode = 'all';
+      // force 'current' mode for single occurrences stored as exception
+      else if (!$old['recurrence'] && !$old['recurrence_id'] && $old['isexception'])
+        $savemode = 'current';
+    }
+
+    // check if update affects scheduling and update attendee status accordingly
+    $reschedule = $this->check_scheduling($event, $old, true);
+
+    // keep saved exceptions (not submitted by the client)
+    if ($old['recurrence']['EXDATE'] && !isset($event['recurrence']['EXDATE']))
+      $event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
+    if (isset($event['recurrence']['EXCEPTIONS']))
+      $with_exceptions = true;  // exceptions already provided (e.g. from iCal import)
+    else if ($old['recurrence']['EXCEPTIONS'])
+      $event['recurrence']['EXCEPTIONS'] = $old['recurrence']['EXCEPTIONS'];
+    else if ($old['exceptions'])
+      $event['exceptions'] = $old['exceptions'];
+
+    // remove some internal properties which should not be saved
+    unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_owner'],
+        $event['_notify'], $event['_method'], $event['_sender'], $event['_sender_utf'], $event['_size']);
+
+    switch ($savemode) {
+      case 'new':
+        // save submitted data as new (non-recurring) event
+        $event['recurrence'] = array();
+        $event['_copyfrom'] = $master['_msguid'];
+        $event['_mailbox'] = $master['_mailbox'];
+        $event['uid'] = $this->cal->generate_uid();
+        unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
+
+        // copy attachment metadata to new event
+        $event = self::from_rcube_event($event, $master);
+
+        self::clear_attandee_noreply($event);
+        if ($success = $storage->insert_event($event))
+          $success = $event['uid'];
+        break;
+
+      case 'future':
+        // create a new recurring event
+        $event['_copyfrom'] = $master['_msguid'];
+        $event['_mailbox'] = $master['_mailbox'];
+        $event['uid'] = $this->cal->generate_uid();
+        unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
+
+        // copy attachment metadata to new event
+        $event = self::from_rcube_event($event, $master);
+
+        // remove recurrence exceptions on re-scheduling
+        if ($reschedule) {
+          unset($event['recurrence']['EXCEPTIONS'], $event['exceptions'], $master['recurrence']['EXDATE']);
+        }
+        else if (is_array($event['recurrence']['EXCEPTIONS'])) {
+          // only keep relevant exceptions
+          $event['recurrence']['EXCEPTIONS'] = array_filter($event['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
+            return $exception['start'] > $event['start'];
+          });
+          if (is_array($event['recurrence']['EXDATE'])) {
+            $event['recurrence']['EXDATE'] = array_filter($event['recurrence']['EXDATE'], function($exdate) use ($event) {
+              return $exdate > $event['start'];
+            });
+          }
+          // set link to top-level exceptions
+          $event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
+        }
+
+        // compute remaining occurrences
+        if ($event['recurrence']['COUNT']) {
+          if (!$old['_count'])
+            $old['_count'] = $this->get_recurrence_count($master, $old['start']);
+          $event['recurrence']['COUNT'] -= intval($old['_count']);
+        }
+
+        // remove fixed weekday when date changed
+        if ($old['start']->format('Y-m-d') != $event['start']->format('Y-m-d')) {
+          if (strlen($event['recurrence']['BYDAY']) == 2)
+            unset($event['recurrence']['BYDAY']);
+          if ($old['recurrence']['BYMONTH'] == $old['start']->format('n'))
+            unset($event['recurrence']['BYMONTH']);
+        }
+
+        // set until-date on master event
+        $master['recurrence']['UNTIL'] = clone $old['start'];
+        $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
+        unset($master['recurrence']['COUNT']);
+
+        // remove all exceptions after $event['start']
+        if (is_array($master['recurrence']['EXCEPTIONS'])) {
+          $master['recurrence']['EXCEPTIONS'] = array_filter($master['recurrence']['EXCEPTIONS'], function($exception) use ($event) {
+            return $exception['start'] < $event['start'];
+          });
+          // set link to top-level exceptions
+          $master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
+        }
+        if (is_array($master['recurrence']['EXDATE'])) {
+          $master['recurrence']['EXDATE'] = array_filter($master['recurrence']['EXDATE'], function($exdate) use ($event) {
+            return $exdate < $event['start'];
+          });
+        }
+
+        // save new event
+        if ($success = $storage->insert_event($event)) {
+          $success = $event['uid'];
+
+          // update master event (no rescheduling!)
+          self::clear_attandee_noreply($master);
+          $storage->update_event($master);
+        }
+        break;
+
+      case 'current':
+        // recurring instances shall not store recurrence rules and attachments
+        $event['recurrence'] = array();
+        $event['thisandfuture'] = $savemode == 'future';
+        unset($event['attachments'], $event['id']);
+
+        // increment sequence of this instance if scheduling is affected
+        if ($reschedule) {
+          $event['sequence'] = max($old['sequence'], $master['sequence']) + 1;
+        }
+        else if (!isset($event['sequence'])) {
+          $event['sequence'] = $old['sequence'] ?: $master['sequence'];
+        }
+
+        // save properties to a recurrence exception instance
+        if ($old['_instance'] && is_array($master['recurrence']['EXCEPTIONS'])) {
+          if ($this->update_recurrence_exceptions($master, $event, $old, $savemode)) {
+            $success = $storage->update_event($master, $old['id']);
+            break;
+          }
+        }
+
+        $add_exception = true;
+
+        // adjust matching RDATE entry if dates changed
+        if (is_array($master['recurrence']['RDATE']) && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
+          foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
+            if ($rdate->format('Ymd') == $old_date) {
+              $master['recurrence']['RDATE'][$j] = $event['start'];
+              sort($master['recurrence']['RDATE']);
+              $add_exception = false;
+              break;
+            }
+          }
+        }
+
+        // save as new exception to master event
+        if ($add_exception) {
+          self::add_exception($master, $event, $old);
+        }
+
+        $success = $storage->update_event($master);
+        break;
+
+      default:  // 'all' is default
+        $event['id'] = $master['uid'];
+        $event['uid'] = $master['uid'];
+
+        // use start date from master but try to be smart on time or duration changes
+        $old_start_date = $old['start']->format('Y-m-d');
+        $old_start_time = $old['allday'] ? '' : $old['start']->format('H:i');
+        $old_duration   = self::event_duration($old['start'], $old['end'], $old['allday']);
+
+        $new_start_date = $event['start']->format('Y-m-d');
+        $new_start_time = $event['allday'] ? '' : $event['start']->format('H:i');
+        $new_duration   = self::event_duration($event['start'], $event['end'], $event['allday']);
+
+        $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
+        $date_shift = $old['start']->diff($event['start']);
+
+        // shifted or resized
+        if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
+          $event['start'] = $master['start']->add($date_shift);
+          $event['end'] = clone $event['start'];
+          $event['end']->add(new DateInterval($new_duration));
+
+          // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::update_event()
+          if ($old_start_date != $new_start_date) {
+            if (strlen($event['recurrence']['BYDAY']) == 2)
+              unset($event['recurrence']['BYDAY']);
+            if ($old['recurrence']['BYMONTH'] == $old['start']->format('n'))
+              unset($event['recurrence']['BYMONTH']);
+          }
+        }
+        // dates did not change, use the ones from master
+        else if ($new_start_date . $new_start_time == $old_start_date . $old_start_time) {
+          $event['start'] = $master['start'];
+          $event['end'] = $master['end'];
+        }
+
+        // when saving an instance in 'all' mode, copy recurrence exceptions over
+        if ($old['recurrence_id']) {
+          $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS'];
+          $event['recurrence']['EXDATE']     = $master['recurrence']['EXDATE'];
+        }
+        else if ($master['_instance']) {
+          $event['_instance'] = $master['_instance'];
+          $event['recurrence_date'] = $master['recurrence_date'];
+        }
+
+        // TODO: forward changes to exceptions (which do not yet have differing values stored)
+        if (is_array($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS']) && !$with_exceptions) {
+          // determine added and removed attendees
+          $old_attendees = $current_attendees = $added_attendees = array();
+          foreach ((array)$old['attendees'] as $attendee) {
+            $old_attendees[] = $attendee['email'];
+          }
+          foreach ((array)$event['attendees'] as $attendee) {
+            $current_attendees[] = $attendee['email'];
+            if (!in_array($attendee['email'], $old_attendees)) {
+              $added_attendees[] = $attendee;
+            }
+          }
+          $removed_attendees = array_diff($old_attendees, $current_attendees);
+
+          foreach ($event['recurrence']['EXCEPTIONS'] as $i => $exception) {
+            calendar::merge_attendee_data($event['recurrence']['EXCEPTIONS'][$i], $added_attendees, $removed_attendees);
+          }
+
+          // adjust recurrence-id when start changed and therefore the entire recurrence chain changes
+          if ($old_start_date != $new_start_date || $old_start_time != $new_start_time) {
+            $recurrence_id_format = libcalendaring::recurrence_id_format($event);
+            foreach ($event['recurrence']['EXCEPTIONS'] as $i => $exception) {
+              $recurrence_id = is_a($exception['recurrence_date'], 'DateTime') ? $exception['recurrence_date'] :
+                  rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone());
+              if (is_a($recurrence_id, 'DateTime')) {
+                $recurrence_id->add($date_shift);
+                $event['recurrence']['EXCEPTIONS'][$i]['recurrence_date'] = $recurrence_id;
+                $event['recurrence']['EXCEPTIONS'][$i]['_instance'] = $recurrence_id->format($recurrence_id_format);
+              }
+            }
+          }
+
+          // set link to top-level exceptions
+          $event['exceptions'] = &$event['recurrence']['EXCEPTIONS'];
+        }
+
+        // unset _dateonly flags in (cached) date objects
+        unset($event['start']->_dateonly, $event['end']->_dateonly);
+
+        $success = $storage->update_event($event) ? $event['id'] : false;  // return master UID
+        break;
+    }
+
+    if ($success && $this->freebusy_trigger)
+      $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
+
+    return $success;
+  }
+
+  /**
+   * Calculate event duration, returns string in DateInterval format
+   */
+  protected static function event_duration($start, $end, $allday = false)
+  {
+    if ($allday) {
+      $diff = $start->diff($end);
+      return 'P' . $diff->days . 'D';
+    }
+
+    return 'PT' . ($end->format('U') - $start->format('U')) . 'S';
+  }
+
+  /**
+   * Determine whether the current change affects scheduling and reset attendee status accordingly
+   */
+  public function check_scheduling(&$event, $old, $update = true)
+  {
+    // skip this check when importing iCal/iTip events
+    if (isset($event['sequence']) || !empty($event['_method'])) {
+      return false;
+    }
+
+    // iterate through the list of properties considered 'significant' for scheduling
+    $kolab_event = $old['_formatobj'] ?: new kolab_format_event();
+    $reschedule = $kolab_event->check_rescheduling($event, $old);
+
+    // reset all attendee status to needs-action (#4360)
+    if ($update && $reschedule && is_array($event['attendees'])) {
+      $is_organizer = false;
+      $emails = $this->cal->get_user_emails();
+      $attendees = $event['attendees'];
+      foreach ($attendees as $i => $attendee) {
+        if ($attendee['role'] == 'ORGANIZER' && $attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+          $is_organizer = true;
+        }
+        else if ($attendee['role'] != 'ORGANIZER' && $attendee['role'] != 'NON-PARTICIPANT' && $attendee['status'] != 'DELEGATED') {
+          $attendees[$i]['status'] = 'NEEDS-ACTION';
+          $attendees[$i]['rsvp'] = true;
+        }
+      }
+
+      // update attendees only if I'm the organizer
+      if ($is_organizer || ($event['organizer'] && in_array(strtolower($event['organizer']['email']), $emails))) {
+        $event['attendees'] = $attendees;
+      }
+    }
+
+    return $reschedule;
+  }
+
+  /**
+   * Apply the given changes to already existing exceptions
+   */
+  protected function update_recurrence_exceptions(&$master, $event, $old, $savemode)
+  {
+    $saved = false;
+    $existing = null;
+
+    // determine added and removed attendees
+    $added_attendees = $removed_attendees = array();
+    if ($savemode == 'future') {
+      $old_attendees = $current_attendees = array();
+      foreach ((array)$old['attendees'] as $attendee) {
+        $old_attendees[] = $attendee['email'];
+      }
+      foreach ((array)$event['attendees'] as $attendee) {
+        $current_attendees[] = $attendee['email'];
+        if (!in_array($attendee['email'], $old_attendees)) {
+          $added_attendees[] = $attendee;
+        }
+      }
+      $removed_attendees = array_diff($old_attendees, $current_attendees);
+    }
+
+    foreach ($master['recurrence']['EXCEPTIONS'] as $i => $exception) {
+      // update a specific instance
+      if ($exception['_instance'] == $old['_instance']) {
+        $existing = $i;
+
+        // check savemode against existing exception mode.
+        // if matches, we can update this existing exception
+        if ((bool)$exception['thisandfuture'] === ($savemode == 'future')) {
+          $event['_instance'] = $old['_instance'];
+          $event['thisandfuture'] = $old['thisandfuture'];
+          $event['recurrence_date'] = $old['recurrence_date'];
+          $master['recurrence']['EXCEPTIONS'][$i] = $event;
+          $saved = true;
+        }
+      }
+      // merge the new event properties onto future exceptions
+      if ($savemode == 'future' && $exception['_instance'] >= $old['_instance']) {
+        unset($event['thisandfuture']);
+        self::merge_exception_data($master['recurrence']['EXCEPTIONS'][$i], $event, array('attendees'));
+
+        if (!empty($added_attendees) || !empty($removed_attendees)) {
+          calendar::merge_attendee_data($master['recurrence']['EXCEPTIONS'][$i], $added_attendees, $removed_attendees);
+        }
+      }
+    }
+/*
+    // we could not update the existing exception due to savemode mismatch...
+    if (!$saved && $existing !== null && $master['recurrence']['EXCEPTIONS'][$existing]['thisandfuture']) {
+      // ... try to move the existing this-and-future exception to the next occurrence
+      foreach ($this->get_recurring_events($master, $existing['start']) as $candidate) {
+        // our old this-and-future exception is obsolete
+        if ($candidate['thisandfuture']) {
+          unset($master['recurrence']['EXCEPTIONS'][$existing]);
+          $saved = true;
+          break;
+        }
+        // this occurrence doesn't yet have an exception
+        else if (!$candidate['isexception']) {
+          $event['_instance'] = $candidate['_instance'];
+          $event['recurrence_date'] = $candidate['recurrence_date'];
+          $master['recurrence']['EXCEPTIONS'][$i] = $event;
+          $saved = true;
+          break;
+        }
+      }
+    }
+*/
+
+    // set link to top-level exceptions
+    $master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
+
+    // returning false here will add a new exception
+    return $saved;
+  }
+
+  /**
+   * Add or update the given event as an exception to $master
+   */
+  public static function add_exception(&$master, $event, $old = null)
+  {
+    if ($old) {
+      $event['_instance'] = $old['_instance'];
+      if (!$event['recurrence_date'])
+        $event['recurrence_date'] = $old['recurrence_date'] ?: $old['start'];
+    }
+    else if (!$event['recurrence_date']) {
+      $event['recurrence_date'] = $event['start'];
+    }
+
+    if (!$event['_instance'] && is_a($event['recurrence_date'], 'DateTime')) {
+      $event['_instance'] = libcalendaring::recurrence_instance_identifier($event, $master['allday']);
+    }
+
+    if (!is_array($master['exceptions']) && is_array($master['recurrence']['EXCEPTIONS'])) {
+      $master['exceptions'] = &$master['recurrence']['EXCEPTIONS'];
+    }
+
+    $existing = false;
+    foreach ((array)$master['exceptions'] as $i => $exception) {
+      if ($exception['_instance'] == $event['_instance']) {
+        $master['exceptions'][$i] = $event;
+        $existing = true;
+      }
+    }
+
+    if (!$existing) {
+      $master['exceptions'][] = $event;
+    }
+
+    return true;
+  }
+
+  /**
+   * Remove the noreply flags from attendees
+   */
+  public static function clear_attandee_noreply(&$event)
+  {
+    foreach ((array)$event['attendees'] as $i => $attendee) {
+      unset($event['attendees'][$i]['noreply']);
+    }
+  }
+
+  /**
+   * Merge certain properties from the overlay event to the base event object
+   *
+   * @param array The event object to be altered
+   * @param array The overlay event object to be merged over $event
+   * @param array List of properties not allowed to be overwritten
+   */
+  public static function merge_exception_data(&$event, $overlay, $blacklist = null)
+  {
+    $forbidden = array('id','uid','recurrence','recurrence_date','thisandfuture','organizer','_attachments');
+
+    if (is_array($blacklist))
+      $forbidden = array_merge($forbidden, $blacklist);
+
+    foreach ($overlay as $prop => $value) {
+      if ($prop == 'start' || $prop == 'end') {
+        // handled by merge_exception_dates() below
+      }
+      else if ($prop == 'thisandfuture' && $overlay['_instance'] == $event['_instance']) {
+        $event[$prop] = $value;
+      }
+      else if ($prop[0] != '_' && !in_array($prop, $forbidden))
+        $event[$prop] = $value;
+    }
+
+    self::merge_exception_dates($event, $overlay);
+  }
+
+  /**
+   * Merge start/end date from the overlay event to the base event object
+   *
+   * @param array The event object to be altered
+   * @param array The overlay event object to be merged over $event
+   */
+  public static function merge_exception_dates(&$event, $overlay)
+  {
+    // compute date offset from the exception
+    if ($overlay['start'] instanceof DateTime && $overlay['recurrence_date'] instanceof DateTime) {
+      $date_offset = $overlay['recurrence_date']->diff($overlay['start']);
+    }
+
+    foreach (array('start', 'end') as $prop) {
+      $value = $overlay[$prop];
+      if (is_object($event[$prop]) && $event[$prop] instanceof DateTime) {
+        // set date value if overlay is an exception of the current instance
+        if (substr($overlay['_instance'], 0, 8) == substr($event['_instance'], 0, 8)) {
+          $event[$prop]->setDate(intval($value->format('Y')), intval($value->format('n')), intval($value->format('j')));
+        }
+        // apply date offset
+        else if ($date_offset) {
+          $event[$prop]->add($date_offset);
+        }
+        // adjust time of the recurring event instance
+        $event[$prop]->setTime($value->format('G'), intval($value->format('i')), intval($value->format('s')));
+      }
+    }
+  }
+
+  /**
+   * Get events from source.
+   *
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  mixed   List of calendar IDs to load events from (either as array or comma-separated string)
+   * @param  boolean Include virtual events (optional)
+   * @param  integer Only list events modified since this time (unix timestamp)
+   * @return array A list of event records
+   */
+  public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1, $modifiedsince = null)
+  {
+    if ($calendars && is_string($calendars))
+      $calendars = explode(',', $calendars);
+    else if (!$calendars) {
+      $this->_read_calendars();
+      $calendars = array_keys($this->calendars);
+    }
+
+    $query = array();
+    if ($modifiedsince)
+      $query[] = array('changed', '>=', $modifiedsince);
+
+    $events = $categories = array();
+    foreach ($calendars as $cid) {
+      if ($storage = $this->get_calendar($cid)) {
+        $events = array_merge($events, $storage->list_events($start, $end, $search, $virtual, $query));
+        $categories += $storage->categories;
+      }
+    }
+
+    // add events from the address books birthday calendar
+    if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars)) {
+      $events = array_merge($events, $this->load_birthday_events($start, $end, $search, $modifiedsince));
+    }
+
+    // add new categories to user prefs
+    $old_categories = $this->rc->config->get('calendar_categories', $this->default_categories);
+    if ($newcats = array_udiff(array_keys($categories), array_keys($old_categories), function($a, $b){ return strcasecmp($a, $b); })) {
+      foreach ($newcats as $category)
+        $old_categories[$category] = '';  // no color set yet
+      $this->rc->user->save_prefs(array('calendar_categories' => $old_categories));
+    }
+
+    array_walk($events, 'kolab_driver::to_rcube_event');
+    return $events;
+  }
+
+  /**
+   * Get number of events in the given calendar
+   *
+   * @param  mixed   List of calendar IDs to count events (either as array or comma-separated string)
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @return array   Hash array with counts grouped by calendar ID
+   */
+  public function count_events($calendars, $start, $end = null)
+  {
+      $counts = array();
+
+      if ($calendars && is_string($calendars))
+        $calendars = explode(',', $calendars);
+      else if (!$calendars) {
+        $this->_read_calendars();
+        $calendars = array_keys($this->calendars);
+      }
+
+      foreach ($calendars as $cid) {
+        if ($storage = $this->get_calendar($cid)) {
+            $counts[$cid] = $storage->count_events($start, $end);
+        }
+      }
+
+      return $counts;
+  }
+
+  /**
+   * Get a list of pending alarms to be displayed to the user
+   *
+   * @see calendar_driver::pending_alarms()
+   */
+  public function pending_alarms($time, $calendars = null)
+  {
+    $interval = 300;
+    $time -= $time % 60;
+    
+    $slot = $time;
+    $slot -= $slot % $interval;
+    
+    $last = $time - max(60, $this->rc->config->get('refresh_interval', 0));
+    $last -= $last % $interval;
+    
+    // only check for alerts once in 5 minutes
+    if ($last == $slot)
+      return array();
+    
+    if ($calendars && is_string($calendars))
+      $calendars = explode(',', $calendars);
+    
+    $time = $slot + $interval;
+    
+    $candidates = array();
+    $query = array(array('tags', '=', 'x-has-alarms'));
+
+    $this->_read_calendars();
+
+    foreach ($this->calendars as $cid => $calendar) {
+      // skip calendars with alarms disabled
+      if (!$calendar->alarms || ($calendars && !in_array($cid, $calendars)))
+        continue;
+
+      foreach ($calendar->list_events($time, $time + 86400 * 365, null, 1, $query) as $e) {
+        // add to list if alarm is set
+        $alarm = libcalendaring::get_next_alarm($e);
+        if ($alarm && $alarm['time'] && $alarm['time'] >= $last && in_array($alarm['action'], $this->alarm_types)) {
+          $id = $alarm['id'];  // use alarm-id as primary identifier
+          $candidates[$id] = array(
+            'id'       => $id,
+            'title'    => $e['title'],
+            'location' => $e['location'],
+            'start'    => $e['start'],
+            'end'      => $e['end'],
+            'notifyat' => $alarm['time'],
+            'action'   => $alarm['action'],
+          );
+        }
+      }
+    }
+
+    // get alarm information stored in local database
+    if (!empty($candidates)) {
+      $alarm_ids = array_map(array($this->rc->db, 'quote'), array_keys($candidates));
+      $result = $this->rc->db->query("SELECT *"
+        . " FROM " . $this->rc->db->table_name('kolab_alarms', true)
+        . " WHERE `alarm_id` IN (" . join(',', $alarm_ids) . ")"
+          . " AND `user_id` = ?",
+        $this->rc->user->ID
+      );
+
+      while ($result && ($e = $this->rc->db->fetch_assoc($result))) {
+        $dbdata[$e['alarm_id']] = $e;
+      }
+    }
+    
+    $alarms = array();
+    foreach ($candidates as $id => $alarm) {
+      // skip dismissed alarms
+      if ($dbdata[$id]['dismissed'])
+        continue;
+      
+      // snooze function may have shifted alarm time
+      $notifyat = $dbdata[$id]['notifyat'] ? strtotime($dbdata[$id]['notifyat']) : $alarm['notifyat'];
+      if ($notifyat <= $time)
+        $alarms[] = $alarm;
+    }
+    
+    return $alarms;
+  }
+
+  /**
+   * Feedback after showing/sending an alarm notification
+   *
+   * @see calendar_driver::dismiss_alarm()
+   */
+  public function dismiss_alarm($alarm_id, $snooze = 0)
+  {
+    $alarms_table = $this->rc->db->table_name('kolab_alarms', true);
+    // delete old alarm entry
+    $this->rc->db->query("DELETE FROM $alarms_table"
+      . " WHERE `alarm_id` = ? AND `user_id` = ?",
+      $alarm_id,
+      $this->rc->user->ID
+    );
+
+    // set new notifyat time or unset if not snoozed
+    $notifyat = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
+
+    $query = $this->rc->db->query("INSERT INTO $alarms_table"
+      . " (`alarm_id`, `user_id`, `dismissed`, `notifyat`)"
+      . " VALUES (?, ?, ?, ?)",
+      $alarm_id,
+      $this->rc->user->ID,
+      $snooze > 0 ? 0 : 1,
+      $notifyat
+    );
+
+    return $this->rc->db->affected_rows($query);
+  }
+
+  /**
+   * List attachments from the given event
+   */
+  public function list_attachments($event)
+  {
+    if (!($storage = $this->get_calendar($event['calendar'])))
+      return false;
+
+    $event = $storage->get_event($event['id']);
+
+    return $event['attachments'];
+  }
+
+  /**
+   * Get attachment properties
+   */
+  public function get_attachment($id, $event)
+  {
+    if (!($storage = $this->get_calendar($event['calendar'])))
+      return false;
+
+    // get old revision of event
+    if ($event['rev']) {
+      $event = $this->get_event_revison($event, $event['rev'], true);
+    }
+    else {
+      $event = $storage->get_event($event['id']);
+    }
+
+    if ($event) {
+      $attachments = isset($event['_attachments']) ? $event['_attachments'] : $event['attachments'];
+      foreach ((array) $attachments as $att) {
+        if ($att['id'] == $id) {
+          return $att;
+        }
+      }
+    }
+  }
+
+  /**
+   * Get attachment body
+   * @see calendar_driver::get_attachment_body()
+   */
+  public function get_attachment_body($id, $event)
+  {
+    if (!($cal = $this->get_calendar($event['calendar'])))
+      return false;
+
+    // get old revision of event
+    if ($event['rev']) {
+      if (empty($this->bonnie_api)) {
+        return false;
+      }
+
+      $cid = substr($id, 4);
+
+      // call Bonnie API and get the raw mime message
+      list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event);
+      if ($msg_raw = $this->bonnie_api->rawdata('event', $uid, $event['rev'], $mailbox, $msguid)) {
+        // parse the message and find the part with the matching content-id
+        $message = rcube_mime::parse_message($msg_raw);
+        foreach ((array)$message->parts as $part) {
+          if ($part->headers['content-id'] && trim($part->headers['content-id'], '<>') == $cid) {
+            return $part->body;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    return $cal->get_attachment_body($id, $event);
+  }
+
+  /**
+   * Build a struct representing the given message reference
+   *
+   * @see calendar_driver::get_message_reference()
+   */
+  public function get_message_reference($uri_or_headers, $folder = null)
+  {
+      if (is_object($uri_or_headers)) {
+          $uri_or_headers = kolab_storage_config::get_message_uri($uri_or_headers, $folder);
+      }
+
+      if (is_string($uri_or_headers)) {
+          return kolab_storage_config::get_message_reference($uri_or_headers, 'event');
+      }
+
+      return false;
+  }
+
+  /**
+   * List availabale categories
+   * The default implementation reads them from config/user prefs
+   */
+  public function list_categories()
+  {
+    // FIXME: complete list with categories saved in config objects (KEP:12)
+    return $this->rc->config->get('calendar_categories', $this->default_categories);
+  }
+
+  /**
+   * Create instances of a recurring event
+   *
+   * @param array  Hash array with event properties
+   * @param object DateTime Start date of the recurrence window
+   * @param object DateTime End date of the recurrence window
+   * @return array List of recurring event instances
+   */
+  public function get_recurring_events($event, $start, $end = null)
+  {
+    // load the given event data into a libkolabxml container
+    if (!$event['_formatobj']) {
+      $event_xml = new kolab_format_event();
+      $event_xml->set($event);
+      $event['_formatobj'] = $event_xml;
+    }
+
+    $this->_read_calendars();
+    $storage = reset($this->calendars);
+    return $storage->get_recurring_events($event, $start, $end);
+  }
+
+  /**
+   *
+   */
+  private function get_recurrence_count($event, $dtstart)
+  {
+    // use libkolab to compute recurring events
+    if (class_exists('kolabcalendaring') && $event['_formatobj']) {
+        $recurrence = new kolab_date_recurrence($event['_formatobj']);
+    }
+    else {
+      // fallback to local recurrence implementation
+      require_once($this->cal->home . '/lib/calendar_recurrence.php');
+      $recurrence = new calendar_recurrence($this->cal, $event);
+    }
+
+    $count = 0;
+    while (($next_event = $recurrence->next_instance()) && $next_event['start'] <= $dtstart && $count < 1000) {
+      $count++;
+    }
+
+    return $count;
+  }
+
+  /**
+   * Fetch free/busy information from a person within the given range
+   */
+  public function get_freebusy_list($email, $start, $end)
+  {
+    if (empty($email)/* || $end < time()*/)
+      return false;
+
+    // map vcalendar fbtypes to internal values
+    $fbtypemap = array(
+      'FREE' => calendar::FREEBUSY_FREE,
+      'BUSY-TENTATIVE' => calendar::FREEBUSY_TENTATIVE,
+      'X-OUT-OF-OFFICE' => calendar::FREEBUSY_OOF,
+      'OOF' => calendar::FREEBUSY_OOF);
+
+    // ask kolab server first
+    try {
+      $request_config = array(
+        'store_body'       => true,
+        'follow_redirects' => true,
+      );
+      $request  = libkolab::http_request(kolab_storage::get_freebusy_url($email), 'GET', $request_config);
+      $response = $request->send();
+
+      // authentication required
+      if ($response->getStatus() == 401) {
+        $request->setAuth($this->rc->user->get_username(), $this->rc->decrypt($_SESSION['password']));
+        $response = $request->send();
+      }
+
+      if ($response->getStatus() == 200)
+        $fbdata = $response->getBody();
+
+      unset($request, $response);
+    }
+    catch (Exception $e) {
+      PEAR::raiseError("Error fetching free/busy information: " . $e->getMessage());
+    }
+
+    // get free-busy url from contacts
+    if (!$fbdata) {
+      $fburl = null;
+      foreach ((array)$this->rc->config->get('autocomplete_addressbooks', 'sql') as $book) {
+        $abook = $this->rc->get_address_book($book);
+
+        if ($result = $abook->search(array('email'), $email, true, true, true/*, 'freebusyurl'*/)) {
+          while ($contact = $result->iterate()) {
+            if ($fburl = $contact['freebusyurl']) {
+              $fbdata = @file_get_contents($fburl);
+              break;
+            }
+          }
+        }
+
+        if ($fbdata)
+          break;
+      }
+    }
+
+    // parse free-busy information using Horde classes
+    if ($fbdata) {
+      $ical = $this->cal->get_ical();
+      $ical->import($fbdata);
+      if ($fb = $ical->freebusy) {
+        $result = array();
+        foreach ($fb['periods'] as $tuple) {
+          list($from, $to, $type) = $tuple;
+          $result[] = array($from->format('U'), $to->format('U'), isset($fbtypemap[$type]) ? $fbtypemap[$type] : calendar::FREEBUSY_BUSY);
+        }
+
+        // we take 'dummy' free-busy lists as "unknown"
+        if (empty($result) && !empty($fb['comment']) && stripos($fb['comment'], 'dummy'))
+          return false;
+
+        // set period from $start till the begin of the free-busy information as 'unknown'
+        if ($fb['start'] && ($fbstart = $fb['start']->format('U')) && $start < $fbstart) {
+          array_unshift($result, array($start, $fbstart, calendar::FREEBUSY_UNKNOWN));
+        }
+        // pad period till $end with status 'unknown'
+        if ($fb['end'] && ($fbend = $fb['end']->format('U')) && $fbend < $end) {
+          $result[] = array($fbend, $end, calendar::FREEBUSY_UNKNOWN);
+        }
+
+        return $result;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Handler to push folder triggers when sent from client.
+   * Used to push free-busy changes asynchronously after updating an event
+   */
+  public function push_freebusy()
+  {
+    // make shure triggering completes
+    set_time_limit(0);
+    ignore_user_abort(true);
+
+    $cal = rcube_utils::get_input_value('source', rcube_utils::INPUT_GPC);
+    if (!($cal = $this->get_calendar($cal)))
+      return false;
+
+    // trigger updates on folder
+    $trigger = $cal->storage->trigger();
+    if (is_object($trigger) && is_a($trigger, 'PEAR_Error')) {
+      rcube::raise_error(array(
+        'code' => 900, 'type' => 'php',
+        'file' => __FILE__, 'line' => __LINE__,
+        'message' => "Failed triggering folder. Error was " . $trigger->getMessage()),
+        true, false);
+    }
+
+    exit;
+  }
+
+
+  /**
+   * Convert from driver format to external caledar app data
+   */
+  public static function to_rcube_event(&$record)
+  {
+    if (!is_array($record))
+      return $record;
+
+    $record['id'] = $record['uid'];
+
+    if ($record['_instance']) {
+      $record['id'] .= '-' . $record['_instance'];
+
+      if (!$record['recurrence_id'] && !empty($record['recurrence']))
+        $record['recurrence_id'] = $record['uid'];
+    }
+
+    // all-day events go from 12:00 - 13:00
+    if (is_a($record['start'], 'DateTime') && $record['end'] <= $record['start'] && $record['allday']) {
+      $record['end'] = clone $record['start'];
+      $record['end']->add(new DateInterval('PT1H'));
+    }
+
+    // translate internal '_attachments' to external 'attachments' list
+    if (!empty($record['_attachments'])) {
+      foreach ($record['_attachments'] as $key => $attachment) {
+        if ($attachment !== false) {
+          if (!$attachment['name'])
+            $attachment['name'] = $key;
+
+          unset($attachment['path'], $attachment['content']);
+          $attachments[] = $attachment;
+        }
+      }
+
+      $record['attachments'] = $attachments;
+    }
+
+    if (!empty($record['attendees'])) {
+      foreach ((array)$record['attendees'] as $i => $attendee) {
+        if (is_array($attendee['delegated-from'])) {
+          $record['attendees'][$i]['delegated-from'] = join(', ', $attendee['delegated-from']);
+        }
+        if (is_array($attendee['delegated-to'])) {
+          $record['attendees'][$i]['delegated-to'] = join(', ', $attendee['delegated-to']);
+        }
+      }
+    }
+
+    // Roundcube only supports one category assignment
+    if (is_array($record['categories']))
+      $record['categories'] = $record['categories'][0];
+
+    // the cancelled flag transltes into status=CANCELLED
+    if ($record['cancelled'])
+      $record['status'] = 'CANCELLED';
+
+    // The web client only supports DISPLAY type of alarms
+    if (!empty($record['alarms']))
+      $record['alarms'] = preg_replace('/:[A-Z]+$/', ':DISPLAY', $record['alarms']);
+
+    // remove empty recurrence array
+    if (empty($record['recurrence']))
+      unset($record['recurrence']);
+
+    // clean up exception data
+    if (is_array($record['recurrence']['EXCEPTIONS'])) {
+      array_walk($record['recurrence']['EXCEPTIONS'], function(&$exception) {
+        unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']);
+      });
+    }
+
+    unset($record['_mailbox'], $record['_msguid'], $record['_type'], $record['_size'],
+      $record['_formatobj'], $record['_attachments'], $record['exceptions'], $record['x-custom']);
+
+    return $record;
+  }
+
+  /**
+   *
+   */
+  public static function from_rcube_event($event, $old = array())
+  {
+    kolab_format::merge_attachments($event, $old);
+
+    return $event;
+  }
+
+
+  /**
+   * Set CSS class according to the event's attendde partstat
+   */
+  public static function add_partstat_class($event, $partstats, $user = null)
+  {
+    // set classes according to PARTSTAT
+    if (is_array($event['attendees'])) {
+      $user_emails = libcalendaring::get_instance()->get_user_emails($user);
+      $partstat = 'UNKNOWN';
+      foreach ($event['attendees'] as $attendee) {
+        if (in_array($attendee['email'], $user_emails)) {
+          $partstat = $attendee['status'];
+          break;
+        }
+      }
+
+      if (in_array($partstat, $partstats)) {
+        $event['className'] = trim($event['className'] . ' fc-invitation-' . strtolower($partstat));
+      }
+    }
+
+    return $event;
+  }
+
+  /**
+   * Provide a list of revisions for the given event
+   *
+   * @param array  $event Hash array with event properties
+   *
+   * @return array List of changes, each as a hash array
+   * @see calendar_driver::get_event_changelog()
+   */
+  public function get_event_changelog($event)
+  {
+    if (empty($this->bonnie_api)) {
+      return false;
+    }
+
+    list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event);
+
+    $result = $this->bonnie_api->changelog('event', $uid, $mailbox, $msguid);
+    if (is_array($result) && $result['uid'] == $uid) {
+      return $result['changes'];
+    }
+
+    return false;
+  }
+
+  /**
+   * Get a list of property changes beteen two revisions of an event
+   *
+   * @param array  $event Hash array with event properties
+   * @param mixed  $rev1  Old Revision
+   * @param mixed  $rev2  New Revision
+   *
+   * @return array List of property changes, each as a hash array
+   * @see calendar_driver::get_event_diff()
+   */
+  public function get_event_diff($event, $rev1, $rev2)
+  {
+    if (empty($this->bonnie_api)) {
+      return false;
+    }
+
+    list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event);
+
+    // get diff for the requested recurrence instance
+    $instance_id = $event['id'] != $uid ? substr($event['id'], strlen($uid) + 1) : null;
+
+    // call Bonnie API
+    $result = $this->bonnie_api->diff('event', $uid, $rev1, $rev2, $mailbox, $msguid, $instance_id);
+    if (is_array($result) && $result['uid'] == $uid) {
+      $result['rev1'] = $rev1;
+      $result['rev2'] = $rev2;
+
+      $keymap = array(
+        'dtstart'  => 'start',
+        'dtend'    => 'end',
+        'dstamp'   => 'changed',
+        'summary'  => 'title',
+        'alarm'    => 'alarms',
+        'attendee' => 'attendees',
+        'attach'   => 'attachments',
+        'rrule'    => 'recurrence',
+        'transparency' => 'free_busy',
+        'classification' => 'sensitivity',
+        'lastmodified-date' => 'changed',
+      );
+      $prop_keymaps = array(
+        'attachments' => array('fmttype' => 'mimetype', 'label' => 'name'),
+        'attendees'   => array('partstat' => 'status'),
+      );
+      $special_changes = array();
+
+      // map kolab event properties to keys the client expects
+      array_walk($result['changes'], function(&$change, $i) use ($keymap, $prop_keymaps, $special_changes) {
+        if (array_key_exists($change['property'], $keymap)) {
+          $change['property'] = $keymap[$change['property']];
+        }
+        // translate free_busy values
+        if ($change['property'] == 'free_busy') {
+          $change['old'] = $old['old'] ? 'free' : 'busy';
+          $change['new'] = $old['new'] ? 'free' : 'busy';
+        }
+        // map alarms trigger value
+        if ($change['property'] == 'alarms') {
+          if (is_array($change['old']) && is_array($change['old']['trigger']))
+            $change['old']['trigger'] = $change['old']['trigger']['value'];
+          if (is_array($change['new']) && is_array($change['new']['trigger']))
+            $change['new']['trigger'] = $change['new']['trigger']['value'];
+        }
+        // make all property keys uppercase
+        if ($change['property'] == 'recurrence') {
+          $special_changes['recurrence'] = $i;
+          foreach (array('old','new') as $m) {
+            if (is_array($change[$m])) {
+              $props = array();
+              foreach ($change[$m] as $k => $v)
+                $props[strtoupper($k)] = $v;
+              $change[$m] = $props;
+            }
+          }
+        }
+        // map property keys names
+        if (is_array($prop_keymaps[$change['property']])) {
+          foreach ($prop_keymaps[$change['property']] as $k => $dest) {
+            if (is_array($change['old']) && array_key_exists($k, $change['old'])) {
+              $change['old'][$dest] = $change['old'][$k];
+              unset($change['old'][$k]);
+            }
+            if (is_array($change['new']) && array_key_exists($k, $change['new'])) {
+              $change['new'][$dest] = $change['new'][$k];
+              unset($change['new'][$k]);
+            }
+          }
+        }
+
+        if ($change['property'] == 'exdate') {
+          $special_changes['exdate'] = $i;
+        }
+        else if ($change['property'] == 'rdate') {
+          $special_changes['rdate'] = $i;
+        }
+      });
+
+      // merge some recurrence changes
+      foreach (array('exdate','rdate') as $prop) {
+        if (array_key_exists($prop, $special_changes)) {
+          $exdate = $result['changes'][$special_changes[$prop]];
+          if (array_key_exists('recurrence', $special_changes)) {
+            $recurrence = &$result['changes'][$special_changes['recurrence']];
+          }
+          else {
+            $i = count($result['changes']);
+            $result['changes'][$i] = array('property' => 'recurrence', 'old' => array(), 'new' => array());
+            $recurrence = &$result['changes'][$i]['recurrence'];
+          }
+          $key = strtoupper($prop);
+          $recurrence['old'][$key] = $exdate['old'];
+          $recurrence['new'][$key] = $exdate['new'];
+          unset($result['changes'][$special_changes[$prop]]);
+        }
+      }
+
+      return $result;
+    }
+
+    return false;
+  }
+
+  /**
+   * Return full data of a specific revision of an event
+   *
+   * @param array  Hash array with event properties
+   * @param mixed  $rev Revision number
+   *
+   * @return array Event object as hash array
+   * @see calendar_driver::get_event_revison()
+   */
+  public function get_event_revison($event, $rev, $internal = false)
+  {
+    if (empty($this->bonnie_api)) {
+      return false;
+    }
+
+    $eventid = $event['id'];
+    $calid = $event['calendar'];
+    list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event);
+
+    // call Bonnie API
+    $result = $this->bonnie_api->get('event', $uid, $rev, $mailbox, $msguid);
+    if (is_array($result) && $result['uid'] == $uid && !empty($result['xml'])) {
+      $format = kolab_format::factory('event');
+      $format->load($result['xml']);
+      $event = $format->to_array();
+      $format->get_attachments($event, true);
+
+      // get the right instance from a recurring event
+      if ($eventid != $event['uid']) {
+        $instance_id = substr($eventid, strlen($event['uid']) + 1);
+
+        // check for recurrence exception first
+        if ($instance = $format->get_instance($instance_id)) {
+          $event = $instance;
+        }
+        else {
+          // not a exception, compute recurrence...
+          $event['_formatobj'] = $format;
+          $recurrence_date = rcube_utils::anytodatetime($instance_id, $event['start']->getTimezone());
+          foreach ($this->get_recurring_events($event, $event['start'], $recurrence_date) as $instance) {
+            if ($instance['id'] == $eventid) {
+              $event = $instance;
+              break;
+            }
+          }
+        }
+      }
+
+      if ($format->is_valid()) {
+        $event['calendar'] = $calid;
+        $event['rev'] = $result['rev'];
+        return $internal ? $event : self::to_rcube_event($event);
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Command the backend to restore a certain revision of an event.
+   * This shall replace the current event with an older version.
+   *
+   * @param mixed  UID string or hash array with event properties:
+   *        id: Event identifier
+   *  calendar: Calendar identifier
+   * @param mixed  $rev Revision number
+   *
+   * @return boolean True on success, False on failure
+   */
+  public function restore_event_revision($event, $rev)
+  {
+    if (empty($this->bonnie_api)) {
+      return false;
+    }
+
+    list($uid, $mailbox, $msguid) = $this->_resolve_event_identity($event);
+    $calendar = $this->get_calendar($event['calendar']);
+    $success = false;
+
+    if ($calendar && $calendar->storage && $calendar->editable) {
+      if ($raw_msg = $this->bonnie_api->rawdata('event', $uid, $rev, $mailbox)) {
+        $imap = $this->rc->get_storage();
+
+        // insert $raw_msg as new message
+        if ($imap->save_message($calendar->storage->name, $raw_msg, null, false)) {
+          $success = true;
+
+          // delete old revision from imap and cache
+          $imap->delete_message($msguid, $calendar->storage->name);
+          $calendar->storage->cache->set($msguid, false);
+        }
+      }
+    }
+
+    return $success;
+  }
+
+  /**
+   * Helper method to resolved the given event identifier into uid and folder
+   *
+   * @return array (uid,folder,msguid) tuple
+   */
+  private function _resolve_event_identity($event)
+  {
+    $mailbox = $msguid = null;
+    if (is_array($event)) {
+      $uid = $event['uid'] ?: $event['id'];
+      if (($cal = $this->get_calendar($event['calendar'])) && !($cal instanceof kolab_invitation_calendar)) {
+        $mailbox = $cal->get_mailbox_id();
+
+        // get event object from storage in order to get the real object uid an msguid
+        if ($ev = $cal->get_event($event['id'])) {
+          $msguid = $ev['_msguid'];
+          $uid = $ev['uid'];
+        }
+      }
+    }
+    else {
+      $uid = $event;
+
+      // get event object from storage in order to get the real object uid an msguid
+      if ($ev = $this->get_event($event)) {
+        $mailbox = $ev['_mailbox'];
+        $msguid = $ev['_msguid'];
+        $uid = $ev['uid'];
+      }
+    }
+
+    return array($uid, $mailbox, $msguid);
+  }
+
+  /**
+   * Callback function to produce driver-specific calendar create/edit form
+   *
+   * @param string Request action 'form-edit|form-new'
+   * @param array  Calendar properties (e.g. id, color)
+   * @param array  Edit form fields
+   *
+   * @return string HTML content of the form
+   */
+  public function calendar_form($action, $calendar, $formfields)
+  {
+    // show default dialog for birthday calendar
+    if (in_array($calendar['id'], array(self::BIRTHDAY_CALENDAR_ID, self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) {
+      if ($calendar['id'] != self::BIRTHDAY_CALENDAR_ID)
+        unset($formfields['showalarms']);
+      return parent::calendar_form($action, $calendar, $formfields);
+    }
+
+    $this->_read_calendars();
+
+    if ($calendar['id'] && ($cal = $this->calendars[$calendar['id']])) {
+      $folder = $cal->get_realname(); // UTF7
+      $color  = $cal->get_color();
+    }
+    else {
+      $folder = '';
+      $color  = '';
+    }
+
+    $hidden_fields[] = array('name' => 'oldname', 'value' => $folder);
+
+    $storage = $this->rc->get_storage();
+    $delim   = $storage->get_hierarchy_delimiter();
+    $form   = array();
+
+    if (strlen($folder)) {
+      $path_imap = explode($delim, $folder);
+      array_pop($path_imap);  // pop off name part
+      $path_imap = implode($path_imap, $delim);
+
+      $options = $storage->folder_info($folder);
+    }
+    else {
+      $path_imap = '';
+    }
+
+    // General tab
+    $form['props'] = array(
+      'name' => $this->rc->gettext('properties'),
+    );
+
+    // Disable folder name input
+    if (!empty($options) && ($options['norename'] || $options['protected'])) {
+      $input_name = new html_hiddenfield(array('name' => 'name', 'id' => 'calendar-name'));
+      $formfields['name']['value'] = kolab_storage::object_name($folder)
+        . $input_name->show($folder);
+    }
+
+    // calendar name (default field)
+    $form['props']['fieldsets']['location'] = array(
+      'name'  => $this->rc->gettext('location'),
+      'content' => array(
+        'name' => $formfields['name']
+      ),
+    );
+
+    if (!empty($options) && ($options['norename'] || $options['protected'])) {
+      // prevent user from moving folder
+      $hidden_fields[] = array('name' => 'parent', 'value' => $path_imap);
+    }
+    else {
+      $select = kolab_storage::folder_selector('event', array('name' => 'parent', 'id' => 'calendar-parent'), $folder);
+      $form['props']['fieldsets']['location']['content']['path'] = array(
+        'id' => 'calendar-parent',
+        'label' => $this->cal->gettext('parentcalendar'),
+        'value' => $select->show(strlen($folder) ? $path_imap : ''),
+      );
+    }
+
+    // calendar color (default field)
+    $form['props']['fieldsets']['settings'] = array(
+      'name'  => $this->rc->gettext('settings'),
+      'content' => array(
+        'color' => $formfields['color'],
+        'showalarms' => $formfields['showalarms'],
+      ),
+    );
+    
+    
+    if ($action != 'form-new') {
+      $form['sharing'] = array(
+          'name'    => rcube::Q($this->cal->gettext('tabsharing')),
+          'content' => html::tag('iframe', array(
+            'src' => $this->cal->rc->url(array('_action' => 'calendar-acl', 'id' => $calendar['id'], 'framed' => 1)),
+            'width' => '100%',
+            'height' => 350,
+            'border' => 0,
+            'style' => 'border:0'),
+        ''),
+      );
+    }
+
+    $this->form_html = '';
+    if (is_array($hidden_fields)) {
+        foreach ($hidden_fields as $field) {
+            $hiddenfield = new html_hiddenfield($field);
+            $this->form_html .= $hiddenfield->show() . "\n";
+        }
+    }
+
+    // Create form output
+    foreach ($form as $tab) {
+      if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) {
+        $content = '';
+        foreach ($tab['fieldsets'] as $fieldset) {
+          $subcontent = $this->get_form_part($fieldset);
+          if ($subcontent) {
+            $content .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($fieldset['name'])) . $subcontent) ."\n";
+          }
+        }
+      }
+      else {
+        $content = $this->get_form_part($tab);
+      }
+
+      if ($content) {
+        $this->form_html .= html::tag('fieldset', null, html::tag('legend', null, rcube::Q($tab['name'])) . $content) ."\n";
+      }
+    }
+
+    // Parse form template for skin-dependent stuff
+    $this->rc->output->add_handler('calendarform', array($this, 'calendar_form_html'));
+    return $this->rc->output->parse('calendar.kolabform', false, false);
+  }
+
+  /**
+   * Handler for template object
+   */
+  public function calendar_form_html()
+  {
+    return $this->form_html;
+  }
+
+  /**
+   * Helper function used in calendar_form_content(). Creates a part of the form.
+   */
+  private function get_form_part($form)
+  {
+    $content = '';
+
+    if (is_array($form['content']) && !empty($form['content'])) {
+      $table = new html_table(array('cols' => 2));
+      foreach ($form['content'] as $col => $colprop) {
+        $label = !empty($colprop['label']) ? $colprop['label'] : $this->cal->gettext($col);
+
+        $table->add('title', html::label($colprop['id'], rcube::Q($label)));
+        $table->add(null, $colprop['value']);
+      }
+      $content = $table->show();
+    }
+    else {
+      $content = $form['content'];
+    }
+
+    return $content;
+  }
+
+
+  /**
+   * Handler to render ACL form for a calendar folder
+   */
+  public function calendar_acl()
+  {
+    $this->rc->output->add_handler('folderacl', array($this, 'calendar_acl_form'));
+    $this->rc->output->send('calendar.kolabacl');
+  }
+
+  /**
+   * Handler for ACL form template object
+   */
+  public function calendar_acl_form()
+  {
+    $calid = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GPC);
+    if ($calid && ($cal = $this->get_calendar($calid))) {
+      $folder = $cal->get_realname(); // UTF7
+      $color  = $cal->get_color();
+    }
+    else {
+      $folder = '';
+      $color  = '';
+    }
+
+    $storage = $this->rc->get_storage();
+    $delim   = $storage->get_hierarchy_delimiter();
+    $form   = array();
+
+    if (strlen($folder)) {
+      $path_imap = explode($delim, $folder);
+      array_pop($path_imap);  // pop off name part
+      $path_imap = implode($path_imap, $delim);
+
+      $options = $storage->folder_info($folder);
+
+      // Allow plugins to modify the form content (e.g. with ACL form)
+      $plugin = $this->rc->plugins->exec_hook('calendar_form_kolab',
+        array('form' => $form, 'options' => $options, 'name' => $folder));
+    }
+
+    if (!$plugin['form']['sharing']['content'])
+        $plugin['form']['sharing']['content'] = html::div('hint', $this->cal->gettext('aclnorights'));
+
+    return $plugin['form']['sharing']['content'];
+  }
+
+  /**
+   * Handler for user_delete plugin hook
+   */
+  public function user_delete($args)
+  {
+    $db = $this->rc->get_dbh();
+    foreach (array('kolab_alarms', 'itipinvitations') as $table) {
+      $db->query("DELETE FROM " . $this->rc->db->table_name($table, true)
+        . " WHERE `user_id` = ?", $args['user']->ID);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,376 @@
+<?php
+
+/**
+ * Kolab calendar storage class simulating a virtual calendar listing pedning/declined invitations
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2014-2015, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+class kolab_invitation_calendar
+{
+  public $id = '__invitation__';
+  public $ready = true;
+  public $alarms = false;
+  public $rights = 'lrsv';
+  public $editable = false;
+  public $attachments = false;
+  public $subscriptions = false;
+  public $partstats = array('unknown');
+  public $categories = array();
+  public $name = 'Invitations';
+
+
+  /**
+   * Default constructor
+   */
+  public function __construct($id, $calendar)
+  {
+    $this->cal = $calendar;
+    $this->id = $id;
+
+    switch ($this->id) {
+      case kolab_driver::INVITATIONS_CALENDAR_PENDING:
+        $this->partstats = array('NEEDS-ACTION');
+        $this->name = $this->cal->gettext('invitationspending');
+        if (!empty($_REQUEST['_quickview']))
+          $this->partstats[] = 'TENTATIVE';
+        break;
+
+      case kolab_driver::INVITATIONS_CALENDAR_DECLINED:
+        $this->partstats = array('DECLINED');
+        $this->name = $this->cal->gettext('invitationsdeclined');
+        break;
+    }
+
+    // user-specific alarms settings win
+    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+    if (isset($prefs[$this->id]['showalarms']))
+      $this->alarms = $prefs[$this->id]['showalarms'];
+  }
+
+  /**
+   * Getter for a nice and human readable name for this calendar
+   *
+   * @return string Name of this calendar
+   */
+  public function get_name()
+  {
+    return $this->name;
+  }
+
+  /**
+   * Getter for the IMAP folder owner
+   *
+   * @return string Name of the folder owner
+   */
+  public function get_owner()
+  {
+    return $this->cal->rc->get_user_name();
+  }
+
+  /**
+   *
+   */
+  public function get_title()
+  {
+    return $this->get_name();
+  }
+
+  /**
+   * Getter for the name of the namespace to which the IMAP folder belongs
+   *
+   * @return string Name of the namespace (personal, other, shared)
+   */
+  public function get_namespace()
+  {
+    return 'x-special';
+  }
+
+  /**
+   * Getter for the top-end calendar folder name (not the entire path)
+   *
+   * @return string Name of this calendar
+   */
+  public function get_foldername()
+  {
+    return $this->get_name();
+  }
+
+  /**
+   * Getter for the Cyrus mailbox identifier corresponding to this folder
+   *
+   * @return string Mailbox ID
+   */
+  public function get_mailbox_id()
+  {
+    // this is a virtual collection and has no concrete mailbox ID
+    return null;
+  }
+
+  /**
+   * Return color to display this calendar
+   */
+  public function get_color()
+  {
+    // calendar color is stored in local user prefs
+    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+
+    if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
+      return $prefs[$this->id]['color'];
+
+    return 'ffffff';
+  }
+
+  /**
+   * Compose an URL for CalDAV access to this calendar (if configured)
+   */
+  public function get_caldav_url()
+  {
+    return false;
+  }
+
+  /**
+   * Check activation status of this folder
+   *
+   * @return boolean True if enabled, false if not
+   */
+  public function is_active()
+  {
+    $prefs = $this->cal->rc->config->get('kolab_calendars', array());  // read local prefs
+    return (bool)$prefs[$this->id]['active'];
+  }
+
+  /**
+   * Update properties of this calendar folder
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function update(&$prop)
+  {
+    // don't change anything.
+    // let kolab_driver save props in local prefs
+    return $prop['id'];
+  }
+
+  /**
+   * Getter for a single event object
+   */
+  public function get_event($id)
+  {
+    // redirect call to kolab_driver::get_event()
+    $event = $this->cal->driver->get_event($id, calendar_driver::FILTER_WRITEABLE);
+
+    if (is_array($event)) {
+      // add pointer to original calendar folder
+      $event['_folder_id'] = $event['calendar'];
+      $event = $this->_mod_event($event);
+    }
+
+    return $event;
+  }
+
+  /**
+   * Get attachment body
+   * @see calendar_driver::get_attachment_body()
+   */
+  public function get_attachment_body($id, $event)
+  {
+    // find the actual folder this event resides in
+    if (!empty($event['_folder_id'])) {
+      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
+    }
+    else {
+      $cal = null;
+      foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
+        $cal = $this->_get_calendar($foldername);
+        if ($cal->ready && $cal->storage && $cal->get_event($event['id'])) {
+          break;
+        }
+      }
+    }
+
+    if ($cal && $cal->storage) {
+      return $cal->get_attachment_body($id, $event);
+    }
+
+    return false;
+  }
+
+  /**
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  boolean Include virtual events (optional)
+   * @param  array   Additional parameters to query storage
+   * @return array A list of event records
+   */
+  public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
+  {
+    // get email addresses of the current user
+    $user_emails = $this->cal->get_user_emails();
+    $subquery = array();
+    foreach ($user_emails as $email) {
+      foreach ($this->partstats as $partstat) {
+        $subquery[] = array('tags', '=', 'x-partstat:' . $email . ':' . strtolower($partstat));
+      }
+    }
+
+    // aggregate events from all calendar folders
+    $events = array();
+    foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
+      $cal = $this->_get_calendar($foldername);
+      if ($cal->get_namespace() == 'other')
+        continue;
+
+      foreach ($cal->list_events($start, $end, $search, 1, $query, array(array($subquery, 'OR'))) as $event) {
+        $match = false;
+
+        // post-filter events to match out partstats
+        if (is_array($event['attendees'])) {
+          foreach ($event['attendees'] as $attendee) {
+            if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $this->partstats)) {
+              $match = true;
+              break;
+            }
+          }
+        }
+
+        if ($match) {
+          $events[$event['id'] ?: $event['uid']] = $this->_mod_event($event);
+        }
+      }
+
+      // merge list of event categories (really?)
+      $this->categories += $cal->categories;
+    }
+
+    return $events;
+  }
+
+  /**
+   *
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @return integer Count
+   */
+  public function count_events($start, $end = null)
+  {
+    // get email addresses of the current user
+    $user_emails = $this->cal->get_user_emails();
+    $subquery = array();
+    foreach ($user_emails as $email) {
+      foreach ($this->partstats as $partstat) {
+        $subquery[] = array('tags', '=', 'x-partstat:' . $email . ':' . strtolower($partstat));
+      }
+    }
+
+    $filter = array(
+      array('tags','!=','x-status:cancelled'),
+      array($subquery, 'OR')
+    );
+
+    // aggregate counts from all calendar folders
+    $count = 0;
+    foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
+      $cal = $this->_get_calendar($foldername);
+      if ($cal->get_namespace() == 'other')
+        continue;
+
+      $count += $cal->count_events($start, $end, $filter);
+    }
+
+    return $count;
+  }
+
+  /**
+   * Get calendar object instance (that maybe already initialized)
+   */
+  private function _get_calendar($folder_name)
+  {
+    $id = kolab_storage::folder_id($folder_name, true);
+    return $this->cal->driver->get_calendar($id);
+  }
+
+  /**
+   * Helper method to modify some event properties
+   */
+  private function _mod_event($event)
+  {
+    // set classes according to PARTSTAT
+    $event = kolab_driver::add_partstat_class($event, $this->partstats);
+
+    if (strpos($event['className'], 'fc-invitation-') !== false) {
+      $event['calendar'] = $this->id;
+    }
+
+    return $event;
+  }
+
+  /**
+   * Create a new event record
+   *
+   * @see calendar_driver::new_event()
+   * 
+   * @return mixed The created record ID on success, False on error
+   */
+  public function insert_event($event)
+  {
+    return false;
+  }
+
+  /**
+   * Update a specific event record
+   *
+   * @see calendar_driver::new_event()
+   * @return boolean True on success, False on error
+   */
+  public function update_event($event, $exception_id = null)
+  {
+    // forward call to the actual storage folder
+    if ($event['_folder_id']) {
+      $cal = $this->cal->driver->get_calendar($event['_folder_id']);
+      if ($cal && $cal->ready) {
+        return $cal->update_event($event, $exception_id);
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Delete an event record
+   *
+   * @see calendar_driver::remove_event()
+   * @return boolean True on success, False on error
+   */
+  public function delete_event($event, $force = true)
+  {
+    return false;
+  }
+
+  /**
+   * Restore deleted event record
+   *
+   * @see calendar_driver::undelete_event()
+   * @return boolean True on success, False on error
+   */
+  public function restore_event($event)
+  {
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/kolab/kolab_user_calendar.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,421 @@
+<?php
+
+/**
+ * Kolab calendar storage class simulating a virtual user calendar
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2014-2016, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+class kolab_user_calendar extends kolab_calendar
+{
+  public $id = 'unknown';
+  public $ready = false;
+  public $editable = false;
+  public $attachments = false;
+  public $subscriptions = false;
+
+  protected $userdata = array();
+  protected $timeindex = array();
+
+
+  /**
+   * Default constructor
+   */
+  public function __construct($user_or_folder, $calendar)
+  {
+    $this->cal = $calendar;
+
+    // full user record is provided
+    if (is_array($user_or_folder)) {
+      $this->userdata = $user_or_folder;
+      $this->storage = new kolab_storage_folder_user($this->userdata['kolabtargetfolder'], '', $this->userdata);
+    }
+    else if ($user_or_folder instanceof kolab_storage_folder_user) {
+      $this->storage  = $user_or_folder;
+      $this->userdata = $this->storage->ldaprec;
+    }
+    else {  // get user record from LDAP
+      $this->storage  = new kolab_storage_folder_user($user_or_folder);
+      $this->userdata = $this->storage->ldaprec;
+    }
+
+    $this->ready = !empty($this->userdata['kolabtargetfolder']);
+    $this->storage->type = 'event';
+
+    if ($this->ready) {
+      // ID is derrived from the user's kolabtargetfolder attribute
+      $this->id = kolab_storage::folder_id($this->userdata['kolabtargetfolder'], true);
+      $this->imap_folder = $this->userdata['kolabtargetfolder'];
+      $this->name = $this->storage->name;
+      $this->parent = '';  // user calendars are top level
+
+      // user-specific alarms settings win
+      $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+      if (isset($prefs[$this->id]['showalarms']))
+        $this->alarms = $prefs[$this->id]['showalarms'];
+    }
+  }
+
+  /**
+   * Getter for a nice and human readable name for this calendar
+   *
+   * @return string Name of this calendar
+   */
+  public function get_name()
+  {
+    return $this->userdata['displayname'] ?: ($this->userdata['name'] ?: $this->userdata['mail']);
+  }
+
+  /**
+   * Getter for the IMAP folder owner
+   *
+   * @param bool Return a fully qualified owner name (unused)
+   *
+   * @return string Name of the folder owner
+   */
+  public function get_owner($fully_qualified = false)
+  {
+    return $this->userdata['mail'];
+  }
+
+  /**
+   *
+   */
+  public function get_title()
+  {
+    return trim($this->userdata['displayname'] . '; ' . $this->userdata['mail'], '; ');
+  }
+
+  /**
+   * Getter for the name of the namespace to which the IMAP folder belongs
+   *
+   * @return string Name of the namespace (personal, other, shared)
+   */
+  public function get_namespace()
+  {
+    return 'other user';
+  }
+
+  /**
+   * Getter for the top-end calendar folder name (not the entire path)
+   *
+   * @return string Name of this calendar
+   */
+  public function get_foldername()
+  {
+    return $this->get_name();
+  }
+
+  /**
+   * Return color to display this calendar
+   */
+  public function get_color($default = null)
+  {
+    // calendar color is stored in local user prefs
+    $prefs = $this->cal->rc->config->get('kolab_calendars', array());
+
+    if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
+      return $prefs[$this->id]['color'];
+
+    return $default ?: 'cc0000';
+  }
+
+  /**
+   * Compose an URL for CalDAV access to this calendar (if configured)
+   */
+  public function get_caldav_url()
+  {
+    return false;
+  }
+
+  /**
+   * Check subscription status of this folder
+   *
+   * @return boolean True if subscribed, false if not
+   */
+  public function is_subscribed()
+  {
+    return $this->storage->is_subscribed();
+  }
+
+  /**
+   * Update properties of this calendar folder
+   *
+   * @see calendar_driver::edit_calendar()
+   */
+  public function update(&$prop)
+  {
+    // don't change anything.
+    // let kolab_driver save props in local prefs
+    return $prop['id'];
+  }
+
+  /**
+   * Getter for a single event object
+   */
+  public function get_event($id)
+  {
+    // TODO: implement this
+    return $this->events[$id];
+  }
+
+  /**
+   * Get attachment body
+   * @see calendar_driver::get_attachment_body()
+   */
+  public function get_attachment_body($id, $event)
+  {
+    if (!$event['calendar'] && ($ev = $this->get_event($event['id']))) {
+      $event['calendar'] = $ev['calendar'];
+    }
+
+    if ($event['calendar'] && ($cal = $this->cal->get_calendar($event['calendar']))) {
+      return $cal->get_attachment_body($id, $event);
+    }
+
+    return false;
+  }
+
+  /**
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @param  string  Search query (optional)
+   * @param  boolean Include virtual events (optional)
+   * @param  array   Additional parameters to query storage
+   * @param  array   Additional query to filter events
+   *
+   * @return array A list of event records
+   */
+  public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null)
+  {
+    // convert to DateTime for comparisons
+    try {
+      $start_dt = new DateTime('@'.$start);
+    }
+    catch (Exception $e) {
+      $start_dt = new DateTime('@0');
+    }
+    try {
+      $end_dt = new DateTime('@'.$end);
+    }
+    catch (Exception $e) {
+      $end_dt = new DateTime('today +10 years');
+    }
+
+    $limit_changed = null;
+    if (!empty($query)) {
+      foreach ($query as $q) {
+        if ($q[0] == 'changed' && $q[1] == '>=') {
+          try { $limit_changed = new DateTime('@'.$q[2]); }
+          catch (Exception $e) { /* ignore */ }
+        }
+      }
+    }
+
+    // aggregate all calendar folders the user shares (but are not activated)
+    foreach (kolab_storage::list_user_folders($this->userdata, 'event', 2) as $foldername) {
+      $cal = new kolab_calendar($foldername, $this->cal);
+      foreach ($cal->list_events($start, $end, $search, 1) as $event) {
+        $uid = $event['id'] ?: $event['uid'];
+        $this->events[$uid] = $event;
+        $this->timeindex[$this->time_key($event)] = $uid;
+      }
+    }
+
+    // get events from the user's free/busy feed (for quickview only)
+    $fbview = $this->cal->rc->config->get('calendar_include_freebusy_data', 1);
+    if ($fbview && ($fbview == 1 || !empty($_REQUEST['_quickview'])) && empty($search)) {
+      $this->fetch_freebusy($limit_changed);
+    }
+
+    $events = array();
+    foreach ($this->events as $event) {
+      // list events in requested time window
+      if ($event['start'] <= $end_dt && $event['end'] >= $start_dt &&
+           (!$limit_changed || !$event['changed'] || $event['changed'] >= $limit_changed)) {
+        $events[] = $event;
+      }
+    }
+
+    // avoid session race conditions that will loose temporary subscriptions
+    $this->cal->rc->session->nowrite = true;
+
+    return $events;
+  }
+
+  /**
+   *
+   * @param  integer Date range start (unix timestamp)
+   * @param  integer Date range end (unix timestamp)
+   * @param  array   Additional query to filter events
+   * @return integer Count
+   */
+  public function count_events($start, $end = null, $filter_query = null)
+  {
+    // not implemented
+    return 0;
+  }
+
+  /**
+   * Helper method to fetch free/busy data for the user and turn it into calendar data
+   */
+  private function fetch_freebusy($limit_changed = null)
+  {
+    // ask kolab server first
+    try {
+      $request_config = array(
+        'store_body'       => true,
+        'follow_redirects' => true,
+      );
+      $request  = libkolab::http_request(kolab_storage::get_freebusy_url($this->userdata['mail']), 'GET', $request_config);
+      $response = $request->send();
+
+      // authentication required
+      if ($response->getStatus() == 401) {
+        $request->setAuth($this->cal->rc->user->get_username(), $this->cal->rc->decrypt($_SESSION['password']));
+        $response = $request->send();
+      }
+
+      if ($response->getStatus() == 200)
+        $fbdata = $response->getBody();
+
+      unset($request, $response);
+    }
+    catch (Exception $e) {
+      rcube::raise_error(array(
+        'code' => 900,
+        'type' => 'php',
+        'file' => __FILE__,
+        'line' => __LINE__,
+        'message' => "Error fetching free/busy information: " . $e->getMessage()),
+        true, false);
+
+      return false;
+    }
+
+    $statusmap = array(
+      'FREE' => 'free',
+      'BUSY' => 'busy',
+      'BUSY-TENTATIVE' => 'tentative',
+      'X-OUT-OF-OFFICE' => 'outofoffice',
+      'OOF' => 'outofoffice',
+    );
+    $titlemap = array(
+      'FREE' => $this->cal->gettext('availfree'),
+      'BUSY' => $this->cal->gettext('availbusy'),
+      'BUSY-TENTATIVE' => $this->cal->gettext('availtentative'),
+      'X-OUT-OF-OFFICE' => $this->cal->gettext('availoutofoffice'),
+    );
+
+    // rcube::console('_fetch_freebusy', kolab_storage::get_freebusy_url($this->userdata['mail']), $fbdata);
+
+    // parse free-busy information
+    $count = 0;
+    if ($fbdata) {
+      $ical = $this->cal->get_ical();
+      $ical->import($fbdata);
+      if ($fb = $ical->freebusy) {
+        // consider 'changed >= X' queries
+        if ($limit_changed && $fb['created'] && $fb['created'] < $limit_changed) {
+          return 0;
+        }
+
+        foreach ($fb['periods'] as $tuple) {
+          list($from, $to, $type) = $tuple;
+          $event = array(
+            'uid'       => md5($this->id . $from->format('U') . '/' . $to->format('U')),
+            'calendar'  => $this->id,
+            'changed'   => $fb['created'] ?: new DateTime(),
+            'title'     => $this->get_name() . ' ' . ($titlemap[$type] ?: $type),
+            'start'     => $from,
+            'end'       => $to,
+            'free_busy' => $statusmap[$type] ?: 'busy',
+            'className' => 'fc-type-freebusy',
+            'organizer' => array(
+              'email' => $this->userdata['mail'],
+              'name'  => $this->userdata['displayname'],
+            ),
+          );
+
+          // avoid duplicate entries
+          $key = $this->time_key($event);
+          if (!$this->timeindex[$key]) {
+            $this->events[$event['uid']] = $event;
+            $this->timeindex[$key] = $event['uid'];
+            $count++;
+          }
+        }
+      }
+    }
+
+    return $count;
+  }
+
+  /**
+   * Helper to build a key for the absolute time slot the given event convers
+   */
+  private function time_key($event)
+  {
+    return sprintf('%s/%s', $event['start']->format('U'), is_object($event['end']) ? $event['end']->format('U') : '0');
+  }
+
+  /**
+   * Create a new event record
+   *
+   * @see calendar_driver::new_event()
+   * 
+   * @return mixed The created record ID on success, False on error
+   */
+  public function insert_event($event)
+  {
+    return false;
+  }
+
+  /**
+   * Update a specific event record
+   *
+   * @see calendar_driver::new_event()
+   * @return boolean True on success, False on error
+   */
+  public function update_event($event, $exception_id = null)
+  {
+    return false;
+  }
+
+  /**
+   * Delete an event record
+   *
+   * @see calendar_driver::remove_event()
+   * @return boolean True on success, False on error
+   */
+  public function delete_event($event, $force = true)
+  {
+    return false;
+  }
+
+  /**
+   * Restore deleted event record
+   *
+   * @see calendar_driver::undelete_event()
+   * @return boolean True on success, False on error
+   */
+  public function restore_event($event)
+  {
+    return false;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/ldap/resources_driver_ldap.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * LDAP-based resource directory class using rcube_ldap functionality
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+/**
+ * LDAP-based resource directory implementation
+ */
+class resources_driver_ldap extends resources_driver
+{
+    private $rc;
+    private $ldap;
+
+    /**
+     * Default constructor
+     */
+    function __construct($cal)
+    {
+        $this->cal = $cal;
+        $this->rc = $cal->rc;
+    }
+
+    /**
+     * Fetch resource objects to be displayed for booking
+     *
+     * @param  string  Search query (optional)
+     * @return array  List of resource records available for booking
+     */
+    public function load_resources($query = null, $num = 5000)
+    {
+      if (!($ldap = $this->connect())) {
+        return array();
+      }
+
+      // TODO: apply paging
+      $ldap->set_pagesize($num);
+
+      if (isset($query)) {
+        $results = $ldap->search('*', $query, 0, true, true);
+      }
+      else {
+        $results = $ldap->list_records();
+      }
+
+      if ($results instanceof ArrayAccess) {
+        foreach ($results as $i => $rec) {
+          $results[$i] = $this->decode_resource($rec);
+        }
+      }
+
+      return $results;
+    }
+
+    /**
+     * Return properties of a single resource
+     *
+     * @param string  Unique resource identifier
+     * @return array Resource object as hash array
+     */
+    public function get_resource($dn)
+    {
+      $rec = null;
+
+      if ($ldap = $this->connect()) {
+        $rec = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
+
+        if (!empty($rec)) {
+          $rec = $this->decode_resource($rec);
+        }
+      }
+
+      return $rec;
+    }
+
+    /**
+     * Return properties of a resource owner
+     *
+     * @param string  Owner identifier
+     * @return array  Resource object as hash array
+     */
+    public function get_resource_owner($dn)
+    {
+      $owner = null;
+
+      if ($ldap = $this->connect()) {
+        $owner = $ldap->get_record(rcube_ldap::dn_encode($dn), true);
+        $owner['ID'] = rcube_ldap::dn_decode($owner['ID']);
+        unset($owner['_raw_attrib'], $owner['_type']);
+      }
+
+      return $owner;
+    }
+
+    /**
+     * Extract JSON-serialized attributes
+     */
+    private function decode_resource($rec)
+    {
+      $rec['ID'] = rcube_ldap::dn_decode($rec['ID']);
+
+      if (is_array($rec['attributes']) && $rec['attributes'][0]) {
+        $attributes = array();
+
+        foreach ($rec['attributes'] as $sattr) {
+          $attr = @json_decode($sattr, true);
+          $attributes += $attr;
+        }
+
+        $rec['attributes'] = $attributes;
+      }
+
+      // force $rec['members'] to be an array
+      if (!empty($rec['members']) && !is_array($rec['members'])) {
+        $rec['members'] = array($rec['members']);
+      }
+
+      // remove unused cruft
+      unset($rec['_raw_attrib']);
+
+      return $rec;
+    }
+
+    private function connect()
+    {
+      if (!isset($this->ldap)) {
+        $this->ldap = new rcube_ldap($this->rc->config->get('calendar_resources_directory'), true);
+      }
+
+      return $this->ldap->ready ? $this->ldap : null;
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/drivers/resources_driver.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Resources directory interface definition
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+
+/**
+ * Interface definition for a resources directory driver classe
+ */
+abstract class resources_driver
+{
+  protected$cal;
+
+  /**
+   * Default constructor
+   */
+  function __construct($cal)
+  {
+      $this->cal = $cal;
+  }
+
+  /**
+   * Fetch resource objects to be displayed for booking
+   *
+   * @param  string  Search query (optional)
+   * @return array  List of resource records available for booking
+   */
+  abstract public function load_resources($query = null);
+
+  /**
+   * Return properties of a single resource
+   *
+   * @param string  Unique resource identifier
+   * @return array  Resource object as hash array
+   */
+  abstract public function get_resource($id);
+
+  /**
+   * Return properties of a resource owner
+   *
+   * @param string  Owner identifier
+   * @return array  Resource object as hash array
+   */
+  public function get_resource_owner($id)
+  {
+      return null;
+  }
+
+  /**
+   * Get event data to display a resource's calendar
+   *
+   * The default implementation extracts the resource's email address
+   * and fetches free-busy data using the calendar backend driver.
+   *
+   * @param  integer Event's new start (unix timestamp)
+   * @param  integer Event's new end (unix timestamp)
+   * @return array A list of event objects (see calendar_driver specification)
+   */
+  public function get_resource_calendar($id, $start, $end)
+  {
+      $events = array();
+      $rec = $this->get_resource($id);
+      if ($rec && !empty($rec['email']) && $this->cal->driver) {
+          $fbtypemap = array(
+              calendar::FREEBUSY_BUSY => 'busy',
+              calendar::FREEBUSY_TENTATIVE => 'tentative',
+              calendar::FREEBUSY_OOF => 'outofoffice',
+          );
+
+          // if the backend has free-busy information
+          $fblist = $this->cal->driver->get_freebusy_list($rec['email'], $start, $end);
+          if (is_array($fblist)) {
+              foreach ($fblist as $slot) {
+                  list($from, $to, $type) = $slot;
+                  if ($type == calendar::FREEBUSY_FREE || $type == calendar::FREEBUSY_UNKNOWN) {
+                      continue;
+                  }
+                  if ($from < $end && $to > $start) {
+                      $event = array(
+                          'id'     => sha1($id . $from . $to),
+                          'title'  => $rec['name'],
+                          'start'  => new DateTime('@' . $from),
+                          'end'    => new DateTime('@' . $to),
+                          'status' => $fbtypemap[$type],
+                          'calendar' => '_resource',
+                      );
+                      $events[] = $event;
+                  }
+              }
+          }
+      }
+
+      return $events;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/Horde_Date.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1304 @@
+<?php
+
+/**
+ * This is a concatenated copy of the following files:
+ *   Horde/Date/Utils.php, Horde/Date/Recurrence.php
+ * Pull the latest version of these files from the PEAR channel of the Horde
+ * project at http://pear.horde.org by installing the Horde_Date package.
+ */
+
+
+/**
+ * Horde Date wrapper/logic class, including some calculation
+ * functions.
+ *
+ * @category Horde
+ * @package  Date
+ *
+ * @TODO in format():
+ *   http://php.net/intldateformatter
+ *
+ * @TODO on timezones:
+ *   http://trac.agavi.org/ticket/1008
+ *   http://trac.agavi.org/changeset/3659
+ *
+ * @TODO on switching to PHP::DateTime:
+ *   The only thing ever stored in the database *IS* Unix timestamps. Doing
+ *   anything other than that is unmanageable, yet some frameworks use 'server
+ *   based' times in their systems, simply because they do not bother with
+ *   daylight saving and only 'serve' one timezone!
+ *
+ *   The second you have to manage 'real' time across timezones then daylight
+ *   saving becomes essential, BUT only on the display side! Since the browser
+ *   only provides a time offset, this is useless and to be honest should simply
+ *   be ignored ( until it is upgraded to provide the correct information ;)
+ *   ). So we need a 'display' function that takes a simple numeric epoch, and a
+ *   separate timezone id into which the epoch is to be 'converted'. My W3C
+ *   mapping works simply because ADOdb then converts that to it's own simple
+ *   offset abbreviation - in my case GMT or BST. As long as DateTime passes the
+ *   full 64 bit number the date range from 100AD is also preserved ( and
+ *   further back if 2 digit years are disabled ). If I want to display the
+ *   'real' timezone with this 'time' then I just add it in place of ADOdb's
+ *   'timezone'. I am tempted to simply adjust the ADOdb class to take a
+ *   timezone in place of the simple GMT switch it currently uses.
+ *
+ *   The return path is just the reverse and simply needs to take the client
+ *   display offset off prior to storage of the UTC epoch. SO we use
+ *   DateTimeZone to get an offset value for the clients timezone and simply add
+ *   or subtract this from a timezone agnostic display on the client end when
+ *   entering new times.
+ *
+ *
+ *   It's not really feasible to store dates in specific timezone, as most
+ *   national/local timezones support DST - and that is a pain to support, as
+ *   eg.  sorting breaks when some timestamps get repeated. That's why it's
+ *   usually better to store datetimes as either UTC datetime or plain unix
+ *   timestamp. I usually go with the former - using database datetime type.
+ */
+
+/**
+ * @category Horde
+ * @package  Date
+ */
+class Horde_Date
+{
+    const DATE_SUNDAY = 0;
+    const DATE_MONDAY = 1;
+    const DATE_TUESDAY = 2;
+    const DATE_WEDNESDAY = 3;
+    const DATE_THURSDAY = 4;
+    const DATE_FRIDAY = 5;
+    const DATE_SATURDAY = 6;
+
+    const MASK_SUNDAY = 1;
+    const MASK_MONDAY = 2;
+    const MASK_TUESDAY = 4;
+    const MASK_WEDNESDAY = 8;
+    const MASK_THURSDAY = 16;
+    const MASK_FRIDAY = 32;
+    const MASK_SATURDAY = 64;
+    const MASK_WEEKDAYS = 62;
+    const MASK_WEEKEND = 65;
+    const MASK_ALLDAYS = 127;
+
+    const MASK_SECOND = 1;
+    const MASK_MINUTE = 2;
+    const MASK_HOUR = 4;
+    const MASK_DAY = 8;
+    const MASK_MONTH = 16;
+    const MASK_YEAR = 32;
+    const MASK_ALLPARTS = 63;
+
+    const DATE_DEFAULT = 'Y-m-d H:i:s';
+    const DATE_JSON = 'Y-m-d\TH:i:s';
+
+    /**
+     * Year
+     *
+     * @var integer
+     */
+    protected $_year;
+
+    /**
+     * Month
+     *
+     * @var integer
+     */
+    protected $_month;
+
+    /**
+     * Day
+     *
+     * @var integer
+     */
+    protected $_mday;
+
+    /**
+     * Hour
+     *
+     * @var integer
+     */
+    protected $_hour = 0;
+
+    /**
+     * Minute
+     *
+     * @var integer
+     */
+    protected $_min = 0;
+
+    /**
+     * Second
+     *
+     * @var integer
+     */
+    protected $_sec = 0;
+
+    /**
+     * String representation of the date's timezone.
+     *
+     * @var string
+     */
+    protected $_timezone;
+
+    /**
+     * Default format for __toString()
+     *
+     * @var string
+     */
+    protected $_defaultFormat = self::DATE_DEFAULT;
+
+    /**
+     * Default specs that are always supported.
+     * @var string
+     */
+    protected static $_defaultSpecs = '%CdDeHImMnRStTyY';
+
+    /**
+     * Internally supported strftime() specifiers.
+     * @var string
+     */
+    protected static $_supportedSpecs = '';
+
+    /**
+     * Map of required correction masks.
+     *
+     * @see __set()
+     *
+     * @var array
+     */
+    protected static $_corrections = array(
+        'year'  => self::MASK_YEAR,
+        'month' => self::MASK_MONTH,
+        'mday'  => self::MASK_DAY,
+        'hour'  => self::MASK_HOUR,
+        'min'   => self::MASK_MINUTE,
+        'sec'   => self::MASK_SECOND,
+    );
+
+    protected $_formatCache = array();
+
+    /**
+     * Builds a new date object. If $date contains date parts, use them to
+     * initialize the object.
+     *
+     * Recognized formats:
+     * - arrays with keys 'year', 'month', 'mday', 'day'
+     *   'hour', 'min', 'minute', 'sec'
+     * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
+     * - yyyy-mm-dd hh:mm:ss
+     * - yyyymmddhhmmss
+     * - yyyymmddThhmmssZ
+     * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and
+     *   03 Mar 1973)
+     * - unix timestamps
+     * - anything parsed by strtotime()/DateTime.
+     *
+     * @throws Horde_Date_Exception
+     */
+    public function __construct($date = null, $timezone = null)
+    {
+        if (!self::$_supportedSpecs) {
+            self::$_supportedSpecs = self::$_defaultSpecs;
+            if (function_exists('nl_langinfo')) {
+                self::$_supportedSpecs .= 'bBpxX';
+            }
+        }
+
+        if (func_num_args() > 2) {
+            // Handle args in order: year month day hour min sec tz
+            $this->_initializeFromArgs(func_get_args());
+            return;
+        }
+
+        $this->_initializeTimezone($timezone);
+
+        if (is_null($date)) {
+            return;
+        }
+
+        if (is_string($date)) {
+            $date = trim($date, '"');
+        }
+
+        if (is_object($date)) {
+            $this->_initializeFromObject($date);
+        } elseif (is_array($date)) {
+            $this->_initializeFromArray($date);
+        } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(?:\.\d+)?(Z?)$/', $date, $parts)) {
+            $this->_year  = (int)$parts[1];
+            $this->_month = (int)$parts[2];
+            $this->_mday  = (int)$parts[3];
+            $this->_hour  = (int)$parts[4];
+            $this->_min   = (int)$parts[5];
+            $this->_sec   = (int)$parts[6];
+            if ($parts[7]) {
+                $this->_initializeTimezone('UTC');
+            }
+        } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) &&
+                  $parts[2] > 0 && $parts[2] <= 12 &&
+                  $parts[3] > 0 && $parts[3] <= 31) {
+            $this->_year  = (int)$parts[1];
+            $this->_month = (int)$parts[2];
+            $this->_mday  = (int)$parts[3];
+            $this->_hour = $this->_min = $this->_sec = 0;
+        } elseif ((string)(int)$date == $date) {
+            // Try as a timestamp.
+            $parts = @getdate($date);
+            if ($parts) {
+                $this->_year  = $parts['year'];
+                $this->_month = $parts['mon'];
+                $this->_mday  = $parts['mday'];
+                $this->_hour  = $parts['hours'];
+                $this->_min   = $parts['minutes'];
+                $this->_sec   = $parts['seconds'];
+            }
+        } else {
+            // Use date_create() so we can catch errors with PHP 5.2. Use
+            // "new DateTime() once we require 5.3.
+            $parsed = date_create($date);
+            if (!$parsed) {
+                throw new Horde_Date_Exception(sprintf(Horde_Date_Translation::t("Failed to parse time string (%s)"), $date));
+            }
+            $parsed->setTimezone(new DateTimeZone(date_default_timezone_get()));
+            $this->_year  = (int)$parsed->format('Y');
+            $this->_month = (int)$parsed->format('m');
+            $this->_mday  = (int)$parsed->format('d');
+            $this->_hour  = (int)$parsed->format('H');
+            $this->_min   = (int)$parsed->format('i');
+            $this->_sec   = (int)$parsed->format('s');
+            $this->_initializeTimezone(date_default_timezone_get());
+        }
+    }
+
+    /**
+     * Returns a simple string representation of the date object
+     *
+     * @return string  This object converted to a string.
+     */
+    public function __toString()
+    {
+        try {
+            return $this->format($this->_defaultFormat);
+        } catch (Exception $e) {
+            return '';
+        }
+    }
+
+    /**
+     * Returns a DateTime object representing this object.
+     *
+     * @return DateTime
+     */
+    public function toDateTime()
+    {
+        $date = new DateTime(null, new DateTimeZone($this->_timezone));
+        $date->setDate($this->_year, $this->_month, $this->_mday);
+        $date->setTime($this->_hour, $this->_min, $this->_sec);
+        return $date;
+    }
+
+    /**
+     * Converts a date in the proleptic Gregorian calendar to the no of days
+     * since 24th November, 4714 B.C.
+     *
+     * Returns the no of days since Monday, 24th November, 4714 B.C. in the
+     * proleptic Gregorian calendar (which is 24th November, -4713 using
+     * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the
+     * proleptic Julian calendar).  This is also the first day of the 'Julian
+     * Period' proposed by Joseph Scaliger in 1583, and the number of days
+     * since this date is known as the 'Julian Day'.  (It is not directly
+     * to do with the Julian calendar, although this is where the name
+     * is derived from.)
+     *
+     * The algorithm is valid for all years (positive and negative), and
+     * also for years preceding 4714 B.C.
+     *
+     * Algorithm is from PEAR::Date_Calc
+     *
+     * @author Monte Ohrt <monte@ispi.net>
+     * @author Pierre-Alain Joye <pajoye@php.net>
+     * @author Daniel Convissor <danielc@php.net>
+     * @author C.A. Woodcock <c01234@netcomuk.co.uk>
+     *
+     * @return integer  The number of days since 24th November, 4714 B.C.
+     */
+    public function toDays()
+    {
+        if (function_exists('GregorianToJD')) {
+            return gregoriantojd($this->_month, $this->_mday, $this->_year);
+        }
+
+        $day = $this->_mday;
+        $month = $this->_month;
+        $year = $this->_year;
+
+        if ($month > 2) {
+            // March = 0, April = 1, ..., December = 9,
+            // January = 10, February = 11
+            $month -= 3;
+        } else {
+            $month += 9;
+            --$year;
+        }
+
+        $hb_negativeyear = $year < 0;
+        $century         = intval($year / 100);
+        $year            = $year % 100;
+
+        if ($hb_negativeyear) {
+            // Subtract 1 because year 0 is a leap year;
+            // And N.B. that we must treat the leap years as occurring
+            // one year earlier than they do, because for the purposes
+            // of calculation, the year starts on 1st March:
+            //
+            return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) +
+                   intval((1461 * $year + 1) / 4) +
+                   intval((153 * $month + 2) / 5) +
+                   $day + 1721118;
+        } else {
+            return intval(146097 * $century / 4) +
+                   intval(1461 * $year / 4) +
+                   intval((153 * $month + 2) / 5) +
+                   $day + 1721119;
+        }
+    }
+
+    /**
+     * Converts number of days since 24th November, 4714 B.C. (in the proleptic
+     * Gregorian calendar, which is year -4713 using 'Astronomical' year
+     * numbering) to Gregorian calendar date.
+     *
+     * Returned date belongs to the proleptic Gregorian calendar, using
+     * 'Astronomical' year numbering.
+     *
+     * The algorithm is valid for all years (positive and negative), and
+     * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
+     * and so the only limitation is platform-dependent (for 32-bit systems
+     * the maximum year would be something like about 1,465,190 A.D.).
+     *
+     * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
+     *
+     * Algorithm is from PEAR::Date_Calc
+     *
+     * @author Monte Ohrt <monte@ispi.net>
+     * @author Pierre-Alain Joye <pajoye@php.net>
+     * @author Daniel Convissor <danielc@php.net>
+     * @author C.A. Woodcock <c01234@netcomuk.co.uk>
+     *
+     * @param int    $days   the number of days since 24th November, 4714 B.C.
+     * @param string $format the string indicating how to format the output
+     *
+     * @return  Horde_Date  A Horde_Date object representing the date.
+     */
+    public static function fromDays($days)
+    {
+        if (function_exists('JDToGregorian')) {
+            list($month, $day, $year) = explode('/', JDToGregorian($days));
+        } else {
+            $days = intval($days);
+
+            $days   -= 1721119;
+            $century = floor((4 * $days - 1) / 146097);
+            $days    = floor(4 * $days - 1 - 146097 * $century);
+            $day     = floor($days / 4);
+
+            $year = floor((4 * $day +  3) / 1461);
+            $day  = floor(4 * $day +  3 - 1461 * $year);
+            $day  = floor(($day +  4) / 4);
+
+            $month = floor((5 * $day - 3) / 153);
+            $day   = floor(5 * $day - 3 - 153 * $month);
+            $day   = floor(($day +  5) /  5);
+
+            $year = $century * 100 + $year;
+            if ($month < 10) {
+                $month +=3;
+            } else {
+                $month -=9;
+                ++$year;
+            }
+        }
+
+        return new Horde_Date($year, $month, $day);
+    }
+
+    /**
+     * Getter for the date and time properties.
+     *
+     * @param string $name  One of 'year', 'month', 'mday', 'hour', 'min' or
+     *                      'sec'.
+     *
+     * @return integer  The property value, or null if not set.
+     */
+    public function __get($name)
+    {
+        if ($name == 'day') {
+            $name = 'mday';
+        }
+
+        return $this->{'_' . $name};
+    }
+
+    /**
+     * Setter for the date and time properties.
+     *
+     * @param string $name    One of 'year', 'month', 'mday', 'hour', 'min' or
+     *                        'sec'.
+     * @param integer $value  The property value.
+     */
+    public function __set($name, $value)
+    {
+        if ($name == 'timezone') {
+            $this->_initializeTimezone($value);
+            return;
+        }
+        if ($name == 'day') {
+            $name = 'mday';
+        }
+
+        if ($name != 'year' && $name != 'month' && $name != 'mday' &&
+            $name != 'hour' && $name != 'min' && $name != 'sec') {
+            throw new InvalidArgumentException('Undefined property ' . $name);
+        }
+
+        $down = $value < $this->{'_' . $name};
+        $this->{'_' . $name} = $value;
+        $this->_correct(self::$_corrections[$name], $down);
+        $this->_formatCache = array();
+    }
+
+    /**
+     * Returns whether a date or time property exists.
+     *
+     * @param string $name  One of 'year', 'month', 'mday', 'hour', 'min' or
+     *                      'sec'.
+     *
+     * @return boolen  True if the property exists and is set.
+     */
+    public function __isset($name)
+    {
+        if ($name == 'day') {
+            $name = 'mday';
+        }
+        return ($name == 'year' || $name == 'month' || $name == 'mday' ||
+                $name == 'hour' || $name == 'min' || $name == 'sec') &&
+            isset($this->{'_' . $name});
+    }
+
+    /**
+     * Adds a number of seconds or units to this date, returning a new Date
+     * object.
+     */
+    public function add($factor)
+    {
+        $d = clone($this);
+        if (is_array($factor) || is_object($factor)) {
+            foreach ($factor as $property => $value) {
+                $d->$property += $value;
+            }
+        } else {
+            $d->sec += $factor;
+        }
+
+        return $d;
+    }
+
+    /**
+     * Subtracts a number of seconds or units from this date, returning a new
+     * Horde_Date object.
+     */
+    public function sub($factor)
+    {
+        if (is_array($factor)) {
+            foreach ($factor as &$value) {
+                $value *= -1;
+            }
+        } else {
+            $factor *= -1;
+        }
+
+        return $this->add($factor);
+    }
+
+    /**
+     * Converts this object to a different timezone.
+     *
+     * @param string $timezone  The new timezone.
+     *
+     * @return Horde_Date  This object.
+     */
+    public function setTimezone($timezone)
+    {
+        $date = $this->toDateTime();
+        $date->setTimezone(new DateTimeZone($timezone));
+        $this->_timezone = $timezone;
+        $this->_year     = (int)$date->format('Y');
+        $this->_month    = (int)$date->format('m');
+        $this->_mday     = (int)$date->format('d');
+        $this->_hour     = (int)$date->format('H');
+        $this->_min      = (int)$date->format('i');
+        $this->_sec      = (int)$date->format('s');
+        $this->_formatCache = array();
+        return $this;
+    }
+
+    /**
+     * Sets the default date format used in __toString()
+     *
+     * @param string $format
+     */
+    public function setDefaultFormat($format)
+    {
+        $this->_defaultFormat = $format;
+    }
+
+    /**
+     * Returns the day of the week (0 = Sunday, 6 = Saturday) of this date.
+     *
+     * @return integer  The day of the week.
+     */
+    public function dayOfWeek()
+    {
+        if ($this->_month > 2) {
+            $month = $this->_month - 2;
+            $year = $this->_year;
+        } else {
+            $month = $this->_month + 10;
+            $year = $this->_year - 1;
+        }
+
+        $day = (floor((13 * $month - 1) / 5) +
+                $this->_mday + ($year % 100) +
+                floor(($year % 100) / 4) +
+                floor(($year / 100) / 4) - 2 *
+                floor($year / 100) + 77);
+
+        return (int)($day - 7 * floor($day / 7));
+    }
+
+    /**
+     * Returns the day number of the year (1 to 365/366).
+     *
+     * @return integer  The day of the year.
+     */
+    public function dayOfYear()
+    {
+        return $this->format('z') + 1;
+    }
+
+    /**
+     * Returns the week of the month.
+     *
+     * @return integer  The week number.
+     */
+    public function weekOfMonth()
+    {
+        return ceil($this->_mday / 7);
+    }
+
+    /**
+     * Returns the week of the year, first Monday is first day of first week.
+     *
+     * @return integer  The week number.
+     */
+    public function weekOfYear()
+    {
+        return $this->format('W');
+    }
+
+    /**
+     * Returns the number of weeks in the given year (52 or 53).
+     *
+     * @param integer $year  The year to count the number of weeks in.
+     *
+     * @return integer $numWeeks   The number of weeks in $year.
+     */
+    public static function weeksInYear($year)
+    {
+        // Find the last Thursday of the year.
+        $date = new Horde_Date($year . '-12-31');
+        while ($date->dayOfWeek() != self::DATE_THURSDAY) {
+            --$date->mday;
+        }
+        return $date->weekOfYear();
+    }
+
+    /**
+     * Sets the date of this object to the $nth weekday of $weekday.
+     *
+     * @param integer $weekday  The day of the week (0 = Sunday, etc).
+     * @param integer $nth      The $nth $weekday to set to (defaults to 1).
+     */
+    public function setNthWeekday($weekday, $nth = 1)
+    {
+        if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) {
+            return;
+        }
+
+        if ($nth < 0) {  // last $weekday of month
+            $this->_mday = $lastday = Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
+            $last = $this->dayOfWeek();
+            $this->_mday += ($weekday - $last);
+            if ($this->_mday > $lastday)
+                $this->_mday -= 7;
+        }
+        else {
+            $this->_mday = 1;
+            $first = $this->dayOfWeek();
+            if ($weekday < $first) {
+                $this->_mday = 8 + $weekday - $first;
+            } else {
+                $this->_mday = $weekday - $first + 1;
+            }
+            $diff = 7 * $nth - 7;
+            $this->_mday += $diff;
+            $this->_correct(self::MASK_DAY, $diff < 0);
+        }
+    }
+
+    /**
+     * Is the date currently represented by this object a valid date?
+     *
+     * @return boolean  Validity, counting leap years, etc.
+     */
+    public function isValid()
+    {
+        return ($this->_year >= 0 && $this->_year <= 9999);
+    }
+
+    /**
+     * Compares this date to another date object to see which one is
+     * greater (later). Assumes that the dates are in the same
+     * timezone.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return integer  ==  0 if they are on the same date
+     *                  >=  1 if $this is greater (later)
+     *                  <= -1 if $other is greater (later)
+     */
+    public function compareDate($other)
+    {
+        if (!($other instanceof Horde_Date)) {
+            $other = new Horde_Date($other);
+        }
+
+        if ($this->_year != $other->year) {
+            return $this->_year - $other->year;
+        }
+        if ($this->_month != $other->month) {
+            return $this->_month - $other->month;
+        }
+
+        return $this->_mday - $other->mday;
+    }
+
+    /**
+     * Returns whether this date is after the other.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return boolean  True if this date is after the other.
+     */
+    public function after($other)
+    {
+        return $this->compareDate($other) > 0;
+    }
+
+    /**
+     * Returns whether this date is before the other.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return boolean  True if this date is before the other.
+     */
+    public function before($other)
+    {
+        return $this->compareDate($other) < 0;
+    }
+
+    /**
+     * Returns whether this date is the same like the other.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return boolean  True if this date is the same like the other.
+     */
+    public function equals($other)
+    {
+        return $this->compareDate($other) == 0;
+    }
+
+    /**
+     * Compares this to another date object by time, to see which one
+     * is greater (later). Assumes that the dates are in the same
+     * timezone.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return integer  ==  0 if they are at the same time
+     *                  >=  1 if $this is greater (later)
+     *                  <= -1 if $other is greater (later)
+     */
+    public function compareTime($other)
+    {
+        if (!($other instanceof Horde_Date)) {
+            $other = new Horde_Date($other);
+        }
+
+        if ($this->_hour != $other->hour) {
+            return $this->_hour - $other->hour;
+        }
+        if ($this->_min != $other->min) {
+            return $this->_min - $other->min;
+        }
+
+        return $this->_sec - $other->sec;
+    }
+
+    /**
+     * Compares this to another date object, including times, to see
+     * which one is greater (later). Assumes that the dates are in the
+     * same timezone.
+     *
+     * @param mixed $other  The date to compare to.
+     *
+     * @return integer  ==  0 if they are equal
+     *                  >=  1 if $this is greater (later)
+     *                  <= -1 if $other is greater (later)
+     */
+    public function compareDateTime($other)
+    {
+        if (!($other instanceof Horde_Date)) {
+            $other = new Horde_Date($other);
+        }
+
+        if ($diff = $this->compareDate($other)) {
+            return $diff;
+        }
+
+        return $this->compareTime($other);
+    }
+
+    /**
+     * Returns number of days between this date and another.
+     *
+     * @param Horde_Date $other  The other day to diff with.
+     *
+     * @return integer  The absolute number of days between the two dates.
+     */
+    public function diff($other)
+    {
+        return abs($this->toDays() - $other->toDays());
+    }
+
+    /**
+     * Returns the time offset for local time zone.
+     *
+     * @param boolean $colon  Place a colon between hours and minutes?
+     *
+     * @return string  Timezone offset as a string in the format +HH:MM.
+     */
+    public function tzOffset($colon = true)
+    {
+        return $colon ? $this->format('P') : $this->format('O');
+    }
+
+    /**
+     * Returns the unix timestamp representation of this date.
+     *
+     * @return integer  A unix timestamp.
+     */
+    public function timestamp()
+    {
+        if ($this->_year >= 1970 && $this->_year < 2038) {
+            return mktime($this->_hour, $this->_min, $this->_sec,
+                          $this->_month, $this->_mday, $this->_year);
+        }
+        return $this->format('U');
+    }
+
+    /**
+     * Returns the unix timestamp representation of this date, 12:00am.
+     *
+     * @return integer  A unix timestamp.
+     */
+    public function datestamp()
+    {
+        if ($this->_year >= 1970 && $this->_year < 2038) {
+            return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year);
+        }
+        $date = new DateTime($this->format('Y-m-d'));
+        return $date->format('U');
+    }
+
+    /**
+     * Formats date and time to be passed around as a short url parameter.
+     *
+     * @return string  Date and time.
+     */
+    public function dateString()
+    {
+        return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday);
+    }
+
+    /**
+     * Formats date and time to the ISO format used by JSON.
+     *
+     * @return string  Date and time.
+     */
+    public function toJson()
+    {
+        return $this->format(self::DATE_JSON);
+    }
+
+    /**
+     * Formats date and time to the RFC 2445 iCalendar DATE-TIME format.
+     *
+     * @param boolean $floating  Whether to return a floating date-time
+     *                           (without time zone information).
+     *
+     * @return string  Date and time.
+     */
+    public function toiCalendar($floating = false)
+    {
+        if ($floating) {
+            return $this->format('Ymd\THis');
+        }
+        $dateTime = $this->toDateTime();
+        $dateTime->setTimezone(new DateTimeZone('UTC'));
+        return $dateTime->format('Ymd\THis\Z');
+    }
+
+    /**
+     * Formats time using the specifiers available in date() or in the DateTime
+     * class' format() method.
+     *
+     * To format in languages other than English, use strftime() instead.
+     *
+     * @param string $format
+     *
+     * @return string  Formatted time.
+     */
+    public function format($format)
+    {
+        if (!isset($this->_formatCache[$format])) {
+            $this->_formatCache[$format] = $this->toDateTime()->format($format);
+        }
+        return $this->_formatCache[$format];
+    }
+
+    /**
+     * Formats date and time using strftime() format.
+     *
+     * @return string  strftime() formatted date and time.
+     */
+    public function strftime($format)
+    {
+        if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) {
+            return strftime($format, $this->timestamp());
+        } else {
+            return $this->_strftime($format);
+        }
+    }
+
+    /**
+     * Formats date and time using a limited set of the strftime() format.
+     *
+     * @return string  strftime() formatted date and time.
+     */
+    protected function _strftime($format)
+    {
+        return preg_replace(
+            array('/%b/e',
+                  '/%B/e',
+                  '/%C/e',
+                  '/%d/e',
+                  '/%D/e',
+                  '/%e/e',
+                  '/%H/e',
+                  '/%I/e',
+                  '/%m/e',
+                  '/%M/e',
+                  '/%n/',
+                  '/%p/e',
+                  '/%R/e',
+                  '/%S/e',
+                  '/%t/',
+                  '/%T/e',
+                  '/%x/e',
+                  '/%X/e',
+                  '/%y/e',
+                  '/%Y/',
+                  '/%%/'),
+            array('$this->_strftime(Horde_Nls::getLangInfo(constant(\'ABMON_\' . (int)$this->_month)))',
+                  '$this->_strftime(Horde_Nls::getLangInfo(constant(\'MON_\' . (int)$this->_month)))',
+                  '(int)($this->_year / 100)',
+                  'sprintf(\'%02d\', $this->_mday)',
+                  '$this->_strftime(\'%m/%d/%y\')',
+                  'sprintf(\'%2d\', $this->_mday)',
+                  'sprintf(\'%02d\', $this->_hour)',
+                  'sprintf(\'%02d\', $this->_hour == 0 ? 12 : ($this->_hour > 12 ? $this->_hour - 12 : $this->_hour))',
+                  'sprintf(\'%02d\', $this->_month)',
+                  'sprintf(\'%02d\', $this->_min)',
+                  "\n",
+                  '$this->_strftime(Horde_Nls::getLangInfo($this->_hour < 12 ? AM_STR : PM_STR))',
+                  '$this->_strftime(\'%H:%M\')',
+                  'sprintf(\'%02d\', $this->_sec)',
+                  "\t",
+                  '$this->_strftime(\'%H:%M:%S\')',
+                  '$this->_strftime(Horde_Nls::getLangInfo(D_FMT))',
+                  '$this->_strftime(Horde_Nls::getLangInfo(T_FMT))',
+                  'substr(sprintf(\'%04d\', $this->_year), -2)',
+                  (int)$this->_year,
+                  '%'),
+            $format);
+    }
+
+    /**
+     * Corrects any over- or underflows in any of the date's members.
+     *
+     * @param integer $mask  We may not want to correct some overflows.
+     * @param integer $down  Whether to correct the date up or down.
+     */
+    protected function _correct($mask = self::MASK_ALLPARTS, $down = false)
+    {
+        if ($mask & self::MASK_SECOND) {
+            if ($this->_sec < 0 || $this->_sec > 59) {
+                $mask |= self::MASK_MINUTE;
+
+                $this->_min += (int)($this->_sec / 60);
+                $this->_sec %= 60;
+                if ($this->_sec < 0) {
+                    $this->_min--;
+                    $this->_sec += 60;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_MINUTE) {
+            if ($this->_min < 0 || $this->_min > 59) {
+                $mask |= self::MASK_HOUR;
+
+                $this->_hour += (int)($this->_min / 60);
+                $this->_min %= 60;
+                if ($this->_min < 0) {
+                    $this->_hour--;
+                    $this->_min += 60;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_HOUR) {
+            if ($this->_hour < 0 || $this->_hour > 23) {
+                $mask |= self::MASK_DAY;
+
+                $this->_mday += (int)($this->_hour / 24);
+                $this->_hour %= 24;
+                if ($this->_hour < 0) {
+                    $this->_mday--;
+                    $this->_hour += 24;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_MONTH) {
+            $this->_correctMonth($down);
+            /* When correcting the month, always correct the day too. Months
+             * have different numbers of days. */
+            $mask |= self::MASK_DAY;
+        }
+
+        if ($mask & self::MASK_DAY) {
+            while ($this->_mday > 28 &&
+                   $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) {
+                if ($down) {
+                    $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
+                } else {
+                    $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
+                    $this->_month++;
+                }
+                $this->_correctMonth($down);
+            }
+            while ($this->_mday < 1) {
+                --$this->_month;
+                $this->_correctMonth($down);
+                $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
+            }
+        }
+    }
+
+    /**
+     * Corrects the current month.
+     *
+     * This cannot be done in _correct() because that would also trigger a
+     * correction of the day, which would result in an infinite loop.
+     *
+     * @param integer $down  Whether to correct the date up or down.
+     */
+    protected function _correctMonth($down = false)
+    {
+        $this->_year += (int)($this->_month / 12);
+        $this->_month %= 12;
+        if ($this->_month < 1) {
+            $this->_year--;
+            $this->_month += 12;
+        }
+    }
+
+    /**
+     * Handles args in order: year month day hour min sec tz
+     */
+    protected function _initializeFromArgs($args)
+    {
+        $tz = (isset($args[6])) ? array_pop($args) : null;
+        $this->_initializeTimezone($tz);
+
+        $args = array_slice($args, 0, 6);
+        $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0);
+        $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args);
+        $date = array_merge($keys, $date);
+
+        $this->_initializeFromArray($date);
+    }
+
+    protected function _initializeFromArray($date)
+    {
+        if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) {
+            if ($date['year'] > 70) {
+                $date['year'] += 1900;
+            } else {
+                $date['year'] += 2000;
+            }
+        }
+
+        foreach ($date as $key => $val) {
+            if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
+                $this->{'_'. $key} = (int)$val;
+            }
+        }
+
+        // If $date['day'] is present and numeric we may have been passed
+        // a Horde_Form_datetime array.
+        if (isset($date['day']) &&
+            (string)(int)$date['day'] == $date['day']) {
+            $this->_mday = (int)$date['day'];
+        }
+        // 'minute' key also from Horde_Form_datetime
+        if (isset($date['minute']) &&
+            (string)(int)$date['minute'] == $date['minute']) {
+            $this->_min = (int)$date['minute'];
+        }
+
+        $this->_correct();
+    }
+
+    protected function _initializeFromObject($date)
+    {
+        if ($date instanceof DateTime) {
+            $this->_year  = (int)$date->format('Y');
+            $this->_month = (int)$date->format('m');
+            $this->_mday  = (int)$date->format('d');
+            $this->_hour  = (int)$date->format('H');
+            $this->_min   = (int)$date->format('i');
+            $this->_sec   = (int)$date->format('s');
+            $this->_initializeTimezone($date->getTimezone()->getName());
+        } else {
+            $is_horde_date = $date instanceof Horde_Date;
+            foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) {
+                if ($is_horde_date || isset($date->$key)) {
+                    $this->{'_' . $key} = (int)$date->$key;
+                }
+            }
+            if (!$is_horde_date) {
+                $this->_correct();
+            } else {
+                $this->_initializeTimezone($date->timezone);
+            }
+        }
+    }
+
+    protected function _initializeTimezone($timezone)
+    {
+        if (empty($timezone)) {
+            $timezone = date_default_timezone_get();
+        }
+        $this->_timezone = $timezone;
+    }
+
+}
+
+/**
+ * @category Horde
+ * @package  Date
+ */
+
+/**
+ * Horde Date wrapper/logic class, including some calculation
+ * functions.
+ *
+ * @category Horde
+ * @package  Date
+ */
+class Horde_Date_Utils
+{
+    /**
+     * Returns whether a year is a leap year.
+     *
+     * @param integer $year  The year.
+     *
+     * @return boolean  True if the year is a leap year.
+     */
+    public static function isLeapYear($year)
+    {
+        if (strlen($year) != 4 || preg_match('/\D/', $year)) {
+            return false;
+        }
+
+        return (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0);
+    }
+
+    /**
+     * Returns the date of the year that corresponds to the first day of the
+     * given week.
+     *
+     * @param integer $week  The week of the year to find the first day of.
+     * @param integer $year  The year to calculate for.
+     *
+     * @return Horde_Date  The date of the first day of the given week.
+     */
+    public static function firstDayOfWeek($week, $year)
+    {
+        return new Horde_Date(sprintf('%04dW%02d', $year, $week));
+    }
+
+    /**
+     * Returns the number of days in the specified month.
+     *
+     * @param integer $month  The month
+     * @param integer $year   The year.
+     *
+     * @return integer  The number of days in the month.
+     */
+    public static function daysInMonth($month, $year)
+    {
+        static $cache = array();
+        if (!isset($cache[$year][$month])) {
+            $date = new DateTime(sprintf('%04d-%02d-01', $year, $month));
+            $cache[$year][$month] = $date->format('t');
+        }
+        return $cache[$year][$month];
+    }
+
+    /**
+     * Returns a relative, natural language representation of a timestamp
+     *
+     * @todo Wider range of values ... maybe future time as well?
+     * @todo Support minimum resolution parameter.
+     *
+     * @param mixed $time          The time. Any format accepted by Horde_Date.
+     * @param string $date_format  Format to display date if timestamp is
+     *                             more then 1 day old.
+     * @param string $time_format  Format to display time if timestamp is 1
+     *                             day old.
+     *
+     * @return string  The relative time (i.e. 2 minutes ago)
+     */
+    public static function relativeDateTime($time, $date_format = '%x',
+                                            $time_format = '%X')
+    {
+        $date = new Horde_Date($time);
+
+        $delta = time() - $date->timestamp();
+        if ($delta < 60) {
+            return sprintf(Horde_Date_Translation::ngettext("%d second ago", "%d seconds ago", $delta), $delta);
+        }
+
+        $delta = round($delta / 60);
+        if ($delta < 60) {
+            return sprintf(Horde_Date_Translation::ngettext("%d minute ago", "%d minutes ago", $delta), $delta);
+        }
+
+        $delta = round($delta / 60);
+        if ($delta < 24) {
+            return sprintf(Horde_Date_Translation::ngettext("%d hour ago", "%d hours ago", $delta), $delta);
+        }
+
+        if ($delta > 24 && $delta < 48) {
+            $date = new Horde_Date($time);
+            return sprintf(Horde_Date_Translation::t("yesterday at %s"), $date->strftime($time_format));
+        }
+
+        $delta = round($delta / 24);
+        if ($delta < 7) {
+            return sprintf(Horde_Date_Translation::t("%d days ago"), $delta);
+        }
+
+        if (round($delta / 7) < 5) {
+            $delta = round($delta / 7);
+            return sprintf(Horde_Date_Translation::ngettext("%d week ago", "%d weeks ago", $delta), $delta);
+        }
+
+        // Default to the user specified date format.
+        return $date->strftime($date_format);
+    }
+
+    /**
+     * Tries to convert strftime() formatters to date() formatters.
+     *
+     * Unsupported formatters will be removed.
+     *
+     * @param string $format  A strftime() formatting string.
+     *
+     * @return string  A date() formatting string.
+     */
+    public static function strftime2date($format)
+    {
+        $replace = array(
+            '/%a/'  => 'D',
+            '/%A/'  => 'l',
+            '/%d/'  => 'd',
+            '/%e/'  => 'j',
+            '/%j/'  => 'z',
+            '/%u/'  => 'N',
+            '/%w/'  => 'w',
+            '/%U/'  => '',
+            '/%V/'  => 'W',
+            '/%W/'  => '',
+            '/%b/'  => 'M',
+            '/%B/'  => 'F',
+            '/%h/'  => 'M',
+            '/%m/'  => 'm',
+            '/%C/'  => '',
+            '/%g/'  => '',
+            '/%G/'  => 'o',
+            '/%y/'  => 'y',
+            '/%Y/'  => 'Y',
+            '/%H/'  => 'H',
+            '/%I/'  => 'h',
+            '/%i/'  => 'g',
+            '/%M/'  => 'i',
+            '/%p/'  => 'A',
+            '/%P/'  => 'a',
+            '/%r/'  => 'h:i:s A',
+            '/%R/'  => 'H:i',
+            '/%S/'  => 's',
+            '/%T/'  => 'H:i:s',
+            '/%X/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(T_FMT))',
+            '/%z/'  => 'O',
+            '/%Z/'  => '',
+            '/%c/'  => '',
+            '/%D/'  => 'm/d/y',
+            '/%F/'  => 'Y-m-d',
+            '/%s/'  => 'U',
+            '/%x/e' => 'Horde_Date_Utils::strftime2date(Horde_Nls::getLangInfo(D_FMT))',
+            '/%n/'  => "\n",
+            '/%t/'  => "\t",
+            '/%%/'  => '%'
+        );
+
+        return preg_replace(array_keys($replace), array_values($replace), $format);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/Horde_Date_Recurrence.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1705 @@
+<?php
+
+/**
+ * This is a modified copy of Horde/Date/Recurrence.php
+ * Pull the latest version of this file from the PEAR channel of the Horde
+ * project at http://pear.horde.org by installing the Horde_Date package.
+ */
+
+if (!class_exists('Horde_Date'))
+	require_once(dirname(__FILE__) . '/Horde_Date.php');
+
+// minimal required implementation of Horde_Date_Translation to avoid a huge dependency nightmare
+class Horde_Date_Translation
+{
+	function t($arg) { return $arg; }
+	function ngettext($sing, $plur, $num) { return ($num > 1 ? $plur : $sing); }
+}
+
+
+/**
+ * This file contains the Horde_Date_Recurrence class and according constants.
+ *
+ * Copyright 2007-2012 Horde LLC (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.horde.org/licenses/lgpl21.
+ *
+ * @category Horde
+ * @package  Date
+ */
+
+/**
+ * The Horde_Date_Recurrence class implements algorithms for calculating
+ * recurrences of events, including several recurrence types, intervals,
+ * exceptions, and conversion from and to vCalendar and iCalendar recurrence
+ * rules.
+ *
+ * All methods expecting dates as parameters accept all values that the
+ * Horde_Date constructor accepts, i.e. a timestamp, another Horde_Date
+ * object, an ISO time string or a hash.
+ *
+ * @author   Jan Schneider <jan@horde.org>
+ * @category Horde
+ * @package  Date
+ */
+class Horde_Date_Recurrence
+{
+    /** No Recurrence **/
+    const RECUR_NONE = 0;
+
+    /** Recurs daily. */
+    const RECUR_DAILY = 1;
+
+    /** Recurs weekly. */
+    const RECUR_WEEKLY = 2;
+
+    /** Recurs monthly on the same date. */
+    const RECUR_MONTHLY_DATE = 3;
+
+    /** Recurs monthly on the same week day. */
+    const RECUR_MONTHLY_WEEKDAY = 4;
+
+    /** Recurs yearly on the same date. */
+    const RECUR_YEARLY_DATE = 5;
+
+    /** Recurs yearly on the same day of the year. */
+    const RECUR_YEARLY_DAY = 6;
+
+    /** Recurs yearly on the same week day. */
+    const RECUR_YEARLY_WEEKDAY = 7;
+
+    /**
+     * The start time of the event.
+     *
+     * @var Horde_Date
+     */
+    public $start;
+
+    /**
+     * The end date of the recurrence interval.
+     *
+     * @var Horde_Date
+     */
+    public $recurEnd = null;
+
+    /**
+     * The number of recurrences.
+     *
+     * @var integer
+     */
+    public $recurCount = null;
+
+    /**
+     * The type of recurrence this event follows. RECUR_* constant.
+     *
+     * @var integer
+     */
+    public $recurType = self::RECUR_NONE;
+
+    /**
+     * The length of time between recurrences. The time unit depends on the
+     * recurrence type.
+     *
+     * @var integer
+     */
+    public $recurInterval = 1;
+
+    /**
+     * Any additional recurrence data.
+     *
+     * @var integer
+     */
+    public $recurData = null;
+
+    /**
+     * BYDAY recurrence number
+     *
+     * @var integer
+     */
+    public $recurNthDay = null;
+
+    /**
+     * BYMONTH recurrence data
+     *
+     * @var array
+     */
+    public $recurMonths = array();
+
+    /**
+     * RDATE recurrence values
+     *
+     * @var array
+     */
+    public $rdates = array();
+
+    /**
+     * All the exceptions from recurrence for this event.
+     *
+     * @var array
+     */
+    public $exceptions = array();
+
+    /**
+     * All the dates this recurrence has been marked as completed.
+     *
+     * @var array
+     */
+    public $completions = array();
+
+    /**
+     * Constructor.
+     *
+     * @param Horde_Date $start  Start of the recurring event.
+     */
+    public function __construct($start)
+    {
+        $this->start = new Horde_Date($start);
+    }
+
+    /**
+     * Resets the class properties.
+     */
+    public function reset()
+    {
+        $this->recurEnd = null;
+        $this->recurCount = null;
+        $this->recurType = self::RECUR_NONE;
+        $this->recurInterval = 1;
+        $this->recurData = null;
+        $this->exceptions = array();
+        $this->completions = array();
+    }
+
+    /**
+     * Checks if this event recurs on a given day of the week.
+     *
+     * @param integer $dayMask  A mask consisting of Horde_Date::MASK_*
+     *                          constants specifying the day(s) to check.
+     *
+     * @return boolean  True if this event recurs on the given day(s).
+     */
+    public function recurOnDay($dayMask)
+    {
+        return ($this->recurData & $dayMask);
+    }
+
+    /**
+     * Specifies the days this event recurs on.
+     *
+     * @param integer $dayMask  A mask consisting of Horde_Date::MASK_*
+     *                          constants specifying the day(s) to recur on.
+     */
+    public function setRecurOnDay($dayMask)
+    {
+        $this->recurData = $dayMask;
+    }
+
+    /**
+     *
+     * @param integer $nthDay The nth weekday of month to repeat events on
+     */
+    public function setRecurNthWeekday($nth)
+    {
+        $this->recurNthDay = (int)$nth;
+    }
+
+    /**
+     *
+     * @return integer  The nth weekday of month to repeat events.
+     */
+    public function getRecurNthWeekday()
+    {
+        return isset($this->recurNthDay) ? $this->recurNthDay : ceil($this->start->mday / 7);
+    }
+
+    /**
+     * Specifies the months for yearly (weekday) recurrence
+     *
+     * @param array $months  List of months (integers) this event recurs on.
+     */
+    function setRecurByMonth($months)
+    {
+        $this->recurMonths = (array)$months;
+    }
+
+    /**
+     * Returns a list of months this yearly event recurs on
+     *
+     * @return array List of months (integers) this event recurs on.
+     */
+    function getRecurByMonth()
+    {
+        return $this->recurMonths;
+    }
+
+    /**
+     * Returns the days this event recurs on.
+     *
+     * @return integer  A mask consisting of Horde_Date::MASK_* constants
+     *                  specifying the day(s) this event recurs on.
+     */
+    public function getRecurOnDays()
+    {
+        return $this->recurData;
+    }
+
+    /**
+     * Returns whether this event has a specific recurrence type.
+     *
+     * @param integer $recurrence  RECUR_* constant of the
+     *                             recurrence type to check for.
+     *
+     * @return boolean  True if the event has the specified recurrence type.
+     */
+    public function hasRecurType($recurrence)
+    {
+        return ($recurrence == $this->recurType);
+    }
+
+    /**
+     * Sets a recurrence type for this event.
+     *
+     * @param integer $recurrence  A RECUR_* constant.
+     */
+    public function setRecurType($recurrence)
+    {
+        $this->recurType = $recurrence;
+    }
+
+    /**
+     * Returns recurrence type of this event.
+     *
+     * @return integer  A RECUR_* constant.
+     */
+    public function getRecurType()
+    {
+        return $this->recurType;
+    }
+
+    /**
+     * Returns a description of this event's recurring type.
+     *
+     * @return string  Human readable recurring type.
+     */
+    public function getRecurName()
+    {
+        switch ($this->getRecurType()) {
+        case self::RECUR_NONE: return Horde_Date_Translation::t("No recurrence");
+        case self::RECUR_DAILY: return Horde_Date_Translation::t("Daily");
+        case self::RECUR_WEEKLY: return Horde_Date_Translation::t("Weekly");
+        case self::RECUR_MONTHLY_DATE:
+        case self::RECUR_MONTHLY_WEEKDAY: return Horde_Date_Translation::t("Monthly");
+        case self::RECUR_YEARLY_DATE:
+        case self::RECUR_YEARLY_DAY:
+        case self::RECUR_YEARLY_WEEKDAY: return Horde_Date_Translation::t("Yearly");
+        }
+    }
+
+    /**
+     * Sets the length of time between recurrences of this event.
+     *
+     * @param integer $interval  The time between recurrences.
+     */
+    public function setRecurInterval($interval)
+    {
+        if ($interval > 0) {
+            $this->recurInterval = $interval;
+        }
+    }
+
+    /**
+     * Retrieves the length of time between recurrences of this event.
+     *
+     * @return integer  The number of seconds between recurrences.
+     */
+    public function getRecurInterval()
+    {
+        return $this->recurInterval;
+    }
+
+    /**
+     * Sets the number of recurrences of this event.
+     *
+     * @param integer $count  The number of recurrences.
+     */
+    public function setRecurCount($count)
+    {
+        if ($count > 0) {
+            $this->recurCount = (int)$count;
+            // Recurrence counts and end dates are mutually exclusive.
+            $this->recurEnd = null;
+        } else {
+            $this->recurCount = null;
+        }
+    }
+
+    /**
+     * Retrieves the number of recurrences of this event.
+     *
+     * @return integer  The number recurrences.
+     */
+    public function getRecurCount()
+    {
+        return $this->recurCount;
+    }
+
+    /**
+     * Returns whether this event has a recurrence with a fixed count.
+     *
+     * @return boolean  True if this recurrence has a fixed count.
+     */
+    public function hasRecurCount()
+    {
+        return isset($this->recurCount);
+    }
+
+    /**
+     * Sets the start date of the recurrence interval.
+     *
+     * @param Horde_Date $start  The recurrence start.
+     */
+    public function setRecurStart($start)
+    {
+        $this->start = clone $start;
+    }
+
+    /**
+     * Retrieves the start date of the recurrence interval.
+     *
+     * @return Horde_Date  The recurrence start.
+     */
+    public function getRecurStart()
+    {
+        return $this->start;
+    }
+
+    /**
+     * Sets the end date of the recurrence interval.
+     *
+     * @param Horde_Date $end  The recurrence end.
+     */
+    public function setRecurEnd($end)
+    {
+        if (!empty($end)) {
+            // Recurrence counts and end dates are mutually exclusive.
+            $this->recurCount = null;
+            $this->recurEnd = clone $end;
+        } else {
+            $this->recurEnd = $end;
+        }
+    }
+
+    /**
+     * Retrieves the end date of the recurrence interval.
+     *
+     * @return Horde_Date  The recurrence end.
+     */
+    public function getRecurEnd()
+    {
+        return $this->recurEnd;
+    }
+
+    /**
+     * Returns whether this event has a recurrence end.
+     *
+     * @return boolean  True if this recurrence ends.
+     */
+    public function hasRecurEnd()
+    {
+        return isset($this->recurEnd) && isset($this->recurEnd->year) &&
+            $this->recurEnd->year != 9999;
+    }
+
+    /**
+     * Finds the next recurrence of this event that's after $afterDate.
+     *
+     * @param Horde_Date|string $after  Return events after this date.
+     *
+     * @return Horde_Date|boolean  The date of the next recurrence or false
+     *                             if the event does not recur after
+     *                             $afterDate.
+     */
+    public function nextRecurrence($after)
+    {
+        if (!($after instanceof Horde_Date)) {
+            $after = new Horde_Date($after);
+        } else {
+            $after = clone($after);
+        }
+
+        // Make sure $after and $this->start are in the same TZ
+        $after->setTimezone($this->start->timezone);
+        if ($this->start->compareDateTime($after) >= 0) {
+            return clone $this->start;
+        }
+
+        if ($this->recurInterval == 0 && empty($this->rdates)) {
+            return false;
+        }
+
+        switch ($this->getRecurType()) {
+        case self::RECUR_DAILY:
+            $diff = $this->start->diff($after);
+            $recur = ceil($diff / $this->recurInterval);
+            if ($this->recurCount && $recur >= $this->recurCount) {
+                return false;
+            }
+
+            $recur *= $this->recurInterval;
+            $next = $this->start->add(array('day' => $recur));
+            if ((!$this->hasRecurEnd() ||
+                 $next->compareDateTime($this->recurEnd) <= 0) &&
+                $next->compareDateTime($after) >= 0) {
+                return $next;
+            }
+            break;
+
+        case self::RECUR_WEEKLY:
+            if (empty($this->recurData)) {
+                return false;
+            }
+
+            $start_week = Horde_Date_Utils::firstDayOfWeek($this->start->format('W'),
+                                                           $this->start->year);
+            $start_week->timezone = $this->start->timezone;
+            $start_week->hour = $this->start->hour;
+            $start_week->min  = $this->start->min;
+            $start_week->sec  = $this->start->sec;
+
+            // Make sure we are not at the ISO-8601 first week of year while
+            // still in month 12...OR in the ISO-8601 last week of year while
+            // in month 1 and adjust the year accordingly.
+            $week = $after->format('W');
+            if ($week == 1 && $after->month == 12) {
+                $theYear = $after->year + 1;
+            } elseif ($week >= 52 && $after->month == 1) {
+                $theYear = $after->year - 1;
+            } else {
+                $theYear = $after->year;
+            }
+
+            $after_week = Horde_Date_Utils::firstDayOfWeek($week, $theYear);
+            $after_week->timezone = $this->start->timezone;
+            $after_week_end = clone $after_week;
+            $after_week_end->mday += 7;
+
+            $diff = $start_week->diff($after_week);
+            $interval = $this->recurInterval * 7;
+            $repeats = floor($diff / $interval);
+            if ($diff % $interval < 7) {
+                $recur = $diff;
+            } else {
+                /**
+                 * If the after_week is not in the first week interval the
+                 * search needs to skip ahead a complete interval. The way it is
+                 * calculated here means that an event that occurs every second
+                 * week on Monday and Wednesday with the event actually starting
+                 * on Tuesday or Wednesday will only have one incidence in the
+                 * first week.
+                 */
+                $recur = $interval * ($repeats + 1);
+            }
+
+            if ($this->hasRecurCount()) {
+                $recurrences = 0;
+                /**
+                 * Correct the number of recurrences by the number of events
+                 * that lay between the start of the start week and the
+                 * recurrence start.
+                 */
+                $next = clone $start_week;
+                while ($next->compareDateTime($this->start) < 0) {
+                    if ($this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
+                        $recurrences--;
+                    }
+                    ++$next->mday;
+                }
+                if ($repeats > 0) {
+                    $weekdays = $this->recurData;
+                    $total_recurrences_per_week = 0;
+                    while ($weekdays > 0) {
+                        if ($weekdays % 2) {
+                            $total_recurrences_per_week++;
+                        }
+                        $weekdays = ($weekdays - ($weekdays % 2)) / 2;
+                    }
+                    $recurrences += $total_recurrences_per_week * $repeats;
+                }
+            }
+
+            $next = clone $start_week;
+            $next->mday += $recur;
+            while ($next->compareDateTime($after) < 0 &&
+                   $next->compareDateTime($after_week_end) < 0) {
+                if ($this->hasRecurCount()
+                    && $next->compareDateTime($after) < 0
+                    && $this->recurOnDay((int)pow(2, $next->dayOfWeek()))) {
+                    $recurrences++;
+                }
+                ++$next->mday;
+            }
+            if ($this->hasRecurCount() &&
+                $recurrences >= $this->recurCount) {
+                return false;
+            }
+            if (!$this->hasRecurEnd() ||
+                $next->compareDateTime($this->recurEnd) <= 0) {
+                if ($next->compareDateTime($after_week_end) >= 0) {
+                    return $this->nextRecurrence($after_week_end);
+                }
+                while (!$this->recurOnDay((int)pow(2, $next->dayOfWeek())) &&
+                       $next->compareDateTime($after_week_end) < 0) {
+                    ++$next->mday;
+                }
+                if (!$this->hasRecurEnd() ||
+                    $next->compareDateTime($this->recurEnd) <= 0) {
+                    if ($next->compareDateTime($after_week_end) >= 0) {
+                        return $this->nextRecurrence($after_week_end);
+                    } else {
+                        return $next;
+                    }
+                }
+            }
+            break;
+
+        case self::RECUR_MONTHLY_DATE:
+            $start = clone $this->start;
+            if ($after->compareDateTime($start) < 0) {
+                $after = clone $start;
+            } else {
+                $after = clone $after;
+            }
+
+            // If we're starting past this month's recurrence of the event,
+            // look in the next month on the day the event recurs.
+            if ($after->mday > $start->mday) {
+                ++$after->month;
+                $after->mday = $start->mday;
+            }
+
+            // Adjust $start to be the first match.
+            $offset = ($after->month - $start->month) + ($after->year - $start->year) * 12;
+            $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
+
+            if ($this->recurCount &&
+                ($offset / $this->recurInterval) >= $this->recurCount) {
+                return false;
+            }
+            $start->month += $offset;
+            $count = $offset / $this->recurInterval;
+
+            do {
+                if ($this->recurCount &&
+                    $count++ >= $this->recurCount) {
+                    return false;
+                }
+
+                // Bail if we've gone past the end of recurrence.
+                if ($this->hasRecurEnd() &&
+                    $this->recurEnd->compareDateTime($start) < 0) {
+                    return false;
+                }
+                if ($start->isValid()) {
+                    return $start;
+                }
+
+                // If the interval is 12, and the date isn't valid, then we
+                // need to see if February 29th is an option. If not, then the
+                // event will _never_ recur, and we need to stop checking to
+                // avoid an infinite loop.
+                if ($this->recurInterval == 12 && ($start->month != 2 || $start->mday > 29)) {
+                    return false;
+                }
+
+                // Add the recurrence interval.
+                $start->month += $this->recurInterval;
+            } while (true);
+
+            break;
+
+        case self::RECUR_MONTHLY_WEEKDAY:
+            // Start with the start date of the event.
+            $estart = clone $this->start;
+
+            // What day of the week, and week of the month, do we recur on?
+            if (isset($this->recurNthDay)) {
+                $nth = $this->recurNthDay;
+                $weekday = log($this->recurData, 2);
+            } else {
+                $nth = ceil($this->start->mday / 7);
+                $weekday = $estart->dayOfWeek();
+            }
+
+            // Adjust $estart to be the first candidate.
+            $offset = ($after->month - $estart->month) + ($after->year - $estart->year) * 12;
+            $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
+
+            // Adjust our working date until it's after $after.
+            $estart->month += $offset - $this->recurInterval;
+
+            $count = $offset / $this->recurInterval;
+            do {
+                if ($this->recurCount &&
+                    $count++ >= $this->recurCount) {
+                    return false;
+                }
+
+                $estart->month += $this->recurInterval;
+
+                $next = clone $estart;
+                $next->setNthWeekday($weekday, $nth);
+
+                if ($next->compareDateTime($after) < 0) {
+                    // We haven't made it past $after yet, try again.
+                    continue;
+                }
+                if ($this->hasRecurEnd() &&
+                    $next->compareDateTime($this->recurEnd) > 0) {
+                    // We've gone past the end of recurrence; we can give up
+                    // now.
+                    return false;
+                }
+
+                // We have a candidate to return.
+                break;
+            } while (true);
+
+            return $next;
+
+        case self::RECUR_YEARLY_DATE:
+            // Start with the start date of the event.
+            $estart = clone $this->start;
+            $after = clone $after;
+
+            if ($after->month > $estart->month ||
+                ($after->month == $estart->month && $after->mday > $estart->mday)) {
+                ++$after->year;
+                $after->month = $estart->month;
+                $after->mday = $estart->mday;
+            }
+
+            // Seperate case here for February 29th
+            if ($estart->month == 2 && $estart->mday == 29) {
+                while (!Horde_Date_Utils::isLeapYear($after->year)) {
+                    ++$after->year;
+                }
+            }
+
+            // Adjust $estart to be the first candidate.
+            $offset = $after->year - $estart->year;
+            if ($offset > 0) {
+                $offset = floor(($offset + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
+                $estart->year += $offset;
+            }
+
+            // We've gone past the end of recurrence; give up.
+            if ($this->recurCount &&
+                $offset >= $this->recurCount) {
+                return false;
+            }
+            if ($this->hasRecurEnd() &&
+                $this->recurEnd->compareDateTime($estart) < 0) {
+                return false;
+            }
+
+            return $estart;
+
+        case self::RECUR_YEARLY_DAY:
+            // Check count first.
+            $dayofyear = $this->start->dayOfYear();
+            $count = ($after->year - $this->start->year) / $this->recurInterval + 1;
+            if ($this->recurCount &&
+                ($count > $this->recurCount ||
+                 ($count == $this->recurCount &&
+                  $after->dayOfYear() > $dayofyear))) {
+                return false;
+            }
+
+            // Start with a rough interval.
+            $estart = clone $this->start;
+            $estart->year += floor($count - 1) * $this->recurInterval;
+
+            // Now add the difference to the required day of year.
+            $estart->mday += $dayofyear - $estart->dayOfYear();
+
+            // Add an interval if the estimation was wrong.
+            if ($estart->compareDate($after) < 0) {
+                $estart->year += $this->recurInterval;
+                $estart->mday += $dayofyear - $estart->dayOfYear();
+            }
+
+            // We've gone past the end of recurrence; give up.
+            if ($this->hasRecurEnd() &&
+                $this->recurEnd->compareDateTime($estart) < 0) {
+                return false;
+            }
+
+            return $estart;
+
+        case self::RECUR_YEARLY_WEEKDAY:
+            // Start with the start date of the event.
+            $estart = clone $this->start;
+
+            // What day of the week, and week of the month, do we recur on?
+            if (isset($this->recurNthDay)) {
+                $nth = $this->recurNthDay;
+                $weekday = log($this->recurData, 2);
+            } else {
+                $nth = ceil($this->start->mday / 7);
+                $weekday = $estart->dayOfWeek();
+            }
+
+            // Adjust $estart to be the first candidate.
+            $offset = floor(($after->year - $estart->year + $this->recurInterval - 1) / $this->recurInterval) * $this->recurInterval;
+
+            // Adjust our working date until it's after $after.
+            $estart->year += $offset - $this->recurInterval;
+
+            $count = $offset / $this->recurInterval;
+            do {
+                if ($this->recurCount &&
+                    $count++ >= $this->recurCount) {
+                    return false;
+                }
+
+                $estart->year += $this->recurInterval;
+
+                $next = clone $estart;
+                $next->setNthWeekday($weekday, $nth);
+
+                if ($next->compareDateTime($after) < 0) {
+                    // We haven't made it past $after yet, try again.
+                    continue;
+                }
+                if ($this->hasRecurEnd() &&
+                    $next->compareDateTime($this->recurEnd) > 0) {
+                    // We've gone past the end of recurrence; we can give up
+                    // now.
+                    return false;
+                }
+
+                // We have a candidate to return.
+                break;
+            } while (true);
+
+            return $next;
+        }
+
+        // fall-back to RDATE properties
+        if (!empty($this->rdates)) {
+            $next = clone $this->start;
+            foreach ($this->rdates as $rdate) {
+                $next->year  = $rdate->year;
+                $next->month = $rdate->month;
+                $next->mday  = $rdate->mday;
+                if ($next->compareDateTime($after) > 0) {
+                    return $next;
+                }
+            }
+        }
+
+        // We didn't find anything, the recurType was bad, or something else
+        // went wrong - return false.
+        return false;
+    }
+
+    /**
+     * Returns whether this event has any date that matches the recurrence
+     * rules and is not an exception.
+     *
+     * @return boolean  True if an active recurrence exists.
+     */
+    public function hasActiveRecurrence()
+    {
+        if (!$this->hasRecurEnd()) {
+            return true;
+        }
+
+        $next = $this->nextRecurrence(new Horde_Date($this->start));
+        while (is_object($next)) {
+            if (!$this->hasException($next->year, $next->month, $next->mday) &&
+                !$this->hasCompletion($next->year, $next->month, $next->mday)) {
+                return true;
+            }
+
+            $next = $this->nextRecurrence($next->add(array('day' => 1)));
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the next active recurrence.
+     *
+     * @param Horde_Date $afterDate  Return events after this date.
+     *
+     * @return Horde_Date|boolean The date of the next active
+     *                             recurrence or false if the event
+     *                             has no active recurrence after
+     *                             $afterDate.
+     */
+    public function nextActiveRecurrence($afterDate)
+    {
+        $next = $this->nextRecurrence($afterDate);
+        while (is_object($next)) {
+            if (!$this->hasException($next->year, $next->month, $next->mday) &&
+                !$this->hasCompletion($next->year, $next->month, $next->mday)) {
+                return $next;
+            }
+            $next->mday++;
+            $next = $this->nextRecurrence($next);
+        }
+
+        return false;
+    }
+
+    /**
+     * Adds an absolute recurrence date.
+     *
+     * @param integer $year   The year of the instance.
+     * @param integer $month  The month of the instance.
+     * @param integer $mday   The day of the month of the instance.
+     */
+    public function addRDate($year, $month, $mday)
+    {
+        $this->rdates[] = new Horde_Date($year, $month, $mday);
+    }
+
+    /**
+     * Adds an exception to a recurring event.
+     *
+     * @param integer $year   The year of the execption.
+     * @param integer $month  The month of the execption.
+     * @param integer $mday   The day of the month of the exception.
+     */
+    public function addException($year, $month, $mday)
+    {
+        $this->exceptions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
+    }
+
+    /**
+     * Deletes an exception from a recurring event.
+     *
+     * @param integer $year   The year of the execption.
+     * @param integer $month  The month of the execption.
+     * @param integer $mday   The day of the month of the exception.
+     */
+    public function deleteException($year, $month, $mday)
+    {
+        $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->exceptions);
+        if ($key !== false) {
+            unset($this->exceptions[$key]);
+        }
+    }
+
+    /**
+     * Checks if an exception exists for a given reccurence of an event.
+     *
+     * @param integer $year   The year of the reucrance.
+     * @param integer $month  The month of the reucrance.
+     * @param integer $mday   The day of the month of the reucrance.
+     *
+     * @return boolean  True if an exception exists for the given date.
+     */
+    public function hasException($year, $month, $mday)
+    {
+        return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
+                        $this->getExceptions());
+    }
+
+    /**
+     * Retrieves all the exceptions for this event.
+     *
+     * @return array  Array containing the dates of all the exceptions in
+     *                YYYYMMDD form.
+     */
+    public function getExceptions()
+    {
+        return $this->exceptions;
+    }
+
+    /**
+     * Adds a completion to a recurring event.
+     *
+     * @param integer $year   The year of the execption.
+     * @param integer $month  The month of the execption.
+     * @param integer $mday   The day of the month of the completion.
+     */
+    public function addCompletion($year, $month, $mday)
+    {
+        $this->completions[] = sprintf('%04d%02d%02d', $year, $month, $mday);
+    }
+
+    /**
+     * Deletes a completion from a recurring event.
+     *
+     * @param integer $year   The year of the execption.
+     * @param integer $month  The month of the execption.
+     * @param integer $mday   The day of the month of the completion.
+     */
+    public function deleteCompletion($year, $month, $mday)
+    {
+        $key = array_search(sprintf('%04d%02d%02d', $year, $month, $mday), $this->completions);
+        if ($key !== false) {
+            unset($this->completions[$key]);
+        }
+    }
+
+    /**
+     * Checks if a completion exists for a given reccurence of an event.
+     *
+     * @param integer $year   The year of the reucrance.
+     * @param integer $month  The month of the recurrance.
+     * @param integer $mday   The day of the month of the recurrance.
+     *
+     * @return boolean  True if a completion exists for the given date.
+     */
+    public function hasCompletion($year, $month, $mday)
+    {
+        return in_array(sprintf('%04d%02d%02d', $year, $month, $mday),
+                        $this->getCompletions());
+    }
+
+    /**
+     * Retrieves all the completions for this event.
+     *
+     * @return array  Array containing the dates of all the completions in
+     *                YYYYMMDD form.
+     */
+    public function getCompletions()
+    {
+        return $this->completions;
+    }
+
+    /**
+     * Parses a vCalendar 1.0 recurrence rule.
+     *
+     * @link http://www.imc.org/pdi/vcal-10.txt
+     * @link http://www.shuchow.com/vCalAddendum.html
+     *
+     * @param string $rrule  A vCalendar 1.0 conform RRULE value.
+     */
+    public function fromRRule10($rrule)
+    {
+        $this->reset();
+
+        if (!$rrule) {
+            return;
+        }
+
+        if (!preg_match('/([A-Z]+)(\d+)?(.*)/', $rrule, $matches)) {
+            // No recurrence data - event does not recur.
+            $this->setRecurType(self::RECUR_NONE);
+        }
+
+        // Always default the recurInterval to 1.
+        $this->setRecurInterval(!empty($matches[2]) ? $matches[2] : 1);
+
+        $remainder = trim($matches[3]);
+
+        switch ($matches[1]) {
+        case 'D':
+            $this->setRecurType(self::RECUR_DAILY);
+            break;
+
+        case 'W':
+            $this->setRecurType(self::RECUR_WEEKLY);
+            if (!empty($remainder)) {
+                $mask = 0;
+                while (preg_match('/^ ?[A-Z]{2} ?/', $remainder, $matches)) {
+                    $day = trim($matches[0]);
+                    $remainder = substr($remainder, strlen($matches[0]));
+                    $mask |= $maskdays[$day];
+                }
+                $this->setRecurOnDay($mask);
+            } else {
+                // Recur on the day of the week of the original recurrence.
+                $maskdays = array(
+                    Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
+                    Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
+                    Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
+                    Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
+                    Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
+                    Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
+                    Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY,
+                );
+                $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
+            }
+            break;
+
+        case 'MP':
+            $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
+            break;
+
+        case 'MD':
+            $this->setRecurType(self::RECUR_MONTHLY_DATE);
+            break;
+
+        case 'YM':
+            $this->setRecurType(self::RECUR_YEARLY_DATE);
+            break;
+
+        case 'YD':
+            $this->setRecurType(self::RECUR_YEARLY_DAY);
+            break;
+        }
+
+        // We don't support modifiers at the moment, strip them.
+        while ($remainder && !preg_match('/^(#\d+|\d{8})($| |T\d{6})/', $remainder)) {
+               $remainder = substr($remainder, 1);
+        }
+        if (!empty($remainder)) {
+            if (strpos($remainder, '#') === 0) {
+                $this->setRecurCount(substr($remainder, 1));
+            } else {
+                list($year, $month, $mday) = sscanf($remainder, '%04d%02d%02d');
+                $this->setRecurEnd(new Horde_Date(array('year' => $year,
+                                                        'month' => $month,
+                                                        'mday' => $mday,
+                                                        'hour' => 23,
+                                                        'min' => 59,
+                                                        'sec' => 59)));
+            }
+        }
+    }
+
+    /**
+     * Creates a vCalendar 1.0 recurrence rule.
+     *
+     * @link http://www.imc.org/pdi/vcal-10.txt
+     * @link http://www.shuchow.com/vCalAddendum.html
+     *
+     * @param Horde_Icalendar $calendar  A Horde_Icalendar object instance.
+     *
+     * @return string  A vCalendar 1.0 conform RRULE value.
+     */
+    public function toRRule10($calendar)
+    {
+        switch ($this->recurType) {
+        case self::RECUR_NONE:
+            return '';
+
+        case self::RECUR_DAILY:
+            $rrule = 'D' . $this->recurInterval;
+            break;
+
+        case self::RECUR_WEEKLY:
+            $rrule = 'W' . $this->recurInterval;
+            $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+
+            for ($i = 0; $i <= 7; ++$i) {
+                if ($this->recurOnDay(pow(2, $i))) {
+                    $rrule .= ' ' . $vcaldays[$i];
+                }
+            }
+            break;
+
+        case self::RECUR_MONTHLY_DATE:
+            $rrule = 'MD' . $this->recurInterval . ' ' . trim($this->start->mday);
+            break;
+
+        case self::RECUR_MONTHLY_WEEKDAY:
+            $nth_weekday = (int)($this->start->mday / 7);
+            if (($this->start->mday % 7) > 0) {
+                $nth_weekday++;
+            }
+
+            $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+            $rrule = 'MP' . $this->recurInterval . ' ' . $nth_weekday . '+ ' . $vcaldays[$this->start->dayOfWeek()];
+
+            break;
+
+        case self::RECUR_YEARLY_DATE:
+            $rrule = 'YM' . $this->recurInterval . ' ' . trim($this->start->month);
+            break;
+
+        case self::RECUR_YEARLY_DAY:
+            $rrule = 'YD' . $this->recurInterval . ' ' . $this->start->dayOfYear();
+            break;
+
+        default:
+            return '';
+        }
+
+        if ($this->hasRecurEnd()) {
+            $recurEnd = clone $this->recurEnd;
+            return $rrule . ' ' . $calendar->_exportDateTime($recurEnd);
+        }
+
+        return $rrule . ' #' . (int)$this->getRecurCount();
+    }
+
+    /**
+     * Parses an iCalendar 2.0 recurrence rule.
+     *
+     * @link http://rfc.net/rfc2445.html#s4.3.10
+     * @link http://rfc.net/rfc2445.html#s4.8.5
+     * @link http://www.shuchow.com/vCalAddendum.html
+     *
+     * @param string $rrule  An iCalendar 2.0 conform RRULE value.
+     */
+    public function fromRRule20($rrule)
+    {
+        $this->reset();
+
+        // Parse the recurrence rule into keys and values.
+        $rdata = array();
+        $parts = explode(';', $rrule);
+        foreach ($parts as $part) {
+            list($key, $value) = explode('=', $part, 2);
+            $rdata[strtoupper($key)] = $value;
+        }
+
+        if (isset($rdata['FREQ'])) {
+            // Always default the recurInterval to 1.
+            $this->setRecurInterval(isset($rdata['INTERVAL']) ? $rdata['INTERVAL'] : 1);
+
+            $maskdays = array(
+                'SU' => Horde_Date::MASK_SUNDAY,
+                'MO' => Horde_Date::MASK_MONDAY,
+                'TU' => Horde_Date::MASK_TUESDAY,
+                'WE' => Horde_Date::MASK_WEDNESDAY,
+                'TH' => Horde_Date::MASK_THURSDAY,
+                'FR' => Horde_Date::MASK_FRIDAY,
+                'SA' => Horde_Date::MASK_SATURDAY,
+            );
+
+            switch (strtoupper($rdata['FREQ'])) {
+            case 'DAILY':
+                $this->setRecurType(self::RECUR_DAILY);
+                break;
+
+            case 'WEEKLY':
+                $this->setRecurType(self::RECUR_WEEKLY);
+                if (isset($rdata['BYDAY'])) {
+                    $days = explode(',', $rdata['BYDAY']);
+                    $mask = 0;
+                    foreach ($days as $day) {
+                        $mask |= $maskdays[$day];
+                    }
+                    $this->setRecurOnDay($mask);
+                } else {
+                    // Recur on the day of the week of the original
+                    // recurrence.
+                    $maskdays = array(
+                        Horde_Date::DATE_SUNDAY => Horde_Date::MASK_SUNDAY,
+                        Horde_Date::DATE_MONDAY => Horde_Date::MASK_MONDAY,
+                        Horde_Date::DATE_TUESDAY => Horde_Date::MASK_TUESDAY,
+                        Horde_Date::DATE_WEDNESDAY => Horde_Date::MASK_WEDNESDAY,
+                        Horde_Date::DATE_THURSDAY => Horde_Date::MASK_THURSDAY,
+                        Horde_Date::DATE_FRIDAY => Horde_Date::MASK_FRIDAY,
+                        Horde_Date::DATE_SATURDAY => Horde_Date::MASK_SATURDAY);
+                    $this->setRecurOnDay($maskdays[$this->start->dayOfWeek()]);
+                }
+                break;
+
+            case 'MONTHLY':
+                if (isset($rdata['BYDAY'])) {
+                    $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
+                    if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
+                        $this->setRecurOnDay($maskdays[$m[2]]);
+                        $this->setRecurNthWeekday($m[1]);
+                    }
+                } else {
+                    $this->setRecurType(self::RECUR_MONTHLY_DATE);
+                }
+                break;
+
+            case 'YEARLY':
+                if (isset($rdata['BYYEARDAY'])) {
+                    $this->setRecurType(self::RECUR_YEARLY_DAY);
+                } elseif (isset($rdata['BYDAY'])) {
+                    $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
+                    if (preg_match('/(-?[1-4])([A-Z]+)/', $rdata['BYDAY'], $m)) {
+                        $this->setRecurOnDay($maskdays[$m[2]]);
+                        $this->setRecurNthWeekday($m[1]);
+                    }
+                    if ($rdata['BYMONTH']) {
+                        $months = explode(',', $rdata['BYMONTH']);
+                        $this->setRecurByMonth($months);
+                    }
+                } else {
+                    $this->setRecurType(self::RECUR_YEARLY_DATE);
+                }
+                break;
+            }
+
+            if (isset($rdata['UNTIL'])) {
+                list($year, $month, $mday) = sscanf($rdata['UNTIL'],
+                                                    '%04d%02d%02d');
+                $this->setRecurEnd(new Horde_Date(array('year' => $year,
+                                                        'month' => $month,
+                                                        'mday' => $mday,
+                                                        'hour' => 23,
+                                                        'min' => 59,
+                                                        'sec' => 59)));
+            }
+            if (isset($rdata['COUNT'])) {
+                $this->setRecurCount($rdata['COUNT']);
+            }
+        } else {
+            // No recurrence data - event does not recur.
+            $this->setRecurType(self::RECUR_NONE);
+        }
+    }
+
+    /**
+     * Creates an iCalendar 2.0 recurrence rule.
+     *
+     * @link http://rfc.net/rfc2445.html#s4.3.10
+     * @link http://rfc.net/rfc2445.html#s4.8.5
+     * @link http://www.shuchow.com/vCalAddendum.html
+     *
+     * @param Horde_Icalendar $calendar  A Horde_Icalendar object instance.
+     *
+     * @return string  An iCalendar 2.0 conform RRULE value.
+     */
+    public function toRRule20($calendar)
+    {
+        switch ($this->recurType) {
+        case self::RECUR_NONE:
+            return '';
+
+        case self::RECUR_DAILY:
+            $rrule = 'FREQ=DAILY;INTERVAL='  . $this->recurInterval;
+            break;
+
+        case self::RECUR_WEEKLY:
+            $rrule = 'FREQ=WEEKLY;INTERVAL=' . $this->recurInterval . ';BYDAY=';
+            $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+
+            for ($i = $flag = 0; $i <= 7; ++$i) {
+                if ($this->recurOnDay(pow(2, $i))) {
+                    if ($flag) {
+                        $rrule .= ',';
+                    }
+                    $rrule .= $vcaldays[$i];
+                    $flag = true;
+                }
+            }
+            break;
+
+        case self::RECUR_MONTHLY_DATE:
+            $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval;
+            break;
+
+        case self::RECUR_MONTHLY_WEEKDAY:
+            if (isset($this->recurNthDay)) {
+                $nth_weekday = $this->recurNthDay;
+                $day_of_week = log($this->recurData, 2);
+            } else {
+                $day_of_week = $this->start->dayOfWeek();
+                $nth_weekday = (int)($this->start->mday / 7);
+                if (($this->start->mday % 7) > 0) {
+                    $nth_weekday++;
+                }
+            }
+            $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+            $rrule = 'FREQ=MONTHLY;INTERVAL=' . $this->recurInterval
+                . ';BYDAY=' . $nth_weekday . $vcaldays[$day_of_week];
+            break;
+
+        case self::RECUR_YEARLY_DATE:
+            $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval;
+            break;
+
+        case self::RECUR_YEARLY_DAY:
+            $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
+                . ';BYYEARDAY=' . $this->start->dayOfYear();
+            break;
+
+        case self::RECUR_YEARLY_WEEKDAY:
+            if (isset($this->recurNthDay)) {
+                $nth_weekday = $this->recurNthDay;
+                $day_of_week = log($this->recurData, 2);
+            } else {
+                $day_of_week = $this->start->dayOfWeek();
+                $nth_weekday = (int)($this->start->mday / 7);
+                if (($this->start->mday % 7) > 0) {
+                    $nth_weekday++;
+                }
+             }
+            $months = !empty($this->recurMonths) ? join(',', $this->recurMonths) : $this->start->month;
+            $vcaldays = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA');
+            $rrule = 'FREQ=YEARLY;INTERVAL=' . $this->recurInterval
+                . ';BYDAY='
+                . $nth_weekday
+                . $vcaldays[$day_of_week]
+                . ';BYMONTH=' . $this->start->month;
+            break;
+        }
+
+        if ($this->hasRecurEnd()) {
+            $recurEnd = clone $this->recurEnd;
+            $rrule .= ';UNTIL=' . $calendar->_exportDateTime($recurEnd);
+        }
+        if ($count = $this->getRecurCount()) {
+            $rrule .= ';COUNT=' . $count;
+        }
+        return $rrule;
+    }
+
+    /**
+     * Parses the recurrence data from a hash.
+     *
+     * @param array $hash  The hash to convert.
+     *
+     * @return boolean  True if the hash seemed valid, false otherwise.
+     */
+    public function fromHash($hash)
+    {
+        $this->reset();
+
+        if (!isset($hash['interval']) || !isset($hash['cycle'])) {
+            $this->setRecurType(self::RECUR_NONE);
+            return false;
+        }
+
+        $this->setRecurInterval((int)$hash['interval']);
+
+        $month2number = array(
+            'january'   => 1,
+            'february'  => 2,
+            'march'     => 3,
+            'april'     => 4,
+            'may'       => 5,
+            'june'      => 6,
+            'july'      => 7,
+            'august'    => 8,
+            'september' => 9,
+            'october'   => 10,
+            'november'  => 11,
+            'december'  => 12,
+        );
+
+        $parse_day = false;
+        $set_daymask = false;
+        $update_month = false;
+        $update_daynumber = false;
+        $update_weekday = false;
+        $nth_weekday = -1;
+
+        switch ($hash['cycle']) {
+        case 'daily':
+            $this->setRecurType(self::RECUR_DAILY);
+            break;
+
+        case 'weekly':
+            $this->setRecurType(self::RECUR_WEEKLY);
+            $parse_day = true;
+            $set_daymask = true;
+            break;
+
+        case 'monthly':
+            if (!isset($hash['daynumber'])) {
+                $this->setRecurType(self::RECUR_NONE);
+                return false;
+            }
+
+            switch ($hash['type']) {
+            case 'daynumber':
+                $this->setRecurType(self::RECUR_MONTHLY_DATE);
+                $update_daynumber = true;
+                break;
+
+            case 'weekday':
+                $this->setRecurType(self::RECUR_MONTHLY_WEEKDAY);
+                $this->setRecurNthWeekday($hash['daynumber']);
+                $parse_day = true;
+                $set_daymask = true;
+                break;
+            }
+            break;
+
+        case 'yearly':
+            if (!isset($hash['type'])) {
+                $this->setRecurType(self::RECUR_NONE);
+                return false;
+            }
+
+            switch ($hash['type']) {
+            case 'monthday':
+                $this->setRecurType(self::RECUR_YEARLY_DATE);
+                $update_month = true;
+                $update_daynumber = true;
+                break;
+
+            case 'yearday':
+                if (!isset($hash['month'])) {
+                    $this->setRecurType(self::RECUR_NONE);
+                    return false;
+                }
+
+                $this->setRecurType(self::RECUR_YEARLY_DAY);
+                // Start counting days in January.
+                $hash['month'] = 'january';
+                $update_month = true;
+                $update_daynumber = true;
+                break;
+
+            case 'weekday':
+                if (!isset($hash['daynumber'])) {
+                    $this->setRecurType(self::RECUR_NONE);
+                    return false;
+                }
+
+                $this->setRecurType(self::RECUR_YEARLY_WEEKDAY);
+                $this->setRecurNthWeekday($hash['daynumber']);
+                $parse_day = true;
+                $set_daymask = true;
+
+                if ($hash['month'] && isset($month2number[$hash['month']])) {
+                    $this->setRecurByMonth($month2number[$hash['month']]);
+                }
+                break;
+            }
+        }
+
+        if (isset($hash['range-type']) && isset($hash['range'])) {
+            switch ($hash['range-type']) {
+            case 'number':
+                $this->setRecurCount((int)$hash['range']);
+                break;
+
+            case 'date':
+                $recur_end = new Horde_Date($hash['range']);
+                $recur_end->hour = 23;
+                $recur_end->min = 59;
+                $recur_end->sec = 59;
+                $this->setRecurEnd($recur_end);
+                break;
+            }
+        }
+
+        // Need to parse <day>?
+        $last_found_day = -1;
+        if ($parse_day) {
+            if (!isset($hash['day'])) {
+                $this->setRecurType(self::RECUR_NONE);
+                return false;
+            }
+
+            $mask = 0;
+            $bits = array(
+                'monday' => Horde_Date::MASK_MONDAY,
+                'tuesday' => Horde_Date::MASK_TUESDAY,
+                'wednesday' => Horde_Date::MASK_WEDNESDAY,
+                'thursday' => Horde_Date::MASK_THURSDAY,
+                'friday' => Horde_Date::MASK_FRIDAY,
+                'saturday' => Horde_Date::MASK_SATURDAY,
+                'sunday' => Horde_Date::MASK_SUNDAY,
+            );
+            $days = array(
+                'monday' => Horde_Date::DATE_MONDAY,
+                'tuesday' => Horde_Date::DATE_TUESDAY,
+                'wednesday' => Horde_Date::DATE_WEDNESDAY,
+                'thursday' => Horde_Date::DATE_THURSDAY,
+                'friday' => Horde_Date::DATE_FRIDAY,
+                'saturday' => Horde_Date::DATE_SATURDAY,
+                'sunday' => Horde_Date::DATE_SUNDAY,
+            );
+
+            foreach ($hash['day'] as $day) {
+                // Validity check.
+                if (empty($day) || !isset($bits[$day])) {
+                    continue;
+                }
+
+                $mask |= $bits[$day];
+                $last_found_day = $days[$day];
+            }
+
+            if ($set_daymask) {
+                $this->setRecurOnDay($mask);
+            }
+        }
+
+        if ($update_month || $update_daynumber || $update_weekday) {
+            if ($update_month) {
+                if (isset($month2number[$hash['month']])) {
+                    $this->start->month = $month2number[$hash['month']];
+                }
+            }
+
+            if ($update_daynumber) {
+                if (!isset($hash['daynumber'])) {
+                    $this->setRecurType(self::RECUR_NONE);
+                    return false;
+                }
+
+                $this->start->mday = $hash['daynumber'];
+            }
+
+            if ($update_weekday) {
+                $this->setNthWeekday($nth_weekday);
+            }
+        }
+
+        // Exceptions.
+        if (isset($hash['exceptions'])) {
+            $this->exceptions = $hash['exceptions'];
+        }
+
+        if (isset($hash['completions'])) {
+            $this->completions = $hash['completions'];
+        }
+
+        return true;
+    }
+
+    /**
+     * Export this object into a hash.
+     *
+     * @return array  The recurrence hash.
+     */
+    public function toHash()
+    {
+        if ($this->getRecurType() == self::RECUR_NONE) {
+            return array();
+        }
+
+        $day2number = array(
+            0 => 'sunday',
+            1 => 'monday',
+            2 => 'tuesday',
+            3 => 'wednesday',
+            4 => 'thursday',
+            5 => 'friday',
+            6 => 'saturday'
+        );
+        $month2number = array(
+            1 => 'january',
+            2 => 'february',
+            3 => 'march',
+            4 => 'april',
+            5 => 'may',
+            6 => 'june',
+            7 => 'july',
+            8 => 'august',
+            9 => 'september',
+            10 => 'october',
+            11 => 'november',
+            12 => 'december'
+        );
+
+        $hash = array('interval' => $this->getRecurInterval());
+        $start = $this->getRecurStart();
+
+        switch ($this->getRecurType()) {
+        case self::RECUR_DAILY:
+            $hash['cycle'] = 'daily';
+            break;
+
+        case self::RECUR_WEEKLY:
+            $hash['cycle'] = 'weekly';
+            $bits = array(
+                'monday' => Horde_Date::MASK_MONDAY,
+                'tuesday' => Horde_Date::MASK_TUESDAY,
+                'wednesday' => Horde_Date::MASK_WEDNESDAY,
+                'thursday' => Horde_Date::MASK_THURSDAY,
+                'friday' => Horde_Date::MASK_FRIDAY,
+                'saturday' => Horde_Date::MASK_SATURDAY,
+                'sunday' => Horde_Date::MASK_SUNDAY,
+            );
+            $days = array();
+            foreach ($bits as $name => $bit) {
+                if ($this->recurOnDay($bit)) {
+                    $days[] = $name;
+                }
+            }
+            $hash['day'] = $days;
+            break;
+
+        case self::RECUR_MONTHLY_DATE:
+            $hash['cycle'] = 'monthly';
+            $hash['type'] = 'daynumber';
+            $hash['daynumber'] = $start->mday;
+            break;
+
+        case self::RECUR_MONTHLY_WEEKDAY:
+            $hash['cycle'] = 'monthly';
+            $hash['type'] = 'weekday';
+            $hash['daynumber'] = $start->weekOfMonth();
+            $hash['day'] = array ($day2number[$start->dayOfWeek()]);
+            break;
+
+        case self::RECUR_YEARLY_DATE:
+            $hash['cycle'] = 'yearly';
+            $hash['type'] = 'monthday';
+            $hash['daynumber'] = $start->mday;
+            $hash['month'] = $month2number[$start->month];
+            break;
+
+        case self::RECUR_YEARLY_DAY:
+            $hash['cycle'] = 'yearly';
+            $hash['type'] = 'yearday';
+            $hash['daynumber'] = $start->dayOfYear();
+            break;
+
+        case self::RECUR_YEARLY_WEEKDAY:
+            $hash['cycle'] = 'yearly';
+            $hash['type'] = 'weekday';
+            $hash['daynumber'] = $start->weekOfMonth();
+            $hash['day'] = array ($day2number[$start->dayOfWeek()]);
+            $hash['month'] = $month2number[$start->month];
+        }
+
+        if ($this->hasRecurCount()) {
+            $hash['range-type'] = 'number';
+            $hash['range'] = $this->getRecurCount();
+        } elseif ($this->hasRecurEnd()) {
+            $date = $this->getRecurEnd();
+            $hash['range-type'] = 'date';
+            $hash['range'] = $date->datestamp();
+        } else {
+            $hash['range-type'] = 'none';
+            $hash['range'] = '';
+        }
+
+        // Recurrence exceptions
+        $hash['exceptions'] = $this->exceptions;
+        $hash['completions'] = $this->completions;
+
+        return $hash;
+    }
+
+    /**
+     * Returns a simple object suitable for json transport representing this
+     * object.
+     *
+     * Possible properties are:
+     * - t: type
+     * - i: interval
+     * - e: end date
+     * - c: count
+     * - d: data
+     * - co: completions
+     * - ex: exceptions
+     *
+     * @return object  A simple object.
+     */
+    public function toJson()
+    {
+        $json = new stdClass;
+        $json->t = $this->recurType;
+        $json->i = $this->recurInterval;
+        if ($this->hasRecurEnd()) {
+            $json->e = $this->recurEnd->toJson();
+        }
+        if ($this->recurCount) {
+            $json->c = $this->recurCount;
+        }
+        if ($this->recurData) {
+            $json->d = $this->recurData;
+        }
+        if ($this->completions) {
+            $json->co = $this->completions;
+        }
+        if ($this->exceptions) {
+            $json->ex = $this->exceptions;
+        }
+        return $json;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/calendar_itip.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,240 @@
+<?php
+
+require_once realpath(__DIR__ . '/../../libcalendaring/lib/libcalendaring_itip.php');
+
+/**
+ * iTIP functions for the Calendar plugin
+ *
+ * Class providing functionality to manage iTIP invitations
+ *
+ * @version @package_version@
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ * @package @package_name@
+ *
+ * Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+class calendar_itip extends libcalendaring_itip
+{
+  /**
+   * Constructor to set text domain to calendar
+   */
+  function __construct($plugin, $domain = 'calendar')
+  {
+    parent::__construct($plugin, $domain);
+
+    $this->db_itipinvitations = $this->rc->db->table_name('itipinvitations', true);
+  }
+
+  /**
+   * Handler for calendar/itip-status requests
+   */
+  public function get_itip_status($event, $existing = null)
+  {
+    $status = parent::get_itip_status($event, $existing);
+
+    // don't ask for deleting events when declining
+    if ($this->rc->config->get('kolab_invitation_calendars'))
+      $status['saved'] = false;
+
+    return $status;
+  }
+
+  /**
+   * Find invitation record by token
+   *
+   * @param string Invitation token
+   * @return mixed Invitation record as hash array or False if not found
+   */
+  public function get_invitation($token)
+  {
+    if ($parts = $this->decode_token($token)) {
+      $result = $this->rc->db->query("SELECT * FROM $this->db_itipinvitations WHERE `token` = ?", $parts['base']);
+      if ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
+        $rec['event'] = unserialize($rec['event']);
+        $rec['attendee'] = $parts['attendee'];
+        return $rec;
+      }
+    }
+    
+    return false;
+  }
+
+  /**
+   * Update the attendee status of the given invitation record
+   *
+   * @param array Invitation record as fetched with calendar_itip::get_invitation()
+   * @param string Attendee email address
+   * @param string New attendee status
+   */
+  public function update_invitation($invitation, $email, $newstatus)
+  {
+    if (is_string($invitation))
+      $invitation = $this->get_invitation($invitation);
+    
+    if ($invitation['token'] && $invitation['event']) {
+      // update attendee record in event data
+      foreach ($invitation['event']['attendees'] as $i => $attendee) {
+        if ($attendee['role'] == 'ORGANIZER') {
+          $organizer = $attendee;
+        }
+        else if ($attendee['email'] == $email) {
+          // nothing to be done here
+          if ($attendee['status'] == $newstatus)
+            return true;
+          
+          $invitation['event']['attendees'][$i]['status'] = $newstatus;
+          $this->sender = $attendee;
+        }
+      }
+      $invitation['event']['changed'] = new DateTime();
+      
+      // send iTIP REPLY message to organizer
+      if ($organizer) {
+        $status = strtolower($newstatus);
+        if ($this->send_itip_message($invitation['event'], 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
+          $this->rc->output->command('display_message', $this->plugin->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
+        else
+          $this->rc->output->command('display_message', $this->plugin->gettext('itipresponseerror'), 'error');
+      }
+      
+      // update record in DB
+      $query = $this->rc->db->query(
+        "UPDATE $this->db_itipinvitations
+         SET `event` = ?
+         WHERE `token` = ?",
+        self::serialize_event($invitation['event']),
+        $invitation['token']
+      );
+
+      if ($this->rc->db->affected_rows($query))
+        return true;
+    }
+    
+    return false;
+  }
+
+
+  /**
+   * Create iTIP invitation token for later replies via URL
+   *
+   * @param array Hash array with event properties
+   * @param string Attendee email address
+   * @return string Invitation token
+   */
+  public function store_invitation($event, $attendee)
+  {
+    static $stored = array();
+    
+    if (!$event['uid'] || !$attendee)
+      return false;
+      
+    // generate token for this invitation
+    $token = $this->generate_token($event, $attendee);
+    $base = substr($token, 0, 40);
+    
+    // already stored this
+    if ($stored[$base])
+      return $token;
+
+    // delete old entry
+    $this->rc->db->query("DELETE FROM $this->db_itipinvitations WHERE `token` = ?", $base);
+
+    $event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : '');
+
+    $query = $this->rc->db->query(
+      "INSERT INTO $this->db_itipinvitations
+       (`token`, `event_uid`, `user_id`, `event`, `expires`)
+       VALUES(?, ?, ?, ?, ?)",
+      $base,
+      $event_uid,
+      $this->rc->user->ID,
+      self::serialize_event($event),
+      date('Y-m-d H:i:s', $event['end']->format('U') + 86400 * 2)
+    );
+    
+    if ($this->rc->db->affected_rows($query)) {
+      $stored[$base] = 1;
+      return $token;
+    }
+    
+    return false;
+  }
+
+  /**
+   * Mark invitations for the given event as cancelled
+   *
+   * @param array Hash array with event properties
+   */
+  public function cancel_itip_invitation($event)
+  {
+    $event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : '');
+
+    // flag invitation record as cancelled
+    $this->rc->db->query(
+      "UPDATE $this->db_itipinvitations
+       SET `cancelled` = 1
+       WHERE `event_uid` = ? AND `user_id` = ?",
+       $event_uid,
+       $this->rc->user->ID
+    );
+  }
+
+  /**
+   * Generate an invitation request token for the given event and attendee
+   *
+   * @param array Event hash array
+   * @param string Attendee email address
+   */
+  public function generate_token($event, $attendee)
+  {
+    $event_uid = $event['uid'] . ($event['_instance'] ? '-' . $event['_instance'] : '');
+    $base = sha1($event_uid . ';' . $this->rc->user->ID);
+    $mail = base64_encode($attendee);
+    $hash = substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6);
+    
+    return "$base.$mail.$hash";
+  }
+
+  /**
+   * Decode the given iTIP request token and return its parts
+   *
+   * @param string Request token to decode
+   * @return mixed Hash array with parts or False if invalid
+   */
+  public function decode_token($token)
+  {
+    list($base, $mail, $hash) = explode('.', $token);
+    
+    // validate and return parts
+    if ($mail && $hash && $hash == substr(md5($base . $mail . $this->rc->config->get('des_key')), 0, 6)) {
+      return array('base' => $base, 'attendee' => base64_decode($mail));
+    }
+    
+    return false;
+  }
+
+  /**
+   * Helper method to serialize the given event for storing in invitations table
+   */
+  private static function serialize_event($event)
+  {
+    $ev = $event;
+    $ev['description'] = abbreviate_string($ev['description'], 100);
+    unset($ev['attachments']);
+    return serialize($ev);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/calendar_recurrence.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,88 @@
+<?php
+
+require_once realpath(__DIR__ . '/../../libcalendaring/lib/libcalendaring_recurrence.php');
+
+/**
+ * Recurrence computation class for the Calendar plugin
+ *
+ * Uitility class to compute instances of recurring events.
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+class calendar_recurrence extends libcalendaring_recurrence
+{
+  private $event;
+  private $duration;
+
+  /**
+   * Default constructor
+   *
+   * @param object calendar The calendar plugin instance
+   * @param array The event object to operate on
+   */
+  function __construct($cal, $event)
+  {
+    parent::__construct($cal->lib);
+
+    $this->event = $event;
+
+    if (is_object($event['start']) && is_object($event['end']))
+      $this->duration = $event['start']->diff($event['end']);
+
+    $event['start']->_dateonly |= $event['allday'];
+    $this->init($event['recurrence'], $event['start']);
+  }
+
+  /**
+   * Alias of libcalendaring_recurrence::next()
+   *
+   * @return mixed DateTime object or False if recurrence ended
+   */
+  public function next_start()
+  {
+    return $this->next();
+  }
+
+  /**
+   * Get the next recurring instance of this event
+   *
+   * @return mixed Array with event properties or False if recurrence ended
+   */
+  public function next_instance()
+  {
+    if ($next_start = $this->next()) {
+      $next = $this->event;
+      $next['start'] = $next_start;
+
+      if ($this->duration) {
+        $next['end'] = clone $next_start;
+        $next['end']->add($this->duration);
+      }
+
+      $next['recurrence_date'] = clone $next_start;
+      $next['_instance'] = libcalendaring::recurrence_instance_identifier($next, $this->event['allday']);
+
+      unset($next['_formatobj']);
+
+      return $next;
+    }
+
+    return false;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/calendar_ui.php	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,883 @@
+<?php
+/**
+ * User Interface class for the Calendar plugin
+ *
+ * @author Lazlo Westerhof <hello@lazlo.me>
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me>
+ * Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ */
+
+
+class calendar_ui
+{
+  private $rc;
+  private $cal;
+  private $ready = false;
+  public $screen;
+
+  function __construct($cal)
+  {
+    $this->cal = $cal;
+    $this->rc = $cal->rc;
+    $this->screen = $this->rc->task == 'calendar' ? ($this->rc->action ? $this->rc->action: 'calendar') : 'other';
+  }
+    
+  /**
+   * Calendar UI initialization and requests handlers
+   */
+  public function init()
+  {
+    if ($this->ready)  // already done
+      return;
+      
+    // add taskbar button
+    $this->cal->add_button(array(
+      'command'    => 'calendar',
+      'class'      => 'button-calendar',
+      'classsel'   => 'button-calendar button-selected',
+      'innerclass' => 'button-inner',
+      'label'      => 'calendar.calendar',
+      'type'       => 'link'
+      ), 'taskbar');
+    
+    // load basic client script
+    $this->cal->include_script('calendar_base.js');
+    
+    $skin_path = $this->cal->local_skin_path();
+    $this->cal->include_stylesheet($skin_path . '/calendar.css');
+    
+    $this->ready = true;
+  }
+
+  /**
+   * Register handler methods for the template engine
+   */
+  public function init_templates()
+  {
+    $this->cal->register_handler('plugin.calendar_css', array($this, 'calendar_css'));
+    $this->cal->register_handler('plugin.calendar_list', array($this, 'calendar_list'));
+    $this->cal->register_handler('plugin.calendar_select', array($this, 'calendar_select'));
+    $this->cal->register_handler('plugin.identity_select', array($this, 'identity_select'));
+    $this->cal->register_handler('plugin.category_select', array($this, 'category_select'));
+    $this->cal->register_handler('plugin.status_select', array($this, 'status_select'));
+    $this->cal->register_handler('plugin.freebusy_select', array($this, 'freebusy_select'));
+    $this->cal->register_handler('plugin.priority_select', array($this, 'priority_select'));
+    $this->cal->register_handler('plugin.sensitivity_select', array($this, 'sensitivity_select'));
+    $this->cal->register_handler('plugin.alarm_select', array($this, 'alarm_select'));
+    $this->cal->register_handler('plugin.recurrence_form', array($this->cal->lib, 'recurrence_form'));
+    $this->cal->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
+    $this->cal->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
+    $this->cal->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
+    $this->cal->register_handler('plugin.attendees_list', array($this, 'attendees_list'));
+    $this->cal->register_handler('plugin.attendees_form', array($this, 'attendees_form'));
+    $this->cal->register_handler('plugin.resources_form', array($this, 'resources_form'));
+    $this->cal->register_handler('plugin.resources_list', array($this, 'resources_list'));
+    $this->cal->register_handler('plugin.resources_searchform', array($this, 'resources_search_form'));
+    $this->cal->register_handler('plugin.resource_info', array($this, 'resource_info'));
+    $this->cal->register_handler('plugin.resource_calendar', array($this, 'resource_calendar'));
+    $this->cal->register_handler('plugin.attendees_freebusy_table', array($this, 'attendees_freebusy_table'));
+    $this->cal->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
+    $this->cal->register_handler('plugin.edit_recurring_warning', array($this, 'recurring_event_warning'));
+    $this->cal->register_handler('plugin.event_rsvp_buttons', array($this, 'event_rsvp_buttons'));
+    $this->cal->register_handler('plugin.angenda_options', array($this, 'angenda_options'));
+    $this->cal->register_handler('plugin.events_import_form', array($this, 'events_import_form'));
+    $this->cal->register_handler('plugin.events_export_form', array($this, 'events_export_form'));
+    $this->cal->register_handler('plugin.object_changelog_table', array('libkolab', 'object_changelog_table'));
+    $this->cal->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));  // use generic method from rcube_template
+  }
+
+  /**
+   * Adds CSS stylesheets to the page header
+   */
+  public function addCSS()
+  {
+    $skin_path = $this->cal->local_skin_path();
+    $this->cal->include_stylesheet($skin_path . '/fullcalendar.css');
+  }
+
+  /**
+   * Adds JS files to the page header
+   */
+  public function addJS()
+  {
+    $this->cal->include_script('calendar_ui.js');
+    $this->cal->include_script('lib/js/fullcalendar.js');
+    $this->rc->output->include_script('treelist.js');
+
+    // include kolab folderlist widget if available
+    if (in_array('libkolab', $this->cal->api->loaded_plugins())) {
+        $this->cal->api->include_script('libkolab/js/folderlist.js');
+        $this->cal->api->include_script('libkolab/js/audittrail.js');
+    }
+
+    jqueryui::miniColors();
+  }
+
+  /**
+   *
+   */
+  function calendar_css($attrib = array())
+  {
+    $mode = $this->rc->config->get('calendar_event_coloring', $this->cal->defaults['calendar_event_coloring']);
+    $categories = $this->cal->driver->list_categories();
+    $css = "\n";
+    
+    foreach ((array)$categories as $class => $color) {
+      if (empty($color))
+        continue;
+      
+      $class = 'cat-' . asciiwords(strtolower($class), true);
+      $css  .= ".$class { color: #$color }\n";
+      if ($mode > 0) {
+        if ($mode == 2) {
+          $css .= ".fc-event-$class .fc-event-bg {";
+          $css .= " opacity: 0.9;";
+          $css .= " filter: alpha(opacity=90);";
+        }
+        else {
+          $css .= ".fc-event-$class.fc-event-skin, ";
+          $css .= ".fc-event-$class .fc-event-skin, ";
+          $css .= ".fc-event-$class .fc-event-inner {";
+        }
+        $css .= " background-color: #" . $color . ";";
+        if ($mode % 2)
+          $css .= " border-color: #$color;";
+        $css .= "}\n";
+      }
+    }
+    
+    $calendars = $this->cal->driver->list_calendars();
+    foreach ((array)$calendars as $id => $prop) {
+      if (!$prop['color'])
+        continue;
+      $css .= $this->calendar_css_classes($id, $prop, $mode);
+    }
+    
+    return html::tag('style', array('type' => 'text/css'), $css);
+  }
+
+  /**
+   *
+   */
+  public function calendar_css_classes($id, $prop, $mode)
+  {
+    $color = $prop['color'];
+    $class = 'cal-' . asciiwords($id, true);
+    $css .= "li .$class, #eventshow .$class { color: #$color; }\n";
+
+    if ($mode != 1) {
+      if ($mode == 3) {
+        $css .= ".fc-event-$class .fc-event-bg {";
+        $css .= " opacity: 0.9;";
+        $css .= " filter: alpha(opacity=90);";
+      }
+      else {
+        $css .= ".fc-event-$class, ";
+        $css .= ".fc-event-$class .fc-event-inner {";
+      }
+      if (!$prop['printmode'])
+        $css .= " background-color: #$color;";
+      if ($mode % 2 == 0)
+      $css .= " border-color: #$color;";
+      $css .= "}\n";
+    }
+
+    return $css . ".$class .handle { background-color: #$color; }\n";
+  }
+
+  /**
+   *
+   */
+  function calendar_list($attrib = array())
+  {
+    $html = '';
+    $jsenv = array();
+    $tree = true;
+    $calendars = $this->cal->driver->list_calendars(0, $tree);
+
+    // walk folder tree
+    if (is_object($tree)) {
+      $html = $this->list_tree_html($tree, $calendars, $jsenv, $attrib);
+
+      // append birthdays calendar which isn't part of $tree
+      if ($bdaycal = $calendars[calendar_driver::BIRTHDAY_CALENDAR_ID]) {
+        $calendars = array(calendar_driver::BIRTHDAY_CALENDAR_ID => $bdaycal);
+      }
+      else {
+        $calendars = array();  // clear array for flat listing
+      }
+    }
+    else {
+      // fall-back to flat folder listing
+      $attrib['class'] .= ' flat';
+    }
+
+    foreach ((array)$calendars as $id => $prop) {
+      if ($attrib['activeonly'] && !$prop['active'])
+        continue;
+
+      $html .= html::tag('li', array('id' => 'rcmlical' . $id, 'class' => $prop['group']),
+        $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly'])
+      );
+    }
+
+    $this->rc->output->set_env('source', rcube_utils::get_input_value('source', rcube_utils::INPUT_GET));
+    $this->rc->output->set_env('calendars', $jsenv);
+    $this->rc->output->add_gui_object('calendarslist', $attrib['id']);
+
+    return html::tag('ul', $attrib, $html, html::$common_attrib);
+  }
+
+  /**
+   * Return html for a structured list <ul> for the folder tree
+   */
+  public function list_tree_html($node, $data, &$jsenv, $attrib)
+  {
+    $out = '';
+    foreach ($node->children as $folder) {
+      $id = $folder->id;
+      $prop = $data[$id];
+      $is_collapsed = false; // TODO: determine this somehow?
+
+      $content = $this->calendar_list_item($id, $prop, $jsenv, $attrib['activeonly']);
+
+      if (!empty($folder->children)) {
+        $content .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
+          $this->list_tree_html($folder, $data, $jsenv, $attrib));
+      }
+
+      if (strlen($content)) {
+        $out .= html::tag('li', array(
+            'id' => 'rcmlical' . rcube_utils::html_identifier($id),
+            'class' => $prop['group'] . ($prop['virtual'] ? ' virtual' : ''),
+          ),
+          $content);
+      }
+    }
+
+    return $out;
+  }
+
+  /**
+   * Helper method to build a calendar list item (HTML content and js data)
+   */
+  public function calendar_list_item($id, $prop, &$jsenv, $activeonly = false)
+  {
+    // enrich calendar properties with settings from the driver
+    if (!$prop['virtual']) {
+      unset($prop['user_id']);
+      $prop['alarms']      = $this->cal->driver->alarms;
+      $prop['attendees']   = $this->cal->driver->attendees;
+      $prop['freebusy']    = $this->cal->driver->freebusy;
+      $prop['attachments'] = $this->cal->driver->attachments;
+      $prop['undelete']    = $this->cal->driver->undelete;
+      $prop['feedurl']     = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
+
+      $jsenv[$id] = $prop;
+    }
+
+    $classes = array('calendar', 'cal-'  . asciiwords($id, true));
+    $title = $prop['title'] ?: ($prop['name'] != $prop['listname'] || strlen($prop['name']) > 25 ?
+      html_entity_decode($prop['name'], ENT_COMPAT, RCUBE_CHARSET) : '');
+
+    if ($prop['virtual'])
+      $classes[] = 'virtual';
+    else if (!$prop['editable'])
+      $classes[] = 'readonly';
+    if ($prop['subscribed'])
+      $classes[] = 'subscribed';
+    if ($prop['subscribed'] === 2)
+      $classes[] = 'partial';
+    if ($prop['class'])
+      $classes[] = $prop['class'];
+
+    $content = '';
+    if (!$activeonly || $prop['active']) {
+      $label_id = 'cl:' . $id;
+      $content = html::div(join(' ', $classes),
+        html::span(array('class' => 'calname', 'id' => $label_id, 'title' => $title), $prop['editname'] ? rcube::Q($prop['editname']) : $prop['listname']) .
+        ($prop['virtual'] ? '' :
+          html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active'], 'aria-labelledby' => $label_id), '') .
+          html::span('actions', 
+            ($prop['removable'] ? html::a(array('href' => '#', 'class' => 'remove', 'title' => $this->cal->gettext('removelist')), ' ') : '') .
+            html::a(array('href' => '#', 'class' => 'quickview', 'title' => $this->cal->gettext('quickview'), 'role' => 'checkbox', 'aria-checked' => 'false'), '') .
+            (isset($prop['subscribed']) ? html::a(array('href' => '#', 'class' => 'subscribed', 'title' => $this->cal->gettext('calendarsubscribe'), 'role' => 'checkbox', 'aria-checked' => $prop['subscribed'] ? 'true' : 'false'), ' ') : '')
+          ) .
+          html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), '&nbsp;')
+        )
+      );
+    }
+
+    return $content;
+  }
+
+  /**
+   *
+   */
+  function angenda_options($attrib = array())
+  {
+    $attrib += array('id' => 'agendaoptions');
+    $attrib['style'] .= 'display:none';
+    
+    $select_range = new html_select(array('name' => 'listrange', 'id' => 'agenda-listrange'));
+    $select_range->add(1 . ' ' . preg_replace('/\(.+\)/', '', $this->cal->lib->gettext('days')), $days);
+    foreach (array(2,5,7,14,30,60,90,180,365) as $days)
+      $select_range->add($days . ' ' . preg_replace('/\(|\)/', '', $this->cal->lib->gettext('days')), $days);
+    
+    $html .= html::label('agenda-listrange', $this->cal->gettext('listrange'));
+    $html .= $select_range->show($this->rc->config->get('calendar_agenda_range', $this->cal->defaults['calendar_agenda_range']));
+    
+    $select_sections = new html_select(array('name' => 'listsections', 'id' => 'agenda-listsections'));
+    $select_sections->add('---', '');
+    foreach (array('day' => 'libcalendaring.days', 'week' => 'libcalendaring.weeks', 'month' => 'libcalendaring.months', 'smart' => 'calendar.smartsections') as $val => $label)
+      $select_sections->add(preg_replace('/\(|\)/', '', ucfirst($this->rc->gettext($label))), $val);
+    
+    $html .= html::span('spacer', '&nbsp;');
+    $html .= html::label('agenda-listsections', $this->cal->gettext('listsections'));
+    $html .= $select_sections->show($this->rc->config->get('calendar_agenda_sections', $this->cal->defaults['calendar_agenda_sections']));
+    
+    return html::div($attrib, $html);
+  }
+
+  /**
+   * Render a HTML select box for calendar selection
+   */
+  function calendar_select($attrib = array())
+  {
+    $attrib['name']       = 'calendar';
+    $attrib['is_escaped'] = true;
+    $select = new html_select($attrib);
+
+    foreach ((array)$this->cal->driver->list_calendars() as $id => $prop) {
+      if ($prop['editable'] || strpos($prop['rights'], 'i') !== false)
+        $select->add($prop['name'], $id);
+    }
+
+    return $select->show(null);
+  }
+
+  /**
+   * Render a HTML select box for user identity selection
+   */
+  function identity_select($attrib = array())
+  {
+    $attrib['name'] = 'identity';
+    $select         = new html_select($attrib);
+    $identities     = $this->rc->user->list_emails();
+
+    foreach ($identities as $ident) {
+        $select->add(format_email_recipient($ident['email'], $ident['name']), $ident['identity_id']);
+    }
+
+    return $select->show(null);
+  }
+
+  /**
+   * Render a HTML select box to select an event category
+   */
+  function category_select($attrib = array())
+  {
+    $attrib['name'] = 'categories';
+    $select = new html_select($attrib);
+    $select->add('---', '');
+    foreach (array_keys((array)$this->cal->driver->list_categories()) as $cat) {
+      $select->add($cat, $cat);
+    }
+
+    return $select->show(null);
+  }
+
+  /**
+   * Render a HTML select box for status property
+   */
+  function status_select($attrib = array())
+  {
+    $attrib['name'] = 'status';
+    $select = new html_select($attrib);
+    $select->add('---', '');
+    $select->add($this->cal->gettext('status-confirmed'), 'CONFIRMED');
+    $select->add($this->cal->gettext('status-cancelled'), 'CANCELLED');
+    $select->add($this->cal->gettext('status-tentative'), 'TENTATIVE');
+    return $select->show(null);
+  }
+
+  /**
+   * Render a HTML select box for free/busy/out-of-office property
+   */
+  function freebusy_select($attrib = array())
+  {
+    $attrib['name'] = 'freebusy';
+    $select = new html_select($attrib);
+    $select->add($this->cal->gettext('free'), 'free');
+    $select->add($this->cal->gettext('busy'), 'busy');
+    // out-of-office is not supported by libkolabxml (#3220)
+    // $select->add($this->cal->gettext('outofoffice'), 'outofoffice');
+    $select->add($this->cal->gettext('tentative'), 'tentative');
+    return $select->show(null);
+  }
+
+  /**
+   * Render a HTML select for event priorities
+   */
+  function priority_select($attrib = array())
+  {
+    $attrib['name'] = 'priority';
+    $select = new html_select($attrib);
+    $select->add('---', '0');
+    $select->add('1 '.$this->cal->gettext('highest'), '1');
+    $select->add('2 '.$this->cal->gettext('high'),    '2');
+    $select->add('3 ',                                '3');
+    $select->add('4 ',                                '4');
+    $select->add('5 '.$this->cal->gettext('normal'),  '5');
+    $select->add('6 ',                                '6');
+    $select->add('7 ',                                '7');
+    $select->add('8 '.$this->cal->gettext('low'),     '8');
+    $select->add('9 '.$this->cal->gettext('lowest'),  '9');
+    return $select->show(null);
+  }
+  
+  /**
+   * Render HTML input for sensitivity selection
+   */
+  function sensitivity_select($attrib = array())
+  {
+    $attrib['name'] = 'sensitivity';
+    $select = new html_select($attrib);
+    $select->add($this->cal->gettext('public'), 'public');
+    $select->add($this->cal->gettext('private'), 'private');
+    $select->add($this->cal->gettext('confidential'), 'confidential');
+    return $select->show(null);
+  }
+  
+  /**
+   * Render HTML form for alarm configuration
+   */
+  function alarm_select($attrib = array())
+  {
+    return $this->cal->lib->alarm_select($attrib, $this->cal->driver->alarm_types, $this->cal->driver->alarm_absolute);
+  }
+
+  /**
+   *
+   */
+  function edit_attendees_notify($attrib = array())
+  {
+    $checkbox = new html_checkbox(array('name' => '_notify', 'id' => 'edit-attendees-donotify', 'value' => 1));
+    return html::div($attrib, html::label(null, $checkbox->show(1) . ' ' . $this->cal->gettext('sendnotifications')));
+  }
+
+  /**
+   * Generate the form for recurrence settings
+   */
+  function recurring_event_warning($attrib = array())
+  {
+    $attrib['id'] = 'edit-recurring-warning';
+    
+    $radio = new html_radiobutton(array('name' => '_savemode', 'class' => 'edit-recurring-savemode'));
+    $form = html::label(null, $radio->show('', array('value' => 'current')) . $this->cal->gettext('currentevent')) . ' ' .
+       html::label(null, $radio->show('', array('value' => 'future')) . $this->cal->gettext('futurevents')) . ' ' .
+       html::label(null, $radio->show('all', array('value' => 'all')) . $this->cal->gettext('allevents')) . ' ' .
+       html::label(null, $radio->show('', array('value' => 'new')) . $this->cal->gettext('saveasnew'));
+       
+    return html::div($attrib, html::div('message', html::span('ui-icon ui-icon-alert', '') . $this->cal->gettext('changerecurringeventwarning')) . html::div('savemode', $form));
+  }
+
+  /**
+   * Form for uploading and importing events
+   */
+  function events_import_form($attrib = array())
+  {
+    if (!$attrib['id'])
+      $attrib['id'] = 'rcmImportForm';
+
+    // Get max filesize, enable upload progress bar
+    $max_filesize = $this->rc->upload_init();
+
+    $accept = '.ics, text/calendar, text/x-vcalendar, application/ics';
+    if (class_exists('ZipArchive', false)) {
+      $accept .= ', .zip, application/zip';
+    }
+
+    $input = new html_inputfield(array(
+      'type' => 'file', 'name' => '_data', 'size' => $attrib['uploadfieldsize'],
+      'accept' => $accept));
+
+    $select = new html_select(array('name' => '_range', 'id' => 'event-import-range'));
+    $select->add(array(
+        $this->cal->gettext('onemonthback'),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))),
+        $this->cal->gettext('all'),
+      ),
+      array('1','2','3','6','12',0));
+
+    $html .= html::div('form-section',
+      html::div(null, $input->show()) .
+      html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))
+    );
+
+    $html .= html::div('form-section',
+      html::label('event-import-calendar', $this->cal->gettext('calendar')) .
+      $this->calendar_select(array('name' => 'calendar', 'id' => 'event-import-calendar'))
+    );
+
+    $html .= html::div('form-section',
+      html::label('event-import-range', $this->cal->gettext('importrange')) .
+      $select->show(1)
+    );
+
+    $this->rc->output->add_gui_object('importform', $attrib['id']);
+    $this->rc->output->add_label('import');
+
+    return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'import_events')),
+      'method' => "post", 'enctype' => 'multipart/form-data', 'id' => $attrib['id']),
+      $html
+    );
+  }
+
+  /**
+   * Form to select options for exporting events
+   */
+  function events_export_form($attrib = array())
+  {
+    if (!$attrib['id'])
+      $attrib['id'] = 'rcmExportForm';
+
+    $html .= html::div('form-section',
+      html::label('event-export-calendar', $this->cal->gettext('calendar')) .
+      $this->calendar_select(array('name' => 'calendar', 'id' => 'event-export-calendar'))
+    );
+
+    $select = new html_select(array('name' => 'range', 'id' => 'event-export-range'));
+    $select->add(array(
+        $this->cal->gettext('all'),
+        $this->cal->gettext('onemonthback'),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>2))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>3))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>6))),
+        $this->cal->gettext(array('name' => 'nmonthsback', 'vars' => array('nr'=>12))),
+        $this->cal->gettext('customdate'),
+      ),
+      array(0,'1','2','3','6','12','custom'));
+
+    $startdate = new html_inputfield(array('name' => 'start', 'size' => 11, 'id' => 'event-export-startdate'));
+
+    $html .= html::div('form-section',
+      html::label('event-export-range', $this->cal->gettext('exportrange')) .
+      $select->show(0) .
+      html::span(array('style'=>'display:none'), $startdate->show())
+    );
+
+    $checkbox = new html_checkbox(array('name' => 'attachments', 'id' => 'event-export-attachments', 'value' => 1));
+    $html .= html::div('form-section',
+      html::label('event-export-attachments', $this->cal->gettext('exportattachments')) .
+      $checkbox->show(1)
+    );
+
+    $this->rc->output->add_gui_object('exportform', $attrib['id']);
+
+    return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'export_events')),
+      'method' => "post", 'id' => $attrib['id']),
+      $html
+    );
+  }
+
+  /**
+   * Generate the form for event attachments upload
+   */
+  function attachments_form($attrib = array())
+  {
+    // add ID if not given
+    if (!$attrib['id'])
+      $attrib['id'] = 'rcmUploadForm';
+
+    // Get max filesize, enable upload progress bar
+    $max_filesize = $this->rc->upload_init();
+
+    $button = new html_inputfield(array('type' => 'button'));
+    $input = new html_inputfield(array(
+      'type' => 'file', 'name' => '_attachments[]',
+      'multiple' => 'multiple', 'size' => $attrib['attachmentfieldsize']));
+
+    return html::div($attrib,
+      html::div(null, $input->show()) .
+      html::div('buttons', $button->show($this->rc->gettext('upload'), array('class' => 'button mainaction',
+        'onclick' => rcmail_output::JS_OBJECT_NAME . ".upload_file(this.form)"))) .
+      html::div('hint', $this->rc->gettext(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))
+    );
+  }
+
+  /**
+   * Register UI object for HTML5 drag & drop file upload
+   */
+  function file_drop_area($attrib = array())
+  {
+      if ($attrib['id']) {
+          $this->rc->output->add_gui_object('filedrop', $attrib['id']);
+          $this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
+      }
+  }
+
+  /**
+   * Generate HTML element for attachments list
+   */
+  function attachments_list($attrib = array())
+  {
+    if (!$attrib['id'])
+      $attrib['id'] = 'rcmAttachmentList';
+
+    $skin_path = $this->cal->local_skin_path();
+    if ($attrib['deleteicon']) {
+      $_SESSION[calendar::SESSION_KEY . '_deleteicon'] = $skin_path . $attrib['deleteicon'];
+      $this->rc->output->set_env('deleteicon', $skin_path . $attrib['deleteicon']);
+    }
+    if ($attrib['cancelicon'])
+      $this->rc->output->set_env('cancelicon', $skin_path . $attrib['cancelicon']);
+    if ($attrib['loadingicon'])
+      $this->rc->output->set_env('loadingicon', $skin_path . $attrib['loadingicon']);
+
+    $this->rc->output->add_gui_object('attachmentlist', $attrib['id']);
+    $this->attachmentlist_id = $attrib['id'];
+
+    return html::tag('ul', $attrib, '', html::$common_attrib);
+  }
+
+  /**
+   * Handler for calendar form template.
+   * The form content could be overriden by the driver
+   */
+  function calendar_editform($action, $calendar = array())
+  {
+    // compose default calendar form fields
+    $input_name = new html_inputfield(array('name' => 'name', 'id' => 'calendar-name', 'size' => 20));
+    $input_color = new html_inputfield(array('name' => 'color', 'id' => 'calendar-color', 'size' => 6));
+
+    $formfields = array(
+      'name' => array(
+        'label' => $this->cal->gettext('name'),
+        'value' => $input_name->show($calendar['name']),
+        'id' => 'calendar-name',
+      ),
+      'color' => array(
+        'label' => $this->cal->gettext('color'),
+        'value' => $input_color->show($calendar['color']),
+        'id' => 'calendar-color',
+      ),
+    );
+
+    if ($this->cal->driver->alarms) {
+      $checkbox = new html_checkbox(array('name' => 'showalarms', 'id' => 'calendar-showalarms', 'value' => 1));
+      $formfields['showalarms'] = array(
+        'label' => $this->cal->gettext('showalarms'),
+        'value' => $checkbox->show($calendar['showalarms']?1:0),
+        'id' => 'calendar-showalarms',
+      );
+    }
+
+    // allow driver to extend or replace the form content
+    return html::tag('form', array('action' => "#", 'method' => "get", 'id' => 'calendarpropform'),
+      $this->cal->driver->calendar_form($action, $calendar, $formfields)
+    );
+  }
+
+  /**
+   *
+   */
+  function attendees_list($attrib = array())
+  {
+    // add "noreply" checkbox to attendees table only
+    $invitations = strpos($attrib['id'], 'attend') !== false;
+
+    $invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite'));
+    $table  = new html_table(array('cols' => 5 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable'));
+
+    $table->add_header('role', $this->cal->gettext('role'));
+    $table->add_header('name', $this->cal->gettext($attrib['coltitle'] ?: 'attendee'));
+    $table->add_header('availability', $this->cal->gettext('availability'));
+    $table->add_header('confirmstate', $this->cal->gettext('confirmstate'));
+    if ($invitations) {
+      $table->add_header(array('class' => 'invite', 'title' => $this->cal->gettext('sendinvitations')),
+        $invite->show(1) . html::label('edit-attendees-invite', $this->cal->gettext('sendinvitations')));
+    }
+    $table->add_header('options', '');
+
+    // hide invite column if disabled by config
+    $itip_notify = (int)$this->rc->config->get('calendar_itip_send_option', $this->cal->defaults['calendar_itip_send_option']);
+    if ($invitations && !($itip_notify & 2)) {
+        $css = sprintf('#%s td.invite, #%s th.invite { display:none !important }', $attrib['id'], $attrib['id']);
+        $this->rc->output->add_footer(html::tag('style', array('type' => 'text/css'), $css));
+    }
+
+    return $table->show($attrib);
+  }
+
+  /**
+   *
+   */
+  function attendees_form($attrib = array())
+  {
+    $input    = new html_inputfield(array('name' => 'participant', 'id' => 'edit-attendee-name', 'size' => 30));
+    $textarea = new html_textarea(array('name' => 'comment', 'id' => 'edit-attendees-comment',
+        'rows' => 4, 'cols' => 55, 'title' => $this->cal->gettext('itipcommenttitle')));
+
+    return html::div($attrib,
+      html::div(null, $input->show() . " " .
+        html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-add', 'value' => $this->cal->gettext('addattendee'))) . " " .
+        html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-attendee-schedule', 'value' => $this->cal->gettext('scheduletime').'...'))) .
+      html::p('attendees-commentbox', html::label(null, $this->cal->gettext('itipcomment') . $textarea->show()))
+    );
+  }
+
+  /**
+   *
+   */
+  function resources_form($attrib = array())
+  {
+    $input = new html_inputfield(array('name' => 'resource', 'id' => 'edit-resource-name', 'size' => 30));
+
+    return html::div($attrib,
+      html::div(null, $input->show() . " " .
+        html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-add', 'value' => $this->cal->gettext('addresource'))) . " " .
+        html::tag('input', array('type' => 'button', 'class' => 'button', 'id' => 'edit-resource-find', 'value' => $this->cal->gettext('findresources').'...')))
+      );
+  }
+
+  /**
+   *
+   */
+  function resources_list($attrib = array())
+  {
+    $attrib += array('id' => 'calendar-resources-list');
+
+    $this->rc->output->add_gui_object('resourceslist', $attrib['id']);
+
+    return html::tag('ul', $attrib, '', html::$common_attrib);
+  }
+
+  /**
+   *
+   */
+  public function resource_info($attrib = array())
+  {
+    $attrib += array('id' => 'calendar-resources-info');
+
+    $this->rc->output->add_gui_object('resourceinfo', $attrib['id']);
+    $this->rc->output->add_gui_object('resourceownerinfo', $attrib['id'] . '-owner');
+
+    // copy address book labels for owner details to client
+    $this->rc->output->add_label('name','firstname','surname','department','jobtitle','email','phone','address');
+
+    $table_attrib = array('id','class','style','width','summary','cellpadding','cellspacing','border');
+
+    return html::tag('table', $attrib,
+        html::tag('tbody', null, ''), $table_attrib) .
+
+      html::tag('table', array('id' => $attrib['id'] . '-owner', 'style' => 'display:none') + $attrib,
+        html::tag('thead', null,
+          html::tag('tr', null,
+            html::tag('td', array('colspan' => 2), rcube::Q($this->cal->gettext('resourceowner')))
+          )
+        ) .
+        html::tag('tbody', null, ''),
+        $table_attrib);
+  }
+
+  /**
+   *
+   */
+  public function resource_calendar($attrib = array())
+  {
+    $attrib += array('id' => 'calendar-resources-calendar');
+
+    $this->rc->output->add_gui_object('resourceinfocalendar', $attrib['id']);
+
+    return html::div($attrib, '');
+  }
+
+  /**
+   * GUI object 'searchform' for the resource finder dialog
+   *
+   * @param array Named parameters
+   * @return string HTML code for the gui object
+   */
+  function resources_search_form($attrib)
+  {
+    $attrib += array('command' => 'search-resource', 'id' => 'rcmcalresqsearchbox', 'autocomplete' => 'off');
+    $attrib['name'] = '_q';
+
+    $input_q = new html_inputfield($attrib);
+    $out = $input_q->show();
+
+    // add form tag around text field
+    $out = $this->rc->output->form_tag(array(
+      'name' => "rcmcalresoursqsearchform",
+      'onsubmit' => rcmail_output::JS_OBJECT_NAME . ".command('" . $attrib['command'] . "'); return false",
+      'style' => "display:inline"),
+      $out);
+
+    return $out;
+  }
+
+  /**
+   *
+   */
+  function attendees_freebusy_table($attrib = array())
+  {
+    $table = new html_table(array('cols' => 2, 'border' => 0, 'cellspacing' => 0));
+    $table->add('attendees',
+      html::tag('h3', 'boxtitle', $this->cal->gettext('tabattendees')) .
+      html::div('timesheader', '&nbsp;') .
+      html::div(array('id' => 'schedule-attendees-list', 'class' => 'attendees-list'), '')
+    );
+    $table->add('times',
+      html::div('scroll',
+        html::tag('table', array('id' => 'schedule-freebusy-times', 'border' => 0, 'cellspacing' => 0), html::tag('thead') . html::tag('tbody')) .
+        html::div(array('id' => 'schedule-event-time', 'style' => 'display:none'), '&nbsp;')
+      )
+    );
+    
+    return $table->show($attrib);
+  }
+
+  /**
+   *
+   */
+  function event_invitebox($attrib = array())
+  {
+    if ($this->cal->event) {
+      return html::div($attrib,
+        $this->cal->itip->itip_object_details_table($this->cal->event, $this->cal->itip->gettext('itipinvitation')) .
+        $this->cal->invitestatus
+      );
+    }
+    
+    return '';
+  }
+
+  function event_rsvp_buttons($attrib = array())
+  {
+    $actions = array('accepted','tentative','declined');
+    if ($attrib['delegate'] !== 'false')
+      $actions[] = 'delegated';
+
+    return $this->cal->itip->itip_rsvp_buttons($attrib, $actions);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/js/fullcalendar.js	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,6845 @@
+/*!
+ * FullCalendar v1.6.4-rcube-1.1.3
+ * Docs & License: http://arshaw.com/fullcalendar/
+ * (c) 2013 Adam Shaw, 2014 Kolab Systems AG
+ */
+
+/*
+ * Use fullcalendar.css for basic styling.
+ * For event drag & drop, requires jQuery UI draggable.
+ * For event resizing, requires jQuery UI resizable.
+ */
+ 
+(function($, undefined) {
+
+
+;;
+
+var defaults = {
+
+	// display
+	defaultView: 'month',
+	aspectRatio: 1.35,
+	header: {
+		left: 'title',
+		center: '',
+		right: 'today prev,next'
+	},
+	weekends: true,
+	weekNumbers: false,
+	weekNumberCalculation: 'iso',
+	weekNumberTitle: 'W',
+	currentTimeIndicator: false,
+	
+	// editing
+	//editable: false,
+	//disableDragging: false,
+	//disableResizing: false,
+	
+	allDayDefault: true,
+	ignoreTimezone: true,
+	
+	// event ajax
+	lazyFetching: true,
+	startParam: 'start',
+	endParam: 'end',
+	
+	// time formats
+	titleFormat: {
+		month: 'MMMM yyyy',
+		week: "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}",
+		day: 'dddd, MMM d, yyyy',
+		list: 'MMM d, yyyy',
+		table: 'MMM d, yyyy'
+	},
+	columnFormat: {
+		month: 'ddd',
+		week: 'ddd M/d',
+		day: 'dddd M/d',
+		list: 'dddd, MMM d, yyyy',
+		table: 'MMM d, yyyy'
+	},
+	timeFormat: { // for event elements
+		'': 'h(:mm)t' // default
+	},
+	
+	// locale
+	isRTL: false,
+	firstDay: 0,
+	monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
+	monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
+	dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
+	dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
+	buttonText: {
+		prev: "<span class='fc-text-arrow'>&lsaquo;</span>",
+		next: "<span class='fc-text-arrow'>&rsaquo;</span>",
+		prevYear: "<span class='fc-text-arrow'>&laquo;</span>",
+		nextYear: "<span class='fc-text-arrow'>&raquo;</span>",
+		today: 'today',
+		month: 'month',
+		week: 'week',
+		day: 'day',
+		list: 'list',
+		table: 'table'
+	},
+	listTexts: {
+		until: 'until',
+		past: 'Past events',
+		today: 'Today',
+		tomorrow: 'Tomorrow',
+		thisWeek: 'This week',
+		nextWeek: 'Next week',
+		thisMonth: 'This month',
+		nextMonth: 'Next month',
+		future: 'Future events',
+		week: 'W'
+	},
+	
+	// list/table options
+	listSections: 'month',  // false|'day'|'week'|'month'|'smart'
+	listRange: 30,  // number of days to be displayed
+	listPage: 7,  // number of days to jump when paging
+	tableCols: ['handle', 'date', 'time', 'title'],
+	
+	// jquery-ui theming
+	theme: false,
+	buttonIcons: {
+		prev: 'circle-triangle-w',
+		next: 'circle-triangle-e'
+	},
+	
+	//selectable: false,
+	unselectAuto: true,
+	
+	dropAccept: '*',
+	
+	handleWindowResize: true
+	
+};
+
+// right-to-left defaults
+var rtlDefaults = {
+	header: {
+		left: 'next,prev today',
+		center: '',
+		right: 'title'
+	},
+	buttonText: {
+		prev: "<span class='fc-text-arrow'>&rsaquo;</span>",
+		next: "<span class='fc-text-arrow'>&lsaquo;</span>",
+		prevYear: "<span class='fc-text-arrow'>&raquo;</span>",
+		nextYear: "<span class='fc-text-arrow'>&laquo;</span>"
+	},
+	buttonIcons: {
+		prev: 'circle-triangle-e',
+		next: 'circle-triangle-w'
+	}
+};
+
+
+
+;;
+
+var fc = $.fullCalendar = { version: "1.6.4-rcube-1.1.3" };
+var fcViews = fc.views = {};
+
+
+$.fn.fullCalendar = function(options) {
+
+
+	// method calling
+	if (typeof options == 'string') {
+		var args = Array.prototype.slice.call(arguments, 1);
+		var res;
+		this.each(function() {
+			var calendar = $.data(this, 'fullCalendar');
+			if (calendar && $.isFunction(calendar[options])) {
+				var r = calendar[options].apply(calendar, args);
+				if (res === undefined) {
+					res = r;
+				}
+				if (options == 'destroy') {
+					$.removeData(this, 'fullCalendar');
+				}
+			}
+		});
+		if (res !== undefined) {
+			return res;
+		}
+		return this;
+	}
+
+	options = options || {};
+	
+	// would like to have this logic in EventManager, but needs to happen before options are recursively extended
+	var eventSources = options.eventSources || [];
+	delete options.eventSources;
+	if (options.events) {
+		eventSources.push(options.events);
+		delete options.events;
+	}
+	
+
+	options = $.extend(true, {},
+		defaults,
+		(options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
+		options
+	);
+	
+	
+	this.each(function(i, _element) {
+		var element = $(_element);
+		var calendar = new Calendar(element, options, eventSources);
+		element.data('fullCalendar', calendar); // TODO: look into memory leak implications
+		calendar.render();
+	});
+	
+	
+	return this;
+	
+};
+
+
+// function for adding/overriding defaults
+function setDefaults(d) {
+	$.extend(true, defaults, d);
+}
+
+
+
+;;
+
+ 
+function Calendar(element, options, eventSources) {
+	var t = this;
+	
+	
+	// exports
+	t.options = options;
+	t.render = render;
+	t.destroy = destroy;
+	t.refetchEvents = refetchEvents;
+	t.reportEvents = reportEvents;
+	t.reportEventChange = reportEventChange;
+	t.rerenderEvents = rerenderEvents;
+	t.changeView = changeView;
+	t.select = select;
+	t.unselect = unselect;
+	t.prev = prev;
+	t.next = next;
+	t.prevYear = prevYear;
+	t.nextYear = nextYear;
+	t.today = today;
+	t.gotoDate = gotoDate;
+	t.incrementDate = incrementDate;
+	t.formatDate = function(format, date) { return formatDate(format, date, options) };
+	t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) };
+	t.getDate = getDate;
+	t.getView = getView;
+	t.option = option;
+	t.trigger = trigger;
+	
+	
+	// imports
+	EventManager.call(t, options, eventSources);
+	var isFetchNeeded = t.isFetchNeeded;
+	var fetchEvents = t.fetchEvents;
+	
+	
+	// locals
+	var _element = element[0];
+	var header;
+	var headerElement;
+	var content;
+	var tm; // for making theme classes
+	var currentView;
+	var elementOuterWidth;
+	var suggestedViewHeight;
+	var resizeUID = 0;
+	var ignoreWindowResize = 0;
+	var lazyRendering = false;
+	var date = new Date();
+	var events = [];
+	var _dragElement;
+	
+	
+	
+	/* Main Rendering
+	-----------------------------------------------------------------------------*/
+	
+	
+	setYMD(date, options.year, options.month, options.date);
+	
+	
+	function render(inc) {
+		if (!content) {
+			initialRender();
+		}
+		else if (elementVisible()) {
+			// mainly for the public API
+			calcSize();
+			_renderView(inc);
+		}
+	}
+	
+	
+	function initialRender() {
+		tm = options.theme ? 'ui' : 'fc';
+		element.addClass('fc');
+		if (options.isRTL) {
+			element.addClass('fc-rtl');
+		}
+		else {
+			element.addClass('fc-ltr');
+		}
+		if (options.theme) {
+			element.addClass('ui-widget');
+		}
+
+		content = $("<div class='fc-content' style='position:relative'/>")
+			.prependTo(element);
+
+		header = new Header(t, options);
+		headerElement = header.render();
+		if (headerElement) {
+			element.prepend(headerElement);
+		}
+
+		changeView(options.defaultView);
+
+		if (options.handleWindowResize) {
+			$(window).resize(windowResize);
+		}
+
+		// needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
+		if (!bodyVisible()) {
+			lateRender();
+		}
+	}
+	
+	
+	// called when we know the calendar couldn't be rendered when it was initialized,
+	// but we think it's ready now
+	function lateRender() {
+		setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
+			if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once
+				renderView();
+			}
+		},0);
+	}
+	
+	
+	function destroy() {
+
+		if (currentView) {
+			trigger('viewDestroy', currentView, currentView, currentView.element);
+			currentView.triggerEventDestroy();
+		}
+
+		$(window).unbind('resize', windowResize);
+
+		header.destroy();
+		content.remove();
+		element.removeClass('fc fc-rtl ui-widget');
+	}
+	
+	
+	function elementVisible() {
+		return element.is(':visible');
+	}
+	
+	
+	function bodyVisible() {
+		return $('body').is(':visible');
+	}
+	
+	
+	
+	/* View Rendering
+	-----------------------------------------------------------------------------*/
+	
+
+	function changeView(newViewName) {
+		if (!currentView || newViewName != currentView.name) {
+			_changeView(newViewName);
+		}
+	}
+
+
+	function _changeView(newViewName) {
+		ignoreWindowResize++;
+
+		if (currentView) {
+			trigger('viewDestroy', currentView, currentView, currentView.element);
+			unselect();
+			currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
+			freezeContentHeight();
+			currentView.element.remove();
+			header.deactivateButton(currentView.name);
+		}
+
+		header.activateButton(newViewName);
+
+		currentView = new fcViews[newViewName](
+			$("<div class='fc-view fc-view-" + newViewName + "' style='position:relative'/>")
+				.appendTo(content),
+			t // the calendar object
+		);
+
+		renderView();
+		unfreezeContentHeight();
+
+		ignoreWindowResize--;
+	}
+
+
+	function renderView(inc) {
+		if (
+			!currentView.start || // never rendered before
+			inc || date < currentView.start || date >= currentView.end // or new date range
+		) {
+			if (elementVisible()) {
+				_renderView(inc);
+			}
+		}
+	}
+
+
+	function _renderView(inc) { // assumes elementVisible
+		ignoreWindowResize++;
+
+		if (currentView.start) { // already been rendered?
+			trigger('viewDestroy', currentView, currentView, currentView.element);
+			unselect();
+			clearEvents();
+		}
+
+		freezeContentHeight();
+		currentView.render(date, inc || 0); // the view's render method ONLY renders the skeleton, nothing else
+		setSize();
+		unfreezeContentHeight();
+		(currentView.afterRender || noop)();
+
+		updateTitle();
+		updateTodayButton();
+
+		trigger('viewRender', currentView, currentView, currentView.element);
+		currentView.trigger('viewDisplay', _element); // deprecated
+
+		ignoreWindowResize--;
+
+		getAndRenderEvents();
+	}
+	
+	
+
+	/* Resizing
+	-----------------------------------------------------------------------------*/
+	
+	
+	function updateSize() {
+		if (elementVisible()) {
+			unselect();
+			clearEvents();
+			calcSize();
+			setSize();
+			unselect();
+			currentView.clearEvents();
+			currentView.trigger('viewRender', currentView);
+			currentView.renderEvents(events);
+			currentView.sizeDirty = false;
+		}
+	}
+	
+	
+	function calcSize() { // assumes elementVisible
+		if (options.contentHeight) {
+			suggestedViewHeight = options.contentHeight;
+		}
+		else if (options.height) {
+			suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content);
+		}
+		else {
+			suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
+		}
+	}
+	
+	
+	function setSize() { // assumes elementVisible
+
+		if (suggestedViewHeight === undefined) {
+			calcSize(); // for first time
+				// NOTE: we don't want to recalculate on every renderView because
+				// it could result in oscillating heights due to scrollbars.
+		}
+
+		ignoreWindowResize++;
+		currentView.setHeight(suggestedViewHeight);
+		currentView.setWidth(content.width());
+		ignoreWindowResize--;
+
+		elementOuterWidth = element.outerWidth();
+	}
+	
+	
+	function windowResize() {
+		if (!ignoreWindowResize) {
+			if (currentView.start) { // view has already been rendered
+				var uid = ++resizeUID;
+				setTimeout(function() { // add a delay
+					if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
+						if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
+							ignoreWindowResize++; // in case the windowResize callback changes the height
+							updateSize();
+							currentView.trigger('windowResize', _element);
+							ignoreWindowResize--;
+						}
+					}
+				}, 200);
+			}else{
+				// calendar must have been initialized in a 0x0 iframe that has just been resized
+				lateRender();
+			}
+		}
+	}
+	
+	
+	
+	/* Event Fetching/Rendering
+	-----------------------------------------------------------------------------*/
+	// TODO: going forward, most of this stuff should be directly handled by the view
+
+
+	function refetchEvents(source, lazy) { // can be called as an API method
+		lazyRendering = lazy || false;
+		if (!lazyRendering) {
+			clearEvents();
+		}
+		fetchAndRenderEvents(source);
+	}
+
+
+	function rerenderEvents(modifiedEventID) { // can be called as an API method
+		clearEvents();
+		renderEvents(modifiedEventID);
+	}
+
+
+	function renderEvents(modifiedEventID) { // TODO: remove modifiedEventID hack
+		if (elementVisible()) {
+			currentView.setEventData(events); // for View.js, TODO: unify with renderEvents
+			currentView.renderEvents(events, modifiedEventID); // actually render the DOM elements
+			currentView.trigger('eventAfterAllRender');
+		}
+	}
+
+
+	function clearEvents() {
+		currentView.triggerEventDestroy(); // trigger 'eventDestroy' for each event
+		currentView.clearEvents(); // actually remove the DOM elements
+		currentView.clearEventData(); // for View.js, TODO: unify with clearEvents
+	}
+	
+
+	function getAndRenderEvents() {
+		if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) {
+			fetchAndRenderEvents();
+		}
+		else {
+			renderEvents();
+		}
+	}
+
+
+	function fetchAndRenderEvents(source) {
+		fetchEvents(currentView.visStart, currentView.visEnd, source);
+			// ... will call reportEvents
+			// ... which will call renderEvents
+	}
+
+
+	// called when event data arrives
+	function reportEvents(_events) {
+		if (lazyRendering) {
+			clearEvents();
+			lazyRendering = false;
+		}
+		events = _events;
+		renderEvents();
+	}
+
+
+	// called when a single event's data has been changed
+	function reportEventChange(eventID) {
+		rerenderEvents(eventID);
+	}
+
+
+
+	/* Header Updating
+	-----------------------------------------------------------------------------*/
+
+
+	function updateTitle() {
+		header.updateTitle(currentView.title);
+	}
+
+
+	function updateTodayButton() {
+		var today = new Date();
+		if (today >= currentView.start && today < currentView.end) {
+			header.disableButton('today');
+		}
+		else {
+			header.enableButton('today');
+		}
+	}
+	
+
+
+	/* Selection
+	-----------------------------------------------------------------------------*/
+	
+
+	function select(start, end, allDay) {
+		currentView.select(start, end, allDay===undefined ? true : allDay);
+	}
+	
+
+	function unselect() { // safe to be called before renderView
+		if (currentView) {
+			currentView.unselect();
+		}
+	}
+	
+	
+	
+	/* Date
+	-----------------------------------------------------------------------------*/
+	
+	
+	function prev() {
+		renderView(-1);
+	}
+	
+	
+	function next() {
+		renderView(1);
+	}
+	
+	
+	function prevYear() {
+		addYears(date, -1);
+		renderView();
+	}
+	
+	
+	function nextYear() {
+		addYears(date, 1);
+		renderView();
+	}
+	
+	
+	function today() {
+		date = new Date();
+		renderView();
+	}
+	
+	
+	function gotoDate(year, month, dateOfMonth) {
+		if (year instanceof Date) {
+			date = cloneDate(year); // provided 1 argument, a Date
+		}else{
+			setYMD(date, year, month, dateOfMonth);
+		}
+		renderView();
+	}
+	
+	
+	function incrementDate(years, months, days) {
+		if (years !== undefined) {
+			addYears(date, years);
+		}
+		if (months !== undefined) {
+			addMonths(date, months);
+		}
+		if (days !== undefined) {
+			addDays(date, days);
+		}
+		renderView();
+	}
+	
+	
+	function getDate() {
+		return cloneDate(date);
+	}
+
+
+
+	/* Height "Freezing"
+	-----------------------------------------------------------------------------*/
+
+
+	function freezeContentHeight() {
+		content.css({
+			width: '100%',
+			height: content.height(),
+			overflow: 'hidden'
+		});
+	}
+
+
+	function unfreezeContentHeight() {
+		content.css({
+			width: '',
+			height: '',
+			overflow: ''
+		});
+	}
+	
+	
+	
+	/* Misc
+	-----------------------------------------------------------------------------*/
+	
+	
+	function getView() {
+		return currentView;
+	}
+	
+	
+	function option(name, value) {
+		if (value === undefined) {
+			return options[name];
+		}
+		if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
+			options[name] = value;
+			updateSize();
+		} else if (name.indexOf('list') == 0 || name == 'tableCols') {
+			options[name] = value;
+			currentView.start = null; // force re-render
+		} else if (name == 'maxHeight') {
+			options[name] = value;
+		}
+	}
+	
+	
+	function trigger(name, thisObj) {
+		if (options[name]) {
+			return options[name].apply(
+				thisObj || _element,
+				Array.prototype.slice.call(arguments, 2)
+			);
+		}
+	}
+	
+	
+	
+	/* External Dragging
+	------------------------------------------------------------------------*/
+	
+	if (options.droppable) {
+		$(document)
+			.bind('dragstart', function(ev, ui) {
+				var _e = ev.target;
+				var e = $(_e);
+				if (!e.parents('.fc').length) { // not already inside a calendar
+					var accept = options.dropAccept;
+					if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) {
+						_dragElement = _e;
+						currentView.dragStart(_dragElement, ev, ui);
+					}
+				}
+			})
+			.bind('dragstop', function(ev, ui) {
+				if (_dragElement) {
+					currentView.dragStop(_dragElement, ev, ui);
+					_dragElement = null;
+				}
+			});
+	}
+	
+
+}
+
+;;
+
+function Header(calendar, options) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	t.destroy = destroy;
+	t.updateTitle = updateTitle;
+	t.activateButton = activateButton;
+	t.deactivateButton = deactivateButton;
+	t.disableButton = disableButton;
+	t.enableButton = enableButton;
+	
+	
+	// locals
+	var element = $([]);
+	var tm;
+	
+
+
+	function render() {
+		tm = options.theme ? 'ui' : 'fc';
+		var sections = options.header;
+		if (sections) {
+			element = $("<table class='fc-header' style='width:100%'/>")
+				.append(
+					$("<tr/>")
+						.append(renderSection('left'))
+						.append(renderSection('center'))
+						.append(renderSection('right'))
+				);
+			return element;
+		}
+	}
+	
+	
+	function destroy() {
+		element.remove();
+	}
+	
+	
+	function renderSection(position) {
+		var e = $("<td class='fc-header-" + position + "'/>");
+		var buttonStr = options.header[position];
+		if (buttonStr) {
+			$.each(buttonStr.split(' '), function(i) {
+				if (i > 0) {
+					e.append("<span class='fc-header-space'/>");
+				}
+				var prevButton;
+				$.each(this.split(','), function(j, buttonName) {
+					if (buttonName == 'title') {
+						e.append("<span class='fc-header-title'><h2 aria-live='polite' aria-relevant='text' aria-atomic='true'>&nbsp;</h2></span>");
+						if (prevButton) {
+							prevButton.addClass(tm + '-corner-right');
+						}
+						prevButton = null;
+					}else{
+						var buttonClick;
+						if (calendar[buttonName]) {
+							buttonClick = calendar[buttonName]; // calendar method
+						}
+						else if (fcViews[buttonName]) {
+							buttonClick = function() {
+								button.removeClass(tm + '-state-hover'); // forget why
+								calendar.changeView(buttonName);
+							};
+						}
+						if (buttonClick) {
+							var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here?
+							var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here?
+							var button = $(
+								"<span class='fc-button fc-button-" + buttonName + " " + tm + "-state-default' role='button' tabindex='0'>" +
+									(icon ?
+										"<span class='fc-icon-wrap'>" +
+											"<span class='ui-icon ui-icon-" + icon + "'/>" +
+										"</span>" :
+										text
+										) +
+								"</span>"
+								)
+								.click(function() {
+									if (!button.hasClass(tm + '-state-disabled')) {
+										buttonClick();
+									}
+								})
+								.mousedown(function() {
+									button
+										.not('.' + tm + '-state-active')
+										.not('.' + tm + '-state-disabled')
+										.addClass(tm + '-state-down');
+								})
+								.mouseup(function() {
+									button.removeClass(tm + '-state-down');
+								})
+								.hover(
+									function() {
+										button
+											.not('.' + tm + '-state-active')
+											.not('.' + tm + '-state-disabled')
+											.addClass(tm + '-state-hover');
+									},
+									function() {
+										button
+											.removeClass(tm + '-state-hover')
+											.removeClass(tm + '-state-down');
+									}
+								)
+								.keypress(function(ev) {
+									if (ev.keyCode == 13)
+										$(ev.target).trigger('click');
+								})
+								.appendTo(e);
+							disableTextSelection(button);
+							if (!prevButton) {
+								button.addClass(tm + '-corner-left');
+							}
+							prevButton = button;
+						}
+					}
+				});
+				if (prevButton) {
+					prevButton.addClass(tm + '-corner-right');
+				}
+			});
+		}
+		return e;
+	}
+	
+	
+	function updateTitle(html) {
+		element.find('h2')
+			.html(html);
+	}
+	
+	
+	function activateButton(buttonName) {
+		element.find('span.fc-button-' + buttonName)
+			.addClass(tm + '-state-active').attr('tabindex', '-1');
+	}
+	
+	
+	function deactivateButton(buttonName) {
+		element.find('span.fc-button-' + buttonName)
+			.removeClass(tm + '-state-active').attr('tabindex', '0');
+	}
+	
+	
+	function disableButton(buttonName) {
+		element.find('span.fc-button-' + buttonName)
+			.addClass(tm + '-state-disabled').attr('tabindex', '-1');
+	}
+	
+	
+	function enableButton(buttonName) {
+		element.find('span.fc-button-' + buttonName)
+			.removeClass(tm + '-state-disabled').attr('tabindex', '0');
+	}
+
+
+}
+
+;;
+
+fc.sourceNormalizers = [];
+fc.sourceFetchers = [];
+
+var ajaxDefaults = {
+	dataType: 'json',
+	cache: false
+};
+
+var eventGUID = 1;
+
+
+function EventManager(options, _sources) {
+	var t = this;
+	
+	
+	// exports
+	t.isFetchNeeded = isFetchNeeded;
+	t.fetchEvents = fetchEvents;
+	t.addEventSource = addEventSource;
+	t.removeEventSource = removeEventSource;
+  t.removeEventSources = removeEventSources;
+	t.updateEvent = updateEvent;
+	t.renderEvent = renderEvent;
+	t.removeEvents = removeEvents;
+	t.clientEvents = clientEvents;
+	t.normalizeEvent = normalizeEvent;
+	
+	
+	// imports
+	var trigger = t.trigger;
+	var getView = t.getView;
+	var reportEvents = t.reportEvents;
+	
+	
+	// locals
+	var stickySource = { events: [] };
+	var sources = [ stickySource ];
+	var rangeStart, rangeEnd;
+	var currentFetchID = 0;
+	var pendingSourceCnt = 0;
+	var loadingLevel = 0;
+	var cache = [];
+	
+	
+	for (var i=0; i<_sources.length; i++) {
+		_addEventSource(_sources[i]);
+	}
+	
+	
+	
+	/* Fetching
+	-----------------------------------------------------------------------------*/
+	
+	
+	function isFetchNeeded(start, end) {
+		return !rangeStart || start < rangeStart || end > rangeEnd;
+	}
+	
+	
+	function fetchEvents(start, end, src) {
+		rangeStart = start;
+		rangeEnd = end;
+		// partially clear cache if refreshing one source only (issue #1061)
+		cache = typeof src != 'undefined' ? $.grep(cache, function(e) { return !isSourcesEqual(e.source, src); }) : [];
+		var fetchID = ++currentFetchID;
+		var len = sources.length;
+		pendingSourceCnt = typeof src == 'undefined' ? len : 1;
+		for (var i=0; i<len; i++) {
+			if (typeof src == 'undefined' || isSourcesEqual(sources[i], src))
+				fetchEventSource(sources[i], fetchID);
+		}
+	}
+
+	
+	
+	function fetchEventSource(source, fetchID) {
+		_fetchEventSource(source, function(events) {
+			if (fetchID == currentFetchID) {
+				if (events) {
+
+					if (options.eventDataTransform) {
+						events = $.map(events, options.eventDataTransform);
+					}
+					if (source.eventDataTransform) {
+						events = $.map(events, source.eventDataTransform);
+					}
+					// TODO: this technique is not ideal for static array event sources.
+					//  For arrays, we'll want to process all events right in the beginning, then never again.
+				
+					for (var i=0; i<events.length; i++) {
+						events[i].source = source;
+						normalizeEvent(events[i]);
+					}
+					cache = cache.concat(events);
+				}
+				pendingSourceCnt--;
+				if (!pendingSourceCnt) {
+					reportEvents(cache);
+				}
+			}
+		});
+	}
+	
+	
+	function _fetchEventSource(source, callback) {
+		var i;
+		var fetchers = fc.sourceFetchers;
+		var res;
+		for (i=0; i<fetchers.length; i++) {
+			res = fetchers[i](source, rangeStart, rangeEnd, callback);
+			if (res === true) {
+				// the fetcher is in charge. made its own async request
+				return;
+			}
+			else if (typeof res == 'object') {
+				// the fetcher returned a new source. process it
+				_fetchEventSource(res, callback);
+				return;
+			}
+		}
+		var events = source.events;
+		if (events) {
+			if ($.isFunction(events)) {
+				pushLoading();
+				events(cloneDate(rangeStart), cloneDate(rangeEnd), function(events) {
+					callback(events);
+					popLoading();
+				});
+			}
+			else if ($.isArray(events)) {
+				callback(events);
+			}
+			else {
+				callback();
+			}
+		}else{
+			var url = source.url;
+			if (url) {
+				var success = source.success;
+				var error = source.error;
+				var complete = source.complete;
+
+				// retrieve any outbound GET/POST $.ajax data from the options
+				var customData;
+				if ($.isFunction(source.data)) {
+					// supplied as a function that returns a key/value object
+					customData = source.data();
+				}
+				else {
+					// supplied as a straight key/value object
+					customData = source.data;
+				}
+
+				// use a copy of the custom data so we can modify the parameters
+				// and not affect the passed-in object.
+				var data = $.extend({}, customData || {});
+
+				var startParam = firstDefined(source.startParam, options.startParam);
+				var endParam = firstDefined(source.endParam, options.endParam);
+				if (startParam) {
+					data[startParam] = Math.round(+rangeStart / 1000);
+				}
+				if (endParam) {
+					data[endParam] = Math.round(+rangeEnd / 1000);
+				}
+
+				pushLoading();
+				$.ajax($.extend({}, ajaxDefaults, source, {
+					data: data,
+					success: function(events) {
+						events = events || [];
+						var res = applyAll(success, this, arguments);
+						if ($.isArray(res)) {
+							events = res;
+						}
+						callback(events);
+					},
+					error: function() {
+						applyAll(error, this, arguments);
+						callback();
+					},
+					complete: function() {
+						applyAll(complete, this, arguments);
+						popLoading();
+					}
+				}));
+			}else{
+				callback();
+			}
+		}
+	}
+	
+	
+	
+	/* Sources
+	-----------------------------------------------------------------------------*/
+	
+
+	function addEventSource(source) {
+		source = _addEventSource(source);
+		if (source) {
+			pendingSourceCnt++;
+			fetchEventSource(source, currentFetchID); // will eventually call reportEvents
+		}
+	}
+	
+	
+	function _addEventSource(source) {
+		if ($.isFunction(source) || $.isArray(source)) {
+			source = { events: source };
+		}
+		else if (typeof source == 'string') {
+			source = { url: source };
+		}
+		if (typeof source == 'object') {
+			normalizeSource(source);
+			sources.push(source);
+			return source;
+		}
+	}
+	
+
+	function removeEventSource(source) {
+		sources = $.grep(sources, function(src) {
+			return !isSourcesEqual(src, source);
+		});
+		// remove all client events from that source
+		cache = $.grep(cache, function(e) {
+			return !isSourcesEqual(e.source, source);
+		});
+		reportEvents(cache);
+	}
+
+
+	function removeEventSources() {
+		sources = [];
+		removeEvents();
+	}
+	
+	
+	
+	/* Manipulation
+	-----------------------------------------------------------------------------*/
+	
+	
+	function updateEvent(event) { // update an existing event
+		var i, len = cache.length, e,
+			defaultEventEnd = getView().defaultEventEnd, // getView???
+			startDelta = event.start - event._start,
+			endDelta = event.end ?
+				(event.end - (event._end || defaultEventEnd(event))) // event._end would be null if event.end
+				: 0;                                                      // was null and event was just resized
+		for (i=0; i<len; i++) {
+			e = cache[i];
+			if (e._id == event._id && e != event) {
+				e.start = new Date(+e.start + startDelta);
+				if (event.end) {
+					if (e.end) {
+						e.end = new Date(+e.end + endDelta);
+					}else{
+						e.end = new Date(+defaultEventEnd(e) + endDelta);
+					}
+				}else{
+					e.end = null;
+				}
+				e.title = event.title;
+				e.url = event.url;
+				e.allDay = event.allDay;
+				e.className = event.className;
+				e.editable = event.editable;
+				e.color = event.color;
+				e.backgroundColor = event.backgroundColor;
+				e.borderColor = event.borderColor;
+				e.textColor = event.textColor;
+				normalizeEvent(e);
+			}
+		}
+		normalizeEvent(event);
+		reportEvents(cache);
+	}
+	
+	
+	function renderEvent(event, stick) {
+		normalizeEvent(event);
+		if (!event.source) {
+			if (stick) {
+				stickySource.events.push(event);
+				event.source = stickySource;
+			}
+		}
+		// always push event to cache (issue #1112:)
+		cache.push(event);
+		reportEvents(cache);
+	}
+	
+	
+	function removeEvents(filter) {
+		if (!filter) { // remove all
+			cache = [];
+			// clear all array sources
+			for (var i=0; i<sources.length; i++) {
+				if ($.isArray(sources[i].events)) {
+					sources[i].events = [];
+				}
+			}
+		}else{
+			if (!$.isFunction(filter)) { // an event ID
+				var id = filter + '';
+				filter = function(e) {
+					return e._id == id;
+				};
+			}
+			cache = $.grep(cache, filter, true);
+			// remove events from array sources
+			for (var i=0; i<sources.length; i++) {
+				if ($.isArray(sources[i].events)) {
+					sources[i].events = $.grep(sources[i].events, filter, true);
+				}
+			}
+		}
+		reportEvents(cache);
+	}
+	
+	
+	function clientEvents(filter) {
+		if ($.isFunction(filter)) {
+			return $.grep(cache, filter);
+		}
+		else if (filter) { // an event ID
+			filter += '';
+			return $.grep(cache, function(e) {
+				return e._id == filter;
+			});
+		}
+		return cache; // else, return all
+	}
+	
+	
+	
+	/* Loading State
+	-----------------------------------------------------------------------------*/
+	
+	
+	function pushLoading() {
+		if (!loadingLevel++) {
+			trigger('loading', null, true, getView());
+		}
+	}
+	
+	
+	function popLoading() {
+		if (!--loadingLevel) {
+			trigger('loading', null, false, getView());
+		}
+	}
+	
+	
+	
+	/* Event Normalization
+	-----------------------------------------------------------------------------*/
+	
+	
+	function normalizeEvent(event) {
+		var source = event.source || {};
+		var ignoreTimezone = firstDefined(source.ignoreTimezone, options.ignoreTimezone);
+		event._id = event._id || (event.id === undefined ? '_fc' + eventGUID++ : event.id + '');
+		if (event.date) {
+			if (!event.start) {
+				event.start = event.date;
+			}
+			delete event.date;
+		}
+		event._start = cloneDate(event.start = parseDate(event.start, ignoreTimezone));
+		event.end = parseDate(event.end, ignoreTimezone);
+		if (event.end && event.end <= event.start) {
+			event.end = null;
+		}
+		event._end = event.end ? cloneDate(event.end) : null;
+		if (event.allDay === undefined) {
+			event.allDay = firstDefined(source.allDayDefault, options.allDayDefault);
+		}
+		if (event.className) {
+			if (typeof event.className == 'string') {
+				event.className = event.className.split(/\s+/);
+			}
+		}else{
+			event.className = [];
+		}
+		// TODO: if there is no start date, return false to indicate an invalid event
+	}
+	
+	
+	
+	/* Utils
+	------------------------------------------------------------------------------*/
+	
+	
+	function normalizeSource(source) {
+		if (source.className) {
+			// TODO: repeat code, same code for event classNames
+			if (typeof source.className == 'string') {
+				source.className = source.className.split(/\s+/);
+			}
+		}else{
+			source.className = [];
+		}
+		var normalizers = fc.sourceNormalizers;
+		for (var i=0; i<normalizers.length; i++) {
+			normalizers[i](source);
+		}
+	}
+	
+	
+	function isSourcesEqual(source1, source2) {
+		return source1 && source2 && getSourcePrimitive(source1) == getSourcePrimitive(source2);
+	}
+	
+	
+	function getSourcePrimitive(source) {
+		return ((typeof source == 'object') ? (source.events || source.url) : '') || source;
+	}
+
+
+}
+
+;;
+
+
+fc.addDays = addDays;
+fc.cloneDate = cloneDate;
+fc.parseDate = parseDate;
+fc.parseISO8601 = parseISO8601;
+fc.parseTime = parseTime;
+fc.formatDate = formatDate;
+fc.formatDates = formatDates;
+
+
+
+/* Date Math
+-----------------------------------------------------------------------------*/
+
+var dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'],
+	DAY_MS = 86400000,
+	HOUR_MS = 3600000,
+	MINUTE_MS = 60000;
+	
+
+function addYears(d, n, keepTime) {
+	d.setFullYear(d.getFullYear() + n);
+	if (!keepTime) {
+		clearTime(d);
+	}
+	return d;
+}
+
+
+function addMonths(d, n, keepTime) { // prevents day overflow/underflow
+	if (+d) { // prevent infinite looping on invalid dates
+		var m = d.getMonth() + n,
+			check = cloneDate(d);
+		check.setDate(1);
+		check.setMonth(m);
+		d.setMonth(m);
+		if (!keepTime) {
+			clearTime(d);
+		}
+		while (d.getMonth() != check.getMonth()) {
+			d.setDate(d.getDate() + (d < check ? 1 : -1));
+		}
+	}
+	return d;
+}
+
+
+function addDays(d, n, keepTime) { // deals with daylight savings
+	if (+d) {
+		var dd = d.getDate() + n,
+			check = cloneDate(d);
+		check.setHours(9); // set to middle of day
+		check.setDate(dd);
+		d.setDate(dd);
+		if (!keepTime) {
+			clearTime(d);
+		}
+		fixDate(d, check);
+	}
+	return d;
+}
+
+
+function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
+	if (+d) { // prevent infinite looping on invalid dates
+		while (d.getDate() != check.getDate()) {
+			d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
+		}
+	}
+}
+
+
+function addMinutes(d, n) {
+	d.setMinutes(d.getMinutes() + n);
+	return d;
+}
+
+
+function clearTime(d) {
+	d.setHours(0);
+	d.setMinutes(0);
+	d.setSeconds(0); 
+	d.setMilliseconds(0);
+	return d;
+}
+
+
+function cloneDate(d, dontKeepTime) {
+	if (dontKeepTime) {
+		return clearTime(new Date(+d));
+	}
+	return new Date(+d);
+}
+
+
+function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
+	var i=0, d;
+	do {
+		d = new Date(1970, i++, 1);
+	} while (d.getHours()); // != 0
+	return d;
+}
+
+
+function dayDiff(d1, d2) { // d1 - d2
+	return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
+}
+
+
+function setYMD(date, y, m, d) {
+	if (y !== undefined && y != date.getFullYear()) {
+		date.setDate(1);
+		date.setMonth(0);
+		date.setFullYear(y);
+	}
+	if (m !== undefined && m != date.getMonth()) {
+		date.setDate(1);
+		date.setMonth(m);
+	}
+	if (d !== undefined) {
+		date.setDate(d);
+	}
+}
+
+
+
+/* Date Parsing
+-----------------------------------------------------------------------------*/
+
+
+function parseDate(s, ignoreTimezone) { // ignoreTimezone defaults to true
+	if (typeof s == 'object') { // already a Date object
+		return s;
+	}
+	if (typeof s == 'number') { // a UNIX timestamp
+		return new Date(s * 1000);
+	}
+	if (typeof s == 'string') {
+		if (s.match(/^\d+(\.\d+)?$/)) { // a UNIX timestamp
+			return new Date(parseFloat(s) * 1000);
+		}
+		if (ignoreTimezone === undefined) {
+			ignoreTimezone = true;
+		}
+		return parseISO8601(s, ignoreTimezone) || (s ? new Date(s) : null);
+	}
+	// TODO: never return invalid dates (like from new Date(<string>)), return null instead
+	return null;
+}
+
+
+function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false
+	// derived from http://delete.me.uk/2005/03/iso8601.html
+	// TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
+	var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);
+	if (!m) {
+		return null;
+	}
+	var date = new Date(m[1], 0, 2);
+	if (ignoreTimezone || !m[13]) {
+		var check = new Date(m[1], 0, 2, 9, 0);
+		if (m[3]) {
+			date.setMonth(m[3] - 1);
+			check.setMonth(m[3] - 1);
+		}
+		if (m[5]) {
+			date.setDate(m[5]);
+			check.setDate(m[5]);
+		}
+		fixDate(date, check);
+		if (m[7]) {
+			date.setHours(m[7]);
+		}
+		if (m[8]) {
+			date.setMinutes(m[8]);
+		}
+		if (m[10]) {
+			date.setSeconds(m[10]);
+		}
+		if (m[12]) {
+			date.setMilliseconds(Number("0." + m[12]) * 1000);
+		}
+		fixDate(date, check);
+	}else{
+		date.setUTCFullYear(
+			m[1],
+			m[3] ? m[3] - 1 : 0,
+			m[5] || 1
+		);
+		date.setUTCHours(
+			m[7] || 0,
+			m[8] || 0,
+			m[10] || 0,
+			m[12] ? Number("0." + m[12]) * 1000 : 0
+		);
+		if (m[14]) {
+			var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0);
+			offset *= m[15] == '-' ? 1 : -1;
+			date = new Date(+date + (offset * 60 * 1000));
+		}
+	}
+	return date;
+}
+
+
+function parseTime(s) { // returns minutes since start of day
+	if (typeof s == 'number') { // an hour
+		return s * 60;
+	}
+	if (typeof s == 'object') { // a Date object
+		return s.getHours() * 60 + s.getMinutes();
+	}
+	var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
+	if (m) {
+		var h = parseInt(m[1], 10);
+		if (m[3]) {
+			h %= 12;
+			if (m[3].toLowerCase().charAt(0) == 'p') {
+				h += 12;
+			}
+		}
+		return h * 60 + (m[2] ? parseInt(m[2], 10) : 0);
+	}
+}
+
+
+
+/* Date Formatting
+-----------------------------------------------------------------------------*/
+// TODO: use same function formatDate(date, [date2], format, [options])
+
+
+function formatDate(date, format, options) {
+	return formatDates(date, null, format, options);
+}
+
+
+function formatDates(date1, date2, format, options) {
+	options = options || defaults;
+	var date = date1,
+		otherDate = date2,
+		i, len = format.length, c,
+		i2, formatter,
+		res = '';
+	for (i=0; i<len; i++) {
+		c = format.charAt(i);
+		if (c == "'") {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == "'") {
+					if (date) {
+						if (i2 == i+1) {
+							res += "'";
+						}else{
+							res += format.substring(i+1, i2);
+						}
+						i = i2;
+					}
+					break;
+				}
+			}
+		}
+		else if (c == '(') {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == ')') {
+					var subres = formatDate(date, format.substring(i+1, i2), options);
+					if (parseInt(subres.replace(/\D/, ''), 10)) {
+						res += subres;
+					}
+					i = i2;
+					break;
+				}
+			}
+		}
+		else if (c == '[') {
+			for (i2=i+1; i2<len; i2++) {
+				if (format.charAt(i2) == ']') {
+					var subformat = format.substring(i+1, i2);
+					var subres = formatDate(date, subformat, options);
+					if (subres != formatDate(otherDate, subformat, options)) {
+						res += subres;
+					}
+					i = i2;
+					break;
+				}
+			}
+		}
+		else if (c == '{') {
+			date = date2;
+			otherDate = date1;
+		}
+		else if (c == '}') {
+			date = date1;
+			otherDate = date2;
+		}
+		else {
+			for (i2=len; i2>i; i2--) {
+				if (formatter = dateFormatters[format.substring(i, i2)]) {
+					if (date) {
+						res += formatter(date, options);
+					}
+					i = i2 - 1;
+					break;
+				}
+			}
+			if (i2 == i) {
+				if (date) {
+					res += c;
+				}
+			}
+		}
+	}
+	return res;
+};
+
+
+var dateFormatters = {
+	s	: function(d)	{ return d.getSeconds() },
+	ss	: function(d)	{ return zeroPad(d.getSeconds()) },
+	m	: function(d)	{ return d.getMinutes() },
+	mm	: function(d)	{ return zeroPad(d.getMinutes()) },
+	h	: function(d)	{ return d.getHours() % 12 || 12 },
+	hh	: function(d)	{ return zeroPad(d.getHours() % 12 || 12) },
+	H	: function(d)	{ return d.getHours() },
+	HH	: function(d)	{ return zeroPad(d.getHours()) },
+	d	: function(d)	{ return d.getDate() },
+	dd	: function(d)	{ return zeroPad(d.getDate()) },
+	ddd	: function(d,o)	{ return o.dayNamesShort[d.getDay()] },
+	dddd: function(d,o)	{ return o.dayNames[d.getDay()] },
+	M	: function(d)	{ return d.getMonth() + 1 },
+	MM	: function(d)	{ return zeroPad(d.getMonth() + 1) },
+	MMM	: function(d,o)	{ return o.monthNamesShort[d.getMonth()] },
+	MMMM: function(d,o)	{ return o.monthNames[d.getMonth()] },
+	yy	: function(d)	{ return (d.getFullYear()+'').substring(2) },
+	yyyy: function(d)	{ return d.getFullYear() },
+	t	: function(d)	{ return d.getHours() < 12 ? 'a' : 'p' },
+	tt	: function(d)	{ return d.getHours() < 12 ? 'am' : 'pm' },
+	T	: function(d)	{ return d.getHours() < 12 ? 'A' : 'P' },
+	TT	: function(d)	{ return d.getHours() < 12 ? 'AM' : 'PM' },
+	u	: function(d)	{ return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
+	S	: function(d)	{
+		var date = d.getDate();
+		if (date > 10 && date < 20) {
+			return 'th';
+		}
+		return ['st', 'nd', 'rd'][date%10-1] || 'th';
+	},
+	w   : function(d, o) { // local
+		return o.weekNumberCalculation(d);
+	},
+	W   : function(d) { // ISO
+		return iso8601Week(d);
+	}
+};
+fc.dateFormatters = dateFormatters;
+
+
+/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js)
+ * 
+ * Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+ * `date` - the date to get the week for
+ * `number` - the number of the week within the year that contains this date
+ */
+function iso8601Week(date) {
+	var time;
+	var checkDate = new Date(date.getTime());
+
+	// Find Thursday of this week starting on Monday
+	checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+
+	time = checkDate.getTime();
+	checkDate.setMonth(0); // Compare with Jan 1
+	checkDate.setDate(1);
+	return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+}
+
+// Determine the week of the year based on the ISO 8601 definition.
+// copied from jquery UI Datepicker
+var iso8601Week = function(date) {
+	var checkDate = cloneDate(date);
+	// Find Thursday of this week starting on Monday
+	checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
+	var time = checkDate.getTime();
+	checkDate.setMonth(0); // Compare with Jan 1
+	checkDate.setDate(1);
+	return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
+};
+
+
+;;
+
+fc.applyAll = applyAll;
+
+
+/* Event Date Math
+-----------------------------------------------------------------------------*/
+
+
+function exclEndDay(event) {
+	if (event.end) {
+		return _exclEndDay(event.end, event.allDay);
+	}else{
+		return addDays(cloneDate(event.start), 1);
+	}
+}
+
+
+function _exclEndDay(end, allDay) {
+	end = cloneDate(end);
+	return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end);
+	// why don't we check for seconds/ms too?
+}
+
+
+
+/* Event Element Binding
+-----------------------------------------------------------------------------*/
+
+
+function lazySegBind(container, segs, bindHandlers) {
+	container.unbind('mouseover focusin').bind('mouseover focusin', function(ev) {
+		var parent=ev.target, e,
+			i, seg;
+		while (parent != this) {
+			e = parent;
+			parent = parent.parentNode;
+		}
+		if ((i = e._fci) !== undefined) {
+			e._fci = undefined;
+			seg = segs[i];
+			bindHandlers(seg.event, seg.element, seg);
+			$(ev.target).trigger(ev);
+		}
+		ev.stopPropagation();
+	});
+}
+
+
+
+/* Element Dimensions
+-----------------------------------------------------------------------------*/
+
+
+function setOuterWidth(element, width, includeMargins) {
+	for (var i=0, e; i<element.length; i++) {
+		e = $(element[i]);
+		e.width(Math.max(0, width - hsides(e, includeMargins)));
+	}
+}
+
+
+function setOuterHeight(element, height, includeMargins) {
+	for (var i=0, e; i<element.length; i++) {
+		e = $(element[i]);
+		e.height(Math.max(0, height - vsides(e, includeMargins)));
+	}
+}
+
+
+function hsides(element, includeMargins) {
+	return hpadding(element) + hborders(element) + (includeMargins ? hmargins(element) : 0);
+}
+
+
+function hpadding(element) {
+	return (parseFloat($.css(element[0], 'paddingLeft', true)) || 0) +
+	       (parseFloat($.css(element[0], 'paddingRight', true)) || 0);
+}
+
+
+function hmargins(element) {
+	return (parseFloat($.css(element[0], 'marginLeft', true)) || 0) +
+	       (parseFloat($.css(element[0], 'marginRight', true)) || 0);
+}
+
+
+function hborders(element) {
+	return (parseFloat($.css(element[0], 'borderLeftWidth', true)) || 0) +
+	       (parseFloat($.css(element[0], 'borderRightWidth', true)) || 0);
+}
+
+
+function vsides(element, includeMargins) {
+	return vpadding(element) +  vborders(element) + (includeMargins ? vmargins(element) : 0);
+}
+
+
+function vpadding(element) {
+	return (parseFloat($.css(element[0], 'paddingTop', true)) || 0) +
+	       (parseFloat($.css(element[0], 'paddingBottom', true)) || 0);
+}
+
+
+function vmargins(element) {
+	return (parseFloat($.css(element[0], 'marginTop', true)) || 0) +
+	       (parseFloat($.css(element[0], 'marginBottom', true)) || 0);
+}
+
+
+function vborders(element) {
+	return (parseFloat($.css(element[0], 'borderTopWidth', true)) || 0) +
+	       (parseFloat($.css(element[0], 'borderBottomWidth', true)) || 0);
+}
+
+
+
+/* Misc Utils
+-----------------------------------------------------------------------------*/
+
+
+//TODO: arraySlice
+//TODO: isFunction, grep ?
+
+
+function noop() { }
+
+
+function dateCompare(a, b) {
+	return a - b;
+}
+
+
+function arrayMax(a) {
+	return Math.max.apply(Math, a);
+}
+
+
+function zeroPad(n) {
+	return (n < 10 ? '0' : '') + n;
+}
+
+
+function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
+	if (obj[name] !== undefined) {
+		return obj[name];
+	}
+	var parts = name.split(/(?=[A-Z])/),
+		i=parts.length-1, res;
+	for (; i>=0; i--) {
+		res = obj[parts[i].toLowerCase()];
+		if (res !== undefined) {
+			return res;
+		}
+	}
+	return obj[''];
+}
+
+
+function htmlEscape(s) {
+	return s.replace(/&/g, '&amp;')
+		.replace(/</g, '&lt;')
+		.replace(/>/g, '&gt;')
+		.replace(/'/g, '&#039;')
+		.replace(/"/g, '&quot;')
+		.replace(/\n/g, '<br />');
+}
+
+
+function disableTextSelection(element) {
+	element
+		.attr('unselectable', 'on')
+		.css('MozUserSelect', 'none')
+		.bind('selectstart.ui', function() { return false; });
+}
+
+
+/*
+function enableTextSelection(element) {
+	element
+		.attr('unselectable', 'off')
+		.css('MozUserSelect', '')
+		.unbind('selectstart.ui');
+}
+*/
+
+
+function markFirstLast(e) {
+	e.children()
+		.removeClass('fc-first fc-last')
+		.filter(':first-child')
+			.addClass('fc-first')
+		.end()
+		.filter(':last-child')
+			.addClass('fc-last');
+}
+
+
+function setDayID(cell, date) {
+	cell.each(function(i, _cell) {
+		_cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]);
+		// TODO: make a way that doesn't rely on order of classes
+	});
+}
+
+
+function getSkinCss(event, opt) {
+	var source = event.source || {};
+	var eventColor = event.color;
+	var sourceColor = source.color;
+	var optionColor = opt('eventColor');
+	var backgroundColor =
+		event.backgroundColor ||
+		eventColor ||
+		source.backgroundColor ||
+		sourceColor ||
+		opt('eventBackgroundColor') ||
+		optionColor;
+	var borderColor =
+		event.borderColor ||
+		eventColor ||
+		source.borderColor ||
+		sourceColor ||
+		opt('eventBorderColor') ||
+		optionColor;
+	var textColor =
+		event.textColor ||
+		source.textColor ||
+		opt('eventTextColor');
+	var statements = [];
+	if (backgroundColor) {
+		statements.push('background-color:' + backgroundColor);
+	}
+	if (borderColor) {
+		statements.push('border-color:' + borderColor);
+	}
+	if (textColor) {
+		statements.push('color:' + textColor);
+	}
+	return statements.join(';');
+}
+
+
+function applyAll(functions, thisObj, args) {
+	if ($.isFunction(functions)) {
+		functions = [ functions ];
+	}
+	if (functions) {
+		var i;
+		var ret;
+		for (i=0; i<functions.length; i++) {
+			ret = functions[i].apply(thisObj, args) || ret;
+		}
+		return ret;
+	}
+}
+
+
+function firstDefined() {
+	for (var i=0; i<arguments.length; i++) {
+		if (arguments[i] !== undefined) {
+			return arguments[i];
+		}
+	}
+}
+
+
+;;
+
+fcViews.month = MonthView;
+
+function MonthView(element, calendar) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	
+	
+	// imports
+	BasicView.call(t, element, calendar, 'month');
+	var opt = t.opt;
+	var renderBasic = t.renderBasic;
+	var skipHiddenDays = t.skipHiddenDays;
+	var getCellsPerWeek = t.getCellsPerWeek;
+	var formatDate = calendar.formatDate;
+	
+	
+	function render(date, delta) {
+
+		if (delta) {
+			addMonths(date, delta);
+			date.setDate(1);
+		}
+
+		var firstDay = opt('firstDay');
+
+		var start = cloneDate(date, true);
+		start.setDate(1);
+
+		var end = addMonths(cloneDate(start), 1);
+
+		var visStart = cloneDate(start);
+		addDays(visStart, -((visStart.getDay() - firstDay + 7) % 7));
+		skipHiddenDays(visStart);
+
+		var visEnd = cloneDate(end);
+		addDays(visEnd, (7 - visEnd.getDay() + firstDay) % 7);
+		skipHiddenDays(visEnd, -1, true);
+
+		var colCnt = getCellsPerWeek();
+		var rowCnt = Math.round(dayDiff(visEnd, visStart) / 7); // should be no need for Math.round
+
+		if (opt('weekMode') == 'fixed') {
+			addDays(visEnd, (6 - rowCnt) * 7); // add weeks to make up for it
+			rowCnt = 6;
+		}
+
+		t.title = formatDate(start, opt('titleFormat'));
+
+		t.start = start;
+		t.end = end;
+		t.visStart = visStart;
+		t.visEnd = visEnd;
+
+		renderBasic(rowCnt, colCnt, true);
+	}
+	
+	
+}
+
+;;
+
+fcViews.basicWeek = BasicWeekView;
+
+function BasicWeekView(element, calendar) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	
+	
+	// imports
+	BasicView.call(t, element, calendar, 'basicWeek');
+	var opt = t.opt;
+	var renderBasic = t.renderBasic;
+	var skipHiddenDays = t.skipHiddenDays;
+	var getCellsPerWeek = t.getCellsPerWeek;
+	var formatDates = calendar.formatDates;
+	
+	
+	function render(date, delta) {
+
+		if (delta) {
+			addDays(date, delta * 7);
+		}
+
+		var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
+		var end = addDays(cloneDate(start), 7);
+
+		var visStart = cloneDate(start);
+		skipHiddenDays(visStart);
+
+		var visEnd = cloneDate(end);
+		skipHiddenDays(visEnd, -1, true);
+
+		var colCnt = getCellsPerWeek();
+
+		t.start = start;
+		t.end = end;
+		t.visStart = visStart;
+		t.visEnd = visEnd;
+
+		t.title = formatDates(
+			visStart,
+			addDays(cloneDate(visEnd), -1),
+			opt('titleFormat')
+		);
+
+		renderBasic(1, colCnt, false);
+	}
+	
+	
+}
+
+;;
+
+fcViews.basicDay = BasicDayView;
+
+
+function BasicDayView(element, calendar) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	
+	
+	// imports
+	BasicView.call(t, element, calendar, 'basicDay');
+	var opt = t.opt;
+	var renderBasic = t.renderBasic;
+	var skipHiddenDays = t.skipHiddenDays;
+	var formatDate = calendar.formatDate;
+	
+	
+	function render(date, delta) {
+
+		if (delta) {
+			addDays(date, delta);
+		}
+		skipHiddenDays(date, delta < 0 ? -1 : 1);
+
+		var start = cloneDate(date, true);
+		var end = addDays(cloneDate(start), 1);
+
+		t.title = formatDate(date, opt('titleFormat'));
+
+		t.start = t.visStart = start;
+		t.end = t.visEnd = end;
+
+		renderBasic(1, 1, false);
+	}
+	
+	
+}
+
+;;
+
+setDefaults({
+	weekMode: 'fixed'
+});
+
+
+function BasicView(element, calendar, viewName) {
+	var t = this;
+	
+	
+	// exports
+	t.renderBasic = renderBasic;
+	t.setHeight = setHeight;
+	t.setWidth = setWidth;
+	t.renderDayOverlay = renderDayOverlay;
+	t.defaultSelectionEnd = defaultSelectionEnd;
+	t.renderSelection = renderSelection;
+	t.clearSelection = clearSelection;
+	t.reportDayClick = reportDayClick; // for selection (kinda hacky)
+	t.dragStart = dragStart;
+	t.dragStop = dragStop;
+	t.defaultEventEnd = defaultEventEnd;
+	t.getHoverListener = function() { return hoverListener };
+	t.colLeft = colLeft;
+	t.colRight = colRight;
+	t.colContentLeft = colContentLeft;
+	t.colContentRight = colContentRight;
+	t.getIsCellAllDay = function() { return true };
+	t.allDayRow = allDayRow;
+	t.getRowCnt = function() { return rowCnt };
+	t.getColCnt = function() { return colCnt };
+	t.getColWidth = function() { return colWidth };
+	t.getDaySegmentContainer = function() { return daySegmentContainer };
+	
+	
+	// imports
+	View.call(t, element, calendar, viewName);
+	OverlayManager.call(t);
+	SelectionManager.call(t);
+	BasicEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var renderOverlay = t.renderOverlay;
+	var clearOverlays = t.clearOverlays;
+	var daySelectionMousedown = t.daySelectionMousedown;
+	var cellToDate = t.cellToDate;
+	var dateToCell = t.dateToCell;
+	var rangeToSegments = t.rangeToSegments;
+	var formatDate = calendar.formatDate;
+	
+	
+	// locals
+	
+	var table;
+	var head;
+	var headCells;
+	var body;
+	var bodyRows;
+	var bodyCells;
+	var bodyFirstCells;
+	var firstRowCellInners;
+	var firstRowCellContentInners;
+	var daySegmentContainer;
+	
+	var viewWidth;
+	var viewHeight;
+	var colWidth;
+	var weekNumberWidth;
+	
+	var rowCnt, colCnt;
+	var showNumbers;
+	var coordinateGrid;
+	var hoverListener;
+	var colPositions;
+	var colContentPositions;
+	
+	var tm;
+	var colFormat;
+	var showWeekNumbers;
+	var weekNumberTitle;
+	var weekNumberFormat;
+	
+	
+	
+	/* Rendering
+	------------------------------------------------------------*/
+	
+	
+	disableTextSelection(element.addClass('fc-grid'));
+	
+	
+	function renderBasic(_rowCnt, _colCnt, _showNumbers) {
+		rowCnt = _rowCnt;
+		colCnt = _colCnt;
+		showNumbers = _showNumbers;
+		updateOptions();
+
+		if (!body) {
+			buildEventContainer();
+		}
+
+		buildTable();
+	}
+	
+	
+	function updateOptions() {
+		tm = opt('theme') ? 'ui' : 'fc';
+		colFormat = opt('columnFormat');
+
+		// week # options. (TODO: bad, logic also in other views)
+		showWeekNumbers = opt('weekNumbers');
+		weekNumberTitle = opt('weekNumberTitle');
+		if (opt('weekNumberCalculation') != 'iso') {
+			weekNumberFormat = "w";
+		}
+		else {
+			weekNumberFormat = "W";
+		}
+	}
+	
+	
+	function buildEventContainer() {
+		daySegmentContainer =
+			$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
+				.appendTo(element);
+	}
+	
+	
+	function buildTable() {
+		var html = buildTableHTML();
+
+		if (table) {
+			table.remove();
+		}
+		table = $(html).appendTo(element);
+
+		head = table.find('thead');
+		headCells = head.find('.fc-day-header');
+		body = table.find('tbody');
+		bodyRows = body.find('tr');
+		bodyCells = body.find('.fc-day');
+		bodyFirstCells = bodyRows.find('td:first-child');
+
+		firstRowCellInners = bodyRows.eq(0).find('.fc-day > div');
+		firstRowCellContentInners = bodyRows.eq(0).find('.fc-day-content > div');
+		
+		markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's
+		markFirstLast(bodyRows); // marks first+last td's
+		bodyRows.eq(0).addClass('fc-first');
+		bodyRows.filter(':last').addClass('fc-last');
+
+		bodyCells.each(function(i, _cell) {
+			var date = cellToDate(
+				Math.floor(i / colCnt),
+				i % colCnt
+			);
+			trigger('dayRender', t, date, $(_cell));
+		});
+
+		dayBind(bodyCells);
+	}
+
+
+
+	/* HTML Building
+	-----------------------------------------------------------*/
+
+
+	function buildTableHTML() {
+		var html =
+			"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
+			buildHeadHTML() +
+			buildBodyHTML() +
+			"</table>";
+
+		return html;
+	}
+
+
+	function buildHeadHTML() {
+		var headerClass = tm + "-widget-header";
+		var html = '';
+		var col;
+		var date;
+
+		html += "<thead><tr>";
+
+		if (showWeekNumbers) {
+			html +=
+				"<th class='fc-week-number " + headerClass + "'>" +
+				htmlEscape(weekNumberTitle) +
+				"</th>";
+		}
+
+		for (col=0; col<colCnt; col++) {
+			date = cellToDate(0, col);
+			html +=
+				"<th class='fc-day-header fc-" + dayIDs[date.getDay()] + " " + headerClass + "'>" +
+				htmlEscape(formatDate(date, colFormat)) +
+				"</th>";
+		}
+
+		html += "</tr></thead>";
+
+		return html;
+	}
+
+
+	function buildBodyHTML() {
+		var contentClass = tm + "-widget-content";
+		var html = '';
+		var row;
+		var col;
+		var date;
+
+		html += "<tbody>";
+
+		for (row=0; row<rowCnt; row++) {
+
+			html += "<tr class='fc-week'>";
+
+			if (showWeekNumbers) {
+				date = cellToDate(row, 0);
+				html +=
+					"<td class='fc-week-number " + contentClass + "'>" +
+					"<div>" +
+					htmlEscape(formatDate(date, weekNumberFormat)) +
+					"</div>" +
+					"</td>";
+			}
+
+			for (col=0; col<colCnt; col++) {
+				date = cellToDate(row, col);
+				html += buildCellHTML(date);
+			}
+
+			html += "</tr>";
+		}
+
+		html += "</tbody>";
+
+		return html;
+	}
+
+
+	function buildCellHTML(date) {
+		var contentClass = tm + "-widget-content";
+		var month = t.start.getMonth();
+		var today = clearTime(new Date());
+		var html = '';
+		var classNames = [
+			'fc-day',
+			'fc-' + dayIDs[date.getDay()],
+			contentClass
+		];
+
+		if (date.getMonth() != month) {
+			classNames.push('fc-other-month');
+		}
+		if (+date == +today) {
+			classNames.push(
+				'fc-today',
+				tm + '-state-highlight'
+			);
+		}
+		else if (date < today) {
+			classNames.push('fc-past');
+		}
+		else {
+			classNames.push('fc-future');
+		}
+
+		html +=
+			"<td" +
+			" class='" + classNames.join(' ') + "'" +
+			" data-date='" + formatDate(date, 'yyyy-MM-dd') + "'" +
+			">" +
+			"<div>";
+
+		if (showNumbers) {
+			html += "<div class='fc-day-number'>" + date.getDate() + "</div>";
+		}
+
+		html +=
+			"<div class='fc-day-content'>" +
+			"<div style='position:relative'>&nbsp;</div>" +
+			"</div>" +
+			"</div>" +
+			"</td>";
+
+		return html;
+	}
+
+
+
+	/* Dimensions
+	-----------------------------------------------------------*/
+	
+	
+	function setHeight(height) {
+		viewHeight = height;
+		
+		var bodyHeight = viewHeight - head.height();
+		var rowHeight;
+		var rowHeightLast;
+		var cell;
+			
+		if (opt('weekMode') == 'variable') {
+			rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6));
+		}else{
+			rowHeight = Math.floor(bodyHeight / rowCnt);
+			rowHeightLast = bodyHeight - rowHeight * (rowCnt-1);
+		}
+		
+		bodyFirstCells.each(function(i, _cell) {
+			if (i < rowCnt) {
+				cell = $(_cell);
+				cell.find('> div').css(
+					'min-height',
+					(i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell)
+				);
+			}
+		});
+		
+	}
+	
+	
+	function setWidth(width) {
+		viewWidth = width;
+		colPositions.clear();
+		colContentPositions.clear();
+
+		weekNumberWidth = 0;
+		if (showWeekNumbers) {
+			weekNumberWidth = head.find('th.fc-week-number').outerWidth();
+		}
+
+		colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt);
+		setOuterWidth(headCells.slice(0, -1), colWidth);
+	}
+	
+	
+	
+	/* Day clicking and binding
+	-----------------------------------------------------------*/
+	
+	
+	function dayBind(days) {
+		days.click(dayClick)
+			.mousedown(daySelectionMousedown);
+	}
+	
+	
+	function dayClick(ev) {
+		if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
+			var date = parseISO8601($(this).data('date'));
+			trigger('dayClick', this, date, true, ev);
+		}
+	}
+	
+	
+	
+	/* Semi-transparent Overlay Helpers
+	------------------------------------------------------*/
+	// TODO: should be consolidated with AgendaView's methods
+
+
+	function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
+
+		if (refreshCoordinateGrid) {
+			coordinateGrid.build();
+		}
+
+		var segments = rangeToSegments(overlayStart, overlayEnd);
+
+		for (var i=0; i<segments.length; i++) {
+			var segment = segments[i];
+			dayBind(
+				renderCellOverlay(
+					segment.row,
+					segment.leftCol,
+					segment.row,
+					segment.rightCol
+				)
+			);
+		}
+	}
+
+	
+	function renderCellOverlay(row0, col0, row1, col1) { // row1,col1 is inclusive
+		var rect = coordinateGrid.rect(row0, col0, row1, col1, element);
+		return renderOverlay(rect, element);
+	}
+	
+	
+	
+	/* Selection
+	-----------------------------------------------------------------------*/
+	
+	
+	function defaultSelectionEnd(startDate, allDay) {
+		return cloneDate(startDate);
+	}
+	
+	
+	function renderSelection(startDate, endDate, allDay) {
+		renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); // rebuild every time???
+	}
+	
+	
+	function clearSelection() {
+		clearOverlays();
+	}
+	
+	
+	function reportDayClick(date, allDay, ev) {
+		var cell = dateToCell(date);
+		var _element = bodyCells[cell.row*colCnt + cell.col];
+		trigger('dayClick', _element, date, allDay, ev);
+	}
+	
+	
+	
+	/* External Dragging
+	-----------------------------------------------------------------------*/
+	
+	
+	function dragStart(_dragElement, ev, ui) {
+		hoverListener.start(function(cell) {
+			clearOverlays();
+			if (cell) {
+				renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
+			}
+		}, ev);
+	}
+	
+	
+	function dragStop(_dragElement, ev, ui) {
+		var cell = hoverListener.stop();
+		clearOverlays();
+		if (cell) {
+			var d = cellToDate(cell);
+			trigger('drop', _dragElement, d, true, ev, ui);
+		}
+	}
+	
+	
+	
+	/* Utilities
+	--------------------------------------------------------*/
+	
+	
+	function defaultEventEnd(event) {
+		return cloneDate(event.start);
+	}
+	
+	
+	coordinateGrid = new CoordinateGrid(function(rows, cols) {
+		var e, n, p;
+		headCells.each(function(i, _e) {
+			e = $(_e);
+			n = e.offset().left;
+			if (i) {
+				p[1] = n;
+			}
+			p = [n];
+			cols[i] = p;
+		});
+		p[1] = n + e.outerWidth();
+		bodyRows.each(function(i, _e) {
+			if (i < rowCnt) {
+				e = $(_e);
+				n = e.offset().top;
+				if (i) {
+					p[1] = n;
+				}
+				p = [n];
+				rows[i] = p;
+			}
+		});
+		p[1] = n + e.outerHeight();
+	});
+	
+	
+	hoverListener = new HoverListener(coordinateGrid);
+	
+	colPositions = new HorizontalPositionCache(function(col) {
+		return firstRowCellInners.eq(col);
+	});
+
+	colContentPositions = new HorizontalPositionCache(function(col) {
+		return firstRowCellContentInners.eq(col);
+	});
+
+
+	function colLeft(col) {
+		return colPositions.left(col);
+	}
+
+
+	function colRight(col) {
+		return colPositions.right(col);
+	}
+	
+	
+	function colContentLeft(col) {
+		return colContentPositions.left(col);
+	}
+	
+	
+	function colContentRight(col) {
+		return colContentPositions.right(col);
+	}
+	
+	
+	function allDayRow(i) {
+		return bodyRows.eq(i);
+	}
+	
+}
+
+;;
+
+function BasicEventRenderer() {
+	var t = this;
+	
+	
+	// exports
+	t.renderEvents = renderEvents;
+	t.clearEvents = clearEvents;
+	
+
+	// imports
+	DayEventRenderer.call(t);
+
+	
+	function renderEvents(events, modifiedEventId) {
+		t.renderDayEvents(events, modifiedEventId);
+	}
+	
+	
+	function clearEvents() {
+		t.getDaySegmentContainer().empty();
+	}
+
+
+	// TODO: have this class (and AgendaEventRenderer) be responsible for creating the event container div
+
+}
+
+;;
+
+fcViews.agendaWeek = AgendaWeekView;
+
+function AgendaWeekView(element, calendar) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	
+	
+	// imports
+	AgendaView.call(t, element, calendar, 'agendaWeek');
+	var opt = t.opt;
+	var renderAgenda = t.renderAgenda;
+	var skipHiddenDays = t.skipHiddenDays;
+	var getCellsPerWeek = t.getCellsPerWeek;
+	var formatDates = calendar.formatDates;
+
+	
+	function render(date, delta) {
+
+		if (delta) {
+			addDays(date, delta * 7);
+		}
+
+		var start = addDays(cloneDate(date), -((date.getDay() - opt('firstDay') + 7) % 7));
+		var end = addDays(cloneDate(start), 7);
+
+		var visStart = cloneDate(start);
+		skipHiddenDays(visStart);
+
+		var visEnd = cloneDate(end);
+		skipHiddenDays(visEnd, -1, true);
+
+		var colCnt = getCellsPerWeek();
+
+		t.title = formatDates(
+			visStart,
+			addDays(cloneDate(visEnd), -1),
+			opt('titleFormat')
+		);
+
+		t.start = start;
+		t.end = end;
+		t.visStart = visStart;
+		t.visEnd = visEnd;
+
+		renderAgenda(colCnt);
+	}
+
+}
+
+;;
+
+fcViews.agendaDay = AgendaDayView;
+
+
+function AgendaDayView(element, calendar) {
+	var t = this;
+	
+	
+	// exports
+	t.render = render;
+	
+	
+	// imports
+	AgendaView.call(t, element, calendar, 'agendaDay');
+	var opt = t.opt;
+	var renderAgenda = t.renderAgenda;
+	var skipHiddenDays = t.skipHiddenDays;
+	var formatDate = calendar.formatDate;
+	
+	
+	function render(date, delta) {
+
+		if (delta) {
+			addDays(date, delta);
+		}
+		skipHiddenDays(date, delta < 0 ? -1 : 1);
+
+		var start = cloneDate(date, true);
+		var end = addDays(cloneDate(start), 1);
+
+		t.title = formatDate(date, opt('titleFormat'));
+
+		t.start = t.visStart = start;
+		t.end = t.visEnd = end;
+
+		renderAgenda(1);
+	}
+	
+
+}
+
+;;
+
+setDefaults({
+	allDaySlot: true,
+	allDayText: 'all-day',
+	firstHour: 6,
+	slotMinutes: 30,
+	defaultEventMinutes: 120,
+	axisFormat: 'h(:mm)tt',
+	timeFormat: {
+		agenda: 'h:mm{ - h:mm}'
+	},
+	dragOpacity: {
+		agenda: .5
+	},
+	minTime: 0,
+	maxTime: 24,
+	slotEventOverlap: true
+});
+
+
+// TODO: make it work in quirks mode (event corners, all-day height)
+// TODO: test liquid width, especially in IE6
+
+
+function AgendaView(element, calendar, viewName) {
+	var t = this;
+	
+	
+	// exports
+	t.renderAgenda = renderAgenda;
+	t.setWidth = setWidth;
+	t.setHeight = setHeight;
+	t.afterRender = afterRender;
+	t.defaultEventEnd = defaultEventEnd;
+	t.timePosition = timePosition;
+	t.getIsCellAllDay = getIsCellAllDay;
+	t.allDayRow = getAllDayRow;
+	t.getCoordinateGrid = function() { return coordinateGrid }; // specifically for AgendaEventRenderer
+	t.getHoverListener = function() { return hoverListener };
+	t.colLeft = colLeft;
+	t.colRight = colRight;
+	t.colContentLeft = colContentLeft;
+	t.colContentRight = colContentRight;
+	t.getDaySegmentContainer = function() { return daySegmentContainer };
+	t.getSlotSegmentContainer = function() { return slotSegmentContainer };
+	t.getMinMinute = function() { return minMinute };
+	t.getMaxMinute = function() { return maxMinute };
+	t.getSlotContainer = function() { return slotContainer };
+	t.getRowCnt = function() { return 1 };
+	t.getColCnt = function() { return colCnt };
+	t.getColWidth = function() { return colWidth };
+	t.getSnapHeight = function() { return snapHeight };
+	t.getSnapMinutes = function() { return snapMinutes };
+	t.defaultSelectionEnd = defaultSelectionEnd;
+	t.renderDayOverlay = renderDayOverlay;
+	t.renderSelection = renderSelection;
+	t.clearSelection = clearSelection;
+	t.reportDayClick = reportDayClick; // selection mousedown hack
+	t.dragStart = dragStart;
+	t.dragStop = dragStop;
+	
+	
+	// imports
+	View.call(t, element, calendar, viewName);
+	OverlayManager.call(t);
+	SelectionManager.call(t);
+	AgendaEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var renderOverlay = t.renderOverlay;
+	var clearOverlays = t.clearOverlays;
+	var reportSelection = t.reportSelection;
+	var unselect = t.unselect;
+	var daySelectionMousedown = t.daySelectionMousedown;
+	var slotSegHtml = t.slotSegHtml;
+	var cellToDate = t.cellToDate;
+	var dateToCell = t.dateToCell;
+	var rangeToSegments = t.rangeToSegments;
+	var formatDate = calendar.formatDate;
+	
+	
+	// locals
+	
+	var dayTable;
+	var dayHead;
+	var dayHeadCells;
+	var dayBody;
+	var dayBodyCells;
+	var dayBodyCellInners;
+	var dayBodyCellContentInners;
+	var dayBodyFirstCell;
+	var dayBodyFirstCellStretcher;
+	var slotLayer;
+	var daySegmentContainer;
+	var allDayTable;
+	var allDayRow;
+	var slotScroller;
+	var slotContainer;
+	var slotSegmentContainer;
+	var slotTable;
+	var selectionHelper;
+	
+	var viewWidth;
+	var viewHeight;
+	var axisWidth;
+	var colWidth;
+	var gutterWidth;
+	var slotHeight; // TODO: what if slotHeight changes? (see issue 650)
+
+	var snapMinutes;
+	var snapRatio; // ratio of number of "selection" slots to normal slots. (ex: 1, 2, 4)
+	var snapHeight; // holds the pixel hight of a "selection" slot
+	
+	var colCnt;
+	var slotCnt;
+	var coordinateGrid;
+	var hoverListener;
+	var colPositions;
+	var colContentPositions;
+	var slotTopCache = {};
+	
+	var tm;
+	var rtl;
+	var minMinute, maxMinute;
+	var colFormat;
+	var showWeekNumbers;
+	var weekNumberTitle;
+	var weekNumberFormat;
+	
+
+	
+	/* Rendering
+	-----------------------------------------------------------------------------*/
+	
+	
+	disableTextSelection(element.addClass('fc-agenda'));
+	
+	
+	function renderAgenda(c) {
+		colCnt = c;
+		updateOptions();
+
+		if (!dayTable) { // first time rendering?
+			buildSkeleton(); // builds day table, slot area, events containers
+		}
+		else {
+			buildDayTable(); // rebuilds day table
+		}
+	}
+	
+	
+	function updateOptions() {
+
+		tm = opt('theme') ? 'ui' : 'fc';
+		rtl = opt('isRTL')
+		minMinute = parseTime(opt('minTime'));
+		maxMinute = parseTime(opt('maxTime'));
+		colFormat = opt('columnFormat');
+
+		// week # options. (TODO: bad, logic also in other views)
+		showWeekNumbers = opt('weekNumbers');
+		weekNumberTitle = opt('weekNumberTitle');
+		if (opt('weekNumberCalculation') != 'iso') {
+			weekNumberFormat = "w";
+		}
+		else {
+			weekNumberFormat = "W";
+		}
+
+		snapMinutes = opt('snapMinutes') || opt('slotMinutes');
+	}
+
+
+
+	/* Build DOM
+	-----------------------------------------------------------------------*/
+
+
+	function buildSkeleton() {
+		var headerClass = tm + "-widget-header";
+		var contentClass = tm + "-widget-content";
+		var s;
+		var d;
+		var i;
+		var maxd;
+		var minutes;
+		var slotNormal = opt('slotMinutes') % 15 == 0;
+		
+		buildDayTable();
+		
+		slotLayer =
+			$("<div style='position:absolute;z-index:2;left:0;width:100%'/>")
+				.appendTo(element);
+				
+		if (opt('allDaySlot')) {
+		
+			daySegmentContainer =
+				$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
+					.appendTo(slotLayer);
+		
+			s =
+				"<table style='width:100%' class='fc-agenda-allday' cellspacing='0'>" +
+				"<tr>" +
+				"<th class='" + headerClass + " fc-agenda-axis'>" + opt('allDayText') + "</th>" +
+				"<td>" +
+				"<div class='fc-day-content'><div style='position:relative'/></div>" +
+				"</td>" +
+				"<th class='" + headerClass + " fc-agenda-gutter'>&nbsp;</th>" +
+				"</tr>" +
+				"</table>";
+			allDayTable = $(s).appendTo(slotLayer);
+			allDayRow = allDayTable.find('tr');
+			
+			dayBind(allDayRow.find('td'));
+			
+			slotLayer.append(
+				"<div class='fc-agenda-divider " + headerClass + "'>" +
+				"<div class='fc-agenda-divider-inner'/>" +
+				"</div>"
+			);
+			
+		}else{
+		
+			daySegmentContainer = $([]); // in jQuery 1.4, we can just do $()
+		
+		}
+		
+		slotScroller =
+			$("<div style='position:absolute;width:100%;overflow-x:hidden;overflow-y:auto'/>")
+				.appendTo(slotLayer);
+				
+		slotContainer =
+			$("<div style='position:relative;width:100%;overflow:hidden'/>")
+				.appendTo(slotScroller);
+				
+		slotSegmentContainer =
+			$("<div class='fc-event-container' style='position:absolute;z-index:8;top:0;left:0'/>")
+				.appendTo(slotContainer);
+		
+		s =
+			"<table class='fc-agenda-slots' style='width:100%' cellspacing='0'>" +
+			"<tbody>";
+		d = zeroDate();
+		maxd = addMinutes(cloneDate(d), maxMinute);
+		addMinutes(d, minMinute);
+		slotCnt = 0;
+		for (i=0; d < maxd; i++) {
+			minutes = d.getMinutes();
+			s +=
+				"<tr class='fc-slot" + i + ' ' + (!minutes ? '' : 'fc-minor') + "'>" +
+				"<th class='fc-agenda-axis " + headerClass + "'>" +
+				((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : '&nbsp;') +
+				"</th>" +
+				"<td class='" + contentClass + "'>" +
+				"<div style='position:relative'>&nbsp;</div>" +
+				"</td>" +
+				"</tr>";
+			addMinutes(d, opt('slotMinutes'));
+			slotCnt++;
+		}
+		s +=
+			"</tbody>" +
+			"</table>";
+		slotTable = $(s).appendTo(slotContainer);
+		
+		slotBind(slotTable.find('td'));
+	}
+
+
+
+	/* Build Day Table
+	-----------------------------------------------------------------------*/
+
+
+	function buildDayTable() {
+		var html = buildDayTableHTML();
+
+		if (dayTable) {
+			dayTable.remove();
+		}
+		dayTable = $(html).appendTo(element);
+
+		dayHead = dayTable.find('thead');
+		dayHeadCells = dayHead.find('th').slice(1, -1); // exclude gutter
+		dayBody = dayTable.find('tbody');
+		dayBodyCells = dayBody.find('td').slice(0, -1); // exclude gutter
+		dayBodyCellInners = dayBodyCells.find('> div');
+		dayBodyCellContentInners = dayBodyCells.find('.fc-day-content > div');
+
+		dayBodyFirstCell = dayBodyCells.eq(0);
+		dayBodyFirstCellStretcher = dayBodyCellInners.eq(0);
+		
+		markFirstLast(dayHead.add(dayHead.find('tr')));
+		markFirstLast(dayBody.add(dayBody.find('tr')));
+
+		// TODO: now that we rebuild the cells every time, we should call dayRender
+	}
+
+
+	function buildDayTableHTML() {
+		var html =
+			"<table style='width:100%' class='fc-agenda-days fc-border-separate' cellspacing='0'>" +
+			buildDayTableHeadHTML() +
+			buildDayTableBodyHTML() +
+			"</table>";
+
+		return html;
+	}
+
+
+	function buildDayTableHeadHTML() {
+		var headerClass = tm + "-widget-header";
+		var date;
+		var html = '';
+		var weekText;
+		var col;
+
+		html +=
+			"<thead>" +
+			"<tr>";
+
+		if (showWeekNumbers) {
+			date = cellToDate(0, 0);
+			weekText = formatDate(date, weekNumberFormat);
+			if (rtl) {
+				weekText += weekNumberTitle;
+			}
+			else {
+				weekText = weekNumberTitle + weekText;
+			}
+			html +=
+				"<th class='fc-agenda-axis fc-week-number " + headerClass + "'>" +
+				htmlEscape(weekText) +
+				"</th>";
+		}
+		else {
+			html += "<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
+		}
+
+		for (col=0; col<colCnt; col++) {
+			date = cellToDate(0, col);
+			html +=
+				"<th class='fc-" + dayIDs[date.getDay()] + " fc-col" + col + ' ' + headerClass + "'>" +
+				htmlEscape(formatDate(date, colFormat)) +
+				"</th>";
+		}
+
+		html +=
+			"<th class='fc-agenda-gutter " + headerClass + "'>&nbsp;</th>" +
+			"</tr>" +
+			"</thead>";
+
+		return html;
+	}
+
+
+	function buildDayTableBodyHTML() {
+		var headerClass = tm + "-widget-header"; // TODO: make these when updateOptions() called
+		var contentClass = tm + "-widget-content";
+		var date;
+		var today = clearTime(new Date());
+		var col;
+		var cellsHTML;
+		var cellHTML;
+		var classNames;
+		var html = '';
+
+		html +=
+			"<tbody>" +
+			"<tr>" +
+			"<th class='fc-agenda-axis " + headerClass + "'>&nbsp;</th>";
+
+		cellsHTML = '';
+
+		for (col=0; col<colCnt; col++) {
+
+			date = cellToDate(0, col);
+
+			classNames = [
+				'fc-col' + col,
+				'fc-' + dayIDs[date.getDay()],
+				contentClass
+			];
+			if (+date == +today) {
+				classNames.push(
+					tm + '-state-highlight',
+					'fc-today'
+				);
+			}
+			else if (date < today) {
+				classNames.push('fc-past');
+			}
+			else {
+				classNames.push('fc-future');
+			}
+
+			cellHTML =
+				"<td class='" + classNames.join(' ') + "'>" +
+				"<div>" +
+				"<div class='fc-day-content'>" +
+				"<div style='position:relative'>&nbsp;</div>" +
+				"</div>" +
+				"</div>" +
+				"</td>";
+
+			cellsHTML += cellHTML;
+		}
+
+		html += cellsHTML;
+		html +=
+			"<td class='fc-agenda-gutter " + contentClass + "'>&nbsp;</td>" +
+			"</tr>" +
+			"</tbody>";
+
+		return html;
+	}
+
+
+	// TODO: data-date on the cells
+
+	
+	
+	/* Dimensions
+	-----------------------------------------------------------------------*/
+
+	
+	function setHeight(height) {
+		if (height === undefined) {
+			height = viewHeight;
+		}
+		viewHeight = height;
+		slotTopCache = {};
+	
+		var headHeight = dayBody.position().top;
+		var allDayHeight = slotScroller.position().top; // including divider
+		var bodyHeight = Math.min( // total body height, including borders
+			height - headHeight,   // when scrollbars
+			slotTable.height() + allDayHeight + 1 // when no scrollbars. +1 for bottom border
+		);
+
+		dayBodyFirstCellStretcher
+			.height(bodyHeight - vsides(dayBodyFirstCell));
+		
+		slotLayer.css('top', headHeight);
+		
+		slotScroller.height(bodyHeight - allDayHeight - 1);
+		
+		// the stylesheet guarantees that the first row has no border.
+		// this allows .height() to work well cross-browser.
+		slotHeight = slotTable.find('tr:first').height() + 1; // +1 for bottom border
+
+		snapRatio = opt('slotMinutes') / snapMinutes;
+		snapHeight = slotHeight / snapRatio;
+	}
+	
+	
+	function setWidth(width) {
+		viewWidth = width;
+		colPositions.clear();
+		colContentPositions.clear();
+
+		var axisFirstCells = dayHead.find('th:first');
+		if (allDayTable) {
+			axisFirstCells = axisFirstCells.add(allDayTable.find('th:first'));
+		}
+		axisFirstCells = axisFirstCells.add(slotTable.find('th:first'));
+		
+		axisWidth = 0;
+		setOuterWidth(
+			axisFirstCells
+				.width('')
+				.each(function(i, _cell) {
+					axisWidth = Math.max(axisWidth, $(_cell).outerWidth());
+				}),
+			axisWidth
+		);
+		
+		var gutterCells = dayTable.find('.fc-agenda-gutter');
+		if (allDayTable) {
+			gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter'));
+		}
+
+		var slotTableWidth = slotScroller[0].clientWidth; // needs to be done after axisWidth (for IE7)
+		
+		gutterWidth = slotScroller.width() - slotTableWidth;
+		if (gutterWidth) {
+			setOuterWidth(gutterCells, gutterWidth);
+			gutterCells
+				.show()
+				.prev()
+				.removeClass('fc-last');
+		}else{
+			gutterCells
+				.hide()
+				.prev()
+				.addClass('fc-last');
+		}
+		
+		colWidth = Math.floor((slotTableWidth - axisWidth) / colCnt);
+		setOuterWidth(dayHeadCells.slice(0, -1), colWidth);
+	}
+	
+
+
+	/* Scrolling
+	-----------------------------------------------------------------------*/
+
+
+	function resetScroll() {
+		var d0 = zeroDate();
+		var scrollDate = cloneDate(d0);
+		scrollDate.setHours(opt('firstHour'));
+		var top = timePosition(d0, scrollDate) + 1; // +1 for the border
+		function scroll() {
+			slotScroller.scrollTop(top);
+		}
+		scroll();
+		setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
+	}
+
+
+	function afterRender() { // after the view has been freshly rendered and sized
+		resetScroll();
+	}
+	
+	
+	
+	/* Slot/Day clicking and binding
+	-----------------------------------------------------------------------*/
+	
+
+	function dayBind(cells) {
+		cells.click(slotClick)
+			.mousedown(daySelectionMousedown);
+	}
+
+
+	function slotBind(cells) {
+		cells.click(slotClick)
+			.mousedown(slotSelectionMousedown);
+	}
+	
+	
+	function slotClick(ev) {
+		if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick
+			var col = Math.min(colCnt-1, Math.floor((ev.pageX - dayTable.offset().left - axisWidth) / colWidth));
+			var date = cellToDate(0, col);
+			var rowMatch = this.parentNode.className.match(/fc-slot(\d+)/); // TODO: maybe use data
+			if (rowMatch) {
+				var mins = parseInt(rowMatch[1]) * opt('slotMinutes');
+				var hours = Math.floor(mins/60);
+				date.setHours(hours);
+				date.setMinutes(mins%60 + minMinute);
+				trigger('dayClick', dayBodyCells[col], date, false, ev);
+			}else{
+				trigger('dayClick', dayBodyCells[col], date, true, ev);
+			}
+		}
+	}
+	
+	
+	
+	/* Semi-transparent Overlay Helpers
+	-----------------------------------------------------*/
+	// TODO: should be consolidated with BasicView's methods
+
+
+	function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive
+
+		if (refreshCoordinateGrid) {
+			coordinateGrid.build();
+		}
+
+		var segments = rangeToSegments(overlayStart, overlayEnd);
+
+		for (var i=0; i<segments.length; i++) {
+			var segment = segments[i];
+			dayBind(
+				renderCellOverlay(
+					segment.row,
+					segment.leftCol,
+					segment.row,
+					segment.rightCol
+				)
+			);
+		}
+	}
+	
+	
+	function renderCellOverlay(row0, col0, row1, col1) { // only for all-day?
+		var rect = coordinateGrid.rect(row0, col0, row1, col1, slotLayer);
+		return renderOverlay(rect, slotLayer);
+	}
+	
+
+	function renderSlotOverlay(overlayStart, overlayEnd) {
+		for (var i=0; i<colCnt; i++) {
+			var dayStart = cellToDate(0, i);
+			var dayEnd = addDays(cloneDate(dayStart), 1);
+			var stretchStart = new Date(Math.max(dayStart, overlayStart));
+			var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
+			if (stretchStart < stretchEnd) {
+				var rect = coordinateGrid.rect(0, i, 0, i, slotContainer); // only use it for horizontal coords
+				var top = timePosition(dayStart, stretchStart);
+				var bottom = timePosition(dayStart, stretchEnd);
+				rect.top = top;
+				rect.height = bottom - top;
+				slotBind(
+					renderOverlay(rect, slotContainer)
+				);
+			}
+		}
+	}
+	
+	
+	
+	/* Coordinate Utilities
+	-----------------------------------------------------------------------------*/
+	
+	
+	coordinateGrid = new CoordinateGrid(function(rows, cols) {
+		var e, n, p;
+		dayHeadCells.each(function(i, _e) {
+			e = $(_e);
+			n = e.offset().left;
+			if (i) {
+				p[1] = n;
+			}
+			p = [n];
+			cols[i] = p;
+		});
+		p[1] = n + e.outerWidth();
+		if (opt('allDaySlot')) {
+			e = allDayRow;
+			n = e.offset().top;
+			rows[0] = [n, n+e.outerHeight()];
+		}
+		var slotTableTop = slotContainer.offset().top;
+		var slotScrollerTop = slotScroller.offset().top;
+		var slotScrollerBottom = slotScrollerTop + slotScroller.outerHeight();
+		function constrain(n) {
+			return Math.max(slotScrollerTop, Math.min(slotScrollerBottom, n));
+		}
+		for (var i=0; i<slotCnt*snapRatio; i++) { // adapt slot count to increased/decreased selection slot count
+			rows.push([
+				constrain(slotTableTop + snapHeight*i),
+				constrain(slotTableTop + snapHeight*(i+1))
+			]);
+		}
+	});
+	
+	
+	hoverListener = new HoverListener(coordinateGrid);
+	
+	colPositions = new HorizontalPositionCache(function(col) {
+		return dayBodyCellInners.eq(col);
+	});
+	
+	colContentPositions = new HorizontalPositionCache(function(col) {
+		return dayBodyCellContentInners.eq(col);
+	});
+	
+	
+	function colLeft(col) {
+		return colPositions.left(col);
+	}
+
+
+	function colContentLeft(col) {
+		return colContentPositions.left(col);
+	}
+
+
+	function colRight(col) {
+		return colPositions.right(col);
+	}
+	
+	
+	function colContentRight(col) {
+		return colContentPositions.right(col);
+	}
+
+
+	function getIsCellAllDay(cell) {
+		return opt('allDaySlot') && !cell.row;
+	}
+
+
+	function realCellToDate(cell) { // ugh "real" ... but blame it on our abuse of the "cell" system
+		var d = cellToDate(0, cell.col);
+		var slotIndex = cell.row;
+		if (opt('allDaySlot')) {
+			slotIndex--;
+		}
+		if (slotIndex >= 0) {
+			addMinutes(d, minMinute + slotIndex * snapMinutes);
+		}
+		return d;
+	}
+	
+	
+	// get the Y coordinate of the given time on the given day (both Date objects)
+	function timePosition(day, time) { // both date objects. day holds 00:00 of current day
+		day = cloneDate(day, true);
+		if (time < addMinutes(cloneDate(day), minMinute)) {
+			return 0;
+		}
+		if (time >= addMinutes(cloneDate(day), maxMinute)) {
+			return slotTable.height();
+		}
+		var slotMinutes = opt('slotMinutes'),
+			minutes = time.getHours()*60 + time.getMinutes() - minMinute,
+			slotI = Math.floor(minutes / slotMinutes),
+			slotTop = slotTopCache[slotI];
+		if (slotTop === undefined) {
+			slotTop = slotTopCache[slotI] =
+				slotTable.find('tr').eq(slotI).find('td div')[0].offsetTop;
+				// .eq() is faster than ":eq()" selector
+				// [0].offsetTop is faster than .position().top (do we really need this optimization?)
+				// a better optimization would be to cache all these divs
+		}
+		return Math.max(0, Math.round(
+			slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
+		));
+	}
+	
+	
+	function getAllDayRow(index) {
+		return allDayRow;
+	}
+	
+	
+	function defaultEventEnd(event) {
+		var start = cloneDate(event.start);
+		if (event.allDay) {
+			return start;
+		}
+		return addMinutes(start, opt('defaultEventMinutes'));
+	}
+	
+	
+	
+	/* Selection
+	---------------------------------------------------------------------------------*/
+	
+	
+	function defaultSelectionEnd(startDate, allDay) {
+		if (allDay) {
+			return cloneDate(startDate);
+		}
+		return addMinutes(cloneDate(startDate), opt('slotMinutes'));
+	}
+	
+	
+	function renderSelection(startDate, endDate, allDay) { // only for all-day
+		if (allDay) {
+			if (opt('allDaySlot')) {
+				renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true);
+			}
+		}else{
+			renderSlotSelection(startDate, endDate);
+		}
+	}
+	
+	
+	function renderSlotSelection(startDate, endDate) {
+		var helperOption = opt('selectHelper');
+		coordinateGrid.build();
+		if (helperOption) {
+			var col = dateToCell(startDate).col;
+			if (col >= 0 && col < colCnt) { // only works when times are on same day
+				var rect = coordinateGrid.rect(0, col, 0, col, slotContainer); // only for horizontal coords
+				var top = timePosition(startDate, startDate);
+				var bottom = timePosition(startDate, endDate);
+				if (bottom > top) { // protect against selections that are entirely before or after visible range
+					rect.top = top;
+					rect.height = bottom - top;
+					rect.left += 2;
+					rect.width -= 5;
+					if ($.isFunction(helperOption)) {
+						var helperRes = helperOption(startDate, endDate);
+						if (helperRes) {
+							rect.position = 'absolute';
+							selectionHelper = $(helperRes)
+								.css(rect)
+								.appendTo(slotContainer);
+						}
+					}else{
+						rect.isStart = true; // conside rect a "seg" now
+						rect.isEnd = true;   //
+						selectionHelper = $(slotSegHtml(
+							{
+								title: '',
+								start: startDate,
+								end: endDate,
+								className: ['fc-select-helper'],
+								editable: false
+							},
+							rect
+						));
+						selectionHelper.css('opacity', opt('dragOpacity'));
+					}
+					if (selectionHelper) {
+						slotBind(selectionHelper);
+						slotContainer.append(selectionHelper);
+						setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
+						setOuterHeight(selectionHelper, rect.height, true);
+					}
+				}
+			}
+		}else{
+			renderSlotOverlay(startDate, endDate);
+		}
+	}
+	
+	
+	function clearSelection() {
+		clearOverlays();
+		if (selectionHelper) {
+			selectionHelper.remove();
+			selectionHelper = null;
+		}
+	}
+	
+	
+	function slotSelectionMousedown(ev) {
+		if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button
+			unselect(ev);
+			var dates, helperOption = opt('selectHelper');
+			hoverListener.start(function(cell, origCell) {
+				clearSelection();
+				if (cell && (cell.col == origCell.col || !helperOption) && !getIsCellAllDay(cell)) {
+					var d1 = realCellToDate(origCell);
+					var d2 = realCellToDate(cell);
+					dates = [
+						d1,
+						addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes 
+						d2,
+						addMinutes(cloneDate(d2), snapMinutes)
+					].sort(dateCompare);
+					renderSlotSelection(dates[0], dates[3]);
+				}else{
+					dates = null;
+				}
+			}, ev);
+			$(document).one('mouseup', function(ev) {
+				hoverListener.stop();
+				if (dates) {
+					if (+dates[0] == +dates[1]) {
+						reportDayClick(dates[0], false, ev);
+					}
+					reportSelection(dates[0], dates[3], false, ev);
+				}
+			});
+		}
+	}
+
+
+	function reportDayClick(date, allDay, ev) {
+		trigger('dayClick', dayBodyCells[dateToCell(date).col], date, allDay, ev);
+	}
+	
+	
+	
+	/* External Dragging
+	--------------------------------------------------------------------------------*/
+	
+	
+	function dragStart(_dragElement, ev, ui) {
+		hoverListener.start(function(cell) {
+			clearOverlays();
+			if (cell) {
+				if (getIsCellAllDay(cell)) {
+					renderCellOverlay(cell.row, cell.col, cell.row, cell.col);
+				}else{
+					var d1 = realCellToDate(cell);
+					var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes'));
+					renderSlotOverlay(d1, d2);
+				}
+			}
+		}, ev);
+	}
+	
+	
+	function dragStop(_dragElement, ev, ui) {
+		var cell = hoverListener.stop();
+		clearOverlays();
+		if (cell) {
+			trigger('drop', _dragElement, realCellToDate(cell), getIsCellAllDay(cell), ev, ui);
+		}
+	}
+	
+
+}
+
+;;
+
+function AgendaEventRenderer() {
+	var t = this;
+	
+	
+	// exports
+	t.renderEvents = renderEvents;
+	t.clearEvents = clearEvents;
+	t.slotSegHtml = slotSegHtml;
+	
+	
+	// imports
+	DayEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var isEventDraggable = t.isEventDraggable;
+	var isEventResizable = t.isEventResizable;
+	var eventEnd = t.eventEnd;
+	var eventElementHandlers = t.eventElementHandlers;
+	var setHeight = t.setHeight;
+	var getDaySegmentContainer = t.getDaySegmentContainer;
+	var getSlotSegmentContainer = t.getSlotSegmentContainer;
+	var getHoverListener = t.getHoverListener;
+	var getMaxMinute = t.getMaxMinute;
+	var getMinMinute = t.getMinMinute;
+	var timePosition = t.timePosition;
+	var getIsCellAllDay = t.getIsCellAllDay;
+	var colContentLeft = t.colContentLeft;
+	var colContentRight = t.colContentRight;
+	var cellToDate = t.cellToDate;
+	var getColCnt = t.getColCnt;
+	var getColWidth = t.getColWidth;
+	var getSnapHeight = t.getSnapHeight;
+	var getSnapMinutes = t.getSnapMinutes;
+	var getSlotContainer = t.getSlotContainer;
+	var reportEventElement = t.reportEventElement;
+	var showEvents = t.showEvents;
+	var hideEvents = t.hideEvents;
+	var eventDrop = t.eventDrop;
+	var eventResize = t.eventResize;
+	var renderDayOverlay = t.renderDayOverlay;
+	var clearOverlays = t.clearOverlays;
+	var renderDayEvents = t.renderDayEvents;
+	var calendar = t.calendar;
+	var formatDate = calendar.formatDate;
+	var formatDates = calendar.formatDates;
+	var timeLineInterval;
+	
+	
+	// overrides
+	t.draggableDayEvent = draggableDayEvent;
+	
+	
+	/* Rendering
+	----------------------------------------------------------------------------*/
+	
+
+	function renderEvents(events, modifiedEventId) {
+		var i, len=events.length,
+			dayEvents=[],
+			slotEvents=[];
+		for (i=0; i<len; i++) {
+			if (events[i].allDay) {
+				dayEvents.push(events[i]);
+			}else{
+				slotEvents.push(events[i]);
+			}
+		}
+
+		if (opt('allDaySlot')) {
+			renderDayEvents(dayEvents, modifiedEventId);
+			setHeight(); // no params means set to viewHeight
+		}
+
+		renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
+		
+		if (opt('currentTimeIndicator')) {
+			window.clearInterval(timeLineInterval);
+			timeLineInterval = window.setInterval(setTimeIndicator, 30000);
+			setTimeIndicator();
+		}
+	}
+	
+	
+	function clearEvents() {
+		getDaySegmentContainer().empty();
+		getSlotSegmentContainer().empty();
+	}
+
+	
+	function compileSlotSegs(events) {
+		var colCnt = getColCnt(),
+			minMinute = getMinMinute(),
+			maxMinute = getMaxMinute(),
+			d,
+			visEventEnds = $.map(events, slotEventEnd),
+			i,
+			j, seg,
+			colSegs,
+			segs = [];
+
+		for (i=0; i<colCnt; i++) {
+
+			d = cellToDate(0, i);
+			addMinutes(d, minMinute);
+
+			colSegs = sliceSegs(
+				events,
+				visEventEnds,
+				d,
+				addMinutes(cloneDate(d), maxMinute-minMinute)
+			);
+
+			colSegs = placeSlotSegs(colSegs); // returns a new order
+
+			for (j=0; j<colSegs.length; j++) {
+				seg = colSegs[j];
+				seg.col = i;
+				segs.push(seg);
+			}
+		}
+
+		return segs;
+	}
+
+
+	function sliceSegs(events, visEventEnds, start, end) {
+		var segs = [],
+			i, len=events.length, event,
+			eventStart, eventEnd,
+			segStart, segEnd,
+			isStart, isEnd;
+		for (i=0; i<len; i++) {
+			event = events[i];
+			eventStart = event.start;
+			eventEnd = visEventEnds[i];
+			if (eventEnd > start && eventStart < end) {
+				if (eventStart < start) {
+					segStart = cloneDate(start);
+					isStart = false;
+				}else{
+					segStart = eventStart;
+					isStart = true;
+				}
+				if (eventEnd > end) {
+					segEnd = cloneDate(end);
+					isEnd = false;
+				}else{
+					segEnd = eventEnd;
+					isEnd = true;
+				}
+				segs.push({
+					event: event,
+					start: segStart,
+					end: segEnd,
+					isStart: isStart,
+					isEnd: isEnd
+				});
+			}
+		}
+		return segs.sort(compareSlotSegs);
+	}
+
+
+	function slotEventEnd(event) {
+		if (event.end) {
+			return cloneDate(event.end);
+		}else{
+			return addMinutes(cloneDate(event.start), opt('defaultEventMinutes'));
+		}
+	}
+	
+	
+	// renders events in the 'time slots' at the bottom
+	// TODO: when we refactor this, when user returns `false` eventRender, don't have empty space
+	// TODO: refactor will include using pixels to detect collisions instead of dates (handy for seg cmp)
+	
+	function renderSlotSegs(segs, modifiedEventId) {
+	
+		var i, segCnt=segs.length, seg,
+			event,
+			top,
+			bottom,
+			columnLeft,
+			columnRight,
+			columnWidth,
+			width,
+			left,
+			right,
+			html = '',
+			eventElements,
+			eventElement,
+			triggerRes,
+			contentElement,
+			height,
+			slotSegmentContainer = getSlotSegmentContainer(),
+			isRTL = opt('isRTL'),
+			colCnt = getColCnt();
+			
+		// calculate position/dimensions, create html
+		for (i=0; i<segCnt; i++) {
+			seg = segs[i];
+			event = seg.event;
+			top = timePosition(seg.start, seg.start);
+			bottom = timePosition(seg.start, seg.end);
+			columnLeft = colContentLeft(seg.col);
+			columnRight = colContentRight(seg.col);
+			columnWidth = columnRight - columnLeft;
+
+			// shave off space on right near scrollbars (2.5%)
+			// TODO: move this to CSS somehow
+			columnRight -= columnWidth * .025;
+			columnWidth = columnRight - columnLeft;
+
+			width = columnWidth * (seg.forwardCoord - seg.backwardCoord);
+
+			// bruederli@kolabsys.com: always disable slotEventOverlap in single day view
+			if (opt('slotEventOverlap') && colCnt > 1) {
+				// double the width while making sure resize handle is visible
+				// (assumed to be 20px wide)
+				width = Math.max(
+					(width - (20/2)) * 2,
+					width // narrow columns will want to make the segment smaller than
+						// the natural width. don't allow it
+				);
+			}
+
+			if (isRTL) {
+				right = columnRight - seg.backwardCoord * columnWidth;
+				left = right - width;
+			}
+			else {
+				left = columnLeft + seg.backwardCoord * columnWidth;
+				right = left + width;
+			}
+
+			// make sure horizontal coordinates are in bounds
+			left = Math.max(left, columnLeft);
+			right = Math.min(right, columnRight);
+			width = right - left;
+
+			seg.top = top;
+			seg.left = left;
+			seg.outerWidth = width;
+			seg.outerHeight = bottom - top;
+			html += slotSegHtml(event, seg);
+		}
+
+		slotSegmentContainer[0].innerHTML = html; // faster than html()
+		eventElements = slotSegmentContainer.children();
+		
+		// retrieve elements, run through eventRender callback, bind event handlers
+		for (i=0; i<segCnt; i++) {
+			seg = segs[i];
+			event = seg.event;
+			eventElement = $(eventElements[i]); // faster than eq()
+			triggerRes = trigger('eventRender', event, event, eventElement);
+			if (triggerRes === false) {
+				eventElement.remove();
+			}else{
+				if (triggerRes && triggerRes !== true) {
+					eventElement.remove();
+					eventElement = $(triggerRes)
+						.css({
+							position: 'absolute',
+							top: seg.top,
+							left: seg.left
+						})
+						.appendTo(slotSegmentContainer);
+				}
+				seg.element = eventElement;
+				if (event._id === modifiedEventId) {
+					bindSlotSeg(event, eventElement, seg);
+				}else{
+					eventElement[0]._fci = i; // for lazySegBind
+				}
+				reportEventElement(event, eventElement);
+			}
+		}
+		
+		lazySegBind(slotSegmentContainer, segs, bindSlotSeg);
+		
+		// record event sides and title positions
+		for (i=0; i<segCnt; i++) {
+			seg = segs[i];
+			if (eventElement = seg.element) {
+				seg.vsides = vsides(eventElement, true);
+				seg.hsides = hsides(eventElement, true);
+				contentElement = eventElement.find('.fc-event-content');
+				if (contentElement.length) {
+					seg.contentTop = contentElement[0].offsetTop;
+				}
+			}
+		}
+		
+		// set all positions/dimensions at once
+		for (i=0; i<segCnt; i++) {
+			seg = segs[i];
+			if (eventElement = seg.element) {
+				eventElement[0].style.width = Math.max(0, seg.outerWidth - seg.hsides) + 'px';
+				height = Math.max(0, seg.outerHeight - seg.vsides);
+				eventElement[0].style.height = height + 'px';
+				event = seg.event;
+				if (seg.contentTop !== undefined && height - seg.contentTop < 10) {
+					// not enough room for title, put it in the time (TODO: maybe make both display:inline instead)
+					eventElement.find('div.fc-event-time')
+						.text(formatDate(event.start, opt('timeFormat')) + ' - ' + event.title);
+					eventElement.find('div.fc-event-title')
+						.remove();
+				}
+				trigger('eventAfterRender', event, event, eventElement);
+			}
+		}
+					
+	}
+	
+	
+	function slotSegHtml(event, seg) {
+		var html = "<";
+		var url = event.url;
+		var skinCss = getSkinCss(event, opt);
+		var skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
+		var classes = ['fc-event', 'fc-event-skin', 'fc-event-vert'];
+		if (isEventDraggable(event)) {
+			classes.push('fc-event-draggable');
+		}
+		if (seg.isStart) {
+			classes.push('fc-event-start');
+		}
+		if (seg.isEnd) {
+			classes.push('fc-event-end');
+		}
+		classes = classes.concat(event.className);
+		if (event.source) {
+			classes = classes.concat(event.source.className || []);
+		}
+		if (url) {
+			html += "a href='" + htmlEscape(event.url) + "'";
+		}else{
+			html += "div";
+		}
+		html +=
+			" class='" + classes.join(' ') + "'" +
+			" style=" +
+				"'" +
+				"position:absolute;" +
+				"top:" + seg.top + "px;" +
+				"left:" + seg.left + "px;" +
+				skinCss +
+				"'" +
+			" tabindex='0'>" +
+			"<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
+			"<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
+			"<div class='fc-event-time'>" +
+			htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) +
+			"</div>" +
+			"</div>" +
+			"<div class='fc-event-content'>" +
+			"<div class='fc-event-title'>" +
+			htmlEscape(event.title || '') +
+			"</div>" +
+			"</div>" +
+			"<div class='fc-event-bg'></div>" +
+			"</div>"; // close inner
+		if (seg.isEnd && isEventResizable(event)) {
+			html +=
+				"<div class='ui-resizable-handle ui-resizable-s' role='presentation'>=</div>";
+		}
+		html +=
+			"</" + (url ? "a" : "div") + ">";
+		return html;
+	}
+	
+	
+	function bindSlotSeg(event, eventElement, seg) {
+		var timeElement = eventElement.find('div.fc-event-time');
+		if (isEventDraggable(event)) {
+			draggableSlotEvent(event, eventElement, timeElement);
+		}
+		if (seg.isEnd && isEventResizable(event)) {
+			resizableSlotEvent(event, eventElement, timeElement);
+		}
+		eventElementHandlers(event, eventElement);
+	}
+	
+	
+	// draw a horizontal line indicating the current time (#143)
+	function setTimeIndicator()
+	{
+		var container = getSlotContainer();
+		var timeline = container.children('.fc-timeline');
+		if (timeline.length == 0) { // if timeline isn't there, add it
+			timeline = $('<hr>').addClass('fc-timeline').appendTo(container);
+		}
+
+		var cur_time = new Date();
+		if (t.visStart < cur_time && t.visEnd > cur_time) {
+			timeline.show();
+		}
+		else {
+			timeline.hide();
+			return;
+		}
+
+		var secs = (cur_time.getHours() * 60 * 60) + (cur_time.getMinutes() * 60) + cur_time.getSeconds();
+		var percents = secs / 86400; // 24 * 60 * 60 = 86400, # of seconds in a day
+
+		timeline.css('top', Math.floor(container.height() * percents - 1) + 'px');
+
+		if (t.name == 'agendaWeek') { // week view, don't want the timeline to go the whole way across
+			var daycol = $('.fc-today', t.element);
+			var left = daycol.position().left + 1;
+			var width = daycol.width();
+			timeline.css({ left: left + 'px', width: width + 'px' });
+		}
+	}
+	
+	
+	/* Dragging
+	-----------------------------------------------------------------------------------*/
+	
+	
+	// when event starts out FULL-DAY
+	// overrides DayEventRenderer's version because it needs to account for dragging elements
+	// to and from the slot area.
+	
+	function draggableDayEvent(event, eventElement, seg) {
+		var isStart = seg.isStart;
+		var origWidth;
+		var revert;
+		var allDay = true;
+		var dayDelta;
+		var hoverListener = getHoverListener();
+		var colWidth = getColWidth();
+		var snapHeight = getSnapHeight();
+		var snapMinutes = getSnapMinutes();
+		var minMinute = getMinMinute();
+		eventElement.draggable({
+			opacity: opt('dragOpacity', 'month'), // use whatever the month view was using
+			revertDuration: opt('dragRevertDuration'),
+			start: function(ev, ui) {
+				trigger('eventDragStart', eventElement, event, ev, ui);
+				hideEvents(event, eventElement);
+				origWidth = eventElement.width();
+				hoverListener.start(function(cell, origCell) {
+					clearOverlays();
+					if (cell) {
+						revert = false;
+						var origDate = cellToDate(0, origCell.col);
+						var date = cellToDate(0, cell.col);
+						dayDelta = dayDiff(date, origDate);
+						if (!cell.row) {
+							// on full-days
+							renderDayOverlay(
+								addDays(cloneDate(event.start), dayDelta),
+								addDays(exclEndDay(event), dayDelta)
+							);
+							resetElement();
+						}else{
+							// mouse is over bottom slots
+							if (isStart) {
+								if (allDay) {
+									// convert event to temporary slot-event
+									eventElement.width(colWidth - 10); // don't use entire width
+									setOuterHeight(
+										eventElement,
+										snapHeight * Math.round(
+											(event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) /
+												snapMinutes
+										)
+									);
+									eventElement.draggable('option', 'grid', [colWidth, 1]);
+									allDay = false;
+								}
+							}else{
+								revert = true;
+							}
+						}
+						revert = revert || (allDay && !dayDelta);
+					}else{
+						resetElement();
+						revert = true;
+					}
+					eventElement.draggable('option', 'revert', revert);
+				}, ev, 'drag');
+			},
+			stop: function(ev, ui) {
+				hoverListener.stop();
+				clearOverlays();
+				trigger('eventDragStop', eventElement, event, ev, ui);
+				if (revert) {
+					// hasn't moved or is out of bounds (draggable has already reverted)
+					resetElement();
+					eventElement.css('filter', ''); // clear IE opacity side-effects
+					showEvents(event, eventElement);
+				}else{
+					// changed!
+					var minuteDelta = 0;
+					if (!allDay) {
+						minuteDelta = Math.round((eventElement.offset().top - getSlotContainer().offset().top) / snapHeight)
+							* snapMinutes
+							+ minMinute
+							- (event.start.getHours() * 60 + event.start.getMinutes());
+					}
+					eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui);
+				}
+			}
+		});
+		function resetElement() {
+			if (!allDay) {
+				eventElement
+					.width(origWidth)
+					.height('')
+					.draggable('option', 'grid', null);
+				allDay = true;
+			}
+		}
+	}
+	
+	
+	// when event starts out IN TIMESLOTS
+	
+	function draggableSlotEvent(event, eventElement, timeElement) {
+		var coordinateGrid = t.getCoordinateGrid();
+		var colCnt = getColCnt();
+		var colWidth = getColWidth();
+		var snapHeight = getSnapHeight();
+		var snapMinutes = getSnapMinutes();
+
+		// states
+		var origPosition; // original position of the element, not the mouse
+		var origCell;
+		var isInBounds, prevIsInBounds;
+		var isAllDay, prevIsAllDay;
+		var colDelta, prevColDelta;
+		var dayDelta; // derived from colDelta
+		var minuteDelta, prevMinuteDelta;
+
+		eventElement.draggable({
+			scroll: false,
+			grid: [ colWidth, snapHeight ],
+			axis: colCnt==1 ? 'y' : false,
+			opacity: opt('dragOpacity'),
+			revertDuration: opt('dragRevertDuration'),
+			start: function(ev, ui) {
+
+				trigger('eventDragStart', eventElement, event, ev, ui);
+				hideEvents(event, eventElement);
+
+				coordinateGrid.build();
+
+				// initialize states
+				origPosition = eventElement.position();
+				origCell = coordinateGrid.cell(ev.pageX, ev.pageY);
+				isInBounds = prevIsInBounds = true;
+				isAllDay = prevIsAllDay = getIsCellAllDay(origCell);
+				colDelta = prevColDelta = 0;
+				dayDelta = 0;
+				minuteDelta = prevMinuteDelta = 0;
+
+			},
+			drag: function(ev, ui) {
+
+				// NOTE: this `cell` value is only useful for determining in-bounds and all-day.
+				// Bad for anything else due to the discrepancy between the mouse position and the
+				// element position while snapping. (problem revealed in PR #55)
+				//
+				// PS- the problem exists for draggableDayEvent() when dragging an all-day event to a slot event.
+				// We should overhaul the dragging system and stop relying on jQuery UI.
+				var cell = coordinateGrid.cell(ev.pageX, ev.pageY);
+
+				// update states
+				isInBounds = !!cell;
+				if (isInBounds) {
+					isAllDay = getIsCellAllDay(cell);
+
+					// calculate column delta
+					colDelta = Math.round((ui.position.left - origPosition.left) / colWidth);
+					if (colDelta != prevColDelta) {
+						// calculate the day delta based off of the original clicked column and the column delta
+						var origDate = cellToDate(0, origCell.col);
+						var col = origCell.col + colDelta;
+						col = Math.max(0, col);
+						col = Math.min(colCnt-1, col);
+						var date = cellToDate(0, col);
+						dayDelta = dayDiff(date, origDate);
+					}
+
+					// calculate minute delta (only if over slots)
+					if (!isAllDay) {
+						minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes;
+					}
+				}
+
+				// any state changes?
+				if (
+					isInBounds != prevIsInBounds ||
+					isAllDay != prevIsAllDay ||
+					colDelta != prevColDelta ||
+					minuteDelta != prevMinuteDelta
+				) {
+
+					updateUI();
+
+					// update previous states for next time
+					prevIsInBounds = isInBounds;
+					prevIsAllDay = isAllDay;
+					prevColDelta = colDelta;
+					prevMinuteDelta = minuteDelta;
+				}
+
+				// if out-of-bounds, revert when done, and vice versa.
+				eventElement.draggable('option', 'revert', !isInBounds);
+
+			},
+			stop: function(ev, ui) {
+
+				clearOverlays();
+				trigger('eventDragStop', eventElement, event, ev, ui);
+
+				if (isInBounds && (isAllDay || dayDelta || minuteDelta)) { // changed!
+					eventDrop(this, event, dayDelta, isAllDay ? 0 : minuteDelta, isAllDay, ev, ui);
+				}
+				else { // either no change or out-of-bounds (draggable has already reverted)
+
+					// reset states for next time, and for updateUI()
+					isInBounds = true;
+					isAllDay = false;
+					colDelta = 0;
+					dayDelta = 0;
+					minuteDelta = 0;
+
+					updateUI();
+					eventElement.css('filter', ''); // clear IE opacity side-effects
+
+					// sometimes fast drags make event revert to wrong position, so reset.
+					// also, if we dragged the element out of the area because of snapping,
+					// but the *mouse* is still in bounds, we need to reset the position.
+					eventElement.css(origPosition);
+
+					showEvents(event, eventElement);
+				}
+			}
+		});
+
+		function updateUI() {
+			clearOverlays();
+			if (isInBounds) {
+				if (isAllDay) {
+					timeElement.hide();
+					eventElement.draggable('option', 'grid', null); // disable grid snapping
+					renderDayOverlay(
+						addDays(cloneDate(event.start), dayDelta),
+						addDays(exclEndDay(event), dayDelta)
+					);
+				}
+				else {
+					updateTimeText(minuteDelta);
+					timeElement.css('display', ''); // show() was causing display=inline
+					eventElement.draggable('option', 'grid', [colWidth, snapHeight]); // re-enable grid snapping
+				}
+			}
+		}
+
+		function updateTimeText(minuteDelta) {
+			var newStart = addMinutes(cloneDate(event.start), minuteDelta);
+			var newEnd;
+			if (event.end) {
+				newEnd = addMinutes(cloneDate(event.end), minuteDelta);
+			}
+			timeElement.text(formatDates(newStart, newEnd, opt('timeFormat')));
+		}
+
+	}
+	
+	
+	
+	/* Resizing
+	--------------------------------------------------------------------------------------*/
+	
+	
+	function resizableSlotEvent(event, eventElement, timeElement) {
+		var snapDelta, prevSnapDelta;
+		var snapHeight = getSnapHeight();
+		var snapMinutes = getSnapMinutes();
+		eventElement.resizable({
+			handles: {
+				s: '.ui-resizable-handle'
+			},
+			grid: snapHeight,
+			start: function(ev, ui) {
+				snapDelta = prevSnapDelta = 0;
+				hideEvents(event, eventElement);
+				trigger('eventResizeStart', this, event, ev, ui);
+			},
+			resize: function(ev, ui) {
+				// don't rely on ui.size.height, doesn't take grid into account
+				snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight);
+				if (snapDelta != prevSnapDelta) {
+					timeElement.text(
+						formatDates(
+							event.start,
+							(!snapDelta && !event.end) ? null : // no change, so don't display time range
+								addMinutes(eventEnd(event), snapMinutes*snapDelta),
+							opt('timeFormat')
+						)
+					);
+					prevSnapDelta = snapDelta;
+				}
+			},
+			stop: function(ev, ui) {
+				trigger('eventResizeStop', this, event, ev, ui);
+				if (snapDelta) {
+					eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui);
+				}else{
+					showEvents(event, eventElement);
+					// BUG: if event was really short, need to put title back in span
+				}
+			}
+		});
+	}
+	
+
+}
+
+
+
+/* Agenda Event Segment Utilities
+-----------------------------------------------------------------------------*/
+
+
+// Sets the seg.backwardCoord and seg.forwardCoord on each segment and returns a new
+// list in the order they should be placed into the DOM (an implicit z-index).
+function placeSlotSegs(segs) {
+	var levels = buildSlotSegLevels(segs);
+	var level0 = levels[0];
+	var i;
+
+	computeForwardSlotSegs(levels);
+
+	if (level0) {
+
+		for (i=0; i<level0.length; i++) {
+			computeSlotSegPressures(level0[i]);
+		}
+
+		for (i=0; i<level0.length; i++) {
+			computeSlotSegCoords(level0[i], 0, 0);
+		}
+	}
+
+	return flattenSlotSegLevels(levels);
+}
+
+
+// Builds an array of segments "levels". The first level will be the leftmost tier of segments
+// if the calendar is left-to-right, or the rightmost if the calendar is right-to-left.
+function buildSlotSegLevels(segs) {
+	var levels = [];
+	var i, seg;
+	var j;
+
+	for (i=0; i<segs.length; i++) {
+		seg = segs[i];
+
+		// go through all the levels and stop on the first level where there are no collisions
+		for (j=0; j<levels.length; j++) {
+			if (!computeSlotSegCollisions(seg, levels[j]).length) {
+				break;
+			}
+		}
+
+		(levels[j] || (levels[j] = [])).push(seg);
+	}
+
+	return levels;
+}
+
+
+// For every segment, figure out the other segments that are in subsequent
+// levels that also occupy the same vertical space. Accumulate in seg.forwardSegs
+function computeForwardSlotSegs(levels) {
+	var i, level;
+	var j, seg;
+	var k;
+
+	for (i=0; i<levels.length; i++) {
+		level = levels[i];
+
+		for (j=0; j<level.length; j++) {
+			seg = level[j];
+
+			seg.forwardSegs = [];
+			for (k=i+1; k<levels.length; k++) {
+				computeSlotSegCollisions(seg, levels[k], seg.forwardSegs);
+			}
+		}
+	}
+}
+
+
+// Figure out which path forward (via seg.forwardSegs) results in the longest path until
+// the furthest edge is reached. The number of segments in this path will be seg.forwardPressure
+function computeSlotSegPressures(seg) {
+	var forwardSegs = seg.forwardSegs;
+	var forwardPressure = 0;
+	var i, forwardSeg;
+
+	if (seg.forwardPressure === undefined) { // not already computed
+
+		for (i=0; i<forwardSegs.length; i++) {
+			forwardSeg = forwardSegs[i];
+
+			// figure out the child's maximum forward path
+			computeSlotSegPressures(forwardSeg);
+
+			// either use the existing maximum, or use the child's forward pressure
+			// plus one (for the forwardSeg itself)
+			forwardPressure = Math.max(
+				forwardPressure,
+				1 + forwardSeg.forwardPressure
+			);
+		}
+
+		seg.forwardPressure = forwardPressure;
+	}
+}
+
+
+// Calculate seg.forwardCoord and seg.backwardCoord for the segment, where both values range
+// from 0 to 1. If the calendar is left-to-right, the seg.backwardCoord maps to "left" and
+// seg.forwardCoord maps to "right" (via percentage). Vice-versa if the calendar is right-to-left.
+//
+// The segment might be part of a "series", which means consecutive segments with the same pressure
+// who's width is unknown until an edge has been hit. `seriesBackwardPressure` is the number of
+// segments behind this one in the current series, and `seriesBackwardCoord` is the starting
+// coordinate of the first segment in the series.
+function computeSlotSegCoords(seg, seriesBackwardPressure, seriesBackwardCoord) {
+	var forwardSegs = seg.forwardSegs;
+	var i;
+
+	if (seg.forwardCoord === undefined) { // not already computed
+
+		if (!forwardSegs.length) {
+
+			// if there are no forward segments, this segment should butt up against the edge
+			seg.forwardCoord = 1;
+		}
+		else {
+
+			// sort highest pressure first
+			forwardSegs.sort(compareForwardSlotSegs);
+
+			// this segment's forwardCoord will be calculated from the backwardCoord of the
+			// highest-pressure forward segment.
+			computeSlotSegCoords(forwardSegs[0], seriesBackwardPressure + 1, seriesBackwardCoord);
+			seg.forwardCoord = forwardSegs[0].backwardCoord;
+		}
+
+		// calculate the backwardCoord from the forwardCoord. consider the series
+		seg.backwardCoord = seg.forwardCoord -
+			(seg.forwardCoord - seriesBackwardCoord) / // available width for series
+			(seriesBackwardPressure + 1); // # of segments in the series
+
+		// use this segment's coordinates to computed the coordinates of the less-pressurized
+		// forward segments
+		for (i=0; i<forwardSegs.length; i++) {
+			computeSlotSegCoords(forwardSegs[i], 0, seg.forwardCoord);
+		}
+	}
+}
+
+
+// Outputs a flat array of segments, from lowest to highest level
+function flattenSlotSegLevels(levels) {
+	var segs = [];
+	var i, level;
+	var j;
+
+	for (i=0; i<levels.length; i++) {
+		level = levels[i];
+
+		for (j=0; j<level.length; j++) {
+			segs.push(level[j]);
+		}
+	}
+
+	return segs;
+}
+
+
+// Find all the segments in `otherSegs` that vertically collide with `seg`.
+// Append into an optionally-supplied `results` array and return.
+function computeSlotSegCollisions(seg, otherSegs, results) {
+	results = results || [];
+
+	for (var i=0; i<otherSegs.length; i++) {
+		if (isSlotSegCollision(seg, otherSegs[i])) {
+			results.push(otherSegs[i]);
+		}
+	}
+
+	return results;
+}
+
+
+// Do these segments occupy the same vertical space?
+function isSlotSegCollision(seg1, seg2) {
+	return seg1.end > seg2.start && seg1.start < seg2.end;
+}
+
+
+// A cmp function for determining which forward segment to rely on more when computing coordinates.
+function compareForwardSlotSegs(seg1, seg2) {
+	// put higher-pressure first
+	return seg2.forwardPressure - seg1.forwardPressure ||
+		// put segments that are closer to initial edge first (and favor ones with no coords yet)
+		(seg1.backwardCoord || 0) - (seg2.backwardCoord || 0) ||
+		// do normal sorting...
+		compareSlotSegs(seg1, seg2);
+}
+
+
+// A cmp function for determining which segment should be closer to the initial edge
+// (the left edge on a left-to-right calendar).
+function compareSlotSegs(seg1, seg2) {
+	return seg1.start - seg2.start || // earlier start time goes first
+		(seg2.end - seg2.start) - (seg1.end - seg1.start) || // tie? longer-duration goes first
+		(seg1.event.title || '').localeCompare(seg2.event.title); // tie? alphabetically by title
+}
+
+
+;;
+
+/* Additional view: list (by bruederli@kolabsys.com)
+---------------------------------------------------------------------------------*/
+
+fcViews.list = ListView;
+
+
+function ListView(element, calendar) {
+	var t = this;
+
+	// exports
+	t.render = render;
+	t.select = dummy;
+	t.unselect = dummy;
+	t.reportSelection = dummy;
+	t.getDaySegmentContainer = function(){ return body; };
+
+	// imports
+	View.call(t, element, calendar, 'list');
+	ListEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var clearEvents = t.clearEvents;
+	var reportEventClear = t.reportEventClear;
+	var formatDates = calendar.formatDates;
+	var formatDate = calendar.formatDate;
+
+	// overrides
+	t.setWidth = setWidth;
+	t.setHeight = setHeight;
+	
+	// locals
+	var body;
+	var firstDay;
+	var nwe;
+	var tm;
+	var colFormat;
+	
+	
+	function render(date, delta) {
+		if (delta) {
+			addDays(date, opt('listPage') * delta);
+		}
+		t.start = t.visStart = cloneDate(date, true);
+		t.end = addDays(cloneDate(t.start), opt('listPage'));
+		t.visEnd = addDays(cloneDate(t.start), opt('listRange'));
+		addMinutes(t.visEnd, -1);  // set end to 23:59
+		t.title = formatDates(date, t.visEnd, opt('titleFormat'));
+		
+		updateOptions();
+
+		if (!body) {
+			buildSkeleton();
+		} else {
+			clearEvents();
+		}
+	}
+	
+	
+	function updateOptions() {
+		firstDay = opt('firstDay');
+		nwe = opt('weekends') ? 0 : 1;
+		tm = opt('theme') ? 'ui' : 'fc';
+		colFormat = opt('columnFormat', 'day');
+	}
+	
+	
+	function buildSkeleton() {
+		body = $('<div>').addClass('fc-list-content').appendTo(element);
+	}
+	
+	function setHeight(height, dateChanged) {
+	  if (!opt('listNoHeight'))
+		  body.css('height', (height-1)+'px').css('overflow', 'auto');
+	}
+
+	function setWidth(width) {
+		// nothing to be done here
+	}
+	
+	function dummy() {
+		// Stub.
+	}
+
+}
+
+;;
+
+/* Additional view renderer: list (by bruederli@kolabsys.com)
+---------------------------------------------------------------------------------*/
+
+function ListEventRenderer() {
+	var t = this;
+	
+	// exports
+	t.renderEvents = renderEvents;
+	t.renderEventTime = renderEventTime;
+	t.compileDaySegs = compileSegs; // for DayEventRenderer
+	t.clearEvents = clearEvents;
+	t.lazySegBind = lazySegBind;
+	t.sortCmp = sortCmp;
+	
+	// imports
+	DayEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var reportEventElement = t.reportEventElement;
+	var eventElementHandlers = t.eventElementHandlers;
+	var showEvents = t.showEvents;
+	var hideEvents = t.hideEvents;
+	var getListContainer = t.getDaySegmentContainer;
+	var calendar = t.calendar;
+	var formatDate = calendar.formatDate;
+	var formatDates = calendar.formatDates;
+	
+	
+	/* Rendering
+	--------------------------------------------------------------------*/
+	
+	function clearEvents() {
+		getListContainer().empty();
+	}
+	
+	function renderEvents(events, modifiedEventId) {
+		events.sort(sortCmp);
+		clearEvents();
+		renderSegs(compileSegs(events), modifiedEventId);
+	}
+	
+	function compileSegs(events) {
+		var segs = [];
+		var colFormat = opt('titleFormat', 'day');
+		var firstDay = opt('firstDay');
+		var segmode = opt('listSections');
+		var event, i, dd, wd, md, seg, segHash, curSegHash, segDate, curSeg = -1;
+		var today = clearTime(new Date());
+		var weekstart = addDays(cloneDate(today), -((today.getDay() - firstDay + 7) % 7));
+		
+		for (i=0; i < events.length; i++) {
+			event = events[i];
+			
+			// skip events out of range
+			if ((event.end || event.start) < t.start || event.start > t.visEnd)
+				continue;
+			
+			// define sections of this event
+			// create smart sections such as today, tomorrow, this week, next week, next month, ect.
+			segDate = cloneDate(event.start < t.start && event.end > t.start ? t.start : event.start, true);
+			dd = dayDiff(segDate, today);
+			wd = Math.floor(dayDiff(segDate, weekstart) / 7);
+			md = segDate.getMonth() + ((segDate.getYear() - today.getYear()) * 12) - today.getMonth();
+			
+			// build section title
+			if (segmode == 'smart') {
+				if (dd < 0) {
+					segHash = opt('listTexts', 'past');
+				} else if (dd == 0) {
+					segHash = opt('listTexts', 'today');
+				} else if (dd == 1) {
+					segHash = opt('listTexts', 'tomorrow');
+				} else if (wd == 0) {
+					segHash = opt('listTexts', 'thisWeek');
+				} else if (wd == 1) {
+					segHash = opt('listTexts', 'nextWeek');
+				} else if (md == 0) {
+					segHash = opt('listTexts', 'thisMonth');
+				} else if (md == 1) {
+					segHash = opt('listTexts', 'nextMonth');
+				} else if (md > 1) {
+					segHash = opt('listTexts', 'future');
+				}
+			} else if (segmode == 'month') {
+				segHash = formatDate(segDate, 'MMMM yyyy');
+			} else if (segmode == 'week') {
+				segHash = opt('listTexts', 'week') + formatDate(segDate, ' W');
+			} else if (segmode == 'day') {
+				segHash = formatDate(segDate, colFormat);
+			} else {
+				segHash = '';
+			}
+			
+			// start new segment
+			if (segHash != curSegHash) {
+				segs[++curSeg] = { events: [], start: segDate, title: segHash, daydiff: dd, weekdiff: wd, monthdiff: md };
+				curSegHash = segHash;
+			}
+			
+			segs[curSeg].events.push(event);
+		}
+		
+		return segs;
+	}
+
+	function sortCmp(a, b) {
+		var sd = a.start.getTime() - b.start.getTime();
+		return sd || (a.end ? a.end.getTime() : 0) - (b.end ? b.end.getTime() : 0);
+	}
+	
+	function renderSegs(segs, modifiedEventId) {
+		var tm = opt('theme') ? 'ui' : 'fc';
+		var headerClass = tm + "-widget-header";
+		var contentClass = tm + "-widget-content";
+		var i, j, seg, event, times, s, skinCss, skinCssAttr, classes, segContainer, eventElement, eventElements, triggerRes;
+
+		for (j=0; j < segs.length; j++) {
+			seg = segs[j];
+			
+			if (seg.title) {
+				$('<div class="fc-list-header ' + headerClass + '">' + htmlEscape(seg.title) + '</div>').appendTo(getListContainer());
+			}
+			segContainer = $('<div>').addClass('fc-list-section ' + contentClass).appendTo(getListContainer());
+			s = '';
+			
+			for (i=0; i < seg.events.length; i++) {
+				event = seg.events[i];
+				times = renderEventTime(event, seg);
+				skinCss = getSkinCss(event, opt);
+				skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
+				classes = ['fc-event', 'fc-event-skin', 'fc-event-vert', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
+				if (event.source && event.source.className) {
+					classes = classes.concat(event.source.className);
+				}
+				
+				s += 
+					"<div class='" + classes.join(' ') + "'" + skinCssAttr + ">" +
+					"<div class='fc-event-inner fc-event-skin'" + skinCssAttr + ">" +
+					"<div class='fc-event-head fc-event-skin'" + skinCssAttr + ">" +
+					"<div class='fc-event-time'>" +
+					(times[0] ? '<span class="fc-col-date">' + times[0] + '</span> ' : '') +
+					(times[1] ? '<span class="fc-col-time">' + times[1] + '</span>' : '') +
+					"</div>" +
+					"</div>" +
+					"<div class='fc-event-content'>" +
+					"<div class='fc-event-title'>" +
+					htmlEscape(event.title) +
+					"</div>" +
+					"</div>" +
+					"<div class='fc-event-bg'></div>" +
+					"</div>" + // close inner
+					"</div>";  // close outer
+			}
+			
+			segContainer[0].innerHTML = s;
+			eventElements = segContainer.children();
+
+			// retrieve elements, run through eventRender callback, bind event handlers
+			for (i=0; i < seg.events.length; i++) {
+				event = seg.events[i];
+				eventElement = $(eventElements[i]); // faster than eq()
+				triggerRes = trigger('eventRender', event, event, eventElement);
+				if (triggerRes === false) {
+					eventElement.remove();
+				} else {
+					if (triggerRes && triggerRes !== true) {
+						eventElement.remove();
+						eventElement = $(triggerRes).appendTo(segContainer);
+					}
+					if (event._id === modifiedEventId) {
+						eventElementHandlers(event, eventElement, seg);
+					} else {
+						eventElement[0]._fci = i; // for lazySegBind
+					}
+					reportEventElement(event, eventElement);
+				}
+			}
+		
+			lazySegBind(segContainer, seg, eventElementHandlers);
+		}
+		
+		markFirstLast(getListContainer());
+	}
+	
+	// event time/date range to display
+	function renderEventTime(event, seg) {
+		var timeFormat = opt('timeFormat');
+		var dateFormat = opt('columnFormat');
+		var segmode = opt('listSections');
+		var duration = event.end ? event.end.getTime() - event.start.getTime() : 0;
+		var datestr = '', timestr = '';
+		
+		if (segmode == 'smart') {
+			if (event.start < seg.start) {
+				datestr = opt('listTexts', 'until') + ' ' + formatDate(event.end, (event.allDay || event.end.getDate() != seg.start.getDate()) ? dateFormat : timeFormat);
+			} else if (duration > DAY_MS) {
+				datestr = formatDates(event.start, event.end, dateFormat + '{ - ' + dateFormat + '}');
+			} else if (seg.daydiff == 0) {
+				datestr = opt('listTexts', 'today');
+			}	else if (seg.daydiff == 1) {
+				datestr = opt('listTexts', 'tomorrow');
+			} else if (seg.weekdiff == 0 || seg.weekdiff == 1) {
+				datestr = formatDate(event.start, 'dddd');
+			} else if (seg.daydiff > 1 || seg.daydiff < 0) {
+				datestr = formatDate(event.start, dateFormat);
+			}
+		} else if (segmode != 'day') {
+			datestr = formatDates(event.start, event.end, dateFormat + (duration > DAY_MS ? '{ - ' + dateFormat + '}' : ''));
+		}
+		
+		if (!datestr && event.allDay) {
+			timestr = opt('allDayText');
+		} else if ((duration < DAY_MS || !datestr) && !event.allDay) {
+			timestr = formatDates(event.start, event.end, timeFormat);
+		}
+		
+		return [datestr, timestr];
+	}
+	
+	function lazySegBind(container, seg, bindHandlers) {
+		container.unbind('mouseover focusin').bind('mouseover focusin', function(ev) {
+			var parent = ev.target, e = parent, i, event;
+			while (parent != this) {
+				e = parent;
+				parent = parent.parentNode;
+			}
+			if ((i = e._fci) !== undefined) {
+				e._fci = undefined;
+				event = seg.events[i];
+				bindHandlers(event, container.children().eq(i), seg);
+				$(ev.target).trigger(ev);
+			}
+			ev.stopPropagation();
+		});
+	}
+	
+}
+
+
+;;
+
+/* Additional view: table (by bruederli@kolabsys.com)
+---------------------------------------------------------------------------------*/
+
+fcViews.table = TableView;
+
+
+function TableView(element, calendar) {
+	var t = this;
+
+	// exports
+	t.render = render;
+	t.select = dummy;
+	t.unselect = dummy;
+	t.getDaySegmentContainer = function(){ return table; };
+
+	// imports
+	View.call(t, element, calendar, 'table');
+	TableEventRenderer.call(t);
+	var opt = t.opt;
+	var trigger = t.trigger;
+  var clearEvents = t.clearEvents;
+	var reportEventClear = t.reportEventClear;
+	var formatDates = calendar.formatDates;
+	var formatDate = calendar.formatDate;
+
+	// overrides
+	t.setWidth = setWidth;
+	t.setHeight = setHeight;
+	
+	// locals
+	var div;
+	var table;
+	var firstDay;
+	var nwe;
+	var tm;
+	var colFormat;
+	
+	
+	function render(date, delta) {
+		if (delta) {
+			addDays(date, opt('listPage') * delta);
+		}
+		t.start = t.visStart = cloneDate(date, true);
+		t.end = addDays(cloneDate(t.start), opt('listPage'));
+		t.visEnd = addDays(cloneDate(t.start), opt('listRange'));
+		addMinutes(t.visEnd, -1);  // set end to 23:59
+		t.title = (t.visEnd.getTime() - t.visStart.getTime() < DAY_MS) ? formatDate(date, opt('titleFormat')) : formatDates(date, t.visEnd, opt('titleFormat'));
+		
+		updateOptions();
+
+		if (!table) {
+			buildSkeleton();
+		} else {
+			clearEvents();
+		}
+	}
+	
+	
+	function updateOptions() {
+		firstDay = opt('firstDay');
+		nwe = opt('weekends') ? 0 : 1;
+		tm = opt('theme') ? 'ui' : 'fc';
+		colFormat = opt('columnFormat');
+	}
+	
+	
+	function buildSkeleton() {
+		var tableCols = opt('tableCols');
+		var s =
+			"<table class='fc-border-separate' style='width:100%' cellspacing='0'>" +
+			"<colgroup>";
+		for (var c=0; c < tableCols.length; c++) {
+			s += "<col class='fc-event-" + tableCols[c] + "' />";
+		}
+		s += "</colgroup>" +
+			"</table>";
+		div = $('<div>').addClass('fc-list-content').appendTo(element);
+		table = $(s).appendTo(div);
+	}
+	
+	function setHeight(height, dateChanged) {
+	  if (!opt('listNoHeight'))
+		  div.css('height', (height-1)+'px').css('overflow', 'auto');
+	}
+
+	function setWidth(width) {
+		// nothing to be done here
+	}
+	
+	function dummy() {
+		// Stub.
+	}
+
+}
+
+;;
+
+/* Additional view renderer: table (by bruederli@kolabsys.com)
+---------------------------------------------------------------------------------*/
+
+function TableEventRenderer() {
+	var t = this;
+	
+	// imports
+	ListEventRenderer.call(t);
+	var opt = t.opt;
+	var sortCmp = t.sortCmp;
+	var trigger = t.trigger;
+	var compileSegs = t.compileDaySegs;
+	var reportEventElement = t.reportEventElement;
+	var eventElementHandlers = t.eventElementHandlers;
+	var renderEventTime = t.renderEventTime;
+	var showEvents = t.showEvents;
+	var hideEvents = t.hideEvents;
+	var getListContainer = t.getDaySegmentContainer;
+	var lazySegBind = t.lazySegBind;
+	var calendar = t.calendar;
+	var formatDate = calendar.formatDate;
+	var formatDates = calendar.formatDates;
+
+	// exports
+	t.renderEvents = renderEvents;
+	t.clearEvents = clearEvents;
+
+
+	/* Rendering
+	--------------------------------------------------------------------*/
+	
+	function clearEvents() {
+		getListContainer().children('tbody').remove();
+	}
+	
+	function renderEvents(events, modifiedEventId) {
+		events.sort(sortCmp);
+		clearEvents();
+		renderSegs(compileSegs(events), modifiedEventId);
+		getListContainer().removeClass('fc-list-smart fc-list-day fc-list-month fc-list-week').addClass('fc-list-' + opt('listSections'));
+	}
+
+	function renderSegs(segs, modifiedEventId) {
+		var tm = opt('theme') ? 'ui' : 'fc';
+		var table = getListContainer();
+		var headerClass = tm + "-widget-header";
+		var contentClass = tm + "-widget-content";
+		var tableCols = opt('tableCols');
+		var timecol = $.inArray('time', tableCols) >= 0;
+		var i, j, seg, event, times, s, skinCss, skinCssAttr, skinClasses, rowClasses, segContainer, eventElements, eventElement, triggerRes;
+
+		for (j=0; j < segs.length; j++) {
+			seg = segs[j];
+			
+			if (seg.title) {
+				$('<tbody class="fc-list-header"><tr><td class="fc-list-header ' + headerClass + '" colspan="' + tableCols.length + '">' + htmlEscape(seg.title) + '</td></tr></tbody>').appendTo(table);
+			}
+			segContainer = $('<tbody>').addClass('fc-list-section ' + contentClass).appendTo(table);
+			s = '';
+			
+			for (i=0; i < seg.events.length; i++) {
+				event = seg.events[i];
+				times = renderEventTime(event, seg);
+				skinCss = getSkinCss(event, opt);
+				skinCssAttr = (skinCss ? " style='" + skinCss + "'" : '');
+				skinClasses = ['fc-event-skin', 'fc-corner-left', 'fc-corner-right', 'fc-corner-top', 'fc-corner-bottom'].concat(event.className);
+				if (event.source && event.source.className) {
+					skinClasses = skinClasses.concat(event.source.className);
+				}
+				rowClasses = ['fc-event', 'fc-event-row', 'fc-'+dayIDs[event.start.getDay()]].concat(event.className);
+				if (seg.daydiff == 0) {
+					rowClasses.push('fc-today');
+				}
+				
+				s +=  "<tr class='" + rowClasses.join(' ') + "' tabindex='0'>";
+				for (var col, c=0; c < tableCols.length; c++) {
+					col = tableCols[c];
+					if (col == 'handle') {
+						s += "<td class='fc-event-handle'>" +
+							"<div class='" + skinClasses.join(' ') + "'" + skinCssAttr + ">" +
+							"<span class='fc-event-inner'></span>" +
+							"</div></td>";
+					} else if (col == 'date') {
+						s += "<td class='fc-event-date' colspan='" + (times[1] || !timecol ? 1 : 2) + "'>" + htmlEscape(times[0]) + "</td>";
+					} else if (col == 'time') {
+						if (times[1]) {
+							s += "<td class='fc-event-time'>" + htmlEscape(times[1]) + "</td>";
+						}
+					} else {
+						s += "<td class='fc-event-" + col + "'>" + (event[col] ? htmlEscape(event[col]) : '&nbsp;') + "</td>";
+					}
+				}
+				s += "</tr>";
+				
+				// IE doesn't like innerHTML on tbody elements so we insert every row individually
+				if (document.all) {
+					$(s).appendTo(segContainer);
+					s = '';
+				}
+			}
+
+			if (!document.all)
+				segContainer[0].innerHTML = s;
+
+			eventElements = segContainer.children();
+
+			// retrieve elements, run through eventRender callback, bind event handlers
+			for (i=0; i < seg.events.length; i++) {
+				event = seg.events[i];
+				eventElement = $(eventElements[i]); // faster than eq()
+				triggerRes = trigger('eventRender', event, event, eventElement);
+				if (triggerRes === false) {
+					eventElement.remove();
+				} else {
+					if (triggerRes && triggerRes !== true) {
+						eventElement.remove();
+						eventElement = $(triggerRes).appendTo(segContainer);
+					}
+					if (event._id === modifiedEventId) {
+						eventElementHandlers(event, eventElement, seg);
+					} else {
+						eventElement[0]._fci = i; // for lazySegBind
+					}
+					reportEventElement(event, eventElement);
+				}
+			}
+		
+			lazySegBind(segContainer, seg, eventElementHandlers);
+			markFirstLast(segContainer);
+		}
+		
+		//markFirstLast(table);
+	}
+
+}
+;;
+
+
+function View(element, calendar, viewName) {
+	var t = this;
+	
+	
+	// exports
+	t.element = element;
+	t.calendar = calendar;
+	t.name = viewName;
+	t.opt = opt;
+	t.trigger = trigger;
+	t.isEventDraggable = isEventDraggable;
+	t.isEventResizable = isEventResizable;
+	t.setEventData = setEventData;
+	t.clearEventData = clearEventData;
+	t.eventEnd = eventEnd;
+	t.reportEventElement = reportEventElement;
+	t.triggerEventDestroy = triggerEventDestroy;
+	t.eventElementHandlers = eventElementHandlers;
+	t.showEvents = showEvents;
+	t.hideEvents = hideEvents;
+	t.eventDrop = eventDrop;
+	t.eventResize = eventResize;
+	// t.title
+	// t.start, t.end
+	// t.visStart, t.visEnd
+	
+	
+	// imports
+	var defaultEventEnd = t.defaultEventEnd;
+	var normalizeEvent = calendar.normalizeEvent; // in EventManager
+	var reportEventChange = calendar.reportEventChange;
+	
+	
+	// locals
+	var eventsByID = {}; // eventID mapped to array of events (there can be multiple b/c of repeating events)
+	var eventElementsByID = {}; // eventID mapped to array of jQuery elements
+	var eventElementCouples = []; // array of objects, { event, element } // TODO: unify with segment system
+	var options = calendar.options;
+	
+	
+	
+	function opt(name, viewNameOverride) {
+		var v = options[name];
+		if ($.isPlainObject(v)) {
+			return smartProperty(v, viewNameOverride || viewName);
+		}
+		return v;
+	}
+
+	
+	function trigger(name, thisObj) {
+		return calendar.trigger.apply(
+			calendar,
+			[name, thisObj || t].concat(Array.prototype.slice.call(arguments, 2), [t])
+		);
+	}
+	
+
+
+	/* Event Editable Boolean Calculations
+	------------------------------------------------------------------------------*/
+
+	
+	function isEventDraggable(event) {
+		var source = event.source || {};
+		return firstDefined(
+				event.startEditable,
+				source.startEditable,
+				opt('eventStartEditable'),
+				event.editable,
+				source.editable,
+				opt('editable')
+			)
+			&& !opt('disableDragging'); // deprecated
+	}
+	
+	
+	function isEventResizable(event) { // but also need to make sure the seg.isEnd == true
+		var source = event.source || {};
+		return firstDefined(
+				event.durationEditable,
+				source.durationEditable,
+				opt('eventDurationEditable'),
+				event.editable,
+				source.editable,
+				opt('editable')
+			)
+			&& !opt('disableResizing'); // deprecated
+	}
+	
+	
+	
+	/* Event Data
+	------------------------------------------------------------------------------*/
+	
+	
+	function setEventData(events) { // events are already normalized at this point
+		eventsByID = {};
+		var i, len=events.length, event;
+		for (i=0; i<len; i++) {
+			event = events[i];
+			if (eventsByID[event._id]) {
+				eventsByID[event._id].push(event);
+			}else{
+				eventsByID[event._id] = [event];
+			}
+		}
+	}
+
+
+	function clearEventData() {
+		eventsByID = {};
+		eventElementsByID = {};
+		eventElementCouples = [];
+	}
+	
+	
+	// returns a Date object for an event's end
+	function eventEnd(event) {
+		return event.end ? cloneDate(event.end) : defaultEventEnd(event);
+	}
+	
+	
+	
+	/* Event Elements
+	------------------------------------------------------------------------------*/
+	
+	
+	// report when view creates an element for an event
+	function reportEventElement(event, element) {
+		eventElementCouples.push({ event: event, element: element });
+		if (eventElementsByID[event._id]) {
+			eventElementsByID[event._id].push(element);
+		}else{
+			eventElementsByID[event._id] = [element];
+		}
+	}
+
+
+	function triggerEventDestroy() {
+		$.each(eventElementCouples, function(i, couple) {
+			t.trigger('eventDestroy', couple.event, couple.event, couple.element);
+		});
+	}
+	
+	
+	// attaches eventClick, eventMouseover, eventMouseout
+	function eventElementHandlers(event, eventElement) {
+		eventElement
+			.click(function(ev) {
+				if (!eventElement.hasClass('ui-draggable-dragging') &&
+					!eventElement.hasClass('ui-resizable-resizing')) {
+						return trigger('eventClick', this, event, ev);
+					}
+			})
+			.hover(
+				function(ev) {
+					trigger('eventMouseover', this, event, ev);
+				},
+				function(ev) {
+					trigger('eventMouseout', this, event, ev);
+				}
+			)
+			.keypress(function(ev) {
+				if (ev.keyCode == 13)
+					$(this).trigger('click', { pointerType:'keyboard' });
+			});
+		// TODO: don't fire eventMouseover/eventMouseout *while* dragging is occuring (on subject element)
+		// TODO: same for resizing
+	}
+	
+	
+	function showEvents(event, exceptElement) {
+		eachEventElement(event, exceptElement, 'show');
+	}
+	
+	
+	function hideEvents(event, exceptElement) {
+		eachEventElement(event, exceptElement, 'hide');
+	}
+	
+	
+	function eachEventElement(event, exceptElement, funcName) {
+		// NOTE: there may be multiple events per ID (repeating events)
+		// and multiple segments per event
+		var elements = eventElementsByID[event._id],
+			i, len = elements.length;
+		for (i=0; i<len; i++) {
+			if (!exceptElement || elements[i][0] != exceptElement[0]) {
+				elements[i][funcName]();
+			}
+		}
+	}
+	
+	
+	
+	/* Event Modification Reporting
+	---------------------------------------------------------------------------------*/
+	
+	
+	function eventDrop(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
+		var oldAllDay = event.allDay;
+		var eventId = event._id;
+		moveEvents(eventsByID[eventId], dayDelta, minuteDelta, allDay);
+		trigger(
+			'eventDrop',
+			e,
+			event,
+			dayDelta,
+			minuteDelta,
+			allDay,
+			function() {
+				// TODO: investigate cases where this inverse technique might not work
+				moveEvents(eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
+				reportEventChange(eventId);
+			},
+			ev,
+			ui
+		);
+		reportEventChange(eventId);
+	}
+	
+	
+	function eventResize(e, event, dayDelta, minuteDelta, ev, ui) {
+		var eventId = event._id;
+		elongateEvents(eventsByID[eventId], dayDelta, minuteDelta);
+		trigger(
+			'eventResize',
+			e,
+			event,
+			dayDelta,
+			minuteDelta,
+			function() {
+				// TODO: investigate cases where this inverse technique might not work
+				elongateEvents(eventsByID[eventId], -dayDelta, -minuteDelta);
+				reportEventChange(eventId);
+			},
+			ev,
+			ui
+		);
+		reportEventChange(eventId);
+	}
+	
+	
+	
+	/* Event Modification Math
+	---------------------------------------------------------------------------------*/
+	
+	
+	function moveEvents(events, dayDelta, minuteDelta, allDay) {
+		minuteDelta = minuteDelta || 0;
+		for (var e, len=events.length, i=0; i<len; i++) {
+			e = events[i];
+			if (allDay !== undefined) {
+				e.allDay = allDay;
+			}
+			addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
+			if (e.end) {
+				e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
+			}
+			normalizeEvent(e, options);
+		}
+	}
+	
+	
+	function elongateEvents(events, dayDelta, minuteDelta) {
+		minuteDelta = minuteDelta || 0;
+		for (var e, len=events.length, i=0; i<len; i++) {
+			e = events[i];
+			e.end = addMinutes(addDays(eventEnd(e), dayDelta, true), minuteDelta);
+			normalizeEvent(e, options);
+		}
+	}
+
+
+
+	// ====================================================================================================
+	// Utilities for day "cells"
+	// ====================================================================================================
+	// The "basic" views are completely made up of day cells.
+	// The "agenda" views have day cells at the top "all day" slot.
+	// This was the obvious common place to put these utilities, but they should be abstracted out into
+	// a more meaningful class (like DayEventRenderer).
+	// ====================================================================================================
+
+
+	// For determining how a given "cell" translates into a "date":
+	//
+	// 1. Convert the "cell" (row and column) into a "cell offset" (the # of the cell, cronologically from the first).
+	//    Keep in mind that column indices are inverted with isRTL. This is taken into account.
+	//
+	// 2. Convert the "cell offset" to a "day offset" (the # of days since the first visible day in the view).
+	//
+	// 3. Convert the "day offset" into a "date" (a JavaScript Date object).
+	//
+	// The reverse transformation happens when transforming a date into a cell.
+
+
+	// exports
+	t.isHiddenDay = isHiddenDay;
+	t.skipHiddenDays = skipHiddenDays;
+	t.getCellsPerWeek = getCellsPerWeek;
+	t.dateToCell = dateToCell;
+	t.dateToDayOffset = dateToDayOffset;
+	t.dayOffsetToCellOffset = dayOffsetToCellOffset;
+	t.cellOffsetToCell = cellOffsetToCell;
+	t.cellToDate = cellToDate;
+	t.cellToCellOffset = cellToCellOffset;
+	t.cellOffsetToDayOffset = cellOffsetToDayOffset;
+	t.dayOffsetToDate = dayOffsetToDate;
+	t.rangeToSegments = rangeToSegments;
+
+
+	// internals
+	var hiddenDays = opt('hiddenDays') || []; // array of day-of-week indices that are hidden
+	var isHiddenDayHash = []; // is the day-of-week hidden? (hash with day-of-week-index -> bool)
+	var cellsPerWeek;
+	var dayToCellMap = []; // hash from dayIndex -> cellIndex, for one week
+	var cellToDayMap = []; // hash from cellIndex -> dayIndex, for one week
+	var isRTL = opt('isRTL');
+
+
+	// initialize important internal variables
+	(function() {
+
+		if (opt('weekends') === false) {
+			hiddenDays.push(0, 6); // 0=sunday, 6=saturday
+		}
+
+		// Loop through a hypothetical week and determine which
+		// days-of-week are hidden. Record in both hashes (one is the reverse of the other).
+		for (var dayIndex=0, cellIndex=0; dayIndex<7; dayIndex++) {
+			dayToCellMap[dayIndex] = cellIndex;
+			isHiddenDayHash[dayIndex] = $.inArray(dayIndex, hiddenDays) != -1;
+			if (!isHiddenDayHash[dayIndex]) {
+				cellToDayMap[cellIndex] = dayIndex;
+				cellIndex++;
+			}
+		}
+
+		cellsPerWeek = cellIndex;
+		if (!cellsPerWeek) {
+			throw 'invalid hiddenDays'; // all days were hidden? bad.
+		}
+
+	})();
+
+
+	// Is the current day hidden?
+	// `day` is a day-of-week index (0-6), or a Date object
+	function isHiddenDay(day) {
+		if (typeof day == 'object') {
+			day = day.getDay();
+		}
+		return isHiddenDayHash[day];
+	}
+
+
+	function getCellsPerWeek() {
+		return cellsPerWeek;
+	}
+
+
+	// Keep incrementing the current day until it is no longer a hidden day.
+	// If the initial value of `date` is not a hidden day, don't do anything.
+	// Pass `isExclusive` as `true` if you are dealing with an end date.
+	// `inc` defaults to `1` (increment one day forward each time)
+	function skipHiddenDays(date, inc, isExclusive) {
+		inc = inc || 1;
+		while (
+			isHiddenDayHash[ ( date.getDay() + (isExclusive ? inc : 0) + 7 ) % 7 ]
+		) {
+			addDays(date, inc);
+		}
+	}
+
+
+	//
+	// TRANSFORMATIONS: cell -> cell offset -> day offset -> date
+	//
+
+	// cell -> date (combines all transformations)
+	// Possible arguments:
+	// - row, col
+	// - { row:#, col: # }
+	function cellToDate() {
+		var cellOffset = cellToCellOffset.apply(null, arguments);
+		var dayOffset = cellOffsetToDayOffset(cellOffset);
+		var date = dayOffsetToDate(dayOffset);
+		return date;
+	}
+
+	// cell -> cell offset
+	// Possible arguments:
+	// - row, col
+	// - { row:#, col:# }
+	function cellToCellOffset(row, col) {
+		var colCnt = t.getColCnt();
+
+		// rtl variables. wish we could pre-populate these. but where?
+		var dis = isRTL ? -1 : 1;
+		var dit = isRTL ? colCnt - 1 : 0;
+
+		if (typeof row == 'object') {
+			col = row.col;
+			row = row.row;
+		}
+		var cellOffset = row * colCnt + (col * dis + dit); // column, adjusted for RTL (dis & dit)
+
+		return cellOffset;
+	}
+
+	// cell offset -> day offset
+	function cellOffsetToDayOffset(cellOffset) {
+		var day0 = t.visStart.getDay(); // first date's day of week
+		cellOffset += dayToCellMap[day0]; // normlize cellOffset to beginning-of-week
+		return Math.floor(cellOffset / cellsPerWeek) * 7 // # of days from full weeks
+			+ cellToDayMap[ // # of days from partial last week
+				(cellOffset % cellsPerWeek + cellsPerWeek) % cellsPerWeek // crazy math to handle negative cellOffsets
+			]
+			- day0; // adjustment for beginning-of-week normalization
+	}
+
+	// day offset -> date (JavaScript Date object)
+	function dayOffsetToDate(dayOffset) {
+		var date = cloneDate(t.visStart);
+		addDays(date, dayOffset);
+		return date;
+	}
+
+
+	//
+	// TRANSFORMATIONS: date -> day offset -> cell offset -> cell
+	//
+
+	// date -> cell (combines all transformations)
+	function dateToCell(date) {
+		var dayOffset = dateToDayOffset(date);
+		var cellOffset = dayOffsetToCellOffset(dayOffset);
+		var cell = cellOffsetToCell(cellOffset);
+		return cell;
+	}
+
+	// date -> day offset
+	function dateToDayOffset(date) {
+		return dayDiff(date, t.visStart);
+	}
+
+	// day offset -> cell offset
+	function dayOffsetToCellOffset(dayOffset) {
+		var day0 = t.visStart.getDay(); // first date's day of week
+		dayOffset += day0; // normalize dayOffset to beginning-of-week
+		return Math.floor(dayOffset / 7) * cellsPerWeek // # of cells from full weeks
+			+ dayToCellMap[ // # of cells from partial last week
+				(dayOffset % 7 + 7) % 7 // crazy math to handle negative dayOffsets
+			]
+			- dayToCellMap[day0]; // adjustment for beginning-of-week normalization
+	}
+
+	// cell offset -> cell (object with row & col keys)
+	function cellOffsetToCell(cellOffset) {
+		var colCnt = t.getColCnt();
+
+		// rtl variables. wish we could pre-populate these. but where?
+		var dis = isRTL ? -1 : 1;
+		var dit = isRTL ? colCnt - 1 : 0;
+
+		var row = Math.floor(cellOffset / colCnt);
+		var col = ((cellOffset % colCnt + colCnt) % colCnt) * dis + dit; // column, adjusted for RTL (dis & dit)
+		return {
+			row: row,
+			col: col
+		};
+	}
+
+
+	//
+	// Converts a date range into an array of segment objects.
+	// "Segments" are horizontal stretches of time, sliced up by row.
+	// A segment object has the following properties:
+	// - row
+	// - cols
+	// - isStart
+	// - isEnd
+	//
+	function rangeToSegments(startDate, endDate) {
+		var rowCnt = t.getRowCnt();
+		var colCnt = t.getColCnt();
+		var segments = []; // array of segments to return
+
+		// day offset for given date range
+		var rangeDayOffsetStart = dateToDayOffset(startDate);
+		var rangeDayOffsetEnd = dateToDayOffset(endDate); // exclusive
+
+		// first and last cell offset for the given date range
+		// "last" implies inclusivity
+		var rangeCellOffsetFirst = dayOffsetToCellOffset(rangeDayOffsetStart);
+		var rangeCellOffsetLast = dayOffsetToCellOffset(rangeDayOffsetEnd) - 1;
+
+		// loop through all the rows in the view
+		for (var row=0; row<rowCnt; row++) {
+
+			// first and last cell offset for the row
+			var rowCellOffsetFirst = row * colCnt;
+			var rowCellOffsetLast = rowCellOffsetFirst + colCnt - 1;
+
+			// get the segment's cell offsets by constraining the range's cell offsets to the bounds of the row
+			var segmentCellOffsetFirst = Math.max(rangeCellOffsetFirst, rowCellOffsetFirst);
+			var segmentCellOffsetLast = Math.min(rangeCellOffsetLast, rowCellOffsetLast);
+
+			// make sure segment's offsets are valid and in view
+			if (segmentCellOffsetFirst <= segmentCellOffsetLast) {
+
+				// translate to cells
+				var segmentCellFirst = cellOffsetToCell(segmentCellOffsetFirst);
+				var segmentCellLast = cellOffsetToCell(segmentCellOffsetLast);
+
+				// view might be RTL, so order by leftmost column
+				var cols = [ segmentCellFirst.col, segmentCellLast.col ].sort();
+
+				// Determine if segment's first/last cell is the beginning/end of the date range.
+				// We need to compare "day offset" because "cell offsets" are often ambiguous and
+				// can translate to multiple days, and an edge case reveals itself when we the
+				// range's first cell is hidden (we don't want isStart to be true).
+				var isStart = cellOffsetToDayOffset(segmentCellOffsetFirst) == rangeDayOffsetStart;
+				var isEnd = cellOffsetToDayOffset(segmentCellOffsetLast) + 1 == rangeDayOffsetEnd; // +1 for comparing exclusively
+
+				segments.push({
+					row: row,
+					leftCol: cols[0],
+					rightCol: cols[1],
+					isStart: isStart,
+					isEnd: isEnd
+				});
+			}
+		}
+
+		return segments;
+	}
+	
+
+}
+
+;;
+
+function DayEventRenderer() {
+	var t = this;
+
+	
+	// exports
+	t.renderDayEvents = renderDayEvents;
+	t.draggableDayEvent = draggableDayEvent; // made public so that subclasses can override
+	t.resizableDayEvent = resizableDayEvent; // "
+	
+	
+	// imports
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var isEventDraggable = t.isEventDraggable;
+	var isEventResizable = t.isEventResizable;
+	var eventEnd = t.eventEnd;
+	var reportEventElement = t.reportEventElement;
+	var eventElementHandlers = t.eventElementHandlers;
+	var showEvents = t.showEvents;
+	var hideEvents = t.hideEvents;
+	var eventDrop = t.eventDrop;
+	var eventResize = t.eventResize;
+	var getRowCnt = t.getRowCnt;
+	var getColCnt = t.getColCnt;
+	var getColWidth = t.getColWidth;
+	var allDayRow = t.allDayRow; // TODO: rename
+	var colLeft = t.colLeft;
+	var colRight = t.colRight;
+	var colContentLeft = t.colContentLeft;
+	var colContentRight = t.colContentRight;
+	var dateToCell = t.dateToCell;
+	var getDaySegmentContainer = t.getDaySegmentContainer;
+	var formatDates = t.calendar.formatDates;
+	var renderDayOverlay = t.renderDayOverlay;
+	var clearOverlays = t.clearOverlays;
+	var clearSelection = t.clearSelection;
+	var getHoverListener = t.getHoverListener;
+	var rangeToSegments = t.rangeToSegments;
+	var cellToDate = t.cellToDate;
+	var cellToCellOffset = t.cellToCellOffset;
+	var cellOffsetToDayOffset = t.cellOffsetToDayOffset;
+	var dateToDayOffset = t.dateToDayOffset;
+	var dayOffsetToCellOffset = t.dayOffsetToCellOffset;
+
+
+	// Render `events` onto the calendar, attach mouse event handlers, and call the `eventAfterRender` callback for each.
+	// Mouse event will be lazily applied, except if the event has an ID of `modifiedEventId`.
+	// Can only be called when the event container is empty (because it wipes out all innerHTML).
+	function renderDayEvents(events, modifiedEventId) {
+
+		// do the actual rendering. Receive the intermediate "segment" data structures.
+		var segments = _renderDayEvents(
+			events,
+			false, // don't append event elements
+			true // set the heights of the rows
+		);
+
+		// report the elements to the View, for general drag/resize utilities
+		segmentElementEach(segments, function(segment, element) {
+			reportEventElement(segment.event, element);
+		});
+
+		// attach mouse handlers
+		attachHandlers(segments, modifiedEventId);
+
+		// call `eventAfterRender` callback for each event
+		segmentElementEach(segments, function(segment, element) {
+			trigger('eventAfterRender', segment.event, segment.event, element);
+		});
+	}
+
+
+	// Render an event on the calendar, but don't report them anywhere, and don't attach mouse handlers.
+	// Append this event element to the event container, which might already be populated with events.
+	// If an event's segment will have row equal to `adjustRow`, then explicitly set its top coordinate to `adjustTop`.
+	// This hack is used to maintain continuity when user is manually resizing an event.
+	// Returns an array of DOM elements for the event.
+	function renderTempDayEvent(event, adjustRow, adjustTop) {
+
+		// actually render the event. `true` for appending element to container.
+		// Recieve the intermediate "segment" data structures.
+		var segments = _renderDayEvents(
+			[ event ],
+			true, // append event elements
+			false // don't set the heights of the rows
+		);
+
+		var elements = [];
+
+		// Adjust certain elements' top coordinates
+		segmentElementEach(segments, function(segment, element) {
+			if (segment.row === adjustRow) {
+				element.css('top', adjustTop);
+			}
+			elements.push(element[0]); // accumulate DOM nodes
+		});
+
+		return elements;
+	}
+
+
+	// Render events onto the calendar. Only responsible for the VISUAL aspect.
+	// Not responsible for attaching handlers or calling callbacks.
+	// Set `doAppend` to `true` for rendering elements without clearing the existing container.
+	// Set `doRowHeights` to allow setting the height of each row, to compensate for vertical event overflow.
+	function _renderDayEvents(events, doAppend, doRowHeights) {
+
+		// where the DOM nodes will eventually end up
+		var finalContainer = getDaySegmentContainer();
+
+		// the container where the initial HTML will be rendered.
+		// If `doAppend`==true, uses a temporary container.
+		var renderContainer = doAppend ? $("<div/>") : finalContainer;
+
+		var segments = buildSegments(events);
+		var html;
+		var elements;
+
+		// calculate the desired `left` and `width` properties on each segment object
+		calculateHorizontals(segments);
+
+		// build the HTML string. relies on `left` property
+		html = buildHTML(segments);
+
+		// render the HTML. innerHTML is considerably faster than jQuery's .html()
+		renderContainer[0].innerHTML = html;
+
+		// retrieve the individual elements
+		elements = renderContainer.children();
+
+		// if we were appending, and thus using a temporary container,
+		// re-attach elements to the real container.
+		if (doAppend) {
+			finalContainer.append(elements);
+		}
+
+		// assigns each element to `segment.event`, after filtering them through user callbacks
+		resolveElements(segments, elements);
+
+		// Calculate the left and right padding+margin for each element.
+		// We need this for setting each element's desired outer width, because of the W3C box model.
+		// It's important we do this in a separate pass from acually setting the width on the DOM elements
+		// because alternating reading/writing dimensions causes reflow for every iteration.
+		segmentElementEach(segments, function(segment, element) {
+			segment.hsides = hsides(element, true); // include margins = `true`
+		});
+
+		// Set the width of each element
+		segmentElementEach(segments, function(segment, element) {
+			element.width(
+				Math.max(0, segment.outerWidth - segment.hsides)
+			);
+		});
+
+		// Grab each element's outerHeight (setVerticals uses this).
+		// To get an accurate reading, it's important to have each element's width explicitly set already.
+		segmentElementEach(segments, function(segment, element) {
+			segment.outerHeight = element.outerHeight(true); // include margins = `true`
+		});
+
+		// Set the top coordinate on each element (requires segment.outerHeight)
+		setVerticals(segments, doRowHeights);
+
+		return segments;
+	}
+
+
+	// Generate an array of "segments" for all events.
+	function buildSegments(events) {
+		var segments = [];
+		for (var i=0; i<events.length; i++) {
+			var eventSegments = buildSegmentsForEvent(events[i]);
+			segments.push.apply(segments, eventSegments); // append an array to an array
+		}
+		return segments;
+	}
+
+
+	// Generate an array of segments for a single event.
+	// A "segment" is the same data structure that View.rangeToSegments produces,
+	// with the addition of the `event` property being set to reference the original event.
+	function buildSegmentsForEvent(event) {
+		var startDate = event.start;
+		var endDate = exclEndDay(event);
+		var segments = rangeToSegments(startDate, endDate);
+		for (var i=0; i<segments.length; i++) {
+			segments[i].event = event;
+		}
+		return segments;
+	}
+
+
+	// Sets the `left` and `outerWidth` property of each segment.
+	// These values are the desired dimensions for the eventual DOM elements.
+	function calculateHorizontals(segments) {
+		var isRTL = opt('isRTL');
+		for (var i=0; i<segments.length; i++) {
+			var segment = segments[i];
+
+			// Determine functions used for calulating the elements left/right coordinates,
+			// depending on whether the view is RTL or not.
+			// NOTE:
+			// colLeft/colRight returns the coordinate butting up the edge of the cell.
+			// colContentLeft/colContentRight is indented a little bit from the edge.
+			var leftFunc = (isRTL ? segment.isEnd : segment.isStart) ? colContentLeft : colLeft;
+			var rightFunc = (isRTL ? segment.isStart : segment.isEnd) ? colContentRight : colRight;
+
+			var left = leftFunc(segment.leftCol);
+			var right = rightFunc(segment.rightCol);
+			segment.left = left;
+			segment.outerWidth = right - left;
+		}
+	}
+
+
+	// Build a concatenated HTML string for an array of segments
+	function buildHTML(segments) {
+		var html = '';
+		for (var i=0; i<segments.length; i++) {
+			html += buildHTMLForSegment(segments[i]);
+		}
+		return html;
+	}
+
+
+	// Build an HTML string for a single segment.
+	// Relies on the following properties:
+	// - `segment.event` (from `buildSegmentsForEvent`)
+	// - `segment.left` (from `calculateHorizontals`)
+	function buildHTMLForSegment(segment) {
+		var html = '';
+		var isRTL = opt('isRTL');
+		var event = segment.event;
+		var url = event.url;
+
+		// generate the list of CSS classNames
+		var classNames = [ 'fc-event', 'fc-event-skin', 'fc-event-hori' ];
+		if (isEventDraggable(event)) {
+			classNames.push('fc-event-draggable');
+		}
+		if (segment.isStart) {
+			classNames.push('fc-event-start');
+		}
+		if (segment.isEnd) {
+			classNames.push('fc-event-end');
+		}
+		// use the event's configured classNames
+		// guaranteed to be an array via `normalizeEvent`
+		classNames = classNames.concat(event.className);
+		if (event.source) {
+			// use the event's source's classNames, if specified
+			classNames = classNames.concat(event.source.className || []);
+		}
+
+		// generate a semicolon delimited CSS string for any of the "skin" properties
+		// of the event object (`backgroundColor`, `borderColor` and such)
+		var skinCss = getSkinCss(event, opt);
+
+		if (url) {
+			html += "<a href='" + htmlEscape(url) + "'";
+		}else{
+			html += "<div";
+		}
+		html +=
+			" class='" + classNames.join(' ') + "'" +
+			" style=" +
+				"'" +
+				"position:absolute;" +
+				"left:" + segment.left + "px;" +
+				skinCss +
+				"'" +
+			" tabindex='0'>" +
+			"<div class='fc-event-inner'>";
+		if (!event.allDay && segment.isStart) {
+			html +=
+				"<span class='fc-event-time'>" +
+				htmlEscape(
+					formatDates(event.start, event.end, opt('timeFormat'))
+				) +
+				"</span>";
+		}
+		html +=
+			"<span class='fc-event-title'>" +
+			htmlEscape(event.title || '') +
+			"</span>" +
+			"</div>";
+		if (segment.isEnd && isEventResizable(event)) {
+			html +=
+				"<div class='ui-resizable-handle ui-resizable-" + (isRTL ? 'w' : 'e') + "'>" +
+				"&nbsp;&nbsp;&nbsp;" + // makes hit area a lot better for IE6/7
+				"</div>";
+		}
+		html += "</" + (url ? "a" : "div") + ">";
+
+		// TODO:
+		// When these elements are initially rendered, they will be briefly visibile on the screen,
+		// even though their widths/heights are not set.
+		// SOLUTION: initially set them as visibility:hidden ?
+
+		return html;
+	}
+
+
+	// Associate each segment (an object) with an element (a jQuery object),
+	// by setting each `segment.element`.
+	// Run each element through the `eventRender` filter, which allows developers to
+	// modify an existing element, supply a new one, or cancel rendering.
+	function resolveElements(segments, elements) {
+		for (var i=0; i<segments.length; i++) {
+			var segment = segments[i];
+			var event = segment.event;
+			var element = elements.eq(i);
+
+			// call the trigger with the original element
+			var triggerRes = trigger('eventRender', event, event, element);
+
+			if (triggerRes === false) {
+				// if `false`, remove the event from the DOM and don't assign it to `segment.event`
+				element.remove();
+			}
+			else {
+				if (triggerRes && triggerRes !== true) {
+					// the trigger returned a new element, but not `true` (which means keep the existing element)
+
+					// re-assign the important CSS dimension properties that were already assigned in `buildHTMLForSegment`
+					triggerRes = $(triggerRes)
+						.css({
+							position: 'absolute',
+							left: segment.left
+						});
+
+					element.replaceWith(triggerRes);
+					element = triggerRes;
+				}
+
+				segment.element = element;
+			}
+		}
+	}
+
+
+
+	/* Top-coordinate Methods
+	-------------------------------------------------------------------------------------------------*/
+
+
+	// Sets the "top" CSS property for each element.
+	// If `doRowHeights` is `true`, also sets each row's first cell to an explicit height,
+	// so that if elements vertically overflow, the cell expands vertically to compensate.
+	function setVerticals(segments, doRowHeights) {
+		var overflowLinks = {};
+		var rowContentHeights = calculateVerticals(segments, overflowLinks); // also sets segment.top
+		var rowContentElements = getRowContentElements(); // returns 1 inner div per row
+		var rowContentTops = [];
+
+		// Set each row's height by setting height of first inner div
+		if (doRowHeights) {
+			for (var i=0; i<rowContentElements.length; i++) {
+				rowContentElements[i].height(rowContentHeights[i]);
+				if (overflowLinks[i])
+					renderOverflowLinks(overflowLinks[i], rowContentElements[i]);
+			}
+		}
+
+		// Get each row's top, relative to the views's origin.
+		// Important to do this after setting each row's height.
+		for (var i=0; i<rowContentElements.length; i++) {
+			rowContentTops.push(
+				rowContentElements[i].position().top
+			);
+		}
+
+		// Set each segment element's CSS "top" property.
+		// Each segment object has a "top" property, which is relative to the row's top, but...
+		segmentElementEach(segments, function(segment, element) {
+			if (!segment.overflow) {
+				element.css(
+					'top',
+					rowContentTops[segment.row] + segment.top // ...now, relative to views's origin
+				);
+			}
+			else {
+				element.hide();
+			}
+		});
+	}
+
+
+	// Calculate the "top" coordinate for each segment, relative to the "top" of the row.
+	// Also, return an array that contains the "content" height for each row
+	// (the height displaced by the vertically stacked events in the row).
+	// Requires segments to have their `outerHeight` property already set.
+	function calculateVerticals(segments, overflowLinks) {
+		var rowCnt = getRowCnt();
+		var colCnt = getColCnt();
+		var rowContentHeights = []; // content height for each row
+		var segmentRows = buildSegmentRows(segments); // an array of segment arrays, one for each row
+		var maxHeight = opt('maxHeight');
+		var top;
+
+		for (var rowI=0; rowI<rowCnt; rowI++) {
+			var segmentRow = segmentRows[rowI];
+
+			// an array of running total heights for each column.
+			// initialize with all zeros.
+			overflowLinks[rowI] = {};
+			var colHeights = [];
+			var overflows = [];
+			for (var colI=0; colI<colCnt; colI++) {
+				colHeights.push(0);
+				overflows.push(0);
+			}
+
+			// loop through every segment
+			for (var segmentI=0; segmentI<segmentRow.length; segmentI++) {
+				var segment = segmentRow[segmentI];
+
+				// find the segment's top coordinate by looking at the max height
+				// of all the columns the segment will be in.
+				top = arrayMax(
+					colHeights.slice(
+						segment.leftCol,
+						segment.rightCol + 1 // make exclusive for slice
+					)
+				);
+
+				if (maxHeight && top + segment.outerHeight > maxHeight) {
+					segment.overflow = true;
+				}
+				else {
+					segment.top = top;
+					top += segment.outerHeight;
+				}
+
+				// adjust the columns to account for the segment's height
+				for (var colI=segment.leftCol; colI<=segment.rightCol; colI++) {
+					if (overflows[colI]) {
+						segment.overflow = true;
+					}
+					if (segment.overflow) {
+						if (segment.isStart && !overflowLinks[rowI][colI])
+							overflowLinks[rowI][colI] = { seg:segment, top:top, date:cloneDate(segment.event.start, true), count:0 };
+						if (overflowLinks[rowI][colI])
+							overflowLinks[rowI][colI].count++;
+						overflows[colI]++;
+					}
+					else {
+						colHeights[colI] = top;
+					}
+				}
+			}
+
+			// the tallest column in the row should be the "content height"
+			rowContentHeights.push(arrayMax(colHeights));
+		}
+
+		return rowContentHeights;
+	}
+
+
+	// Build an array of segment arrays, each representing the segments that will
+	// be in a row of the grid, sorted by which event should be closest to the top.
+	function buildSegmentRows(segments) {
+		var rowCnt = getRowCnt();
+		var segmentRows = [];
+		var segmentI;
+		var segment;
+		var rowI;
+
+		// group segments by row
+		for (segmentI=0; segmentI<segments.length; segmentI++) {
+			segment = segments[segmentI];
+			rowI = segment.row;
+			if (segment.element) { // was rendered?
+				if (segmentRows[rowI]) {
+					// already other segments. append to array
+					segmentRows[rowI].push(segment);
+				}
+				else {
+					// first segment in row. create new array
+					segmentRows[rowI] = [ segment ];
+				}
+			}
+		}
+
+		// sort each row
+		for (rowI=0; rowI<rowCnt; rowI++) {
+			segmentRows[rowI] = sortSegmentRow(
+				segmentRows[rowI] || [] // guarantee an array, even if no segments
+			);
+		}
+
+		return segmentRows;
+	}
+
+
+	// Sort an array of segments according to which segment should appear closest to the top
+	function sortSegmentRow(segments) {
+		var sortedSegments = [];
+
+		// build the subrow array
+		var subrows = buildSegmentSubrows(segments);
+
+		// flatten it
+		for (var i=0; i<subrows.length; i++) {
+			sortedSegments.push.apply(sortedSegments, subrows[i]); // append an array to an array
+		}
+
+		return sortedSegments;
+	}
+
+
+	// Take an array of segments, which are all assumed to be in the same row,
+	// and sort into subrows.
+	function buildSegmentSubrows(segments) {
+
+		// Give preference to elements with certain criteria, so they have
+		// a chance to be closer to the top.
+		segments.sort(compareDaySegments);
+
+		var subrows = [];
+		for (var i=0; i<segments.length; i++) {
+			var segment = segments[i];
+
+			// loop through subrows, starting with the topmost, until the segment
+			// doesn't collide with other segments.
+			for (var j=0; j<subrows.length; j++) {
+				if (!isDaySegmentCollision(segment, subrows[j])) {
+					break;
+				}
+			}
+			// `j` now holds the desired subrow index
+			if (subrows[j]) {
+				subrows[j].push(segment);
+			}
+			else {
+				subrows[j] = [ segment ];
+			}
+		}
+
+		return subrows;
+	}
+
+
+	// Return an array of jQuery objects for the placeholder content containers of each row.
+	// The content containers don't actually contain anything, but their dimensions should match
+	// the events that are overlaid on top.
+	function getRowContentElements() {
+		var i;
+		var rowCnt = getRowCnt();
+		var rowDivs = [];
+		for (i=0; i<rowCnt; i++) {
+			rowDivs[i] = allDayRow(i)
+				.find('div.fc-day-content > div');
+		}
+		return rowDivs;
+	}
+
+
+	function renderOverflowLinks(overflowLinks, rowDiv) {
+		var container = getDaySegmentContainer();
+		var colCnt = getColCnt();
+		var element, triggerRes, link;
+		for (var j=0; j<colCnt; j++) {
+			if ((link = overflowLinks[j])) {
+				if (link.count > 1) {
+					element = $('<a>').addClass('fc-more-link').html('+'+link.count).appendTo(container);
+					element[0].style.position = 'absolute';
+					element[0].style.left = link.seg.left + 'px';
+					element[0].style.top = (link.top + rowDiv[0].offsetTop) + 'px';
+					triggerRes = trigger('overflowRender', link, { count:link.count, date:link.date }, element);
+					if (triggerRes === false)
+						element.remove();
+				}
+				else {
+					link.seg.top = link.top;
+					link.seg.overflow = false;
+				}
+			}
+		}
+	}
+
+
+	/* Mouse Handlers
+	---------------------------------------------------------------------------------------------------*/
+	// TODO: better documentation!
+
+
+	function attachHandlers(segments, modifiedEventId) {
+		var segmentContainer = getDaySegmentContainer();
+
+		segmentElementEach(segments, function(segment, element, i) {
+			var event = segment.event;
+			if (event._id === modifiedEventId) {
+				bindDaySeg(event, element, segment);
+			}else{
+				element[0]._fci = i; // for lazySegBind
+			}
+		});
+
+		lazySegBind(segmentContainer, segments, bindDaySeg);
+	}
+
+
+	function bindDaySeg(event, eventElement, segment) {
+
+		if (isEventDraggable(event)) {
+			t.draggableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
+		}
+
+		if (
+			segment.isEnd && // only allow resizing on the final segment for an event
+			isEventResizable(event)
+		) {
+			t.resizableDayEvent(event, eventElement, segment); // use `t` so subclasses can override
+		}
+
+		// attach all other handlers.
+		// needs to be after, because resizableDayEvent might stopImmediatePropagation on click
+		eventElementHandlers(event, eventElement);
+	}
+
+	
+	function draggableDayEvent(event, eventElement) {
+		var hoverListener = getHoverListener();
+		var dayDelta;
+		eventElement.draggable({
+			delay: 50,
+			opacity: opt('dragOpacity'),
+			revertDuration: opt('dragRevertDuration'),
+			start: function(ev, ui) {
+				trigger('eventDragStart', eventElement, event, ev, ui);
+				hideEvents(event, eventElement);
+				hoverListener.start(function(cell, origCell, rowDelta, colDelta) {
+					eventElement.draggable('option', 'revert', !cell || !rowDelta && !colDelta);
+					clearOverlays();
+					if (cell) {
+						var origDate = cellToDate(origCell);
+						var date = cellToDate(cell);
+						dayDelta = dayDiff(date, origDate);
+						renderDayOverlay(
+							addDays(cloneDate(event.start), dayDelta),
+							addDays(exclEndDay(event), dayDelta)
+						);
+					}else{
+						dayDelta = 0;
+					}
+				}, ev, 'drag');
+			},
+			stop: function(ev, ui) {
+				hoverListener.stop();
+				clearOverlays();
+				trigger('eventDragStop', eventElement, event, ev, ui);
+				if (dayDelta) {
+					eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
+				}else{
+					eventElement.css('filter', ''); // clear IE opacity side-effects
+					showEvents(event, eventElement);
+				}
+			}
+		});
+	}
+
+	
+	function resizableDayEvent(event, element, segment) {
+		var isRTL = opt('isRTL');
+		var direction = isRTL ? 'w' : 'e';
+		var handle = element.find('.ui-resizable-' + direction); // TODO: stop using this class because we aren't using jqui for this
+		var isResizing = false;
+		
+		// TODO: look into using jquery-ui mouse widget for this stuff
+		disableTextSelection(element); // prevent native <a> selection for IE
+		element
+			.mousedown(function(ev) { // prevent native <a> selection for others
+				ev.preventDefault();
+			})
+			.click(function(ev) {
+				if (isResizing) {
+					ev.preventDefault(); // prevent link from being visited (only method that worked in IE6)
+					ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called
+					                               // (eventElementHandlers needs to be bound after resizableDayEvent)
+				}
+			});
+		
+		handle.mousedown(function(ev) {
+			if (ev.which != 1) {
+				return; // needs to be left mouse button
+			}
+			isResizing = true;
+			var hoverListener = getHoverListener();
+			var rowCnt = getRowCnt();
+			var colCnt = getColCnt();
+			var elementTop = element.css('top');
+			var dayDelta;
+			var helpers;
+			var eventCopy = $.extend({}, event);
+			var minCellOffset = dayOffsetToCellOffset( dateToDayOffset(event.start) );
+			clearSelection();
+			$('body')
+				.css('cursor', direction + '-resize')
+				.one('mouseup', mouseup);
+			trigger('eventResizeStart', this, event, ev);
+			hoverListener.start(function(cell, origCell) {
+				if (cell) {
+
+					var origCellOffset = cellToCellOffset(origCell);
+					var cellOffset = cellToCellOffset(cell);
+
+					// don't let resizing move earlier than start date cell
+					cellOffset = Math.max(cellOffset, minCellOffset);
+
+					dayDelta =
+						cellOffsetToDayOffset(cellOffset) -
+						cellOffsetToDayOffset(origCellOffset);
+
+					if (dayDelta) {
+						eventCopy.end = addDays(eventEnd(event), dayDelta, true);
+						var oldHelpers = helpers;
+
+						helpers = renderTempDayEvent(eventCopy, segment.row, elementTop);
+						helpers = $(helpers); // turn array into a jQuery object
+
+						helpers.find('*').css('cursor', direction + '-resize');
+						if (oldHelpers) {
+							oldHelpers.remove();
+						}
+
+						hideEvents(event);
+					}
+					else {
+						if (helpers) {
+							showEvents(event);
+							helpers.remove();
+							helpers = null;
+						}
+					}
+					clearOverlays();
+					renderDayOverlay( // coordinate grid already rebuilt with hoverListener.start()
+						event.start,
+						addDays( exclEndDay(event), dayDelta )
+						// TODO: instead of calling renderDayOverlay() with dates,
+						// call _renderDayOverlay (or whatever) with cell offsets.
+					);
+				}
+			}, ev);
+			
+			function mouseup(ev) {
+				trigger('eventResizeStop', this, event, ev);
+				$('body').css('cursor', '');
+				hoverListener.stop();
+				clearOverlays();
+				if (dayDelta) {
+					eventResize(this, event, dayDelta, 0, ev);
+					// event redraw will clear helpers
+				}
+				// otherwise, the drag handler already restored the old events
+				
+				setTimeout(function() { // make this happen after the element's click event
+					isResizing = false;
+				},0);
+			}
+		});
+	}
+	
+
+}
+
+
+
+/* Generalized Segment Utilities
+-------------------------------------------------------------------------------------------------*/
+
+
+function isDaySegmentCollision(segment, otherSegments) {
+	for (var i=0; i<otherSegments.length; i++) {
+		var otherSegment = otherSegments[i];
+		if (
+			otherSegment.leftCol <= segment.rightCol &&
+			otherSegment.rightCol >= segment.leftCol
+		) {
+			return true;
+		}
+	}
+	return false;
+}
+
+
+function segmentElementEach(segments, callback) { // TODO: use in AgendaView?
+	for (var i=0; i<segments.length; i++) {
+		var segment = segments[i];
+		var element = segment.element;
+		if (element) {
+			callback(segment, element, i);
+		}
+	}
+}
+
+
+// A cmp function for determining which segments should appear higher up
+function compareDaySegments(a, b) {
+	return (b.rightCol - b.leftCol) - (a.rightCol - a.leftCol) || // put wider events first
+		b.event.allDay - a.event.allDay || // if tie, put all-day events first (booleans cast to 0/1)
+		a.event.start - b.event.start || // if a tie, sort by event start date
+		(a.event.title || '').localeCompare(b.event.title) // if a tie, sort by event title
+}
+
+
+;;
+
+//BUG: unselect needs to be triggered when events are dragged+dropped
+
+function SelectionManager() {
+	var t = this;
+	
+	
+	// exports
+	t.select = select;
+	t.unselect = unselect;
+	t.reportSelection = reportSelection;
+	t.daySelectionMousedown = daySelectionMousedown;
+	
+	
+	// imports
+	var opt = t.opt;
+	var trigger = t.trigger;
+	var defaultSelectionEnd = t.defaultSelectionEnd;
+	var renderSelection = t.renderSelection;
+	var clearSelection = t.clearSelection;
+	
+	
+	// locals
+	var selected = false;
+
+
+
+	// unselectAuto
+	if (opt('selectable') && opt('unselectAuto')) {
+		$(document).mousedown(function(ev) {
+			var ignore = opt('unselectCancel');
+			if (ignore) {
+				if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
+					return;
+				}
+			}
+			unselect(ev);
+		});
+	}
+	
+
+	function select(startDate, endDate, allDay) {
+		unselect();
+		if (!endDate) {
+			endDate = defaultSelectionEnd(startDate, allDay);
+		}
+		renderSelection(startDate, endDate, allDay);
+		reportSelection(startDate, endDate, allDay);
+	}
+	
+	
+	function unselect(ev) {
+		if (selected) {
+			selected = false;
+			clearSelection();
+			trigger('unselect', null, ev);
+		}
+	}
+	
+	
+	function reportSelection(startDate, endDate, allDay, ev) {
+		selected = true;
+		trigger('select', null, startDate, endDate, allDay, ev);
+	}
+	
+	
+	function daySelectionMousedown(ev) { // not really a generic manager method, oh well
+		var cellToDate = t.cellToDate;
+		var getIsCellAllDay = t.getIsCellAllDay;
+		var hoverListener = t.getHoverListener();
+		var reportDayClick = t.reportDayClick; // this is hacky and sort of weird
+		if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button
+			unselect(ev);
+			var _mousedownElement = this;
+			var dates;
+			hoverListener.start(function(cell, origCell) { // TODO: maybe put cellToDate/getIsCellAllDay info in cell
+				clearSelection();
+				if (cell && getIsCellAllDay(cell)) {
+					dates = [ cellToDate(origCell), cellToDate(cell) ].sort(dateCompare);
+					renderSelection(dates[0], dates[1], true);
+				}else{
+					dates = null;
+				}
+			}, ev);
+			$(document).one('mouseup', function(ev) {
+				hoverListener.stop();
+				if (dates) {
+					if (+dates[0] == +dates[1]) {
+						reportDayClick(dates[0], true, ev);
+					}
+					reportSelection(dates[0], dates[1], true, ev);
+				}
+			});
+		}
+	}
+
+
+}
+
+;;
+ 
+function OverlayManager() {
+	var t = this;
+	
+	
+	// exports
+	t.renderOverlay = renderOverlay;
+	t.clearOverlays = clearOverlays;
+	
+	
+	// locals
+	var usedOverlays = [];
+	var unusedOverlays = [];
+	
+	
+	function renderOverlay(rect, parent) {
+		var e = unusedOverlays.shift();
+		if (!e) {
+			e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
+		}
+		if (e[0].parentNode != parent[0]) {
+			e.appendTo(parent);
+		}
+		usedOverlays.push(e.css(rect).show());
+		return e;
+	}
+	
+
+	function clearOverlays() {
+		var e;
+		while (e = usedOverlays.shift()) {
+			unusedOverlays.push(e.hide().unbind());
+		}
+	}
+
+
+}
+
+;;
+
+function CoordinateGrid(buildFunc) {
+
+	var t = this;
+	var rows;
+	var cols;
+	
+	
+	t.build = function() {
+		rows = [];
+		cols = [];
+		buildFunc(rows, cols);
+	};
+	
+	
+	t.cell = function(x, y) {
+		var rowCnt = rows.length;
+		var colCnt = cols.length;
+		var i, r=-1, c=-1;
+		for (i=0; i<rowCnt; i++) {
+			if (y >= rows[i][0] && y < rows[i][1]) {
+				r = i;
+				break;
+			}
+		}
+		for (i=0; i<colCnt; i++) {
+			if (x >= cols[i][0] && x < cols[i][1]) {
+				c = i;
+				break;
+			}
+		}
+		return (r>=0 && c>=0) ? { row:r, col:c } : null;
+	};
+	
+	
+	t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive
+		var origin = originElement.offset();
+		return {
+			top: rows[row0][0] - origin.top,
+			left: cols[col0][0] - origin.left,
+			width: cols[col1][1] - cols[col0][0],
+			height: rows[row1][1] - rows[row0][0]
+		};
+	};
+
+}
+
+;;
+
+function HoverListener(coordinateGrid) {
+
+
+	var t = this;
+	var bindType;
+	var change;
+	var firstCell;
+	var cell;
+	
+	
+	t.start = function(_change, ev, _bindType) {
+		change = _change;
+		firstCell = cell = null;
+		coordinateGrid.build();
+		mouse(ev);
+		bindType = _bindType || 'mousemove';
+		$(document).bind(bindType, mouse);
+	};
+	
+	
+	function mouse(ev) {
+		_fixUIEvent(ev); // see below
+		var newCell = coordinateGrid.cell(ev.pageX, ev.pageY);
+		if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) {
+			if (newCell) {
+				if (!firstCell) {
+					firstCell = newCell;
+				}
+				change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col);
+			}else{
+				change(newCell, firstCell);
+			}
+			cell = newCell;
+		}
+	}
+	
+	
+	t.stop = function() {
+		$(document).unbind(bindType, mouse);
+		return cell;
+	};
+	
+	
+}
+
+
+
+// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1)
+// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem
+// but keep this in here for 1.8.16 users
+// and maybe remove it down the line
+
+function _fixUIEvent(event) { // for issue 1168
+	if (event.pageX === undefined) {
+		event.pageX = event.originalEvent.pageX;
+		event.pageY = event.originalEvent.pageY;
+	}
+}
+;;
+
+function HorizontalPositionCache(getElement) {
+
+	var t = this,
+		elements = {},
+		lefts = {},
+		rights = {};
+		
+	function e(i) {
+		return elements[i] = elements[i] || getElement(i);
+	}
+	
+	t.left = function(i) {
+		return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
+	};
+	
+	t.right = function(i) {
+		return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
+	};
+	
+	t.clear = function() {
+		elements = {};
+		lefts = {};
+		rights = {};
+	};
+	
+}
+
+;;
+
+})(jQuery);
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/lib/js/jquery.miniColors.min.js	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,16 @@
+// http://plugins.jquery.com/project/jQueryMiniColors
+jQuery&&function(d){d.extend(d.fn,{miniColors:function(j,k){var x=function(a,b){var e=l(a.val());e||(e="FFFFFF");var c=p(e),e=d('<a class="miniColors-trigger" style="background-color: #'+e+'" href="#"></a>');e.insertAfter(a);a.addClass("miniColors").attr("maxlength",7).attr("autocomplete","off");a.data("trigger",e);a.data("hsb",c);b.change&&a.data("change",b.change);b.readonly&&a.attr("readonly",true);b.disabled&&q(a);b.colorValues&&a.data("colorValues",b.colorValues);e.bind("click.miniColors",function(b){b.preventDefault();
+a.trigger("focus")});a.bind("focus.miniColors",function(){w(a)});a.bind("blur.miniColors",function(){var b=l(a.val());a.val(b?"#"+b:"")});a.bind("keydown.miniColors",function(b){b.keyCode===9&&i(a)});a.bind("keyup.miniColors",function(){var b=a.val().replace(/[^A-F0-9#]/ig,"");a.val(b);r(a)||a.data("trigger").css("backgroundColor","#FFF")});a.bind("paste.miniColors",function(){setTimeout(function(){a.trigger("keyup")},5)})},q=function(a){i(a);a.attr("disabled",true);a.data("trigger").css("opacity",
+0.5)},w=function(a){if(a.attr("disabled"))return false;i();var b=d('<div class="miniColors-selector"></div>');b.append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"></div></div>');b.append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>');b.css({top:a.is(":visible")?a.offset().top+a.outerHeight():a.data("trigger").offset().top+a.data("trigger").outerHeight(),left:a.is(":visible")?a.offset().left:a.data("trigger").offset().left,
+display:"none"}).addClass(a.attr("class"));var e=a.data("colorValues");if(e&&e.length){var c,f='<div class="miniColors-presets">',g;for(g in e)c=l(e[g]),f+='<div class="miniColors-colorPreset" style="background-color:#'+c+'" rel="'+c+'"></div>';f+="</div>";b.append(f);c=Math.ceil(e.length/7)*24;b.css("width",b.width()+c+5+"px");b.find(".miniColors-presets").css("width",c+"px")}c=a.data("hsb");b.find(".miniColors-colors").css("backgroundColor","#"+n(m({h:c.h,s:100,b:100})));(f=a.data("colorPosition"))||
+(f=s(c));b.find(".miniColors-colorPicker").css("top",f.y+"px").css("left",f.x+"px");(f=a.data("huePosition"))||(f=t(c));b.find(".miniColors-huePicker").css("top",f.y+"px");a.data("selector",b);a.data("huePicker",b.find(".miniColors-huePicker"));a.data("colorPicker",b.find(".miniColors-colorPicker"));a.data("mousebutton",0);d("BODY").append(b);b.fadeIn(100);b.bind("selectstart",function(){return false});d(document).bind("mousedown.miniColors",function(b){a.data("mousebutton",1);d(b.target).parents().andSelf().hasClass("miniColors-colors")&&
+(b.preventDefault(),a.data("moving","colors"),u(a,b));d(b.target).parents().andSelf().hasClass("miniColors-hues")&&(b.preventDefault(),a.data("moving","hues"),v(a,b));d(b.target).parents().andSelf().hasClass("miniColors-selector")?b.preventDefault():d(b.target).parents().andSelf().hasClass("miniColors")||i(a)});d(document).bind("mouseup.miniColors",function(){a.data("mousebutton",0);a.removeData("moving")});d(document).bind("mousemove.miniColors",function(b){a.data("mousebutton")===1&&(a.data("moving")===
+"colors"&&u(a,b),a.data("moving")==="hues"&&v(a,b))});e&&(b.find(".miniColors-colorPreset").click(function(){a.val(d(this).attr("rel"));r(a)}),b.find('.miniColors-presets div[rel="'+a.val().replace(/#/,"")+'"]').addClass("miniColors-colorPreset-active"))},i=function(a){a||(a=".miniColors");d(a).each(function(){var a=d(this).data("selector");d(this).removeData("selector");d(a).fadeOut(100,function(){d(this).remove()})});d(document).unbind("mousedown.miniColors");d(document).unbind("mousemove.miniColors")},
+u=function(a,b){var e=a.data("colorPicker");e.hide();var c={x:b.clientX-a.data("selector").find(".miniColors-colors").offset().left+d(document).scrollLeft()-5,y:b.clientY-a.data("selector").find(".miniColors-colors").offset().top+d(document).scrollTop()-5};if(c.x<=-5)c.x=-5;if(c.x>=144)c.x=144;if(c.y<=-5)c.y=-5;if(c.y>=144)c.y=144;a.data("colorPosition",c);e.css("left",c.x).css("top",c.y).show();e=Math.round((c.x+5)*0.67);e<0&&(e=0);e>100&&(e=100);c=100-Math.round((c.y+5)*0.67);c<0&&(c=0);c>100&&
+(c=100);var f=a.data("hsb");f.s=e;f.b=c;o(a,f,true)},v=function(a,b){var e=a.data("huePicker");e.hide();var c={y:b.clientY-a.data("selector").find(".miniColors-colors").offset().top+d(document).scrollTop()-1};if(c.y<=-1)c.y=-1;if(c.y>=149)c.y=149;a.data("huePosition",c);e.css("top",c.y).show();e=Math.round((150-c.y-1)*2.4);e<0&&(e=0);e>360&&(e=360);c=a.data("hsb");c.h=e;o(a,c,true)},o=function(a,b,e){a.data("hsb",b);var c=n(m(b));e&&a.val("#"+c);a.data("trigger").css("backgroundColor","#"+c);a.data("selector")&&
+a.data("selector").find(".miniColors-colors").css("backgroundColor","#"+n(m({h:b.h,s:100,b:100})));a.data("change")&&a.data("change").call(a,"#"+c,m(b));a.data("colorValues")&&(a.data("selector").find(".miniColors-colorPreset-active").removeClass("miniColors-colorPreset-active"),a.data("selector").find('.miniColors-presets div[rel="'+c+'"]').addClass("miniColors-colorPreset-active"))},r=function(a){var b=l(a.val());if(!b)return false;var b=p(b),e=a.data("hsb");if(b.h===e.h&&b.s===e.s&&b.b===e.b)return true;
+e=s(b);d(a.data("colorPicker")).css("top",e.y+"px").css("left",e.x+"px");e=t(b);d(a.data("huePicker")).css("top",e.y+"px");o(a,b,false);return true},s=function(a){var b=Math.ceil(a.s/0.67);b<0&&(b=0);b>150&&(b=150);a=150-Math.ceil(a.b/0.67);a<0&&(a=0);a>150&&(a=150);return{x:b-5,y:a-5}},t=function(a){a=150-a.h/2.4;a<0&&(h=0);a>150&&(h=150);return{y:a-1}},l=function(a){a=a.replace(/[^A-Fa-f0-9]/,"");a.length==3&&(a=a[0]+a[0]+a[1]+a[1]+a[2]+a[2]);return a.length===6?a:null},m=function(a){var b,e,c;
+b=Math.round(a.h);var d=Math.round(a.s*255/100),a=Math.round(a.b*255/100);if(d==0)b=e=c=a;else{var d=(255-d)*a/255,g=(a-d)*(b%60)/60;b==360&&(b=0);b<60?(b=a,c=d,e=d+g):b<120?(e=a,c=d,b=a-g):b<180?(e=a,b=d,c=d+g):b<240?(c=a,b=d,e=a-g):b<300?(c=a,e=d,b=d+g):b<360?(b=a,e=d,c=a-g):c=e=b=0}return{r:Math.round(b),g:Math.round(e),b:Math.round(c)}},n=function(a){var b=[a.r.toString(16),a.g.toString(16),a.b.toString(16)];d.each(b,function(a,c){c.length==1&&(b[a]="0"+c)});return b.join("")},p=function(a){var b=
+a,b=parseInt(b.indexOf("#")>-1?b.substring(1):b,16),a=b>>16,d=(b&65280)>>8;b&=255;var c={h:0,s:0,b:0},f=Math.min(a,d,b),g=Math.max(a,d,b),f=g-f;c.b=g;c.s=g!=0?255*f/g:0;c.h=c.s!=0?a==g?(d-b)/f:d==g?2+(b-a)/f:4+(a-d)/f:-1;c.h*=60;c.h<0&&(c.h+=360);c.s*=100/255;c.b*=100/255;if(c.s===0)c.h=360;return c};switch(j){case "readonly":return d(this).each(function(){d(this).attr("readonly",k)}),d(this);case "disabled":return d(this).each(function(){if(k)q(d(this));else{var a=d(this);a.attr("disabled",false);
+a.data("trigger").css("opacity",1)}}),d(this);case "value":return d(this).each(function(){d(this).val(k).trigger("keyup")}),d(this);case "destroy":return d(this).each(function(){var a=d(this);i();a=d(a);a.data("trigger").remove();a.removeAttr("autocomplete");a.removeData("trigger");a.removeData("selector");a.removeData("hsb");a.removeData("huePicker");a.removeData("colorPicker");a.removeData("mousebutton");a.removeData("moving");a.unbind("click.miniColors");a.unbind("focus.miniColors");a.unbind("blur.miniColors");
+a.unbind("keyup.miniColors");a.unbind("keydown.miniColors");a.unbind("paste.miniColors");d(document).unbind("mousedown.miniColors");d(document).unbind("mousemove.miniColors")}),d(this);default:return j||(j={}),d(this).each(function(){d(this)[0].tagName.toLowerCase()==="input"&&(d(this).data("trigger")||x(d(this),j,k))}),d(this)}}})}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/bg_BG.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,197 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Изглед по подразбиране';
+$labels['time_format'] = 'Формат на чаÑовете';
+$labels['timeslots'] = 'Ð”ÐµÐ»ÐµÐ½Ð¸Ñ Ð·Ð° чаÑ';
+$labels['first_day'] = 'Първи ден от Ñедмицата';
+$labels['first_hour'] = 'Първи Ñ‡Ð°Ñ Ð¿Ñ€Ð¸ показване';
+$labels['workinghours'] = 'Работни чаÑове';
+$labels['add_category'] = 'ДобавÑне на категориÑ';
+$labels['remove_category'] = 'Премахване на категориÑ';
+$labels['defaultcalendar'] = 'Създаване на нови ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð² ';
+$labels['eventcoloring'] = 'ОцветÑване на ÑъбитиÑта';
+$labels['coloringmode0'] = 'Според календара';
+$labels['coloringmode1'] = 'ОтноÑно категориÑта';
+$labels['coloringmode2'] = 'Календар за очертание, ÐºÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð° Ñъдържание';
+$labels['coloringmode3'] = 'ÐšÐ°Ñ‚ÐµÐ³Ð¾Ñ€Ð¸Ñ Ð·Ð° очертание, календар за Ñъдържание';
+$labels['afternothing'] = 'Ðе предприемай нищо';
+$labels['aftertrash'] = 'ПремеÑти в Кошчето';
+$labels['afterdelete'] = 'Изтрий Ñъобщението';
+$labels['afterflagdeleted'] = 'Маркирай като изтрито';
+$labels['aftermoveto'] = 'ПремеÑти в...';
+$labels['itipoptions'] = 'Покани за Ñъбитие';
+$labels['calendar'] = 'Календар';
+$labels['calendars'] = 'Календари';
+$labels['category'] = 'КатегориÑ';
+$labels['categories'] = 'Категории';
+$labels['createcalendar'] = 'Създаване на нов календар';
+$labels['editcalendar'] = 'ПромÑна на ÑвойÑтвата на календара';
+$labels['name'] = 'Име';
+$labels['color'] = 'ЦвÑÑ‚';
+$labels['day'] = 'Ден';
+$labels['week'] = 'Седмица';
+$labels['month'] = 'МеÑец';
+$labels['agenda'] = 'Бележник';
+$labels['new'] = 'Ðово';
+$labels['new_event'] = 'ДобавÑне на Ñъбитие';
+$labels['edit_event'] = 'ПромÑна на Ñъбитие';
+$labels['edit'] = 'ПромÑна';
+$labels['save'] = 'ЗапиÑ';
+$labels['removelist'] = 'Премахни от ÑпиÑъка';
+$labels['cancel'] = 'Отказ';
+$labels['select'] = 'Избиране';
+$labels['print'] = 'Печат';
+$labels['printtitle'] = 'Печат на календарите';
+$labels['title'] = 'Заглавие';
+$labels['description'] = 'ОпиÑание';
+$labels['all-day'] = 'цÑл ден';
+$labels['export'] = 'Извличане';
+$labels['exporttitle'] = 'Извличане към iCalendar';
+$labels['exportrange'] = 'Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¾Ñ‚';
+$labels['exportattachments'] = 'С прикачени файлове';
+$labels['customdate'] = 'Дата по избор';
+$labels['location'] = 'МеÑтоположение';
+$labels['url'] = 'URL адреÑ';
+$labels['date'] = 'Дата';
+$labels['start'] = 'Ðачало';
+$labels['starttime'] = 'Време за начало';
+$labels['end'] = 'Край';
+$labels['endtime'] = 'Време за край';
+$labels['repeat'] = 'Повтори';
+$labels['selectdate'] = 'Избор на дата';
+$labels['freebusy'] = 'Показване като';
+$labels['free'] = 'Свободно';
+$labels['busy'] = 'Заето';
+$labels['outofoffice'] = 'Извън офиÑа';
+$labels['tentative'] = 'Предварително';
+$labels['mystatus'] = 'МоÑÑ‚ ÑтатуÑ';
+$labels['status'] = 'СтатуÑ';
+$labels['status-confirmed'] = 'Потвърдено';
+$labels['status-cancelled'] = 'Отхвърлено';
+$labels['priority'] = 'Приоритет';
+$labels['sensitivity'] = 'ПоверителноÑÑ‚';
+$labels['public'] = 'публично';
+$labels['private'] = 'чаÑтно';
+$labels['confidential'] = 'конфиденциално';
+$labels['links'] = 'ЦикличноÑÑ‚';
+$labels['alarms'] = 'ÐапомнÑне';
+$labels['comment'] = 'Коментар';
+$labels['created'] = 'Създаден';
+$labels['changed'] = 'ПоÑледна промÑна';
+$labels['unknown'] = 'ÐеизвеÑтно';
+$labels['eventoptions'] = 'Опции';
+$labels['generated'] = 'генерирано в';
+$labels['eventhistory'] = 'ИÑториÑ';
+$labels['removelink'] = 'Премахни е-мейл препратките';
+$labels['printdescriptions'] = 'Печат на опиÑаниÑта';
+$labels['parentcalendar'] = 'ВнаÑÑне вътре';
+$labels['searchearlierdates'] = '« ТърÑене за по- Ñтари ÑъбитиÑ';
+$labels['searchlaterdates'] = 'ТърÑене за по- нови ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Â»';
+$labels['andnmore'] = '$nr повече...';
+$labels['togglerole'] = 'ÐатиÑнете за превключване на ролÑ';
+$labels['createfrommail'] = 'Запазване като Ñъбитие';
+$labels['importevents'] = 'ВнаÑÑне на ÑъбитиÑ';
+$labels['importrange'] = 'Ð¡ÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¾Ñ‚';
+$labels['onemonthback'] = '1 меÑец назад';
+$labels['nmonthsback'] = '$nr меÑеца назад';
+$labels['showurl'] = 'Показване на URL на календара';
+$labels['showurldescription'] = 'Използвайте ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð°Ð´Ñ€ÐµÑ, за да доÑтъпвате (Ñамо за четене) Ð²Ð°ÑˆÐ¸Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€ от други приложениÑ. Можете да копирате и поÑтавÑте това във вÑеки календарен Ñофтуер, поддържащ форматът iCal';
+$labels['searchterms'] = 'ТърÑене за';
+$labels['calendarsubscribe'] = 'ПоÑтоÑнен ÑпиÑък';
+$labels['nocalendarsfound'] = 'Ðе бÑха намерени календари';
+$labels['nrcalendarsfound'] = 'БÑха намерени $nr календара';
+$labels['quickview'] = 'Покажи Ñамо този календар';
+$labels['invitationspending'] = 'Чакащи покани';
+$labels['invitationsdeclined'] = 'Отхвърлени покани';
+$labels['changepartstat'] = 'Промени ÑÑŠÑтоÑнието на учаÑтник';
+$labels['rsvpcomment'] = 'ТекÑÑ‚ на поканата';
+$labels['listrange'] = 'ОразмерÑване към екран:';
+$labels['listsections'] = 'РазделÑне на:';
+$labels['smartsections'] = 'Интелигентни Ñекции';
+$labels['until'] = 'до';
+$labels['today'] = 'ДнеÑ';
+$labels['tomorrow'] = 'Утре';
+$labels['thisweek'] = 'Тази Ñедмица';
+$labels['nextweek'] = 'Следващата Ñедмица';
+$labels['prevweek'] = 'Миналата Ñедмица';
+$labels['thismonth'] = 'Този меÑец';
+$labels['nextmonth'] = 'Ð¡Ð»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð¼ÐµÑец';
+$labels['weekofyear'] = 'Седмица';
+$labels['pastevents'] = 'Минали';
+$labels['futureevents'] = 'Бъдещи';
+$labels['showalarms'] = 'Покажи напомнÑниÑ';
+$labels['defaultalarmtype'] = 'ÐаÑтройка за напомнÑне по подразбиране';
+$labels['defaultalarmoffset'] = 'Време за напомнÑне по подразбиране';
+$labels['attendee'] = 'УчаÑтник';
+$labels['role'] = 'РолÑ';
+$labels['availability'] = 'Ðалич.';
+$labels['confirmstate'] = 'СтатуÑ';
+$labels['addattendee'] = 'ДобавÑне на учаÑтник';
+$labels['roleorganizer'] = 'Организатор';
+$labels['rolerequired'] = 'Задължителен';
+$labels['roleoptional'] = 'По избор';
+$labels['rolechair'] = 'ПредÑедател';
+$labels['rolenonparticipant'] = 'ЛипÑващ';
+$labels['cutypeindividual'] = 'Индивидуален';
+$labels['cutypegroup'] = 'Група';
+$labels['cutyperesource'] = 'РеÑурÑ';
+$labels['cutyperoom'] = 'СтаÑ';
+$labels['availfree'] = 'Свободен';
+$labels['availbusy'] = 'Зает';
+$labels['availunknown'] = 'ÐÑма информациÑ';
+$labels['availtentative'] = 'Предварително';
+$labels['availoutofoffice'] = 'Извън офиÑа';
+$labels['delegatedto'] = 'Делегирано към:';
+$labels['delegatedfrom'] = 'Делегирано от:';
+$labels['sendinvitations'] = 'Изпращане на покани';
+$labels['sendnotifications'] = 'ИзвеÑÑ‚Ñване на учаÑтниците отноÑно промените';
+$labels['sendcancellation'] = 'ИзвеÑÑ‚Ñване на учаÑтниците отноÑно отмÑна на ÑъбитиÑ';
+$labels['invitationsubject'] = 'БÑхте поканен на "$title"';
+$labels['eventupdatesubject'] = '"$title" беше отмнено';
+$labels['itipdeclineevent'] = 'ИÑкате ли да отхвърлите поканата за това Ñъбитие?';
+$labels['saveincalendar'] = 'запазване в';
+$labels['updatemycopy'] = 'Обнови в Ð¼Ð¾Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€';
+$labels['savetocalendar'] = 'Запази в календар';
+$labels['openpreview'] = 'Провери календар';
+$labels['noearlierevents'] = 'ÐÑма по-ранни ÑъбитиÑ';
+$labels['nolaterevents'] = 'ÐÑма по-къÑни ÑъбитиÑ';
+$labels['resource'] = 'РеÑурÑ';
+$labels['resourcedetails'] = 'Детайли';
+$labels['resourceowner'] = 'СобÑтвеник';
+$labels['tabsummary'] = 'Заглавие';
+$labels['tabrecurrence'] = 'Да Ñе повтарÑ';
+$labels['tabattendees'] = 'УчаÑтници';
+$labels['tabresources'] = 'РеÑурÑи';
+$labels['tabattachments'] = 'Прикрепени файлове';
+$labels['tabsharing'] = 'СподелÑне';
+$labels['deleteobjectconfirm'] = 'ÐаиÑтина ли иÑкате да премахнете това Ñъбитие?';
+$labels['deleteventconfirm'] = 'ÐаиÑтина ли иÑкате да премахнете това Ñъбитие?';
+$labels['deletecalendarconfirm'] = 'ÐаиÑтина ли иÑкате да премахнете този календар Ñ Ð²Ñичките му ÑъбитиÑ?';
+$labels['deletecalendarconfirmrecursive'] = 'ÐаиÑтина ли иÑкате да премахнете този календар Ñ Ð²Ñичките му ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð¸ допълнителни календари?';
+$labels['savingdata'] = 'Запазване на данни...';
+$labels['errorsaving'] = 'ÐеуÑпешно запиÑването на промените.';
+$labels['successremoval'] = 'Събитието беше премахнато уÑпешно.';
+$labels['successrestore'] = 'Събитието беше възÑтановено уÑпешно.';
+$labels['importedsuccessfully'] = 'Събитието е добавено уÑпешно към \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Събитието беше обновено уÑпешно в \'$calendar\'';
+$labels['importsuccess'] = '$nr ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð±Ñха внеÑени уÑпешно.';
+$labels['importnone'] = 'Ðе бÑха намерени ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð·Ð° внаÑÑне';
+$labels['importerror'] = 'Възникна грешка при внаÑÑнето на ÑъбитиÑ';
+$labels['aclnorights'] = 'Ðе разполагате Ñ Ð°Ð´Ð¼Ð¸Ð½Ð¸Ñтративни права върху този календар.';
+$labels['changeeventconfirm'] = 'ПромÑна на Ñъбитие';
+$labels['removeeventconfirm'] = 'Изтриване на Ñъбитие';
+$labels['currentevent'] = 'ÐаÑтоÑщи';
+$labels['futurevents'] = 'Бъдещи';
+$labels['allevents'] = 'Ð’Ñички';
+$labels['saveasnew'] = 'Запази като нов';
+$labels['birthdays'] = 'Рождени дни';
+$labels['arialabelcalendarview'] = 'Преглед на календара';
+$labels['arialabeleventattendees'] = 'СпиÑък Ñ ÑƒÑ‡Ð°Ñтниците в Ñъбитието';
+$labels['arialabelresourceselection'] = 'Ðалични реÑурÑи';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/ca_ES.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,256 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Vista per defecte';
+$labels['time_format'] = 'Format de l\'hora';
+$labels['timeslots'] = 'Espais de temps per hora';
+$labels['first_day'] = 'Primer dia de la setmana';
+$labels['first_hour'] = 'Primera hora a mostrar';
+$labels['workinghours'] = 'Hores de feina';
+$labels['add_category'] = 'Afegeix categoria';
+$labels['remove_category'] = 'Suprimeix categoria';
+$labels['defaultcalendar'] = 'Crea nous esdeveniments a';
+$labels['eventcoloring'] = 'Colors dels esdeveniments';
+$labels['coloringmode0'] = 'Depenent del calendari';
+$labels['coloringmode1'] = 'Depenent de la categoria';
+$labels['coloringmode2'] = 'Calendari pel contorn, categoria pel contingut';
+$labels['coloringmode3'] = 'Categoria pel contorn, calendari pel contingut\'';
+$labels['afternothing'] = 'No facis res';
+$labels['aftertrash'] = 'Mou a la Paperera';
+$labels['afterdelete'] = 'Suprimeix el missatge';
+$labels['afterflagdeleted'] = 'Marca\'l com a suprimit';
+$labels['aftermoveto'] = 'Mou a...';
+$labels['itipoptions'] = 'Invitacions a esdeveniments';
+$labels['afteraction'] = 'S\'envia un missatge després d\'una invitació o una actualització';
+$labels['calendar'] = 'Calendari';
+$labels['calendars'] = 'Calendaris';
+$labels['category'] = 'Categoria';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Crea un nou calendari';
+$labels['editcalendar'] = 'Edita les propietats del calendari';
+$labels['name'] = 'Nom';
+$labels['color'] = 'Color';
+$labels['day'] = 'Dia';
+$labels['week'] = 'Setmana';
+$labels['month'] = 'Mes';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nou';
+$labels['new_event'] = 'Nou esdeveniment';
+$labels['edit_event'] = 'Edita esdeveniment';
+$labels['edit'] = 'Edita';
+$labels['save'] = 'Desa';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel·la';
+$labels['select'] = 'Selecciona';
+$labels['print'] = 'Imprimeix';
+$labels['printtitle'] = 'Imprimeix calendaris';
+$labels['title'] = 'Resum';
+$labels['description'] = 'Descripció';
+$labels['all-day'] = 'Tot el dia';
+$labels['export'] = 'Exporta';
+$labels['exporttitle'] = 'Exporta a iCalendari';
+$labels['exportrange'] = 'Esdeveniments de';
+$labels['exportattachments'] = 'Amb fitxers adjunts';
+$labels['customdate'] = 'Personalitza la data';
+$labels['location'] = 'Ubicació';
+$labels['url'] = 'URL';
+$labels['date'] = 'Data';
+$labels['start'] = 'Inici';
+$labels['starttime'] = 'Hora d\'inici';
+$labels['end'] = 'Final';
+$labels['endtime'] = 'Hora de finalització';
+$labels['repeat'] = 'Repeteix';
+$labels['selectdate'] = 'Tria la data';
+$labels['freebusy'] = 'Mostra\'m com';
+$labels['free'] = 'Lliure';
+$labels['busy'] = 'Ocupat';
+$labels['outofoffice'] = 'Fora de l\'oficina';
+$labels['tentative'] = 'Provisional';
+$labels['mystatus'] = 'El meu estat';
+$labels['status'] = 'Estat';
+$labels['status-confirmed'] = 'Confirmat';
+$labels['status-cancelled'] = 'Cancel·lat';
+$labels['priority'] = 'Prioritat';
+$labels['sensitivity'] = 'Privadesa';
+$labels['public'] = 'públic';
+$labels['private'] = 'privat';
+$labels['confidential'] = 'confidencial';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Recordatori';
+$labels['comment'] = 'Comentari';
+$labels['created'] = 'Creat';
+$labels['changed'] = 'Darrera modificació';
+$labels['unknown'] = 'Desconegut';
+$labels['eventoptions'] = 'Opcions';
+$labels['generated'] = 'generat a';
+$labels['eventhistory'] = 'Historial';
+$labels['removelink'] = 'Remove email reference';
+$labels['printdescriptions'] = 'Imprimeix descripcions';
+$labels['parentcalendar'] = 'Insereix dins';
+$labels['searchearlierdates'] = '« Cerca els esdeveniments d\'abans';
+$labels['searchlaterdates'] = 'Cerca els esdeveniments de després »';
+$labels['andnmore'] = '$nr més...';
+$labels['togglerole'] = 'Feu clic per commutar el rol';
+$labels['createfrommail'] = 'Desa com a esdeveniment';
+$labels['importevents'] = 'Importa esdeveniments';
+$labels['importrange'] = 'Esdeveniments de';
+$labels['onemonthback'] = '1 mes abans';
+$labels['nmonthsback'] = '$nr mesos abans';
+$labels['showurl'] = 'Mostra la URL del calendari';
+$labels['showurldescription'] = 'Podeu fer servir aquesta adreça per accedir (només lectura) el vostre calendari des d\'altres aplicacions. Copieu i enganxeu-la dins d\'un altre programari de calendari que suporti el format iCal.';
+$labels['caldavurldescription'] = 'Copieu aquesta adreça a una aplicació client <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> (p.ex.: Evolution o Mozilla Thunderbird) per sincronitzar aquest calendari amb el vostre ordinador o dispositiu mòbil.';
+$labels['findcalendars'] = 'Cerca calendaris...';
+$labels['searchterms'] = 'Termes de cerca';
+$labels['calsearchresults'] = 'Calendaris disponibles';
+$labels['calendarsubscribe'] = 'Llista permanentment';
+$labels['nocalendarsfound'] = 'No s\'ha trobat cap calendari';
+$labels['nrcalendarsfound'] = 'S\'han trobat $nr calendaris';
+$labels['quickview'] = 'Mostra només aquest calendari';
+$labels['invitationspending'] = 'Invitacions pendents';
+$labels['invitationsdeclined'] = 'Invitacions declinades';
+$labels['changepartstat'] = 'Canvia l\'estat d\'un participant';
+$labels['rsvpcomment'] = 'Text d\'invitació';
+$labels['listrange'] = 'Rang per mostrar:';
+$labels['listsections'] = 'Divideix en:';
+$labels['smartsections'] = 'Seccions petites';
+$labels['until'] = 'fins';
+$labels['today'] = 'Avui';
+$labels['tomorrow'] = 'Demà';
+$labels['thisweek'] = 'Aquesta setmana';
+$labels['nextweek'] = 'Setmana vinent';
+$labels['prevweek'] = 'Setmana anterior';
+$labels['thismonth'] = 'Aquest mes';
+$labels['nextmonth'] = 'Mes vinent';
+$labels['weekofyear'] = 'Setmana';
+$labels['pastevents'] = 'Passat';
+$labels['futureevents'] = 'Futur';
+$labels['showalarms'] = 'Mostra els recordatoris';
+$labels['defaultalarmtype'] = 'Recordatori per defecte';
+$labels['defaultalarmoffset'] = 'Temps de recordatori per defecte';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Rol';
+$labels['availability'] = 'Disp.';
+$labels['confirmstate'] = 'Estat';
+$labels['addattendee'] = 'Afegeix participant';
+$labels['roleorganizer'] = 'Organitzador';
+$labels['rolerequired'] = 'Obligatori';
+$labels['roleoptional'] = 'Opcional';
+$labels['rolechair'] = 'President';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Grup';
+$labels['cutyperesource'] = 'Recurs';
+$labels['cutyperoom'] = 'Sala';
+$labels['availfree'] = 'Lliure';
+$labels['availbusy'] = 'Ocupat';
+$labels['availunknown'] = 'Desconegut';
+$labels['availtentative'] = 'Provisional';
+$labels['availoutofoffice'] = 'Fora de l\'oficina';
+$labels['delegatedto'] = 'Delegat a:';
+$labels['delegatedfrom'] = 'Delegat de:';
+$labels['scheduletime'] = 'Cerca disponibilitat';
+$labels['sendinvitations'] = 'Envia invitacions';
+$labels['sendnotifications'] = 'Notifica als participants quan hi hagi modificacions';
+$labels['sendcancellation'] = 'Notifica als participants si es cancel·la l\'esdeveniment';
+$labels['onlyworkinghours'] = 'Cerca disponibilitat dins de les hores de feina';
+$labels['reqallattendees'] = 'Obligatori/tots els participants';
+$labels['prevslot'] = 'Lloc anterior';
+$labels['nextslot'] = 'Lloc següent';
+$labels['suggestedslot'] = 'Lloc suggerit';
+$labels['noslotfound'] = 'No s\'ha pogut trobar un espai de temps lliure';
+$labels['invitationsubject'] = 'Heu estat convidats a "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees\n\nSi us plau cerqueu el fitxer iCalendar adjunt dins dels detalls de l'esdeveniment per poder-lo importar a la vostra aplicació de calendari.";
+$labels['invitationattendlinks'] = "En cas que el vostre client de correu electrònic no suporti peticions de tipus iTip, podeu fer servir el següent enllaç per acceptar o declinar aquesta invitació:\n\$url";
+$labels['eventupdatesubject'] = '"$title" ha estat actualitzat';
+$labels['eventupdatesubjectempty'] = 'Un esdeveniment que us afecta ha estat actualitzat';
+$labels['eventupdatemailbody'] = "*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees\n\nSi us plau cerqueu el fitxer iCalendar adjunt dins dels detalls actualitzats de l'esdeveniment per poder-lo importar a la vostra aplicació de calendari.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees\n\nL'esdeveniment ha estat cancel·lat per \$organizer.\n\nSi us plau cerqueu el fitxer iCalendar adjunt amb els detalls actualitzats de l'esdeveniment.";
+$labels['itipobjectnotfound'] = 'L\'esdeveniment que fa referència aquest missatge no s\'ha trobat al vostre calendari.';
+$labels['itipmailbodyaccepted'] = "\$sender ha acceptat la invitació al següent esdeveniment:\n\n*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender ha acceptat provisionalment la invitació al següent esdeveniment:\n\n*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ha declinat la invitació al següent esdeveniment:\n\n*\$title*\n\nQuan: \$date\n\nConvidats: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender ha rebutjat la vostra participació en el següent esdeveniment:\n\n*\$title*\n\nQuan: \$date";
+$labels['itipdeclineevent'] = 'Voleu declinar la vostra invitació a aquest esdeveniment?';
+$labels['declinedeleteconfirm'] = 'Voleu també suprimir aquest esdeveniment declinat del vostre calendari?';
+$labels['itipcomment'] = 'Comentari de la invitació/notificació';
+$labels['itipcommenttitle'] = 'Aquest comentari serà adjuntat al missatge d\'invitació/notificació que s\'envia als participants';
+$labels['notanattendee'] = 'No sou a la llista d\'assistents d\'aquest esdeveniment';
+$labels['eventcancelled'] = 'L\'esdeveniment s\'ha cancel·lat.';
+$labels['saveincalendar'] = 'desa a';
+$labels['updatemycopy'] = 'Actualitza en el meu calendari';
+$labels['savetocalendar'] = 'Desa al calendari';
+$labels['resource'] = 'Recurs';
+$labels['addresource'] = 'Recurs de llibre';
+$labels['findresources'] = 'Cerca recursos';
+$labels['resourcedetails'] = 'Detalls';
+$labels['resourceavailability'] = 'Disponibilitat';
+$labels['resourceowner'] = 'Propietari';
+$labels['resourceadded'] = 'El recurs ha estat afegit al vostre esdeveniment';
+$labels['tabsummary'] = 'Resum';
+$labels['tabrecurrence'] = 'Periodicitat';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Recursos';
+$labels['tabattachments'] = 'Fitxers adjunts';
+$labels['tabsharing'] = 'Compartit';
+$labels['deleteobjectconfirm'] = 'Esteu segur de voler suprimir aquest esdeveniment?';
+$labels['deleteventconfirm'] = 'Esteu segur de voler suprimir aquest esdeveniment?';
+$labels['deletecalendarconfirm'] = 'Esteu segurs de voler suprimir aquest calendari amb tots els seus esdeveniments?';
+$labels['deletecalendarconfirmrecursive'] = 'Esteu segurs de voler suprimir aquest calendari amb tots els seus esdeveniments i sub-calendaris?';
+$labels['savingdata'] = 'S\'estan desant les dades...';
+$labels['errorsaving'] = 'No s\'han pogut desar els canvis.';
+$labels['operationfailed'] = 'L\'operació sol·licitada ha fallat.';
+$labels['invalideventdates'] = 'Les dades entrades no són vàlides!. Si us plau verifiqueu l\'entrada.';
+$labels['invalidcalendarproperties'] = 'Les propietats del calendari no són vàlides!. Si us plau introduïu un nom vàlid.';
+$labels['searchnoresults'] = 'No s\'ha trobat cap esdeveniment en els calendaris seleccionats.';
+$labels['successremoval'] = 'L\'esdeveniment ha estat suprimit correctament.';
+$labels['successrestore'] = 'L\'esdeveniment ha estat recuperat correctament.';
+$labels['errornotifying'] = 'No s\'ha pogut enviar les notificacions als participants de l\'esdeveniment';
+$labels['errorimportingevent'] = 'No s\'ha pogut importar aquest esdeveniment';
+$labels['importwarningexists'] = 'Ja existeix una còpia d\'aquest esdeveniment al vostre calendari.';
+$labels['newerversionexists'] = 'Ja existeix una nova versió d\'aquest esdeveniment!. S\'ha avortat.';
+$labels['nowritecalendarfound'] = 'No s\'ha trobat el calendari per desar l\'esdeveniment';
+$labels['importedsuccessfully'] = 'L\'esdeveniment ha estat correctament afegit a \'$calendar\'';
+$labels['updatedsuccessfully'] = 'L\'esdeveniment ha estat correctament actualitzat dins de \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'L\'estat del participant ha estat actualitzat correctament';
+$labels['itipsendsuccess'] = 'La invitació ha estat enviada als participants.';
+$labels['itipresponseerror'] = 'No s\'ha pogut enviar la resposta a la invitació d\'aquest esdeveniment';
+$labels['itipinvalidrequest'] = 'Aquesta invitació ja no és vàlida';
+$labels['sentresponseto'] = 'S\'ha enviat correctament la resposta de la invitació a $mailto';
+$labels['localchangeswarning'] = 'Esteu a punt de fer canvis que només seran reflectits al vostre calendari i no seran enviats a l\'organitzador de l\'esdeveniment.';
+$labels['importsuccess'] = 'S\'han importat correctament $nr esdeveniments';
+$labels['importnone'] = 'No s\'ha trobat cap esdeveniment per importar';
+$labels['importerror'] = 'Hi ha hagut un error mentre s\'importava';
+$labels['aclnorights'] = 'No teniu drets d\'administrador en aquest calendari.';
+$labels['changeeventconfirm'] = 'Canvia l\'esdeveniment';
+$labels['changerecurringeventwarning'] = 'Aquest és un esdeveniment periòdic. Voleu editar només l\'esdeveniment actual, aquesta i totes les futures ocurrències, totes les ocurrències o desar-lo com un esdeveniment nou?';
+$labels['currentevent'] = 'Actual';
+$labels['futurevents'] = 'Futurs';
+$labels['allevents'] = 'Tots';
+$labels['saveasnew'] = 'Desa com a nou';
+$labels['birthdays'] = 'Aniversaris';
+$labels['birthdayscalendar'] = 'Calendari d\'aniversaris';
+$labels['displaybirthdayscalendar'] = 'Mostra el calendari d\'aniversaris';
+$labels['birthdayscalendarsources'] = 'D\'aquestes llibretes d\'adreces';
+$labels['birthdayeventtitle'] = 'Aniversari de $name';
+$labels['birthdayage'] = 'Edat $age';
+$labels['objectchangelog'] = 'Canvia historial';
+$labels['objectnotfound'] = 'No s\'han pogut carregar les dades d\'aquest esdeveniment';
+$labels['objectchangelognotavailable'] = 'No està disponible canviar l\'historial d\'aquest esdeveniment';
+$labels['objectdiffnotavailable'] = 'No és possible comparar les revisions seleccionades';
+$labels['revisionrestoreconfirm'] = 'Esteu segurs de voler restaurar la revisió $rev d\'aquest esdeveniment? Això substituirà l\'actual esdeveniment per una versió antiga.';
+$labels['arialabelminical'] = 'Selecció de la data del calendari';
+$labels['arialabelcalendarview'] = 'Vista del calendari';
+$labels['arialabelsearchform'] = 'Formulari per cercar esdeveniments';
+$labels['arialabelquicksearchbox'] = 'Entrada de cerca d\'esdeveniments';
+$labels['arialabelcalsearchform'] = 'Formulari de cerca de calendaris';
+$labels['calendaractions'] = 'Accions del calendari';
+$labels['arialabeleventattendees'] = 'Llista de participants de l\'esdeveniment';
+$labels['arialabeleventresources'] = 'Llista de recursos de l\'esdeveniment';
+$labels['arialabelresourcesearchform'] = 'Formulari de cerca de recursos';
+$labels['arialabelresourceselection'] = 'Recursos disponibles';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/cs_CZ.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Výchozí pohled';
+$labels['time_format'] = 'Formát data';
+$labels['timeslots'] = 'Míst v rozvrhu na hodinu';
+$labels['first_day'] = 'První den v týdnu';
+$labels['first_hour'] = 'První hodina k zobrazení';
+$labels['workinghours'] = 'Pracovní hodiny';
+$labels['add_category'] = 'Přidat kategorii';
+$labels['remove_category'] = 'Odstranit kategorii';
+$labels['defaultcalendar'] = 'Vytvářet nové události v';
+$labels['eventcoloring'] = 'Barvy událostí';
+$labels['coloringmode0'] = 'Podle kalendáře';
+$labels['coloringmode1'] = 'Podle kategorie';
+$labels['coloringmode2'] = 'Kalendář pro orámování, kategorie pro obsah';
+$labels['coloringmode3'] = 'Kategorie pro orámování, kalendář pro obsah';
+$labels['afternothing'] = 'Nedělat nic';
+$labels['aftertrash'] = 'Přesunout do koše';
+$labels['afterdelete'] = 'Smazat zprávu';
+$labels['afterflagdeleted'] = 'OznaÄit jako smazané';
+$labels['aftermoveto'] = 'Přesunout do...';
+$labels['itipoptions'] = 'Pozvání na událost';
+$labels['afteraction'] = 'Poté co jsou pozvání nebo aktualizace zprávy zpracovány';
+$labels['calendar'] = 'Kalendář';
+$labels['calendars'] = 'Kalendáře';
+$labels['category'] = 'Kategorie';
+$labels['categories'] = 'Kategorie';
+$labels['createcalendar'] = 'Vytvořit nový kalendář';
+$labels['editcalendar'] = 'Upravit vlastnosti kalendáře';
+$labels['name'] = 'Název';
+$labels['color'] = 'Barva';
+$labels['day'] = 'Den';
+$labels['week'] = 'Týden';
+$labels['month'] = 'Měsíc';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nová';
+$labels['new_event'] = 'Nová událost';
+$labels['edit_event'] = 'Upravit událost';
+$labels['edit'] = 'Upravit';
+$labels['save'] = 'Uložit';
+$labels['removelist'] = 'Odstranit ze seznamu';
+$labels['cancel'] = 'Storno';
+$labels['select'] = 'Vybrat';
+$labels['print'] = 'Tisk';
+$labels['printtitle'] = 'Vytisknout kalendáře';
+$labels['title'] = 'Souhrn';
+$labels['description'] = 'Popis';
+$labels['all-day'] = 'celý den';
+$labels['export'] = 'Uložit jako ICS';
+$labels['exporttitle'] = 'Uložit jako iCalendar';
+$labels['exportrange'] = 'Události od';
+$labels['exportattachments'] = 'S přílohami';
+$labels['customdate'] = 'Vlastní datum';
+$labels['location'] = 'Místo';
+$labels['url'] = 'URL';
+$labels['date'] = 'Datum';
+$labels['start'] = 'ZaÄátek';
+$labels['starttime'] = 'ZaÄáteÄní Äas';
+$labels['end'] = 'Konec';
+$labels['endtime'] = 'ÄŒas konce';
+$labels['repeat'] = 'Opakování';
+$labels['selectdate'] = 'Vyberte datum';
+$labels['freebusy'] = 'Zobrazovat mÄ› jako';
+$labels['free'] = 'volno';
+$labels['busy'] = 'obsazeno';
+$labels['outofoffice'] = 'mimo kancelář';
+$labels['tentative'] = 'nezávazně';
+$labels['mystatus'] = 'Můj stav';
+$labels['status'] = 'Stav';
+$labels['status-confirmed'] = 'Potvrzeno';
+$labels['status-cancelled'] = 'Zrušeno';
+$labels['priority'] = 'Přednost';
+$labels['sensitivity'] = 'Soukromí';
+$labels['public'] = 'veřejné';
+$labels['private'] = 'soukromé';
+$labels['confidential'] = 'důvěrné';
+$labels['links'] = 'Odkaz';
+$labels['alarms'] = 'Připomenutí';
+$labels['comment'] = 'Poznámka';
+$labels['created'] = 'Vytvořeno';
+$labels['changed'] = 'Naposledy změněno';
+$labels['unknown'] = 'Neznámý';
+$labels['eventoptions'] = 'Volby';
+$labels['generated'] = 'vytvořeno';
+$labels['eventhistory'] = 'Historie';
+$labels['removelink'] = 'Odstranit odkaz na e-mail';
+$labels['printdescriptions'] = 'Vytisknout popisy';
+$labels['parentcalendar'] = 'Vložit dovnitř';
+$labels['searchearlierdates'] = '« Hledat dřívější události';
+$labels['searchlaterdates'] = 'Hledat pozdější události »';
+$labels['andnmore'] = 'dalších $nr...';
+$labels['togglerole'] = 'Klepněte k přepnutí role';
+$labels['createfrommail'] = 'Uložit jako událost';
+$labels['importevents'] = 'Zavést události';
+$labels['importrange'] = 'Události od';
+$labels['onemonthback'] = '1 měsíc nazpátek';
+$labels['nmonthsback'] = '$nr měsíců nazpátek';
+$labels['showurl'] = 'Ukázat adresu (URL) kalendáře';
+$labels['showurldescription'] = 'Tuto adresu použijte pro přístup (jen ke Ätení) ke kalendáři z jiných aplikací. Můžete ji zkopírovat a vložit do jakéhokoli kalendářového softwaru, který podporuje formát iCal.';
+$labels['caldavurldescription'] = 'Zkopírujte tuto adresu do aplikace <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> klienta (napÅ™. Evolution nebo Mozilla Thunderbird) pro úplné synchronizování tohoto adresáře s vaším poÄítaÄem nebo mobilním zařízením.';
+$labels['findcalendars'] = 'Najít kalendáře...';
+$labels['searchterms'] = 'Hledané výrazy';
+$labels['calsearchresults'] = 'Dostupné kalendáře';
+$labels['calendarsubscribe'] = 'Ukazovat seznam trvale';
+$labels['nocalendarsfound'] = 'Nenalezeny žádné kalendáře';
+$labels['nrcalendarsfound'] = '$nr kalendářů nalezeno';
+$labels['quickview'] = 'Zobrazit jen tento kalendář';
+$labels['invitationspending'] = 'Pozvání Äekající na vyřízení';
+$labels['invitationsdeclined'] = 'Odmítnutá pozvání';
+$labels['changepartstat'] = 'Změnit stav příjemce';
+$labels['rsvpcomment'] = 'Text pozvánky';
+$labels['listrange'] = 'Rozsah k zobrazení:';
+$labels['listsections'] = 'Rozdělit na:';
+$labels['smartsections'] = 'Chytré sekce';
+$labels['until'] = 'do';
+$labels['today'] = 'Dnes';
+$labels['tomorrow'] = 'Zítra';
+$labels['thisweek'] = 'Tento týden';
+$labels['nextweek'] = 'Příští týden';
+$labels['prevweek'] = 'Předchozí týden';
+$labels['thismonth'] = 'Tento měsíc';
+$labels['nextmonth'] = 'Příští měsíc';
+$labels['weekofyear'] = 'Týden';
+$labels['pastevents'] = 'Minulost';
+$labels['futureevents'] = 'Budoucnost';
+$labels['showalarms'] = 'Ukázat připomenutí';
+$labels['defaultalarmtype'] = 'Výchozí nastavení připomenutí';
+$labels['defaultalarmoffset'] = 'Výchozí Äas pÅ™ipomenutí';
+$labels['attendee'] = 'ÚÄastník';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Dost.';
+$labels['confirmstate'] = 'Stav';
+$labels['addattendee'] = 'PÅ™idat úÄastníka';
+$labels['roleorganizer'] = 'Organizátor';
+$labels['rolerequired'] = 'Povinný';
+$labels['roleoptional'] = 'Nepovinný';
+$labels['rolechair'] = 'Předsednictví';
+$labels['rolenonparticipant'] = 'Nepřítomný';
+$labels['cutypeindividual'] = 'Jednotlivec';
+$labels['cutypegroup'] = 'Skupina';
+$labels['cutyperesource'] = 'Zdroj';
+$labels['cutyperoom'] = 'Místnost';
+$labels['availfree'] = 'Volno';
+$labels['availbusy'] = 'Obsazeno';
+$labels['availunknown'] = 'Neznámý';
+$labels['availtentative'] = 'Nezávazně';
+$labels['availoutofoffice'] = 'Mimo kancelář';
+$labels['delegatedto'] = 'Pověřený:';
+$labels['delegatedfrom'] = 'Pověřující:';
+$labels['scheduletime'] = 'Najít dostupnost';
+$labels['sendinvitations'] = 'Poslat pozvánky';
+$labels['sendnotifications'] = 'UvÄ›domit úÄastníky o zmÄ›nách';
+$labels['sendcancellation'] = 'UvÄ›domit úÄastníky o zruÅ¡ení události';
+$labels['onlyworkinghours'] = 'Najít dostupnost v mé pracovní době';
+$labels['reqallattendees'] = 'Povinní/vÅ¡ichni úÄastníci';
+$labels['prevslot'] = 'Předchozí místo v rozvrhu';
+$labels['nextslot'] = 'Další místo v rozvrhu';
+$labels['suggestedslot'] = 'Navržené místo v rozvrhu';
+$labels['noslotfound'] = 'Nelze najít volné místo v rozvrhu';
+$labels['invitationsubject'] = 'Byl(a) jste pozván(a) na událost "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees\n\nPodrobnosti o události najdete v přiloženém souboru typu iCalendar. Můžete si ho zavést do kalendářového programu.";
+$labels['invitationattendlinks'] = "Pokud váš poštovní klient nepodporuje pozvánky iTip, použijte prosím následující odkaz k potvrzení nebo odmítnutí pozvání:\n\$url";
+$labels['eventupdatesubject'] = 'Událost "$title" byla aktualizována';
+$labels['eventupdatesubjectempty'] = 'Událost, která se vás týká, byla aktualizována';
+$labels['eventupdatemailbody'] = "*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees\n\nPodrobnosti o aktualizované události najdete v přiloženém souboru typu iCalendar. Můžete si ho zavést do kalendářového programu.";
+$labels['eventcancelsubject'] = 'Událost "$title" byla zrušena';
+$labels['eventcancelmailbody'] = "*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees\n\nUdálost byla zrušena organizátorem (\$organizer).\n\nPodrobnosti najdete v přiloženém souboru ve formátu iCalendar.";
+$labels['itipobjectnotfound'] = 'Událost, na který tato zpráva odkazuje, nebyl nalezen ve vašem kalendáři.';
+$labels['itipmailbodyaccepted'] = "\$sender přijal(a) pozvání na tuto událost:\n\n*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender nezávazně přijal(a) pozvání na tuto událost:\n\n*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender odmítl(a) pozvání na tuto událost:\n\n*\$title*\n\nKdy: \$date\n\nPozváni: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender odmítl vaÅ¡i úÄast na následující události:\n\n*\$title*\n\nTermín: \$date";
+$labels['itipmailbodydelegated'] = "\$sender delegoval úÄast na následující události:\n\n*\$title*\n\nTermín: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender delegoval úÄast na následující události na vás:\n\n*\$title*\n\nTermín: \$date";
+$labels['itipdeclineevent'] = 'Opravdu chcete odmítnout pozvání na tuto událost?';
+$labels['declinedeleteconfirm'] = 'Chcete také ze svého kalendáře smazat tuto odmítnutou událost?';
+$labels['itipcomment'] = 'Poznámka k pozvání/oznámení';
+$labels['itipcommenttitle'] = 'Tato poznámka bude pÅ™ipojena ke zprávÄ› s pozváním/oznámením poslané úÄastníkům';
+$labels['notanattendee'] = 'Nejste na seznamu úÄastníků této události';
+$labels['eventcancelled'] = 'Tato událost byla zrušena';
+$labels['saveincalendar'] = 'uložit do';
+$labels['updatemycopy'] = 'Aktualizovat v mém kalendáři';
+$labels['savetocalendar'] = 'Uložit do kalenáře';
+$labels['openpreview'] = 'Ověřit kalendář';
+$labels['noearlierevents'] = 'Žádné dřívější události';
+$labels['nolaterevents'] = 'Žádné pozdější události';
+$labels['resource'] = 'Zdroj';
+$labels['addresource'] = 'Zapsat zdroj';
+$labels['findresources'] = 'Najít zdroje';
+$labels['resourcedetails'] = 'Podrobnosti';
+$labels['resourceavailability'] = 'Dostupnost';
+$labels['resourceowner'] = 'Vlastník';
+$labels['resourceadded'] = 'Zdroj byl přidán do vaší události';
+$labels['tabsummary'] = 'Souhrn';
+$labels['tabrecurrence'] = 'Opakování';
+$labels['tabattendees'] = 'ÚÄastníci';
+$labels['tabresources'] = 'Zdroje';
+$labels['tabattachments'] = 'Přílohy';
+$labels['tabsharing'] = 'Sdílení';
+$labels['deleteobjectconfirm'] = 'Opravdu chcete smazat tuto událost?';
+$labels['deleteventconfirm'] = 'Opravdu chcete smazat tuto událost?';
+$labels['deletecalendarconfirm'] = 'Opravdu chcete smazat tento kalendář se všemi událostmi?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Ukládají se data...';
+$labels['errorsaving'] = 'Nelze uložit změny.';
+$labels['operationfailed'] = 'Požavovaná operace selhala.';
+$labels['invalideventdates'] = 'Vložená data nejsou platná! Zkontrolujte prosím zadávané údaje.';
+$labels['invalidcalendarproperties'] = 'Neplatné vlastnosti kalendáře! Vložte prosím platné jméno.';
+$labels['searchnoresults'] = 'Ve vybraných kalendářích nebyly nalezeny žádné události.';
+$labels['successremoval'] = 'Událost byla úspěšně smazána.';
+$labels['successrestore'] = 'Událost byla úspěšně obnovena.';
+$labels['errornotifying'] = 'Nelze odeslat notifikace úÄastníkům události';
+$labels['errorimportingevent'] = 'Událost se nepodařilo zavést';
+$labels['importwarningexists'] = 'Kopie této události již ve vašem kalendáři existuje.';
+$labels['newerversionexists'] = 'Existuje již novější verze této události! Operace byla zrušena.';
+$labels['nowritecalendarfound'] = 'Nebyl nalezen žádný kalendář, do kterého by šlo uložit tuto událost.';
+$labels['importedsuccessfully'] = 'Událost byla úspěšně přidána do kalendáře \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Událost byla úspěšně aktualizována v \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Stav úÄastníka byl úspěšnÄ› aktualizován';
+$labels['itipsendsuccess'] = 'Pozvánky byly rozeslány úÄastníkům.';
+$labels['itipresponseerror'] = 'Nelze odeslat odpovÄ›Ä na tuto pozvánku';
+$labels['itipinvalidrequest'] = 'Tato pozvánka již není platná';
+$labels['sentresponseto'] = 'OdpovÄ›Ä na pozvánku byla úspěšnÄ› odeslána na adresu $mailto';
+$labels['localchangeswarning'] = 'Chystáte se provést změny, které se projeví jen ve vašem vlastním kalendáři a nebudou poslány organizátorovi události.';
+$labels['importsuccess'] = 'Úspěšně zavedeno $nr událostí';
+$labels['importnone'] = 'Nebyly nalezeny žádné události k zavedení';
+$labels['importerror'] = 'Při zavádění došlo k chybě';
+$labels['aclnorights'] = 'Nemáte administrátorská práva k tomuto kalendáři.';
+$labels['changeeventconfirm'] = 'Změnit událost';
+$labels['removeeventconfirm'] = 'Smazat událost';
+$labels['changerecurringeventwarning'] = 'Toto je opakovaná událost. Chcete upravit jen toto konání, toto a všechna následující konání, úplně všechna konání nebo uložit událost jako novou?';
+$labels['removerecurringeventwarning'] = 'Toto je opakovaná událost. Chcete smazat jen toto konání, toto a všechna následující konání, nebo úplně všechna konání?';
+$labels['removerecurringallonly'] = 'Toto je opakovaná událost. Jako úÄastník můžete smazat pouze celou událost se vÅ¡emi konáními.';
+$labels['currentevent'] = 'Nynější';
+$labels['futurevents'] = 'Budoucí';
+$labels['allevents'] = 'VÅ¡e';
+$labels['saveasnew'] = 'Uložit jako novou';
+$labels['birthdays'] = 'Narozeniny';
+$labels['birthdayscalendar'] = 'Kalendář narozenin';
+$labels['displaybirthdayscalendar'] = 'Zobrazit kalendář narozenin';
+$labels['birthdayscalendarsources'] = 'Z těchto adresářů';
+$labels['birthdayeventtitle'] = 'Narozeniny $name';
+$labels['birthdayage'] = 'Věk $age';
+$labels['objectchangelog'] = 'Historie změn';
+$labels['objectdiff'] = 'Změny od $rev1 do $rev2';
+$labels['objectnotfound'] = 'Nepodařilo se nahrát data události';
+$labels['objectchangelognotavailable'] = 'Historie změn není pro tuto událost dostupná';
+$labels['objectdiffnotavailable'] = 'Pro vybrané verze není žádné srovnání možné';
+$labels['revisionrestoreconfirm'] = 'Opravdu chcete obnovit změnu $rev této události? Tímto dojde k nahrazení nynější události starou verzí.';
+$labels['objectrestoresuccess'] = 'Pozměnění $rev úspěšně obnoveno';
+$labels['objectrestoreerror'] = 'Nepodařilo se obnovit staré pozměnění';
+$labels['arialabelminical'] = 'Výběr data v kalendáři';
+$labels['arialabelcalendarview'] = 'Pohled na kalendář';
+$labels['arialabelsearchform'] = 'Hledání události';
+$labels['arialabelquicksearchbox'] = 'Zadání hledání události';
+$labels['arialabelcalsearchform'] = 'Hledání kalendářů';
+$labels['calendaractions'] = 'Činnosti v kalendáři';
+$labels['arialabeleventattendees'] = 'Seznam úÄastníků události';
+$labels['arialabeleventresources'] = 'Seznam zdrojů události';
+$labels['arialabelresourcesearchform'] = 'Hledání zdrojů';
+$labels['arialabelresourceselection'] = 'Dostupné zdroje';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/da_DK.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Standardvisning';
+$labels['time_format'] = 'Tidsformat';
+$labels['timeslots'] = 'Tidsblokke per time';
+$labels['first_day'] = 'Første ugedag';
+$labels['first_hour'] = 'Første time som vises';
+$labels['workinghours'] = 'Arbejdstider';
+$labels['add_category'] = 'Tilføj kategori';
+$labels['remove_category'] = 'Fjern kategori';
+$labels['defaultcalendar'] = 'Opret nye arragementer i';
+$labels['eventcoloring'] = 'Farver for arrangementer';
+$labels['coloringmode0'] = 'Ifølge kalender';
+$labels['coloringmode1'] = 'Ifølge kategori';
+$labels['coloringmode2'] = 'Kalender til oversigt, kategori til indhold';
+$labels['coloringmode3'] = 'Kategori til oversigt, kalender til indhold';
+$labels['afternothing'] = 'Undlad at gøre noget';
+$labels['aftertrash'] = 'Flyt til papirkurv';
+$labels['afterdelete'] = 'Slet beskeden';
+$labels['afterflagdeleted'] = 'Markér som slettet';
+$labels['aftermoveto'] = 'Flyt til ...';
+$labels['itipoptions'] = 'Begivenhedsinvitationer';
+$labels['afteraction'] = 'Efter en invitation eller opdateringsbesked er behandlet';
+$labels['calendar'] = 'Kalender';
+$labels['calendars'] = 'Kalendere';
+$labels['category'] = 'Kategori';
+$labels['categories'] = 'Kategorier';
+$labels['createcalendar'] = 'Opret ny kalender';
+$labels['editcalendar'] = 'Redigér kalenderegenskaber';
+$labels['name'] = 'Navn';
+$labels['color'] = 'Farve';
+$labels['day'] = 'Dag';
+$labels['week'] = 'Uge';
+$labels['month'] = 'MÃ¥ned';
+$labels['agenda'] = 'Dagsorden';
+$labels['new'] = 'Ny';
+$labels['new_event'] = 'Nyt arrangement';
+$labels['edit_event'] = 'Redigér arrangement';
+$labels['edit'] = 'Redigér';
+$labels['save'] = 'Gem';
+$labels['removelist'] = 'Fjern fra liste';
+$labels['cancel'] = 'Annullér';
+$labels['select'] = 'Vælg';
+$labels['print'] = 'Udskriv';
+$labels['printtitle'] = 'Udskriv kalendere';
+$labels['title'] = 'Resumé';
+$labels['description'] = 'Beskrivelse';
+$labels['all-day'] = 'hele-dagen';
+$labels['export'] = 'Eksport';
+$labels['exporttitle'] = 'Eksportér til iCalendar';
+$labels['exportrange'] = 'Arrangementer fra';
+$labels['exportattachments'] = 'Med vedhæftninger';
+$labels['customdate'] = 'Brugerdefineret dato';
+$labels['location'] = 'Placering';
+$labels['url'] = 'URL';
+$labels['date'] = 'Dato';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Starttidspunkt';
+$labels['end'] = 'Slut';
+$labels['endtime'] = 'Sluttidspunkt';
+$labels['repeat'] = 'Gentag';
+$labels['selectdate'] = 'Vælg dato';
+$labels['freebusy'] = 'Vis mig som';
+$labels['free'] = 'Ledig';
+$labels['busy'] = 'Optaget';
+$labels['outofoffice'] = 'Ikke på kontoret';
+$labels['tentative'] = 'Forsøgsvis';
+$labels['mystatus'] = 'Min status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Bekræftet';
+$labels['status-cancelled'] = 'Annulleret';
+$labels['priority'] = 'Prioritet';
+$labels['sensitivity'] = 'Privatliv';
+$labels['public'] = 'offentlig';
+$labels['private'] = 'privat';
+$labels['confidential'] = 'fortrolig';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'PÃ¥mindelse';
+$labels['comment'] = 'Kommentar';
+$labels['created'] = 'Oprettet';
+$labels['changed'] = 'Sidst ændret';
+$labels['unknown'] = 'Ukendt';
+$labels['eventoptions'] = 'Tilvalg';
+$labels['generated'] = 'oprettet per';
+$labels['eventhistory'] = 'Historik';
+$labels['removelink'] = 'Fjern email reference';
+$labels['printdescriptions'] = 'Udskriv beskrivelser';
+$labels['parentcalendar'] = 'Indsæt indeni';
+$labels['searchearlierdates'] = '« Søg efter tidligere arrangementer';
+$labels['searchlaterdates'] = 'Søg efter senere arrangementer »';
+$labels['andnmore'] = '$nr flere...';
+$labels['togglerole'] = 'Klik for at vise eller skjule rolle';
+$labels['createfrommail'] = 'Gem som arrangement';
+$labels['importevents'] = 'Importér arrangement';
+$labels['importrange'] = 'Arrangementer fra';
+$labels['onemonthback'] = '1 måned tilbage';
+$labels['nmonthsback'] = '$nr måneder tilbage';
+$labels['showurl'] = 'Vis kalenderens URL';
+$labels['showurldescription'] = 'Brug følgende adresse for at tilgå din kalender (skrivebeskyttet) fra andre programmer.  Du kan kopiere og indsætet denne i ethvert kalenderprogram, der understøtter iCal-formatet.';
+$labels['caldavurldescription'] = 'Kopiér denne adresse til en <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-klientprogram (eks. Evolution eller Mozilla Thunderbird) for at synkronisere denne kalender komplet med din computer eller mobilenhed.';
+$labels['findcalendars'] = 'Find kalendere ...';
+$labels['searchterms'] = 'Søgetermer';
+$labels['calsearchresults'] = 'Tilgængelige kalendere';
+$labels['calendarsubscribe'] = 'List permanent';
+$labels['nocalendarsfound'] = 'Der blev ikke fundet nogen kalender';
+$labels['nrcalendarsfound'] = '$nr kalendere blev fundet';
+$labels['quickview'] = 'Vis kun denne kalender';
+$labels['invitationspending'] = 'Afventende invitationer';
+$labels['invitationsdeclined'] = 'Afviste invitationer';
+$labels['changepartstat'] = 'Skift deltagerstatus';
+$labels['rsvpcomment'] = 'Invitationstekst';
+$labels['listrange'] = 'Interval som skal vises:';
+$labels['listsections'] = 'Del op i:';
+$labels['smartsections'] = 'Smarte sektioner';
+$labels['until'] = 'indtil';
+$labels['today'] = 'I dag';
+$labels['tomorrow'] = 'I morgen';
+$labels['thisweek'] = 'Denne uge';
+$labels['nextweek'] = 'Næste uge';
+$labels['prevweek'] = 'Forrige uge';
+$labels['thismonth'] = 'Denne måned';
+$labels['nextmonth'] = 'Næste måned';
+$labels['weekofyear'] = 'Uge';
+$labels['pastevents'] = 'Tidligere';
+$labels['futureevents'] = 'Fremtid';
+$labels['showalarms'] = 'Vis påmindelser';
+$labels['defaultalarmtype'] = 'Standardindstilling for påmindelse';
+$labels['defaultalarmoffset'] = 'Standardtidspunkt for påmindelse';
+$labels['attendee'] = 'Deltager';
+$labels['role'] = 'Rolle';
+$labels['availability'] = 'Tilg.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Tilføj deltager';
+$labels['roleorganizer'] = 'Organisator';
+$labels['rolerequired'] = 'Påkrævet';
+$labels['roleoptional'] = 'Valgfri';
+$labels['rolechair'] = 'Formand';
+$labels['rolenonparticipant'] = 'Fraværende';
+$labels['cutypeindividual'] = 'Individuel';
+$labels['cutypegroup'] = 'Gruppe';
+$labels['cutyperesource'] = 'Ressource';
+$labels['cutyperoom'] = 'Lokale';
+$labels['availfree'] = 'Ledig';
+$labels['availbusy'] = 'Optaget';
+$labels['availunknown'] = 'Ukendt';
+$labels['availtentative'] = 'Forsøgsvis';
+$labels['availoutofoffice'] = 'Ikke på kontoret';
+$labels['delegatedto'] = 'Delegere til:';
+$labels['delegatedfrom'] = 'Delegere fra:';
+$labels['scheduletime'] = 'Find ledigt tidspunkt';
+$labels['sendinvitations'] = 'Send invitationer';
+$labels['sendnotifications'] = 'Gør deltagere opmærksom på ændringer';
+$labels['sendcancellation'] = 'Giv deltagere besked om aflysning af arrangementer';
+$labels['onlyworkinghours'] = 'Find ledigt tidspunkt inden for mine arbejdstider';
+$labels['reqallattendees'] = 'Påkrævet/alle deltagere';
+$labels['prevslot'] = 'Forrige blok';
+$labels['nextslot'] = 'Næste blok';
+$labels['suggestedslot'] = 'Foreslået blok';
+$labels['noslotfound'] = 'Kunne ikke finde en ledig tidsblok';
+$labels['invitationsubject'] = 'Du er blevet inviteret til "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees\n\nBemærk venligst vedhæftede iCalendar-fil med alle detaljer om arrangementet, som du kan importere til dit kalenderprogram.";
+$labels['invitationattendlinks'] = "Hvis dit e-postprogram ikke understøtter iTip-forespørgsler, så kan du benytte følgende henvisning til enten at acceptere eller afvise denne invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" er blevet opdateret';
+$labels['eventupdatesubjectempty'] = 'Et arrangement der vedrører dig er blevet opdateret';
+$labels['eventupdatemailbody'] = "*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees\n\nBemærk venligst vedhæftede iCalendar-fil med alle detaljer om arrangementet, som du kan importere til dit kalenderprogram.";
+$labels['eventcancelsubject'] = '"$title" er blevet annulleret';
+$labels['eventcancelmailbody'] = "*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees\n\nDette arrangement er blevet aflyst af \$organizer.\n\nBemærk venligst vedhæftede iCalendard-fil med de opdaterede detaljer om arrangementet.";
+$labels['itipobjectnotfound'] = 'Begivenheden som denne besked henviser til, blev ikke fundet i din kalender.';
+$labels['itipmailbodyaccepted'] = "\$sender har accepteret invitationen til det følgende arrangement:\n\n*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender har forsøgsvist accepteret invitationen til det følgende arrangement:\n\n*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender har afvist invitationen til det følgende arrangement:\n\n*\$title*\n\nTidspunkt: \$date\n\nInviterede: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender har afvist din deltagelse i følgende begivenhed:\n\n*\$title*\n\nTidspunkt: \$date";
+$labels['itipmailbodydelegated'] = "\$sender har delegeret deltagelsen i følgende begivenhed:\n\n*\$title*\n\nTidspunkt: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender har delegeret deltagelsen i følgende begivenhed til dig:\n\n*\$title*\n\nTidspunkt: \$date";
+$labels['itipdeclineevent'] = 'Sikker på at du vil afvise dette arrangement?';
+$labels['declinedeleteconfirm'] = 'Vil du også slette dette afviste arrangement fra din kalender?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'Denne kommentar vil blive føjet til den besked med invitation/notifikation, der sendes til deltagerne';
+$labels['notanattendee'] = 'Du er ikke opført som deltager for dette arrangement';
+$labels['eventcancelled'] = 'Arrangementet er blevet aflyst';
+$labels['saveincalendar'] = 'gem i';
+$labels['updatemycopy'] = 'Opdatér i min kalender';
+$labels['savetocalendar'] = 'Gem til kalender';
+$labels['openpreview'] = 'Tjek kalender';
+$labels['noearlierevents'] = 'Ingen tidligere begivenheder';
+$labels['nolaterevents'] = 'Ingen senere begivenheder';
+$labels['resource'] = 'Ressource';
+$labels['addresource'] = 'Booking af ressource';
+$labels['findresources'] = 'Find ressourcer';
+$labels['resourcedetails'] = 'Detaljer';
+$labels['resourceavailability'] = 'Tilgængelighed';
+$labels['resourceowner'] = 'Ejer';
+$labels['resourceadded'] = 'Ressourcen til føjet til din begivenhed';
+$labels['tabsummary'] = 'Resumé';
+$labels['tabrecurrence'] = 'Gentagelse';
+$labels['tabattendees'] = 'Deltagere';
+$labels['tabresources'] = 'Ressourcer';
+$labels['tabattachments'] = 'Vedhæftninger';
+$labels['tabsharing'] = 'Deling';
+$labels['deleteobjectconfirm'] = 'Sikker på at du vil slette dette arrangement?';
+$labels['deleteventconfirm'] = 'Sikker på at du vil slette dette arrangement?';
+$labels['deletecalendarconfirm'] = 'Sikker på at du vil slette denne kalender med alle dets arrangementer?';
+$labels['deletecalendarconfirmrecursive'] = 'Sikker på du vil slette denne kalender med alle dens arrangementer og delkalendere?';
+$labels['savingdata'] = 'Gemmer data...';
+$labels['errorsaving'] = 'Kunne ikke gemme ændringer.';
+$labels['operationfailed'] = 'Den forespurgte handling mislykkedes.';
+$labels['invalideventdates'] = 'Ugyldig dato indtastet! Tjek venligst dit input.';
+$labels['invalidcalendarproperties'] = 'Ugyldige kalenderegenskaber! Angiv venligst et gyldigt navn.';
+$labels['searchnoresults'] = 'Der blev ikke fundet arrangementer i de valgte kalendere.';
+$labels['successremoval'] = 'Sletning af arrangementet blev gennemført.';
+$labels['successrestore'] = 'Gendannelse af arrangementet blev gennemført.';
+$labels['errornotifying'] = 'Kunne ikke sende notifikation til arrangementets deltagere';
+$labels['errorimportingevent'] = 'Kunne ikke importere arrangementet';
+$labels['importwarningexists'] = 'En kopi af denne begivenhed findes allerede i din kalender.';
+$labels['newerversionexists'] = 'Der findes allerede en nyere version af arrangementet! Afbrød.';
+$labels['nowritecalendarfound'] = 'Ingen funden kalender til lagring af arrangementet';
+$labels['importedsuccessfully'] = 'Arrangementet blev tilføjet til \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Opdatering af begivenheden blev gennemført i "$calendar"';
+$labels['attendeupdateesuccess'] = 'Opdatering af deltagernes status blev gennemført';
+$labels['itipsendsuccess'] = 'Invitation blev sendt til deltagerne.';
+$labels['itipresponseerror'] = 'Kunne ikke sende svar til denne arrangementsinvitation';
+$labels['itipinvalidrequest'] = 'Denne invitation er ikke længere gyldig';
+$labels['sentresponseto'] = 'Gennemførte afsendelse af invitationssvar til $mailto';
+$labels['localchangeswarning'] = 'Du er i færd med at foretage ændringer, der vil påvirke din kalender og som ikke vil blive sendt til afholderen af arrangementet.';
+$labels['importsuccess'] = 'Gennemførte import af $nr arrangementer';
+$labels['importnone'] = 'Fandt ingen arrangementer som kunne importeres';
+$labels['importerror'] = 'Der opstod en fejl under import';
+$labels['aclnorights'] = 'Du har ikke administratorrettigheder for denne kalender.';
+$labels['changeeventconfirm'] = 'Tilpas arrangement';
+$labels['removeeventconfirm'] = 'Slet begivenhed';
+$labels['changerecurringeventwarning'] = 'Dette er et tilbagevendende arrangement. Ønsker du kun at redige det aktuelle arrangement, dette og alle fremtidige forekomster, alle forekomster eller gemme det som et nyt arrangement?';
+$labels['removerecurringeventwarning'] = 'Dette er en tilbagevendende begivenhed. Ønsker du kun at fjerne den aktuelle begivenhed, denne og alle fremtidige forekomster for denne begivenhed?';
+$labels['removerecurringallonly'] = 'Dette er en tilbagevendende begivenhed. Som deltager kan du slette kun slette hele begivenheden med alle dens forekomster.';
+$labels['currentevent'] = 'Nuværende';
+$labels['futurevents'] = 'Fremtid';
+$labels['allevents'] = 'Alle';
+$labels['saveasnew'] = 'Gem som ny';
+$labels['birthdays'] = 'Fødselsdage';
+$labels['birthdayscalendar'] = 'Fødselsdagskalender';
+$labels['displaybirthdayscalendar'] = 'Vis fødselsdagskalender';
+$labels['birthdayscalendarsources'] = 'Fra disse adressebøger';
+$labels['birthdayeventtitle'] = '$name har fødselsdag';
+$labels['birthdayage'] = '$age år';
+$labels['objectchangelog'] = 'Ændringshistorik';
+$labels['objectdiff'] = 'Ændringer fra $rev1 til $rev2';
+$labels['objectnotfound'] = 'Kunne ikke indlæse begivenhedsdata';
+$labels['objectchangelognotavailable'] = 'Ændringshistorikken er ikke tilgængelig for denne begivenhed';
+$labels['objectdiffnotavailable'] = 'Det er ikke muligt at sammenligne de valgte revisioner';
+$labels['revisionrestoreconfirm'] = 'Sikker på at du vil genskabe revision $rev af denne begivenhed? Dette vil erstatte den nuværende begivenhed med den tidligere version.';
+$labels['objectrestoresuccess'] = 'Revision $rev blev genskabt';
+$labels['objectrestoreerror'] = 'Mislykkedes med at genskabe den gamle revision';
+$labels['arialabelminical'] = 'Valg af kalenderdato';
+$labels['arialabelcalendarview'] = 'Kalendervisning';
+$labels['arialabelsearchform'] = 'Søgeformular for begivenheder';
+$labels['arialabelquicksearchbox'] = 'Søgeinput for begivenhed';
+$labels['arialabelcalsearchform'] = 'Søgeformular for kalendere';
+$labels['calendaractions'] = 'Kalenderhandlinger';
+$labels['arialabeleventattendees'] = 'Deltagerliste for begivenhed';
+$labels['arialabeleventresources'] = 'Ressourceliste for begivenhed';
+$labels['arialabelresourcesearchform'] = 'Søgeformular for ressourcer';
+$labels['arialabelresourceselection'] = 'Tilgængelige ressourcer';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/de_CH.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Standardansicht';
+$labels['time_format'] = 'Zeitformatierung';
+$labels['timeslots'] = 'Abschnitte pro Stunde';
+$labels['first_day'] = 'Erster Wochentag';
+$labels['first_hour'] = 'Erste angezeigte Stunde';
+$labels['workinghours'] = 'Arbeitszeiten';
+$labels['add_category'] = 'Kategorie hinzufügen';
+$labels['remove_category'] = 'Kategorie entfernen';
+$labels['defaultcalendar'] = 'Neue Termine erstellen in';
+$labels['eventcoloring'] = 'Färbung der Termine';
+$labels['coloringmode0'] = 'Farbe des Kalenders';
+$labels['coloringmode1'] = 'Farbe der Kategorie';
+$labels['coloringmode2'] = 'Kalenderfarbe aussen, Kategoriefarbe innen';
+$labels['coloringmode3'] = 'Kategoriefarbe aussen, Kalenderfarbe innen';
+$labels['afternothing'] = 'nichts unternehmen';
+$labels['aftertrash'] = 'In den Papierkorb verschieben';
+$labels['afterdelete'] = 'Nachricht löschen';
+$labels['afterflagdeleted'] = 'Als gelöscht markieren';
+$labels['aftermoveto'] = 'Verschiebe nach...';
+$labels['itipoptions'] = 'Termineinladungen';
+$labels['afteraction'] = 'Nachdem eine Einladungs- oder Update-Nachricht verarbetet wurde';
+$labels['calendar'] = 'Kalender';
+$labels['calendars'] = 'Kalender';
+$labels['category'] = 'Kategorie';
+$labels['categories'] = 'Kategorien';
+$labels['createcalendar'] = 'Neuen Kalender erstellen';
+$labels['editcalendar'] = 'Kalendereigenschaften bearbeiten';
+$labels['name'] = 'Name';
+$labels['color'] = 'Farbe';
+$labels['day'] = 'Tag';
+$labels['week'] = 'Woche';
+$labels['month'] = 'Monat';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Neu';
+$labels['new_event'] = 'Neuer Termin';
+$labels['edit_event'] = 'Termin bearbeiten';
+$labels['edit'] = 'Bearbeiten';
+$labels['save'] = 'Speichern';
+$labels['removelist'] = 'Aus der Liste entfernen';
+$labels['cancel'] = 'Abbrechen';
+$labels['select'] = 'Auswählen';
+$labels['print'] = 'Drucken';
+$labels['printtitle'] = 'Kalender drucken';
+$labels['title'] = 'Titel';
+$labels['description'] = 'Beschrieb';
+$labels['all-day'] = 'ganztägig';
+$labels['export'] = 'Exportieren';
+$labels['exporttitle'] = 'Kalender als iCalendar exportieren';
+$labels['exportrange'] = 'Termine ab';
+$labels['exportattachments'] = 'Mit Anhängen';
+$labels['customdate'] = 'Spezifisches Datum';
+$labels['location'] = 'Ort';
+$labels['url'] = 'URL';
+$labels['date'] = 'Datum';
+$labels['start'] = 'Beginn';
+$labels['starttime'] = 'Startzeit';
+$labels['end'] = 'Ende';
+$labels['endtime'] = 'Endzeit';
+$labels['repeat'] = 'Wiederholung';
+$labels['selectdate'] = 'Datum auswählen';
+$labels['freebusy'] = 'Zeige mich als';
+$labels['free'] = 'Frei';
+$labels['busy'] = 'Gebucht';
+$labels['outofoffice'] = 'Abwesend';
+$labels['tentative'] = 'Mit Vorbehalt';
+$labels['mystatus'] = 'Mein Status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Bestätigt';
+$labels['status-cancelled'] = 'Gekündigt';
+$labels['priority'] = 'Priorität';
+$labels['sensitivity'] = 'Sichtbarkeit';
+$labels['public'] = 'öffentlich';
+$labels['private'] = 'privat';
+$labels['confidential'] = 'vertraulich';
+$labels['links'] = 'Referenz';
+$labels['alarms'] = 'Erinnerung';
+$labels['comment'] = 'Kommentar';
+$labels['created'] = 'Erstellt';
+$labels['changed'] = 'Geändert';
+$labels['unknown'] = 'Unbekannt';
+$labels['eventoptions'] = 'Optionen';
+$labels['generated'] = 'erstellt am';
+$labels['eventhistory'] = 'Verlauf';
+$labels['removelink'] = 'E-Mail-Referenz entfernen';
+$labels['printdescriptions'] = 'Beschrieb drucken';
+$labels['parentcalendar'] = 'Erstellen in';
+$labels['searchearlierdates'] = '« Frühere Termine suchen';
+$labels['searchlaterdates'] = 'Spätere Termine suchen »';
+$labels['andnmore'] = '$nr weitere...';
+$labels['togglerole'] = 'Zum Ändern der Rolle klicken';
+$labels['createfrommail'] = 'Als Termin speichern';
+$labels['importevents'] = 'Termine importieren';
+$labels['importrange'] = 'Termine ab';
+$labels['onemonthback'] = '1 Monat zurück';
+$labels['nmonthsback'] = '$nr Monate zurück';
+$labels['showurl'] = 'URL anzeigen';
+$labels['showurldescription'] = 'Über die folgende Adresse können Sie mit einem beliebigen Kalenderprogramm Ihren Kalender abrufen (nur lesend), sofern dieses das iCal-Format unterstützt.';
+$labels['caldavurldescription'] = 'Diese Adresse in einen <a href="http://de.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-Klienten (z.B. Evolution oder Mozilla Thunderbird) kopieren, um den Kalender mit Ihrem Computer oder einem mobilen Gerät zu synchronisieren.';
+$labels['findcalendars'] = 'Kalender finden...';
+$labels['searchterms'] = 'Suchbegriffe';
+$labels['calsearchresults'] = 'Verfügbare Kalender';
+$labels['calendarsubscribe'] = 'Permanent anzeigen';
+$labels['nocalendarsfound'] = 'Keine Kalender gefunden';
+$labels['nrcalendarsfound'] = '$nr Kalender gefunden';
+$labels['quickview'] = 'Nur diesen Kalender anzeigen';
+$labels['invitationspending'] = 'Pendente Einladungen';
+$labels['invitationsdeclined'] = 'Abgelehnte Einladungen';
+$labels['changepartstat'] = 'Teilnehmerstatus ändern';
+$labels['rsvpcomment'] = 'Einladungstext';
+$labels['listrange'] = 'Angezeigter Bereich:';
+$labels['listsections'] = 'Unterteilung:';
+$labels['smartsections'] = 'Intelligent';
+$labels['until'] = 'bis';
+$labels['today'] = 'Heute';
+$labels['tomorrow'] = 'Morgen';
+$labels['thisweek'] = 'Diese Woche';
+$labels['nextweek'] = 'Nächste Woche';
+$labels['prevweek'] = 'Vorige Woche';
+$labels['thismonth'] = 'Diesen Monat';
+$labels['nextmonth'] = 'Nächsten Monat';
+$labels['weekofyear'] = 'KW';
+$labels['pastevents'] = 'Vergangene';
+$labels['futureevents'] = 'Zukünftige';
+$labels['showalarms'] = 'Erinnerungen anzeigen';
+$labels['defaultalarmtype'] = 'Standard-Erinnerungseinstellung';
+$labels['defaultalarmoffset'] = 'Standard-Erinnerungszeit';
+$labels['attendee'] = 'Teilnehmer';
+$labels['role'] = 'Rolle';
+$labels['availability'] = 'Verfüg.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Hinzufügen';
+$labels['roleorganizer'] = 'Organisator';
+$labels['rolerequired'] = 'Erforderlich';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Vorsitz';
+$labels['rolenonparticipant'] = 'Abwesend';
+$labels['cutypeindividual'] = 'Person';
+$labels['cutypegroup'] = 'Gruppe';
+$labels['cutyperesource'] = 'Ressource';
+$labels['cutyperoom'] = 'Raum';
+$labels['availfree'] = 'Frei';
+$labels['availbusy'] = 'Gebucht';
+$labels['availunknown'] = 'Unbekannt';
+$labels['availtentative'] = 'Mit Vorbehalt';
+$labels['availoutofoffice'] = 'Abwesend';
+$labels['delegatedto'] = 'Delegiert an:';
+$labels['delegatedfrom'] = 'Delegiert von:';
+$labels['scheduletime'] = 'Verfügbarkeit anzeigen';
+$labels['sendinvitations'] = 'Einladungen versenden';
+$labels['sendnotifications'] = 'Teilnehmer über die Änderungen informieren';
+$labels['sendcancellation'] = 'Teilnehmer über die Terminabsage informieren';
+$labels['onlyworkinghours'] = 'Verfügbarkeit innerhalb meiner Arbeitszeiten suchen';
+$labels['reqallattendees'] = 'Erforderliche/alle Teilnehmer';
+$labels['prevslot'] = 'Vorheriger Vorschlag';
+$labels['nextslot'] = 'Nächster Vorschlag';
+$labels['suggestedslot'] = 'Empfohlener Terminplatz';
+$labels['noslotfound'] = 'Es konnten keine freien Zeiten gefunden werden';
+$labels['invitationsubject'] = 'Sie wurden zu "$title" eingeladen';
+$labels['invitationmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nIm Anhang finden Sie eine iCalendar-Datei mit allen Details des Termins. Diese können Sie in Ihre Kalenderanwendung importieren.";
+$labels['invitationattendlinks'] = "Falls Ihr E-Mail-Programm keine iTip-Anfragen unterstützt, können Sie den folgenden Link verwenden, um den Termin zu bestätigen oder abzulehnen:\n\$url";
+$labels['eventupdatesubject'] = '"$title" wurde aktualisiert';
+$labels['eventupdatesubjectempty'] = 'Termin wurde aktualisiert';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nIm Anhang finden Sie eine iCalendar-Datei mit den aktualisiereten Termindaten. Diese können Sie in Ihre Kalenderanwendung importieren.";
+$labels['eventcancelsubject'] = '"$title" wurde abgesagt';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nDer Termin wurde von \$organizer abgesagt.\n\nIm Anhang finden Sie eine iCalendar-Datei mit den Termindaten.";
+$labels['itipobjectnotfound'] = 'Der Termin auf den sich diese Nachricht bezieht, wurde in Ihrem Kalender nicht gefunden.';
+$labels['itipmailbodyaccepted'] = "\$sender hat die Einladung zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender hat die Einladung mit Vorbehalt zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender hat die Einladung zum folgenden Termin abgelehnt:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender hat ihre Teilnahme bei der folgenden Veranstaltung zurückgewiesen:\n\n*\$title*\n\nam: \$date";
+$labels['itipmailbodydelegated'] = "\$sender hat die Teilnahme an folgendem Event delegiert:\n\n*\$title*\n\nWann: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender hat die Teilnahme an folgendem Event an Sie delegiert:\n\n*\$title*\n\nWann: \$date";
+$labels['itipdeclineevent'] = 'Möchten Sie die Einladung zu diesem Termin ablehnen?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Kommentar zur Einladungs/Benachrichtigung';
+$labels['itipcommenttitle'] = 'Dieser Kommentar wird an die Einladungs/Benachrichtigung angehängt, die an die Teilnehmer verschickt wird';
+$labels['notanattendee'] = 'Sie sind nicht in der Liste der Teilnehmer aufgeführt';
+$labels['eventcancelled'] = 'Der Termin wurde vom Organisator abgesagt';
+$labels['saveincalendar'] = 'speichern in';
+$labels['updatemycopy'] = 'In meinem Kalender aktualisieren';
+$labels['savetocalendar'] = 'In Kalender übernehmen';
+$labels['openpreview'] = 'Kalender überprüfen';
+$labels['noearlierevents'] = 'Keine früheren Ereignisse';
+$labels['nolaterevents'] = 'Keine späteren Ereignisse';
+$labels['resource'] = 'Ressource';
+$labels['addresource'] = 'Ressource buchen';
+$labels['findresources'] = 'Ressourcen finden';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Verfügbarkeit';
+$labels['resourceowner'] = 'Eigentümer';
+$labels['resourceadded'] = 'Diese Ressource wurde Ihrem Termin hinzugefügt';
+$labels['tabsummary'] = 'Ãœbersicht';
+$labels['tabrecurrence'] = 'Wiederholung';
+$labels['tabattendees'] = 'Teilnehmer';
+$labels['tabresources'] = 'Ressourcen';
+$labels['tabattachments'] = 'Anhänge';
+$labels['tabsharing'] = 'Freigabe';
+$labels['deleteobjectconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
+$labels['deleteventconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
+$labels['deletecalendarconfirm'] = 'Möchten Sie diesen Kalender mit allen Terminen wirklich löschen?';
+$labels['deletecalendarconfirmrecursive'] = 'Möchten Sie diesen Kalender mit allen Terminen und Unterkalendern wirklich löschen?';
+$labels['savingdata'] = 'Speichere Daten...';
+$labels['errorsaving'] = 'Fehler beim Speichern.';
+$labels['operationfailed'] = 'Die Aktion ist fehlgeschlagen.';
+$labels['invalideventdates'] = 'Ungültige Daten eingegeben! Bitte überprüfen Sie die Eingaben.';
+$labels['invalidcalendarproperties'] = 'Ungültige Kalenderinformationen! Bitte geben Sie einen Namen ein.';
+$labels['searchnoresults'] = 'Keine Termine in den gewählten Kalendern gefunden.';
+$labels['successremoval'] = 'Der Termin wurde erfolgreich gelöscht.';
+$labels['successrestore'] = 'Der Termin wurde erfolgreich wieder hergestellt.';
+$labels['errornotifying'] = 'Benachrichtigung an die Teilnehmer konnten nicht gesendet werden';
+$labels['errorimportingevent'] = 'Fehler beim Importieren';
+$labels['importwarningexists'] = 'Eine Kopie dieses Termins existiert bereits in Ihrem Kalender.';
+$labels['newerversionexists'] = 'Eine neuere Version dieses Termins exisitert bereits! Import abgebrochen.';
+$labels['nowritecalendarfound'] = 'Kein Kalender zum Speichern gefunden';
+$labels['importedsuccessfully'] = 'Der Termin wurde erfolgreich in \'$calendar\' gespeichert';
+$labels['updatedsuccessfully'] = 'Der Termin wurde erfolgreich in \'$calendar\' geändert';
+$labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert';
+$labels['itipsendsuccess'] = 'Einladung an Teilnehmer versendet.';
+$labels['itipresponseerror'] = 'Die Antwort auf diese Einladung konnte nicht versendet werden';
+$labels['itipinvalidrequest'] = 'Diese Einladung ist nicht mehr gültig';
+$labels['sentresponseto'] = 'Antwort auf diese Einladung erfolgreich an $mailto gesendet';
+$labels['localchangeswarning'] = 'Die auszuführenden Änderungen werden sich nur auf den persönlichen Kalender auswirken und nicht an den Organisator des Termins weitergeleitet.';
+$labels['importsuccess'] = 'Es wurden $nr Termine erfolgreich importiert';
+$labels['importnone'] = 'Keine Termine zum Importieren gefunden';
+$labels['importerror'] = 'Fehler beim Importieren';
+$labels['aclnorights'] = 'Sie haben keine Administrator-Rechte für diesen Kalender.';
+$labels['changeeventconfirm'] = 'Termin ändern';
+$labels['removeeventconfirm'] = 'Termin löschen';
+$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
+$labels['removerecurringeventwarning'] = 'Dies ist ein wiederkehrender Termin. Möchten Sie nur den aktuellen, diesen und alle zukünftige Vorkommen oder die gesamte Terminreihe löschen?';
+$labels['removerecurringallonly'] = 'Dieses ist ein wiederkehrender Termin. Als ein Teilnehmer können Sie nur die gesamte Terminreihe inklusive aller Wiederholungen löschen.';
+$labels['currentevent'] = 'Aktuellen';
+$labels['futurevents'] = 'Zukünftige';
+$labels['allevents'] = 'Alle';
+$labels['saveasnew'] = 'Als neu speichern';
+$labels['birthdays'] = 'Geburtstage';
+$labels['birthdayscalendar'] = 'Geburtstags-Kalender';
+$labels['displaybirthdayscalendar'] = 'Geburtstags-Kalender anzeigen';
+$labels['birthdayscalendarsources'] = 'Für diese Adressbücher';
+$labels['birthdayeventtitle'] = '$names Geburtstag';
+$labels['birthdayage'] = 'Alter $age';
+$labels['objectchangelog'] = 'Änderungsverlauf';
+$labels['objectdiff'] = 'Änderungen aus $rev1 nach $rev2';
+$labels['objectnotfound'] = 'Termindaten konnten nicht geladen werden';
+$labels['objectchangelognotavailable'] = 'Änderungsverlauf für diesen Termin ist nicht verfügbar';
+$labels['objectdiffnotavailable'] = 'Vergleich für die gewählten Versionen nicht möglich';
+$labels['revisionrestoreconfirm'] = 'Wollen Sie wirklich die Version $rev dieses Termins wiederherstellen? Diese Aktion wird die aktuelle Kopie mit der älteren Version überschreiben.';
+$labels['objectrestoresuccess'] = 'Version $rev erfolgreich wiederhergestellt';
+$labels['objectrestoreerror'] = 'Fehler beim Wiederherstellen der alten Version';
+$labels['arialabelminical'] = 'Kalender Datumswahl';
+$labels['arialabelcalendarview'] = 'Kalender Ansicht';
+$labels['arialabelsearchform'] = 'Suchformular für Termine';
+$labels['arialabelquicksearchbox'] = 'Sucheingabe für Termine';
+$labels['arialabelcalsearchform'] = 'Suchformular für Kalender';
+$labels['calendaractions'] = 'Kalenderaktionen';
+$labels['arialabeleventattendees'] = 'Teilehmerliste';
+$labels['arialabeleventresources'] = 'Liste der Terminressourcen';
+$labels['arialabelresourcesearchform'] = 'Suchformular für Ressourcen';
+$labels['arialabelresourceselection'] = 'Verfügbare Ressourcen';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/de_DE.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Standardansicht';
+$labels['time_format'] = 'Zeitformatierung';
+$labels['timeslots'] = 'Zeitfenster pro Stunde';
+$labels['first_day'] = 'Erster Wochentag';
+$labels['first_hour'] = 'Erste angezeigte Stunde';
+$labels['workinghours'] = 'Arbeitszeiten';
+$labels['add_category'] = 'Kategorie hinzufügen';
+$labels['remove_category'] = 'Kategorie entfernen';
+$labels['defaultcalendar'] = 'Neue Termine erstellen in';
+$labels['eventcoloring'] = 'Färbung der Termine';
+$labels['coloringmode0'] = 'Farbe des Kalenders';
+$labels['coloringmode1'] = 'Farbe der Kategorie';
+$labels['coloringmode2'] = 'Kalenderfarbe außen, Kategoriefarbe innen';
+$labels['coloringmode3'] = 'Kategoriefarbe außen, Kalenderfarbe innen';
+$labels['afternothing'] = 'nichts unternehmen';
+$labels['aftertrash'] = 'In den Papierkorb verschieben';
+$labels['afterdelete'] = 'Nachricht löschen';
+$labels['afterflagdeleted'] = 'Als gelöscht markieren';
+$labels['aftermoveto'] = 'Verschiebe nach...';
+$labels['itipoptions'] = 'Veranstaltungseinladungen';
+$labels['afteraction'] = 'Nachdem eine Einladungs- oder Aktualisierungsnachricht verarbeitet wurde';
+$labels['calendar'] = 'Kalender';
+$labels['calendars'] = 'Kalender';
+$labels['category'] = 'Kategorie';
+$labels['categories'] = 'Kategorien';
+$labels['createcalendar'] = 'Neuen Kalender erstellen';
+$labels['editcalendar'] = 'Kalendereigenschaften bearbeiten';
+$labels['name'] = 'Name';
+$labels['color'] = 'Farbe';
+$labels['day'] = 'Tag';
+$labels['week'] = 'Woche';
+$labels['month'] = 'Monat';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Neu';
+$labels['new_event'] = 'Neuer Termin';
+$labels['edit_event'] = 'Termin bearbeiten';
+$labels['edit'] = 'Bearbeiten';
+$labels['save'] = 'Speichern';
+$labels['removelist'] = 'Von der Liste entfernen';
+$labels['cancel'] = 'Abbrechen';
+$labels['select'] = 'Auswählen';
+$labels['print'] = 'Drucken';
+$labels['printtitle'] = 'Kalender drucken';
+$labels['title'] = 'Titel';
+$labels['description'] = 'Beschreibung';
+$labels['all-day'] = 'ganztägig';
+$labels['export'] = 'Exportieren';
+$labels['exporttitle'] = 'Kalender als iCalendar exportieren';
+$labels['exportrange'] = 'Termine ab';
+$labels['exportattachments'] = 'Mit Anhängen';
+$labels['customdate'] = 'Benutzerdefiniertes Datum';
+$labels['location'] = 'Ort';
+$labels['url'] = 'Internetadresse';
+$labels['date'] = 'Datum';
+$labels['start'] = 'Beginn';
+$labels['starttime'] = 'Startzeit';
+$labels['end'] = 'Ende';
+$labels['endtime'] = 'Endzeit';
+$labels['repeat'] = 'Wiederholung';
+$labels['selectdate'] = 'Datum auswählen';
+$labels['freebusy'] = 'Mich anzeigen als';
+$labels['free'] = 'Frei';
+$labels['busy'] = 'Gebucht';
+$labels['outofoffice'] = 'Abwesend';
+$labels['tentative'] = 'Mit Vorbehalt';
+$labels['mystatus'] = 'Mein Status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Bestätigt';
+$labels['status-cancelled'] = 'Abgesagt';
+$labels['priority'] = 'Priorität';
+$labels['sensitivity'] = 'Sichtbarkeit';
+$labels['public'] = 'öffentlich';
+$labels['private'] = 'privat';
+$labels['confidential'] = 'vertraulich';
+$labels['links'] = 'Referenz';
+$labels['alarms'] = 'Erinnerung';
+$labels['comment'] = 'Kommentar';
+$labels['created'] = 'Erstellt';
+$labels['changed'] = 'Geändert';
+$labels['unknown'] = 'Unbekannt';
+$labels['eventoptions'] = 'Optionen';
+$labels['generated'] = 'erstellt am';
+$labels['eventhistory'] = 'Historie';
+$labels['removelink'] = 'E-Mail-Referenz entfernen';
+$labels['printdescriptions'] = 'Beschreibung drucken';
+$labels['parentcalendar'] = 'Erstellen in';
+$labels['searchearlierdates'] = '« Frühere Termine suchen';
+$labels['searchlaterdates'] = 'Spätere Termine suchen »';
+$labels['andnmore'] = '$nr weitere …';
+$labels['togglerole'] = 'Zum Ändern der Rolle klicken';
+$labels['createfrommail'] = 'Als Termin speichern';
+$labels['importevents'] = 'Termine importieren';
+$labels['importrange'] = 'Termine ab';
+$labels['onemonthback'] = '1 Monat zurück';
+$labels['nmonthsback'] = '$nr Monate zurück';
+$labels['showurl'] = 'URL anzeigen';
+$labels['showurldescription'] = 'Über die folgende Adresse können Sie mit einem beliebigen Kalenderprogramm Ihren Kalender abrufen (nur lesend), sofern dieses das iCal-Format unterstützt.';
+$labels['caldavurldescription'] = 'Diese Adresse in einen <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-Klienten (z.B. Evolution oder Mozilla Thunderbird) kopieren, um den Kalender in Gänze mit einem mobilen Gerät zu synchronisieren.';
+$labels['findcalendars'] = 'Kalender finden …';
+$labels['searchterms'] = 'Suchbegriffe';
+$labels['calsearchresults'] = 'Verfügbare Kalender';
+$labels['calendarsubscribe'] = 'Permanent anzeigen';
+$labels['nocalendarsfound'] = 'Keine Kalender gefunden';
+$labels['nrcalendarsfound'] = '$nr Kalender gefunden';
+$labels['quickview'] = 'Nur diesen Kalender anzeigen';
+$labels['invitationspending'] = 'Ausstehende Einladungen';
+$labels['invitationsdeclined'] = 'Abgelehnte Einladungen';
+$labels['changepartstat'] = 'Teilnehmerstatus ändern';
+$labels['rsvpcomment'] = 'Einladungstext';
+$labels['listrange'] = 'Angezeigter Bereich:';
+$labels['listsections'] = 'Unterteilung:';
+$labels['smartsections'] = 'Intelligent';
+$labels['until'] = 'bis';
+$labels['today'] = 'Heute';
+$labels['tomorrow'] = 'Morgen';
+$labels['thisweek'] = 'Diese Woche';
+$labels['nextweek'] = 'Nächste Woche';
+$labels['prevweek'] = 'Vorige Woche';
+$labels['thismonth'] = 'Diesen Monat';
+$labels['nextmonth'] = 'Nächsten Monat';
+$labels['weekofyear'] = 'Woche';
+$labels['pastevents'] = 'Vergangene';
+$labels['futureevents'] = 'Zukünftige';
+$labels['showalarms'] = 'Erinnerungen anzeigen';
+$labels['defaultalarmtype'] = 'Standard-Erinnerungseinstellung';
+$labels['defaultalarmoffset'] = 'Standard-Erinnerungszeit';
+$labels['attendee'] = 'Teilnehmer';
+$labels['role'] = 'Rolle';
+$labels['availability'] = 'Verfüg.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Hinzufügen';
+$labels['roleorganizer'] = 'Organisator';
+$labels['rolerequired'] = 'Erforderlich';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Vorsitz';
+$labels['rolenonparticipant'] = 'Abwesend';
+$labels['cutypeindividual'] = 'Person';
+$labels['cutypegroup'] = 'Gruppe';
+$labels['cutyperesource'] = 'Ressource';
+$labels['cutyperoom'] = 'Raum';
+$labels['availfree'] = 'Frei';
+$labels['availbusy'] = 'Gebucht';
+$labels['availunknown'] = 'Unbekannt';
+$labels['availtentative'] = 'Mit Vorbehalt';
+$labels['availoutofoffice'] = 'Abwesend';
+$labels['delegatedto'] = 'Delegiert an:';
+$labels['delegatedfrom'] = 'Delegiert von:';
+$labels['scheduletime'] = 'Verfügbarkeit anzeigen';
+$labels['sendinvitations'] = 'Einladungen versenden';
+$labels['sendnotifications'] = 'Teilnehmer über die Änderungen informieren';
+$labels['sendcancellation'] = 'Teilnehmer über die Terminabsage informieren';
+$labels['onlyworkinghours'] = 'Verfügbarkeit innerhalb meiner Arbeitszeiten suchen';
+$labels['reqallattendees'] = 'Erforderliche/alle Teilnehmer';
+$labels['prevslot'] = 'Vorheriger Vorschlag';
+$labels['nextslot'] = 'Nächster Vorschlag';
+$labels['suggestedslot'] = 'Empfohlener Slot';
+$labels['noslotfound'] = 'Es konnten keine freien Zeiten gefunden werden';
+$labels['invitationsubject'] = 'Sie wurden zu »$title« eingeladen';
+$labels['invitationmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nIm Anhang finden Sie eine iCalendar-Datei mit allen Details des Termins. Diese können Sie in Ihre Kalenderanwendung importieren.";
+$labels['invitationattendlinks'] = "Falls Ihr E-Mail-Programm keine iTip-Anfragen unterstützt, können Sie den folgenden Link verwenden, um den Termin zu bestätigen oder abzulehnen:\n\$url";
+$labels['eventupdatesubject'] = '»$title« wurde aktualisiert';
+$labels['eventupdatesubjectempty'] = 'Termin wurde aktualisiert';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nIm Anhang finden Sie eine iCalendar-Datei mit den aktualisiereten Termindaten. Diese können Sie in Ihre Kalenderanwendung importieren.";
+$labels['eventcancelsubject'] = '»$title« wurde abgesagt';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nDer Termin wurde von \$organizer abgesagt.\n\nIm Anhang finden Sie eine iCalendar-Datei mit den Termindaten.";
+$labels['itipobjectnotfound'] = 'Der Termin auf den sich diese Nachricht bezieht, wurde in Ihrem Kalender nicht gefunden.';
+$labels['itipmailbodyaccepted'] = "\$sender hat die Einladung zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender hat die Einladung mit Vorbehalt zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender hat die Einladung zum folgenden Termin abgelehnt:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender hat Ihre Teilnahme bei der folgenden Veranstaltung zurückgewiesen:\n\n*\$title*\n\nWann: \$date";
+$labels['itipmailbodydelegated'] = "\$sender hat die Teilnahme an folgendem Event delegiert:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender hat die Teilnahme an folgendem Event an Sie delegiert:\n\n*\$title*\n\nWann: \$date";
+$labels['itipdeclineevent'] = 'Möchten Sie die Einladung zu diesem Termin ablehnen?';
+$labels['declinedeleteconfirm'] = 'Soll der abgelehnte Termin zusätzlich aus dem Kalender gelöscht werden?';
+$labels['itipcomment'] = 'Kommentar zur Einladungs-/Benachrichtigungsnachricht';
+$labels['itipcommenttitle'] = 'Dieser Kommentar wird an die Einladungs-/Benachrichtigungsnachricht angehängt, die an die Teilnehmer verschickt wird';
+$labels['notanattendee'] = 'Sie sind nicht in der Liste der Teilnehmer aufgeführt';
+$labels['eventcancelled'] = 'Der Termin wurde vom Organisator abgesagt';
+$labels['saveincalendar'] = 'speichern in';
+$labels['updatemycopy'] = 'In meinem Kalender aktualisieren';
+$labels['savetocalendar'] = 'In Kalender übernehmen';
+$labels['openpreview'] = 'Kalender überprüfen';
+$labels['noearlierevents'] = 'Keine früheren Ereignisse';
+$labels['nolaterevents'] = 'Keine späteren Ereignisse';
+$labels['resource'] = 'Ressource';
+$labels['addresource'] = 'Ressource buchen';
+$labels['findresources'] = 'Ressourcen finden';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Verfügbarkeit';
+$labels['resourceowner'] = 'Eigentümer';
+$labels['resourceadded'] = 'Diese Ressource wurde Ihrem Termin hinzugefügt';
+$labels['tabsummary'] = 'Ãœbersicht';
+$labels['tabrecurrence'] = 'Wiederholung';
+$labels['tabattendees'] = 'Teilnehmer';
+$labels['tabresources'] = 'Ressourcen';
+$labels['tabattachments'] = 'Anhänge';
+$labels['tabsharing'] = 'Freigabe';
+$labels['deleteobjectconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
+$labels['deleteventconfirm'] = 'Möchten Sie diesen Termin wirklich löschen?';
+$labels['deletecalendarconfirm'] = 'Möchten Sie diesen Kalender mit allen Terminen wirklich löschen?';
+$labels['deletecalendarconfirmrecursive'] = 'Soll dieser Kalender wirklich mit allen Terminen und Unterkalendern gelöscht werden?';
+$labels['savingdata'] = 'Daten werden gespeichert …';
+$labels['errorsaving'] = 'Fehler beim Speichern.';
+$labels['operationfailed'] = 'Die Aktion ist fehlgeschlagen.';
+$labels['invalideventdates'] = 'Ungültige Daten eingegeben! Bitte überprüfen Sie die Eingaben.';
+$labels['invalidcalendarproperties'] = 'Ungültige Kalenderinformationen! Bitte geben Sie einen Namen ein.';
+$labels['searchnoresults'] = 'Keine Termine in den gewählten Kalendern gefunden.';
+$labels['successremoval'] = 'Der Termin wurde erfolgreich gelöscht.';
+$labels['successrestore'] = 'Der Termin wurde erfolgreich wieder hergestellt.';
+$labels['errornotifying'] = 'Benachrichtigung an die Teilnehmer konnten nicht gesendet werden';
+$labels['errorimportingevent'] = 'Fehler beim Importieren';
+$labels['importwarningexists'] = 'Eine Kopie dieses Termins existiert bereits in Ihrem Kalender.';
+$labels['newerversionexists'] = 'Eine neuere Version dieses Termins exisitert bereits! Import abgebrochen.';
+$labels['nowritecalendarfound'] = 'Kein Kalender zum Speichern gefunden';
+$labels['importedsuccessfully'] = 'Der Termin wurde erfolgreich in »$calendar« gespeichert';
+$labels['updatedsuccessfully'] = 'Der Termin wurde erfolgreich in »$calendar« geändert';
+$labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert';
+$labels['itipsendsuccess'] = 'Einladung an Teilnehmer versendet.';
+$labels['itipresponseerror'] = 'Die Antwort auf diese Einladung konnte nicht versendet werden';
+$labels['itipinvalidrequest'] = 'Diese Einladung ist nicht mehr gültig.';
+$labels['sentresponseto'] = 'Antwort auf diese Einladung erfolgreich an $mailto gesendet';
+$labels['localchangeswarning'] = 'Die auszuführenden Änderungen werden sich nur auf den persönlichen Kalender auswirken und nicht an den Organisator des Termins weitergeleitet.';
+$labels['importsuccess'] = 'Es wurden $nr Termine erfolgreich importiert';
+$labels['importnone'] = 'Keine Termine zum Importieren gefunden';
+$labels['importerror'] = 'Fehler beim Importieren';
+$labels['aclnorights'] = 'Der Zugriff auf diesen Kalender erfordert Administrator-Rechte.';
+$labels['changeeventconfirm'] = 'Termin ändern';
+$labels['removeeventconfirm'] = 'Termin löschen';
+$labels['changerecurringeventwarning'] = 'Dies ist eine Terminreihe. Möchten Sie nur den aktuellen, diesen und alle zukünftigen oder alle Termine bearbeiten oder die Änderungen als neuen Termin speichern?';
+$labels['removerecurringeventwarning'] = 'Dies ist ein wiederkehrender Termin. Wollen Sie nur diesen Termin bearbeiten oder alle zukünftigen Vorkommen? Alternativ können auch alle Vorkommen bearbeitet werden.';
+$labels['removerecurringallonly'] = 'Dieses ist ein wiederkehrender Termin. Als ein Teilnehmer können Sie nur den gesamten Termin inklusive aller Wiederholungen löschen.';
+$labels['currentevent'] = 'Aktuellen';
+$labels['futurevents'] = 'Zukünftige';
+$labels['allevents'] = 'Alle';
+$labels['saveasnew'] = 'Als neu speichern';
+$labels['birthdays'] = 'Geburtstage';
+$labels['birthdayscalendar'] = 'Geburtstagskalender';
+$labels['displaybirthdayscalendar'] = 'Geburtstagskalender anzeigen';
+$labels['birthdayscalendarsources'] = 'Für diese Adressbücher';
+$labels['birthdayeventtitle'] = '$names Geburtstag';
+$labels['birthdayage'] = 'Alter $age';
+$labels['objectchangelog'] = 'Änderungsverlauf';
+$labels['objectdiff'] = 'Änderungen aus $rev1 nach $rev2';
+$labels['objectnotfound'] = 'Termindaten sind leider nicht vergübar';
+$labels['objectchangelognotavailable'] = 'Änderungshistorie ist nicht verfügbar für diesen Termin';
+$labels['objectdiffnotavailable'] = 'Vergleich für die gewählten Versionen nicht möglich';
+$labels['revisionrestoreconfirm'] = 'Wollen Sie wirklich die Version $rev dieses Termins wiederherstellen? Diese Aktion wird die aktuelle Kopie mit der älteren Version ersetzen.';
+$labels['objectrestoresuccess'] = 'Revision $rev erfolgreich wiederhergestellt';
+$labels['objectrestoreerror'] = 'Fehler beim Wiederherstellen der alten Revision';
+$labels['arialabelminical'] = 'Kalender Datumswahl';
+$labels['arialabelcalendarview'] = 'Kalender Ansicht';
+$labels['arialabelsearchform'] = 'Suchformular für Termine';
+$labels['arialabelquicksearchbox'] = 'Sucheingabe für Termine';
+$labels['arialabelcalsearchform'] = 'Suchformular für Kalender';
+$labels['calendaractions'] = 'Kalenderaktionen';
+$labels['arialabeleventattendees'] = 'Teilehmerliste';
+$labels['arialabeleventresources'] = 'Liste der Terminressourcen';
+$labels['arialabelresourcesearchform'] = 'Suchformular für Ressourcen';
+$labels['arialabelresourceselection'] = 'Verfügbare Ressourcen';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/en_US.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,302 @@
+<?php
+
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+
+$labels = array();
+
+// preferences
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+
+// calendar
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['status-tentative'] = 'Tentative';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['removelink'] = 'Remove email reference';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+
+// agenda view
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+
+// alarm/reminder settings
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+
+// attendees
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been cancelled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+
+// invitation handling (overrides labels from libcalendaring)
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+
+// resources
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+
+// event dialog tabs
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+
+// messages
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['removerecurringallonly'] = 'This is a recurring event. As a participant, you can only delete the entire event with all occurences.';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+
+// birthdays calendar
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+
+// history dialog
+$labels['objectchangelog'] = 'Change History';
+$labels['objectdiff'] = 'Changes from $rev1 to $rev2';
+$labels['objectnotfound'] = 'Failed to load event data';
+$labels['objectchangelognotavailable'] = 'Change history is not available for this event';
+$labels['objectdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['revisionrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['objectrestoresuccess'] = 'Revision $rev successfully restored';
+$labels['objectrestoreerror'] = 'Failed to restore the old revision';
+
+
+// (hidden) titles and labels for accessibility annotations
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/es_AR.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Vista predeterminada';
+$labels['time_format'] = 'Formato de hora';
+$labels['timeslots'] = 'Espacios por hora';
+$labels['first_day'] = 'Primer día de semana';
+$labels['first_hour'] = 'Primer hora a mostrar';
+$labels['workinghours'] = 'Horario laboral';
+$labels['add_category'] = 'Agregar categoría';
+$labels['remove_category'] = 'Eliminar categoría';
+$labels['defaultcalendar'] = 'Crear nuevos eventos en';
+$labels['eventcoloring'] = 'Colores de eventos';
+$labels['coloringmode0'] = 'De acuerdo al calendario';
+$labels['coloringmode1'] = 'De acuerdo a la categoría';
+$labels['coloringmode2'] = 'Calendario para borde, categoría para contenido';
+$labels['coloringmode3'] = 'Categoría para borde, calendario para contenido';
+$labels['afternothing'] = 'Hacer nada';
+$labels['aftertrash'] = 'Mover a la papelera';
+$labels['afterdelete'] = 'Eliminar el mensaje';
+$labels['afterflagdeleted'] = 'Marcar como eliminado';
+$labels['aftermoveto'] = 'Mover a...';
+$labels['itipoptions'] = 'Invitaciones del evento';
+$labels['afteraction'] = 'Luego que una invitación o actualización de mensaje es procesado';
+$labels['calendar'] = 'Calendario';
+$labels['calendars'] = 'Calendarios';
+$labels['category'] = 'Categoría';
+$labels['categories'] = 'Categorías';
+$labels['createcalendar'] = 'Crear nuevo calendario';
+$labels['editcalendar'] = 'Editar propiedades del calendario';
+$labels['name'] = 'Nombre';
+$labels['color'] = 'Color';
+$labels['day'] = 'Día';
+$labels['week'] = 'Semana';
+$labels['month'] = 'Mes';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nuevo';
+$labels['new_event'] = 'Nuevo evento';
+$labels['edit_event'] = 'Editar evento';
+$labels['edit'] = 'Editar';
+$labels['save'] = 'Guardar';
+$labels['removelist'] = 'Eliminar de la lista';
+$labels['cancel'] = 'Cancelar';
+$labels['select'] = 'Seleccionar';
+$labels['print'] = 'Imprimir';
+$labels['printtitle'] = 'Imprimir calendarios';
+$labels['title'] = 'Sumario';
+$labels['description'] = 'Descripción';
+$labels['all-day'] = 'Todo el día';
+$labels['export'] = 'Exportar';
+$labels['exporttitle'] = 'Exportar a iCalendar';
+$labels['exportrange'] = 'Eventos de';
+$labels['exportattachments'] = 'Con adjunto';
+$labels['customdate'] = 'Fecha personalizada';
+$labels['location'] = 'Localización';
+$labels['url'] = 'URL';
+$labels['date'] = 'Fecha';
+$labels['start'] = 'Inicio';
+$labels['starttime'] = 'Hora de inicio';
+$labels['end'] = 'Fin';
+$labels['endtime'] = 'Hora de finalización';
+$labels['repeat'] = 'Repetir';
+$labels['selectdate'] = 'Elegir fecha';
+$labels['freebusy'] = 'Mostrarme como';
+$labels['free'] = 'Libre';
+$labels['busy'] = 'Ocupado';
+$labels['outofoffice'] = 'Fuera de la oficina';
+$labels['tentative'] = 'Tentativo';
+$labels['mystatus'] = 'Mi estado';
+$labels['status'] = 'Estado';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancelado';
+$labels['priority'] = 'Prioridad';
+$labels['sensitivity'] = 'Privacidad';
+$labels['public'] = 'público';
+$labels['private'] = 'privado';
+$labels['confidential'] = 'confidencial';
+$labels['links'] = 'Referencia';
+$labels['alarms'] = 'Recordatorio';
+$labels['comment'] = 'Comentario';
+$labels['created'] = 'Creado';
+$labels['changed'] = 'Última modificación';
+$labels['unknown'] = 'Desconocido';
+$labels['eventoptions'] = 'Opciones';
+$labels['generated'] = 'generado en';
+$labels['eventhistory'] = 'Historial';
+$labels['removelink'] = 'Eliminar referencia de correo';
+$labels['printdescriptions'] = 'Imprimir descripciones';
+$labels['parentcalendar'] = 'Insertar dentro';
+$labels['searchearlierdates'] = '« Buscar eventos anteriores';
+$labels['searchlaterdates'] = 'Buscar eventos posteriores »';
+$labels['andnmore'] = '$nr más...';
+$labels['togglerole'] = 'Click para cambiar rol';
+$labels['createfrommail'] = 'Guardar como evento';
+$labels['importevents'] = 'Importar eventos';
+$labels['importrange'] = 'Eventos de';
+$labels['onemonthback'] = '1 mes atrás';
+$labels['nmonthsback'] = '$nr meses atrás';
+$labels['showurl'] = 'Mostrar URL del calendario';
+$labels['showurldescription'] = 'Use la siguiente direccion para acceder (sólo lectura) a su calendario desde otras aplicaciones. Puede copiar y pegar esto dentro de cualquier software de calendario que soporte el formato iCal.';
+$labels['caldavurldescription'] = 'Copie esta direccion a su aplicación cliente <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> (por ejemplo, Evolution o Mozilla Thunderbird) para sincronizar completamente este calendario específico con su ordenador o dispositivo móvil.';
+$labels['findcalendars'] = 'Encontrar calendarios...';
+$labels['searchterms'] = 'Buscar términos';
+$labels['calsearchresults'] = 'Calendarios disponibles';
+$labels['calendarsubscribe'] = 'Listar permanentemente';
+$labels['nocalendarsfound'] = 'No se encontraron calendarios';
+$labels['nrcalendarsfound'] = '$nr calendarios encontrados';
+$labels['quickview'] = 'Ver sólo este calendario';
+$labels['invitationspending'] = 'Invitaciones pendientes';
+$labels['invitationsdeclined'] = 'Invitaciones rechazadas';
+$labels['changepartstat'] = 'Cambiar el estado del participante';
+$labels['rsvpcomment'] = 'Texto de invitación';
+$labels['listrange'] = 'Rango a mostrar:';
+$labels['listsections'] = 'Dividir en:';
+$labels['smartsections'] = 'Secciones inteligentes';
+$labels['until'] = 'hasta';
+$labels['today'] = 'Hoy';
+$labels['tomorrow'] = 'Mañana';
+$labels['thisweek'] = 'Esta semana';
+$labels['nextweek'] = 'Próxima semana';
+$labels['prevweek'] = 'Semana anterior';
+$labels['thismonth'] = 'Este mes';
+$labels['nextmonth'] = 'Próximo mes';
+$labels['weekofyear'] = 'Semana';
+$labels['pastevents'] = 'Pasado';
+$labels['futureevents'] = 'Futuro';
+$labels['showalarms'] = 'Mostrar alarmas';
+$labels['defaultalarmtype'] = 'Configuración predeterminada de recordatorio';
+$labels['defaultalarmoffset'] = 'Tiempo predeterminado de recordatorio';
+$labels['attendee'] = 'Participante';
+$labels['role'] = 'Rol';
+$labels['availability'] = 'Disp.';
+$labels['confirmstate'] = 'Estado';
+$labels['addattendee'] = 'Agregar participante';
+$labels['roleorganizer'] = 'Organizador';
+$labels['rolerequired'] = 'Requerido';
+$labels['roleoptional'] = 'Opcional';
+$labels['rolechair'] = 'Jefe';
+$labels['rolenonparticipant'] = 'Ausente';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Grupo';
+$labels['cutyperesource'] = 'Recurso';
+$labels['cutyperoom'] = 'Habitación';
+$labels['availfree'] = 'Libre';
+$labels['availbusy'] = 'Ocupado';
+$labels['availunknown'] = 'Desconocido';
+$labels['availtentative'] = 'Tentativo';
+$labels['availoutofoffice'] = 'Fuera de la oficina';
+$labels['delegatedto'] = 'Delegado a:';
+$labels['delegatedfrom'] = 'Delegado de:';
+$labels['scheduletime'] = 'Buscar disponibilidad';
+$labels['sendinvitations'] = 'Enviar invitaciones';
+$labels['sendnotifications'] = 'Notificar a los participantes sobre las modificaciones';
+$labels['sendcancellation'] = 'Notificar a los participantes sobre la cancelación del evento';
+$labels['onlyworkinghours'] = 'Buscar disponibilidad dentro de mi horario laboral';
+$labels['reqallattendees'] = 'Requerido/todos los participantes';
+$labels['prevslot'] = 'Espacio anterior';
+$labels['nextslot'] = 'Espacio siguiente';
+$labels['suggestedslot'] = 'Espacio sugerido';
+$labels['noslotfound'] = 'Imposible encontrar un espacio libre';
+$labels['invitationsubject'] = 'Ha sido invitado a "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees\n\nEncontrará adjunto un archivo iCalendar con todos los detalles del evento, el cual puede importar a su aplicación de calendario.";
+$labels['invitationattendlinks'] = "En caso que su cliente de correo electrónico no soporte peticiones iTip puede usar el siguiente link para aceptar o declinar esta invitación:\n\$url";
+$labels['eventupdatesubject'] = '"$title" ha sido actualizado';
+$labels['eventupdatesubjectempty'] = 'Un evento que le interesa ha sido actualizado';
+$labels['eventupdatemailbody'] = "*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees\n\nEncontrará adjunto un archivo iCalendar con todos los detalles del evento, el cual puede importar a su aplicación de calendario.";
+$labels['eventcancelsubject'] = '"$title" ha sido cancelado';
+$labels['eventcancelmailbody'] = "*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees\n\nEl evento ha sido cancelado por \$organizer.\n\nEncontrará adjunto un archivo iCalendar con todos los detalles actualizados del evento.";
+$labels['itipobjectnotfound'] = 'El evento referido por este mensaje no fue encontrado en su calendario.';
+$labels['itipmailbodyaccepted'] = "\$sender ha aceptado la invitación al siguiente evento:\n\n*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender ha tentativamente aceptado la invitación al siguiente evento:\n\n*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ha rechazado la invitación al siguiente evento:\n\n*\$title*\n\nCuándo: \$date\n\nInvitados: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender ha rechazado tu participación en el siguiente evento:\n\n*\$title*\n\nCuándo:\$date";
+$labels['itipmailbodydelegated'] = "\$sender ha delegado la invitación en el siguiente evento:\n\n*\$title*\n\nCuándo: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender ha delegado la participación en el siguiente evento a usted:\n\n*\$title*\n\nCuándo: \$date";
+$labels['itipdeclineevent'] = '¿Quiere rechazar la invitación a este evento?';
+$labels['declinedeleteconfirm'] = '¿Quiere también eliminar este evento rechazado de su calendario?';
+$labels['itipcomment'] = 'Comentario de la invitación/notificación';
+$labels['itipcommenttitle'] = 'Este comentario será adjuntado al mensaje de invitación/notificación enviado a los participantes';
+$labels['notanattendee'] = 'No esta incluído en la lista de invitados a este evento';
+$labels['eventcancelled'] = 'El evento ha sido cancelado';
+$labels['saveincalendar'] = 'guardar en';
+$labels['updatemycopy'] = 'Actualizar mi calendario';
+$labels['savetocalendar'] = 'Guardar en el calendario';
+$labels['openpreview'] = 'Comprobar Calendario';
+$labels['noearlierevents'] = 'No hay eventos anteriores';
+$labels['nolaterevents'] = 'No hay eventos posteriores';
+$labels['resource'] = 'Recurso';
+$labels['addresource'] = 'Agendar recurso';
+$labels['findresources'] = 'Encontrar recursos';
+$labels['resourcedetails'] = 'Detalles';
+$labels['resourceavailability'] = 'Disponibilidad';
+$labels['resourceowner'] = 'Propietario';
+$labels['resourceadded'] = 'El recurso fue agregado a su evento';
+$labels['tabsummary'] = 'Sumario';
+$labels['tabrecurrence'] = 'Recurrencia';
+$labels['tabattendees'] = 'Participantes';
+$labels['tabresources'] = 'Recursos';
+$labels['tabattachments'] = 'Adjuntos';
+$labels['tabsharing'] = 'Compartir';
+$labels['deleteobjectconfirm'] = 'Confirme que desea eliminar este evento';
+$labels['deleteventconfirm'] = 'Confirme que desea eliminar este evento';
+$labels['deletecalendarconfirm'] = 'Confirme que desea eliminar este calendario con todos sus eventos';
+$labels['deletecalendarconfirmrecursive'] = 'Confirme que desea eliminar este calendario con todos sus eventos y sub-calendarios';
+$labels['savingdata'] = 'Guardando...';
+$labels['errorsaving'] = 'Falló guardando los cambios.';
+$labels['operationfailed'] = 'La operación falló.';
+$labels['invalideventdates'] = 'Ingresó fechas erroneas. Por favor compruebe los datos.';
+$labels['invalidcalendarproperties'] = 'Propiedades del calendario erroneas. Por favor ingrese un nombre válido.';
+$labels['searchnoresults'] = 'No hay eventos en los calendarios seleccionados.';
+$labels['successremoval'] = 'El evento ha sido eliminado exitosamente.';
+$labels['successrestore'] = 'El evento ha sido recuperado exitosamente.';
+$labels['errornotifying'] = 'Fallo al enviar las notificaciones del evento a los participantes';
+$labels['errorimportingevent'] = 'Fallo al importar el evento';
+$labels['importwarningexists'] = 'Una copia de este evento ya existe en su calendario.';
+$labels['newerversionexists'] = 'Ya existe una versión actualizada de este evento. Cancelado.';
+$labels['nowritecalendarfound'] = 'No hay calendarios para guardar el evento.';
+$labels['importedsuccessfully'] = 'El evento fue guardado en \'$calendar\' exitosamente';
+$labels['updatedsuccessfully'] = 'El evento fue actualizado exitosamente en \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Se actualizaron los estados de los participantes exitosamente';
+$labels['itipsendsuccess'] = 'Invitaciones enviadas a los participantes.';
+$labels['itipresponseerror'] = 'Fallo enviando la respuesta a la invitación de este evento';
+$labels['itipinvalidrequest'] = 'Esta invitación no es válida';
+$labels['sentresponseto'] = 'Se envió la respuesta a la invitación $mailto exitosamente';
+$labels['localchangeswarning'] = 'Se realizarán cambios que sólo serán reflejadas en su calendario y no serán enviadas al organizador del evento';
+$labels['importsuccess'] = 'Importados $nr eventos exitosamente';
+$labels['importnone'] = 'No se importaron eventos';
+$labels['importerror'] = 'Fallo al importar';
+$labels['aclnorights'] = 'No tiene permisos de administrador en este calendario.';
+$labels['changeeventconfirm'] = 'Cambiar evento';
+$labels['removeeventconfirm'] = 'Eliminar evento';
+$labels['changerecurringeventwarning'] = 'Este es un evento recurrente. ¿Desea editar solo el evento actual, este y las ocurrencias futuras, todas las ocurrencias o guardarlo como un evento nuevo?';
+$labels['removerecurringeventwarning'] = 'Este es un evento recurrente. ¿Desea eliminar solo el evento actual, este y las ocurrencias futuras o todas las ocurrencias del evento?';
+$labels['removerecurringallonly'] = 'Este es un evento recurrente. Como participante, usted solamente puede eliminar el evento completo con todas las ocurrencias.';
+$labels['currentevent'] = 'Actual';
+$labels['futurevents'] = 'Futuro';
+$labels['allevents'] = 'Todos';
+$labels['saveasnew'] = 'Guardar como nuevo';
+$labels['birthdays'] = 'Cumpleaños';
+$labels['birthdayscalendar'] = 'Calendario de cumpleaños';
+$labels['displaybirthdayscalendar'] = 'Mostrar calendario de cumpleaños';
+$labels['birthdayscalendarsources'] = 'De estas libretas de direcciones';
+$labels['birthdayeventtitle'] = 'Cumpleaños de $name';
+$labels['birthdayage'] = 'Edad $age';
+$labels['objectchangelog'] = 'Cambiar Historial';
+$labels['objectdiff'] = 'Cambios de $rev1 a $rev2';
+$labels['objectnotfound'] = 'Fallo al cargar datos del evento';
+$labels['objectchangelognotavailable'] = 'Cambiar historial no esta disponible para este evento';
+$labels['objectdiffnotavailable'] = 'No es posible comparar las revisiones seleccionadas';
+$labels['revisionrestoreconfirm'] = 'Confirme que quiere recuperar la revisión $rev de este evento. Esta acción reemplazará el evento actual con la versión anterior.';
+$labels['objectrestoresuccess'] = 'Revisión $rev recuperada exitosamente';
+$labels['objectrestoreerror'] = 'Fallo al restaurar el evento anterior';
+$labels['arialabelminical'] = 'Selección de fecha del calendario';
+$labels['arialabelcalendarview'] = 'Vista del calendario';
+$labels['arialabelsearchform'] = 'Formulario de búsqueda de evento';
+$labels['arialabelquicksearchbox'] = 'Entrada de búsqueda de evento';
+$labels['arialabelcalsearchform'] = 'Formulario de búsqueda de calendario';
+$labels['calendaractions'] = 'Acciones del calendario';
+$labels['arialabeleventattendees'] = 'Lista de participantes del evento';
+$labels['arialabeleventresources'] = 'Lista de recursos del evento';
+$labels['arialabelresourcesearchform'] = 'Formulario de búsqueda de recursos';
+$labels['arialabelresourceselection'] = 'Recursos disponibles';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/es_ES.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Vista predeterminada';
+$labels['time_format'] = 'Formato de tiempo';
+$labels['timeslots'] = 'Los intervalos de tiempo por hora';
+$labels['first_day'] = 'Primer día de la semana';
+$labels['first_hour'] = 'Primera hora para mostrar';
+$labels['workinghours'] = 'Horas laborales';
+$labels['add_category'] = 'Añadir categoría';
+$labels['remove_category'] = 'Borrar la categoría';
+$labels['defaultcalendar'] = 'Crear nuevos eventos en';
+$labels['eventcoloring'] = 'Evento para colorear';
+$labels['coloringmode0'] = 'De acuerdo con el calendario';
+$labels['coloringmode1'] = 'De acuerdo con la categoría';
+$labels['coloringmode2'] = 'Calendario para el esquema, la categoría de contenido';
+$labels['coloringmode3'] = 'Calendario para el esquema, la categoría de contenido';
+$labels['afternothing'] = 'No hacer nada';
+$labels['aftertrash'] = 'Mover a la papelera';
+$labels['afterdelete'] = 'Eliminar el mensaje';
+$labels['afterflagdeleted'] = 'Marcar como eliminado';
+$labels['aftermoveto'] = 'Mover a...';
+$labels['itipoptions'] = 'Invitaciones para el evento';
+$labels['afteraction'] = 'Se procesa después de un mensaje de invitación o actualización';
+$labels['calendar'] = 'Calendario';
+$labels['calendars'] = 'Calendarios';
+$labels['category'] = 'Categoría';
+$labels['categories'] = 'Categorías';
+$labels['createcalendar'] = 'Crear nuevo calendario';
+$labels['editcalendar'] = 'Editar propiedades del calendario';
+$labels['name'] = 'Nombre';
+$labels['color'] = 'Color';
+$labels['day'] = 'Día';
+$labels['week'] = 'Semana';
+$labels['month'] = 'Mes';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nuevo';
+$labels['new_event'] = 'Nuevo evento';
+$labels['edit_event'] = 'Editar evento';
+$labels['edit'] = 'Editar';
+$labels['save'] = 'Guardar';
+$labels['removelist'] = 'Borrar de la lista';
+$labels['cancel'] = 'Cancelar';
+$labels['select'] = 'Seleccionar';
+$labels['print'] = 'Imprimir';
+$labels['printtitle'] = 'Imprimir calendarios';
+$labels['title'] = 'Resumen';
+$labels['description'] = 'Descripción';
+$labels['all-day'] = 'Todo el día';
+$labels['export'] = 'Exportar';
+$labels['exporttitle'] = 'Exportar a iCalendar';
+$labels['exportrange'] = 'Eventos de';
+$labels['exportattachments'] = 'con los adjuntos';
+$labels['customdate'] = 'Fecha personalizada';
+$labels['location'] = 'Ubicación';
+$labels['url'] = 'URL';
+$labels['date'] = 'Fecha';
+$labels['start'] = 'Inicio';
+$labels['starttime'] = 'Tiempo de inicio';
+$labels['end'] = 'Fin';
+$labels['endtime'] = 'Hora de finalización';
+$labels['repeat'] = 'Repetir';
+$labels['selectdate'] = 'Seleccione la fecha';
+$labels['freebusy'] = 'Mostrarme como';
+$labels['free'] = 'Disponible';
+$labels['busy'] = 'Ocupado';
+$labels['outofoffice'] = 'Fuera de la oficina';
+$labels['tentative'] = 'Provisional';
+$labels['mystatus'] = 'Mi estado';
+$labels['status'] = 'Estado';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancelado';
+$labels['priority'] = 'Prioridad';
+$labels['sensitivity'] = 'Privacidad';
+$labels['public'] = 'Pública';
+$labels['private'] = 'Privada';
+$labels['confidential'] = 'Confidencial';
+$labels['links'] = 'Referencia';
+$labels['alarms'] = 'Recordatorio';
+$labels['comment'] = 'Comentario';
+$labels['created'] = 'Creado';
+$labels['changed'] = 'Última modificación';
+$labels['unknown'] = 'Desconocido';
+$labels['eventoptions'] = 'Opciones';
+$labels['generated'] = 'Creado a las';
+$labels['eventhistory'] = 'Historial';
+$labels['removelink'] = 'Eliminar la referencia de correo electrónico';
+$labels['printdescriptions'] = 'Imprimir descripción ';
+$labels['parentcalendar'] = 'Inserte en el interior';
+$labels['searchearlierdates'] = '« Búsqueda de eventos anteriores';
+$labels['searchlaterdates'] = 'Búsqueda de eventos posteriores  »';
+$labels['andnmore'] = '$nr más...';
+$labels['togglerole'] = 'Haga clic para cambiar el rol';
+$labels['createfrommail'] = 'Guardar como evento';
+$labels['importevents'] = 'Importar eventos';
+$labels['importrange'] = 'Eventos de';
+$labels['onemonthback'] = '1 mes atrás';
+$labels['nmonthsback'] = '$nr meses atrás';
+$labels['showurl'] = 'Mostrar URL del calendario';
+$labels['showurldescription'] = 'Usar la siguiente dirección para acceder (sólo lectura) en su calendario desde otras aplicaciones. Puede copiar y pegar esto en cualquier software de calendario que admita el formato iCal.';
+$labels['caldavurldescription'] = 'Copie esta dirección en un <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> cliente (Evolution o Mozilla Thunderbird) para sincronizar esta tarea con su ordenador o celular.';
+$labels['findcalendars'] = 'Buscar calendarios ...';
+$labels['searchterms'] = 'Buscar términos';
+$labels['calsearchresults'] = 'Calendarios disponibles';
+$labels['calendarsubscribe'] = 'Lista Permanente';
+$labels['nocalendarsfound'] = 'No se han encontrado calendarios';
+$labels['nrcalendarsfound'] = '$nr calendarios encontrados';
+$labels['quickview'] = 'Ver solo este calendario';
+$labels['invitationspending'] = 'Invitaciones pendientes';
+$labels['invitationsdeclined'] = 'Invitaciones rechazada';
+$labels['changepartstat'] = 'Cambiar el estado del participante';
+$labels['rsvpcomment'] = 'Texto de la invitación';
+$labels['listrange'] = 'Rango de visualización:';
+$labels['listsections'] = 'Dividir en:';
+$labels['smartsections'] = 'Secciones inteligentes';
+$labels['until'] = 'hasta';
+$labels['today'] = 'Hoy';
+$labels['tomorrow'] = 'Mañana';
+$labels['thisweek'] = 'Esta semana';
+$labels['nextweek'] = 'Próxima semana';
+$labels['prevweek'] = 'Semana pasada';
+$labels['thismonth'] = 'Este mes';
+$labels['nextmonth'] = 'Próximo mes';
+$labels['weekofyear'] = 'Semana';
+$labels['pastevents'] = 'Pasado';
+$labels['futureevents'] = 'Futuro';
+$labels['showalarms'] = 'Mostrar recordatorios';
+$labels['defaultalarmtype'] = 'Configuración predeterminada del recordatorio';
+$labels['defaultalarmoffset'] = 'Tiempo de aviso predeterminado';
+$labels['attendee'] = 'Participante';
+$labels['role'] = 'Rol';
+$labels['availability'] = 'Disponible';
+$labels['confirmstate'] = 'Estado';
+$labels['addattendee'] = 'Añada participante';
+$labels['roleorganizer'] = 'Organizador';
+$labels['rolerequired'] = 'Requerido';
+$labels['roleoptional'] = 'Opcional';
+$labels['rolechair'] = 'Silla';
+$labels['rolenonparticipant'] = 'Ausente';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Grupo';
+$labels['cutyperesource'] = 'Recurso';
+$labels['cutyperoom'] = 'Habitación';
+$labels['availfree'] = 'Disponible';
+$labels['availbusy'] = 'Ocupado';
+$labels['availunknown'] = 'Desconocido';
+$labels['availtentative'] = 'Provisional';
+$labels['availoutofoffice'] = 'Fuera de la oficina';
+$labels['delegatedto'] = 'Delegar a: ';
+$labels['delegatedfrom'] = 'Delegado de:';
+$labels['scheduletime'] = 'Buscar disponibilidad';
+$labels['sendinvitations'] = 'Enviar invitaciones';
+$labels['sendnotifications'] = 'Notificar a los participantes acerca de las modificaciones';
+$labels['sendcancellation'] = 'Notificar a los participantes sobre la cancelación de eventos';
+$labels['onlyworkinghours'] = 'Encuentra disponibilidad dentro de mis horas de trabajo';
+$labels['reqallattendees'] = 'Requeridos/todos los participantes';
+$labels['prevslot'] = 'Ranura anterior';
+$labels['nextslot'] = 'Siguiente ranura';
+$labels['suggestedslot'] = 'Ranura sugerida';
+$labels['noslotfound'] = 'Incapaz de encontrar un intervalo de tiempo libre';
+$labels['invitationsubject'] = 'Usted  sido invitado a "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nSe adjunta un archivo iCalendar con todos los detalles del evento que se puede importar a la aplicación de calendario.";
+$labels['invitationattendlinks'] = "En caso de que su cliente de correo electrónico no admite solicitudes iTIP que puede utilizar el siguiente enlace para aceptar o rechazar esta invitación:\n\$url";
+$labels['eventupdatesubject'] = '"$title" Ha sido actualizado';
+$labels['eventupdatesubjectempty'] = 'Un evento que le concierne ha sido actualizado';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nSe adjunta un archivo iCalendar con los detalles del evento actualizados que se puede importar a la aplicación de calendario.";
+$labels['eventcancelsubject'] = 'Se ha cancelado "$título"';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nEl evento ha sido cancelado por\$organizer.\n\nSe adjunta un archivo iCalendar con los detalles del evento actualizados.";
+$labels['itipobjectnotfound'] = 'El evento referido por este mensaje no se encontró en su calendario.';
+$labels['itipmailbodyaccepted'] = "\$sender ha aceptado la invitación al evento siguiente:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender ha aceptado provisionalmente la invitación al evento siguiente:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ha declinado la invitación al evento siguiente:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender ha rechazado su participación en el evento siguiente:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender ha delegado la participación en el evento siguiente:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender ha delegado la participación en el siguiente evento a usted:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = '¿Quieres rechazar la invitación a este evento?';
+$labels['declinedeleteconfirm'] = '¿Usted también desea eliminar este declinado evento del calendario?';
+$labels['itipcomment'] = 'Añadir comentarios a la Invitación/notificación';
+$labels['itipcommenttitle'] = 'Este comentario se adjunta al mensaje de invitación/notificación enviada a los participantes';
+$labels['notanattendee'] = 'Usted no está en la lista como un asistente de este evento';
+$labels['eventcancelled'] = 'El evento ha sido cancelado';
+$labels['saveincalendar'] = 'guardar en';
+$labels['updatemycopy'] = 'actualizar en mi calendario';
+$labels['savetocalendar'] = 'guardar en calendario';
+$labels['openpreview'] = 'Revisar en calendario';
+$labels['noearlierevents'] = 'No hay eventos anteriores';
+$labels['nolaterevents'] = 'No hay eventos posteriores';
+$labels['resource'] = 'Recurso';
+$labels['addresource'] = 'Reservar recursos';
+$labels['findresources'] = 'Encontrar recursos';
+$labels['resourcedetails'] = 'Detalles';
+$labels['resourceavailability'] = 'Disponibilidad';
+$labels['resourceowner'] = 'Propietario';
+$labels['resourceadded'] = 'Se ha añadido un recurso a su evento';
+$labels['tabsummary'] = 'Sumario';
+$labels['tabrecurrence'] = 'Recurrencia ';
+$labels['tabattendees'] = 'Participantes';
+$labels['tabresources'] = 'Recursos';
+$labels['tabattachments'] = 'Adjuntos';
+$labels['tabsharing'] = 'Compartir';
+$labels['deleteobjectconfirm'] = '¿Esta seguro de eliminar  este evento?';
+$labels['deleteventconfirm'] = '¿Esta seguro de eliminar este evento?';
+$labels['deletecalendarconfirm'] = '¿Esta seguro de eliminar este calendario con todos sus eventos?';
+$labels['deletecalendarconfirmrecursive'] = '¿Esta seguro de eliminar este calendario con todos sus eventos y subcalendarios?';
+$labels['savingdata'] = 'Guardando datos...';
+$labels['errorsaving'] = 'Error al guardar cambios';
+$labels['operationfailed'] = 'Error en la operación solicitada';
+$labels['invalideventdates'] = 'Se han introducido fechas erróneas; por favor, revise su entrada';
+$labels['invalidcalendarproperties'] = 'Propiedades de portátiles erróneas; establezca un nombre válido.';
+$labels['searchnoresults'] = 'No se han encontrado eventos en los calendarios seleccionados.';
+$labels['successremoval'] = 'El evento se ha eliminado correctamente.';
+$labels['successrestore'] = 'El evento se ha restaurado correctamente.';
+$labels['errornotifying'] = 'Error al enviar notificaciones a los participantes del evento';
+$labels['errorimportingevent'] = 'Error al importar el evento';
+$labels['importwarningexists'] = 'Ya existe una copia de este evento en su calendario.';
+$labels['newerversionexists'] = 'Ya existe una versión más nueva de este evento. Abortado.';
+$labels['nowritecalendarfound'] = 'No se ha encontrado ningún calendario para guardar el evento.';
+$labels['importedsuccessfully'] = 'El evento se agregó correctamente a \'$calendar\'';
+$labels['updatedsuccessfully'] = 'El evento se actualizó correctamente en \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Se ha actualizado correctamente el estado del participante';
+$labels['itipsendsuccess'] = 'Invitación enviada a los participantes.';
+$labels['itipresponseerror'] = 'Error al enviar la respuesta a esta invitación de evento';
+$labels['itipinvalidrequest'] = 'Esta invitación ya no es válida';
+$labels['sentresponseto'] = 'Respuesta de invitación con éxito enviada a $mailto';
+$labels['localchangeswarning'] = 'Está a punto de realizar cambios que sólo se reflejarán en su calendario y no se enviarán al organizador del evento.';
+$labels['importsuccess'] = 'Se han importado $nr eventos correctamente';
+$labels['importnone'] = 'No se han encontrado eventos para importar';
+$labels['importerror'] = 'Se ha producido un error durante la importación';
+$labels['aclnorights'] = 'No tiene derechos de administrador en este calendario.';
+$labels['changeeventconfirm'] = 'Cambiar evento';
+$labels['removeeventconfirm'] = 'Eliminar evento';
+$labels['changerecurringeventwarning'] = 'Este es un evento recurrente. ¿Desea editar solo el evento actual, este y todos los futuros casos, todos los casos o guardarlo como un nuevo evento?';
+$labels['removerecurringeventwarning'] = 'Este es un evento recurrente. ¿Desea eliminar solo el evento actual, este y todos los futuros casos o todos los casos de este evento?';
+$labels['removerecurringallonly'] = 'Este es un evento recurrente. Como participante, sólo puede eliminar el evento al completo con todos los casos..';
+$labels['currentevent'] = 'Actual';
+$labels['futurevents'] = 'Futuro';
+$labels['allevents'] = 'Todo';
+$labels['saveasnew'] = 'Guardar como nuevo';
+$labels['birthdays'] = 'Cumpleaños';
+$labels['birthdayscalendar'] = 'Calendario de cumpleaños';
+$labels['displaybirthdayscalendar'] = 'Mostrar calendarios de cumpleaños';
+$labels['birthdayscalendarsources'] = 'Desde esta libreta de direcciones';
+$labels['birthdayeventtitle'] = 'Cumpleaños de $nombre';
+$labels['birthdayage'] = 'Edad $edad';
+$labels['objectchangelog'] = 'Cambiar historial';
+$labels['objectdiff'] = 'Cambiar de $rev1 a $rev2';
+$labels['objectnotfound'] = 'Error al cargar los datos del evento';
+$labels['objectchangelognotavailable'] = 'El historial de cambios no está disponible para este evento';
+$labels['objectdiffnotavailable'] = 'No hay comparación posible que las revisiones seleccionadas';
+$labels['revisionrestoreconfirm'] = '¿Realmente desea restaurar la revisión $rev de este evento? Esto reemplazará el evento actual con la antigua versión.';
+$labels['objectrestoresuccess'] = 'Revisión $rev restaurado correctamente';
+$labels['objectrestoreerror'] = 'No se pudo restaurar la revisión anterior';
+$labels['arialabelminical'] = 'Seleccionar una fecha en el calendario';
+$labels['arialabelcalendarview'] = 'Vista del calendario';
+$labels['arialabelsearchform'] = 'Formulario de búsqueda de eventos';
+$labels['arialabelquicksearchbox'] = 'Entrada en búsqueda de eventos';
+$labels['arialabelcalsearchform'] = 'Formulario de búsqueda de calendarios';
+$labels['calendaractions'] = 'acciones del calendario';
+$labels['arialabeleventattendees'] = 'Lista de participantes del evento';
+$labels['arialabeleventresources'] = 'Lista de recursos del evento';
+$labels['arialabelresourcesearchform'] = 'Formulario de búsqueda de recursos';
+$labels['arialabelresourceselection'] = 'Recursos disponibles';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/et_EE.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,207 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Nimi';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Salvesta';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Absent';
+$labels['tentative'] = 'Tentative';
+$labels['status'] = 'Status';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Kommentaar';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['generated'] = 'generated at';
+$labels['removelink'] = 'Remove email reference';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['searchterms'] = 'Search terms';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Kohustuslik';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Absent';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['resource'] = 'Resource';
+$labels['resourcedetails'] = 'Details';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['operation'] = 'Toiming';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/fi_FI.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,266 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Oletusnäkymä';
+$labels['time_format'] = 'Aikamuoto';
+$labels['timeslots'] = 'Ajankohdat per tunti';
+$labels['first_day'] = 'Viikon ensimmäinen päivä';
+$labels['first_hour'] = 'Ensimmäinen näytettävä tunti';
+$labels['workinghours'] = 'Työaika';
+$labels['add_category'] = 'Lisää luokka';
+$labels['remove_category'] = 'Poista luokka';
+$labels['defaultcalendar'] = 'Luo uudet tapahtumat kohteeseen';
+$labels['eventcoloring'] = 'Tapahtuman väritys';
+$labels['coloringmode0'] = 'Kalenterin mukaan';
+$labels['coloringmode1'] = 'Luokan mukaan';
+$labels['afternothing'] = 'Älä tee mitään';
+$labels['aftertrash'] = 'Siirrä roskakoriin';
+$labels['afterdelete'] = 'Poista viesti';
+$labels['afterflagdeleted'] = 'Merkitse poistettavaksi';
+$labels['aftermoveto'] = 'Siirrä...';
+$labels['itipoptions'] = 'Tapahtuman kutsut';
+$labels['afteraction'] = 'Kun kutsu tai päivitysviesti on käsitelty';
+$labels['calendar'] = 'Kalenteri';
+$labels['calendars'] = 'Kalenterit';
+$labels['category'] = 'Luokka';
+$labels['categories'] = 'Luokat';
+$labels['createcalendar'] = 'Luo uusi kalenteri';
+$labels['editcalendar'] = 'Muokkaa kalenterin ominaisuuksia';
+$labels['name'] = 'Nimi';
+$labels['color'] = 'Väri';
+$labels['day'] = 'Päivä';
+$labels['week'] = 'Viikko';
+$labels['month'] = 'Kuukausi';
+$labels['agenda'] = 'Asialista';
+$labels['new'] = 'Uusi';
+$labels['new_event'] = 'Uusi tapahtuma';
+$labels['edit_event'] = 'Muokkaa tapahtumaa';
+$labels['edit'] = 'Muokkaa';
+$labels['save'] = 'Tallenna';
+$labels['removelist'] = 'Poista listasta';
+$labels['cancel'] = 'Peru';
+$labels['select'] = 'Valitse';
+$labels['print'] = 'Tulosta';
+$labels['printtitle'] = 'Tulosta kalenterit';
+$labels['title'] = 'Yhteenveto';
+$labels['description'] = 'Kuvaus';
+$labels['all-day'] = 'koko päivä';
+$labels['export'] = 'Vie';
+$labels['exporttitle'] = 'Vie iCalendar-muotoon';
+$labels['exportrange'] = 'Tapahtumat';
+$labels['exportattachments'] = 'Liitteiden kanssa';
+$labels['customdate'] = 'Kustomoitu aika';
+$labels['location'] = 'Sijainti';
+$labels['url'] = 'Osoite';
+$labels['date'] = 'Päiväys';
+$labels['start'] = 'Alkaa';
+$labels['starttime'] = 'Aloitusaika';
+$labels['end'] = 'Päättyy';
+$labels['endtime'] = 'Päättymisaika';
+$labels['repeat'] = 'Toista';
+$labels['selectdate'] = 'Valitse päiväys';
+$labels['freebusy'] = 'Aseta tilakseni';
+$labels['free'] = 'Vapaa';
+$labels['busy'] = 'Varattu';
+$labels['outofoffice'] = 'Ei toimistolla';
+$labels['tentative'] = 'Alustava';
+$labels['mystatus'] = 'Tilani';
+$labels['status'] = 'Tila';
+$labels['status-confirmed'] = 'Vahvistettu';
+$labels['status-cancelled'] = 'Peruttu';
+$labels['priority'] = 'Tärkeys';
+$labels['sensitivity'] = 'Yksityisyys';
+$labels['public'] = 'julkinen';
+$labels['private'] = 'yksityinen';
+$labels['confidential'] = 'luottamuksellinen';
+$labels['links'] = 'Viittaus';
+$labels['alarms'] = 'Muistutus';
+$labels['comment'] = 'Kommentti';
+$labels['created'] = 'Luotu';
+$labels['changed'] = 'Viimeksi muokattu';
+$labels['unknown'] = 'Tuntematon';
+$labels['eventoptions'] = 'Valinnat';
+$labels['generated'] = 'generoitu';
+$labels['eventhistory'] = 'Historia';
+$labels['removelink'] = 'Poista sähköpostiviittaus';
+$labels['printdescriptions'] = 'Tulosta kuvaukset';
+$labels['parentcalendar'] = 'Aseta sisään';
+$labels['searchearlierdates'] = '« Etsi aiempia tapahtumia';
+$labels['searchlaterdates'] = 'Etsi myöhempiä tapahtumia »';
+$labels['andnmore'] = '$nr lisää...';
+$labels['togglerole'] = 'Klikkaa vaihtaaksesi rooli';
+$labels['createfrommail'] = 'Tallenna tapahtumana';
+$labels['importevents'] = 'Tuo tapahtumat';
+$labels['importrange'] = 'Tapahtumat';
+$labels['onemonthback'] = '1 kuukauden ajalta';
+$labels['nmonthsback'] = '$nr kuukauden ajalta';
+$labels['showurl'] = 'Näytä kalenterin osoite';
+$labels['showurldescription'] = 'Käytä seuraavia osoitteita avataksesi kalenterisi pelkässä lukumuodossa muissa sovelluksissa. Voit kopioida ja liittää osoitteen mihin tahansa iCal-muotoa tukevaan kalenterisovellukseen.';
+$labels['caldavurldescription'] = 'Kopioi tämä osoite <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a-asiakassovellukseen (esim. Evolution tai Mozilla Thunderbird) synkronoidaksesi tämän kalenterin tietokoneesi tai mobiililaitteesi kanssa.';
+$labels['findcalendars'] = 'Etsi kalentereita...';
+$labels['searchterms'] = 'Hakuehdot';
+$labels['calsearchresults'] = 'Saatavilla olevat kalenterit';
+$labels['calendarsubscribe'] = 'Luetteloi pysyvästi';
+$labels['nocalendarsfound'] = 'Kalentereita ei löytynyt';
+$labels['nrcalendarsfound'] = '$nr kalenteria löytynyt';
+$labels['quickview'] = 'Näytä vain tämä kalenteri';
+$labels['invitationspending'] = 'Odottavat kutsut';
+$labels['invitationsdeclined'] = 'Torjutut kutsut';
+$labels['changepartstat'] = 'Muuta osallistujan tilaa';
+$labels['rsvpcomment'] = 'Kutsuteksti';
+$labels['listrange'] = 'Näytettävä aikaväli';
+$labels['listsections'] = 'Jaa osiin:';
+$labels['smartsections'] = 'Älykkäät osiot';
+$labels['until'] = 'kunnes';
+$labels['today'] = 'Tänään';
+$labels['tomorrow'] = 'Huomenna';
+$labels['thisweek'] = 'Tällä viikolla';
+$labels['nextweek'] = 'Ensi viikolla';
+$labels['prevweek'] = 'Edellinen viikko';
+$labels['thismonth'] = 'Tässä kuussa';
+$labels['nextmonth'] = 'Ensi kuussa';
+$labels['weekofyear'] = 'Viikko';
+$labels['pastevents'] = 'Menneet';
+$labels['futureevents'] = 'Tulevat';
+$labels['showalarms'] = 'Näytä muistutukset';
+$labels['defaultalarmtype'] = 'Muistutuksen oletusasetus';
+$labels['defaultalarmoffset'] = 'Muistutuksen oletusaika';
+$labels['attendee'] = 'Osallistujat';
+$labels['role'] = 'Rooli';
+$labels['availability'] = 'Saatavilla';
+$labels['confirmstate'] = 'Tila';
+$labels['addattendee'] = 'Lisää osallistuja';
+$labels['roleorganizer'] = 'Järjestäjä';
+$labels['rolerequired'] = 'Vaadittu';
+$labels['roleoptional'] = 'Valinnainen';
+$labels['rolechair'] = 'Kutsuja';
+$labels['rolenonparticipant'] = 'Poissaoleva';
+$labels['cutypeindividual'] = 'Yksityishenkilö';
+$labels['cutypegroup'] = 'Ryhmä';
+$labels['cutyperesource'] = 'Resurssi';
+$labels['cutyperoom'] = 'Huone';
+$labels['availfree'] = 'Vapaa';
+$labels['availbusy'] = 'Varattu';
+$labels['availunknown'] = 'Tuntematon';
+$labels['availtentative'] = 'Alustava';
+$labels['availoutofoffice'] = 'Ei toimistolla';
+$labels['delegatedto'] = 'Delegoitu henkilölle:';
+$labels['delegatedfrom'] = 'Delegoitus henkilöltä:';
+$labels['scheduletime'] = 'Etsi saatavuus';
+$labels['sendinvitations'] = 'Lähetä kutsut';
+$labels['sendnotifications'] = 'Ilmoita osallistujille muutoksista';
+$labels['sendcancellation'] = 'Ilmoita osallistujille tapahtuman perumisesta';
+$labels['onlyworkinghours'] = 'Etsi saatavuutta työajan sisältä';
+$labels['reqallattendees'] = 'Vaadittu/kaikki osallistujat';
+$labels['prevslot'] = 'Edellinen ajankohta';
+$labels['nextslot'] = 'Seuraava ajankohta';
+$labels['suggestedslot'] = 'Ehdotettu ajankohta';
+$labels['noslotfound'] = 'Vapaata ajankohtaa ei löytynyt';
+$labels['invitationsubject'] = 'Sinut on kutsuttu tapahtumaan "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees\n\nOhessa iCalendar -tiedosto mistä löytyvät kaikki tapahtuman yksityistiedot. Voit tuoda tämän tiedoston kalenteriohjelmaasi.";
+$labels['invitationattendlinks'] = "Mikäli sähköpostiohjelmasi ei tue iTip pyyntöjä, voit aina käyttää ao. osoitetta kutsun hyväksymiseen / hylkäämiseen:\n\$url";
+$labels['eventupdatesubject'] = '"$title" on päivitetty';
+$labels['eventupdatesubjectempty'] = 'Sinua koskeva tapahtuma on päivitetty';
+$labels['eventupdatemailbody'] = "*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees\n\nOhessa iCalendar -tiedosto mistä löytyvät kaikki päivitetyn tapahtuman yksityistiedot. Voit tuoda tämän tiedoston kalenteriohjelmaasi.";
+$labels['eventcancelsubject'] = '"$title" on peruttu';
+$labels['eventcancelmailbody'] = "*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees\n\nTämä tapahtuma on peruttu  \$organizer toimesta.\n\nLöydät liitteenä iCalendar -tiedoston tapahtuman päivitetyin tiedoin.";
+$labels['itipobjectnotfound'] = 'Viestissä mainittua tapahtumaa ei löydy kalenteristasi.';
+$labels['itipmailbodyaccepted'] = "\$sender on hyväksynyt kutsun seuraavaan tapahtumaan:\n\n*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender on alustavasti hyväksynyt kutsun seuraavaan tapahtumaan:\n\n*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender on hylännyt kutsun seuraavaan tapahtumaan:\n\n*\$title*\n\nMilloin: \$date\n\nKutsutut: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender on hylännyt osallistumisesi tapahtumaan:\n\n*\$title*\n\nMilloin: \$date";
+$labels['itipmailbodydelegated'] = "\$sender on delegoinut osallistumisensa seuraavaan tapahtumaan:\n\n*\$title*\n\nAjankohta: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender on delegoinut osallistumisensa sinulle seuraavaan tapahtumaan:\n\n*\$title*\n\nAjankohta: \$date";
+$labels['itipdeclineevent'] = 'Haluatko perua kutsun tähän tapahtumaan?';
+$labels['declinedeleteconfirm'] = 'Haluatko poistaa tämän hylätyn tapahtuman kalenteristasi?';
+$labels['itipcomment'] = 'Kutsun/herätteen kommentit';
+$labels['itipcommenttitle'] = 'Tämä kommentti liitetään osallistujille lähetettävään kutsuun/heräteviestiin';
+$labels['notanattendee'] = 'Sinua ei ole määritetty tapahtuman osanottajaksi';
+$labels['eventcancelled'] = 'Tapahtuma on peruttu';
+$labels['saveincalendar'] = 'tallennuskohde';
+$labels['updatemycopy'] = 'Päivitä kalenterini';
+$labels['savetocalendar'] = 'Tallenna kalenteriin';
+$labels['openpreview'] = 'Tarkista kalenteri';
+$labels['noearlierevents'] = 'Ei aiempia tapahtumia';
+$labels['nolaterevents'] = 'Ei myöhempiä tapahtumia';
+$labels['resource'] = 'Resurssi';
+$labels['addresource'] = 'Varaa resurssi';
+$labels['findresources'] = 'Etsi resursseja';
+$labels['resourcedetails'] = 'Tiedot';
+$labels['resourceavailability'] = 'Saatavuus';
+$labels['resourceowner'] = 'Omistaja';
+$labels['resourceadded'] = 'Resurssi on liitetty tapahtumaasi';
+$labels['tabsummary'] = 'Yhteenveto';
+$labels['tabrecurrence'] = 'Toistuminen';
+$labels['tabattendees'] = 'Osallistujat';
+$labels['tabresources'] = 'Resurssit';
+$labels['tabattachments'] = 'Liitteet';
+$labels['tabsharing'] = 'Jakaminen';
+$labels['deleteobjectconfirm'] = 'Haluatko varmasti poistaa tämän tapahtuman?';
+$labels['deleteventconfirm'] = 'Haluatko varmasti poistaa tämän tapahtuman?';
+$labels['deletecalendarconfirm'] = 'Haluatko varmasti poistaa tämän kalenterin ja kaikki sen tapahtumat?';
+$labels['deletecalendarconfirmrecursive'] = 'Haluatko varmasti poistaa tämän kalenterin ja kaikki sen tapahtumat sekä alikalenterit?';
+$labels['savingdata'] = 'Tallennetaan tietoja...';
+$labels['errorsaving'] = 'Muutosten tallentaminen epäonnistui.';
+$labels['operationfailed'] = 'Pyydetty toiminto epäonnistui.';
+$labels['invalideventdates'] = 'Annettu virheellisiä päivämääriä! Tarkista syöte.';
+$labels['invalidcalendarproperties'] = 'Kalenterin ominaisuudet ovat virheelliset. Aseta kelvollinen nimi.';
+$labels['searchnoresults'] = 'Valituista kalentereista ei löytynyt tapahtumia.';
+$labels['successremoval'] = 'Tapahtuma on poistettu onnistuneesti.';
+$labels['successrestore'] = 'Tapahtuma on palautettu onnistuneesti.';
+$labels['errornotifying'] = 'Herätteen lähetys osallistujille epäonnistui';
+$labels['errorimportingevent'] = 'Tapahtuman tuonti epäonnistui';
+$labels['importwarningexists'] = 'Tämän tapahtuman kopio löytyy jo kalenteristasi';
+$labels['newerversionexists'] = 'Uudempi versio tästä tapahtumasta on jo olemassa! Keskeytetty.';
+$labels['nowritecalendarfound'] = 'Tapahtuman tallentamiseksi ei löytynyt kalenteria';
+$labels['importedsuccessfully'] = 'Tapahtuma lisättiin onnistuneesti kalenteriin \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Tapahtuma on onnistuneesti päivitetty kalenterissa \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Osallistujan tila päivitetty onnistuneesti';
+$labels['itipsendsuccess'] = 'Kutsu lähetetty osallistujille.';
+$labels['itipresponseerror'] = 'Vastauksen lähettäminen tapahtumakutsuun epäonnistui';
+$labels['itipinvalidrequest'] = 'Kutsu ei ole enää kelvollinen';
+$labels['sentresponseto'] = 'Vastaus kutsuun lähetetty onnistuneesti sähköpostiin $mailto';
+$labels['localchangeswarning'] = 'Olet tekemässä muutoksia, jotka vaikuttavat ainoastaan omaan kalenteriisi. Muutoksia ei lähetetä tapahtuman järjestäjälle.';
+$labels['importsuccess'] = '$nr tapahtumaa tuotiin onnistuneesti';
+$labels['importnone'] = 'Tuotavaksi tarkoitettuja tapahtumia ei löytynyt';
+$labels['importerror'] = 'Tuotaessa tapahtui virhe';
+$labels['aclnorights'] = 'Sinulla ei ole ylläpitäjän oikeuksia tähän kalenteriin.';
+$labels['changeeventconfirm'] = 'Vaihda tapahtuma';
+$labels['removeeventconfirm'] = 'Poista tapahtuma';
+$labels['changerecurringeventwarning'] = 'Tämä on tuistuva tapahtuma. Haluatko muokata vain tätä ajankohtaa, tätä ja tulevia tapahtuman ajankohtia, kaikkia tapahtuman ajankohtia vai tallentaa kokonaan uutena tapahtumana? ';
+$labels['removerecurringeventwarning'] = 'Tämä on toistuva tapahtuma. Haluatko poistaa vain nykyisen tapahtuman, nykyisen tapahtuman ja tulevaisuuden tapahtumat vai kaikki tapahtumaan liittyvät merkinnät?';
+$labels['removerecurringallonly'] = 'Tämä on toistuva tapahtuma. Osallistujana voit poistaa vain koko tapahtuman kaikkine toistumiskertoineen.';
+$labels['currentevent'] = 'Nykyinen';
+$labels['futurevents'] = 'Tulevat';
+$labels['allevents'] = 'Kaikki';
+$labels['saveasnew'] = 'Tallenna uutena';
+$labels['birthdays'] = 'Syntymäpäivät';
+$labels['birthdayscalendar'] = 'Syntymäpäivät kalenteri';
+$labels['displaybirthdayscalendar'] = 'Näytä syntymäpäivät kalenterissa';
+$labels['birthdayscalendarsources'] = 'Näistä osoitekirjoista';
+$labels['birthdayeventtitle'] = 'Syntymäpäivä: $name';
+$labels['birthdayage'] = 'Ikä $age';
+$labels['objectchangelog'] = 'Muutoshistoria';
+$labels['objectdiff'] = 'Muutokset versiosta $rev1 versioon $rev2';
+$labels['objectnotfound'] = 'Tapahtuman tietojen lataaminen epäonnistui';
+$labels['objectchangelognotavailable'] = 'Tapahtuman muutoshistoria ei ole saatavilla';
+$labels['objectdiffnotavailable'] = 'Valituille versioille ei ole mahdollista tehdä vertailua';
+$labels['revisionrestoreconfirm'] = 'Haluatko varmasti palauttaa tämän tapahtuman version $rev? Nykyinen tapahtuma korvataan vanhalla versiolla.';
+$labels['objectrestoresuccess'] = 'Versio $rev palautettu onnistuneesti';
+$labels['objectrestoreerror'] = 'Vanhan version palauttaminen epäonnistui';
+$labels['arialabelminical'] = 'Kalenterin ajankohdan valinta';
+$labels['arialabelcalendarview'] = 'Kalenterinäkymä';
+$labels['arialabelsearchform'] = 'Tapahtumahaun lomake';
+$labels['arialabelquicksearchbox'] = 'Tapatumahaun syöte';
+$labels['arialabelcalsearchform'] = 'Kalenterihakujen lomake';
+$labels['calendaractions'] = 'Kalenterin toiminnot';
+$labels['arialabeleventattendees'] = 'Tapahtuman osallistujalista';
+$labels['arialabeleventresources'] = 'Tapahtuman resurssilista';
+$labels['arialabelresourcesearchform'] = 'Resurssien hakulomake';
+$labels['arialabelresourceselection'] = 'Saatavilla olevat resurssit';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/fr_FR.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Vue par défaut';
+$labels['time_format'] = 'Format de l\'heure';
+$labels['timeslots'] = 'Créneau horaire';
+$labels['first_day'] = 'Premier jour de la semaine';
+$labels['first_hour'] = 'Première heure à afficher';
+$labels['workinghours'] = 'Heures de travail';
+$labels['add_category'] = 'Ajouter une catégorie';
+$labels['remove_category'] = 'Supprimer une catégorie';
+$labels['defaultcalendar'] = 'Ajouter un nouvel événement';
+$labels['eventcoloring'] = 'Couleurs des événements';
+$labels['coloringmode0'] = 'Selon le calendrier';
+$labels['coloringmode1'] = 'Selon la catégorie';
+$labels['coloringmode2'] = 'Calendrier en contour, catégorie en contenu';
+$labels['coloringmode3'] = 'Catégorie en contour, calendrier en contenu';
+$labels['afternothing'] = 'Ne rien faire';
+$labels['aftertrash'] = 'Déplacer dans la corbeille';
+$labels['afterdelete'] = 'Supprimer ce message';
+$labels['afterflagdeleted'] = 'Marquer comme supprimer';
+$labels['aftermoveto'] = 'Déplacer vers...';
+$labels['itipoptions'] = 'Invitations à l\'événement';
+$labels['afteraction'] = 'Après une invitation ou une modification, le message est traité';
+$labels['calendar'] = 'Calendrier';
+$labels['calendars'] = 'Calendriers';
+$labels['category'] = 'Catégorie';
+$labels['categories'] = 'Catégories';
+$labels['createcalendar'] = 'Créer un nouveau calendrier';
+$labels['editcalendar'] = 'Modifier les propriétés du calendrier';
+$labels['name'] = 'Nom';
+$labels['color'] = 'Couleur';
+$labels['day'] = 'Jour';
+$labels['week'] = 'Semaine';
+$labels['month'] = 'Mois';
+$labels['agenda'] = 'Ordre du jour';
+$labels['new'] = 'Nouveau';
+$labels['new_event'] = 'Nouvel événement';
+$labels['edit_event'] = 'Modifier l\'événement';
+$labels['edit'] = 'Modifier';
+$labels['save'] = 'Enregistrer';
+$labels['removelist'] = 'Supprimer de la liste';
+$labels['cancel'] = 'Annuler';
+$labels['select'] = 'Sélectionner';
+$labels['print'] = 'Imprimer';
+$labels['printtitle'] = 'Imprimer les calendriers';
+$labels['title'] = 'Résumé';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'toute la journée';
+$labels['export'] = 'Exporter';
+$labels['exporttitle'] = 'Exporter vers iCalendar';
+$labels['exportrange'] = 'Événements depuis';
+$labels['exportattachments'] = 'Avec pièces jointes';
+$labels['customdate'] = 'Date personnalisée';
+$labels['location'] = 'Lieu';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Début';
+$labels['starttime'] = 'Heure de début';
+$labels['end'] = 'Fin';
+$labels['endtime'] = 'Heure de fin';
+$labels['repeat'] = 'Répéter';
+$labels['selectdate'] = 'Sélectionner une date';
+$labels['freebusy'] = 'Montrez-moi en tant que';
+$labels['free'] = 'Libre';
+$labels['busy'] = 'Occupé';
+$labels['outofoffice'] = 'Absent';
+$labels['tentative'] = 'Provisoire';
+$labels['mystatus'] = 'Mon statut';
+$labels['status'] = 'Statut';
+$labels['status-confirmed'] = 'Confirmé';
+$labels['status-cancelled'] = 'Annulé';
+$labels['priority'] = 'Priorité';
+$labels['sensitivity'] = 'Diffusion';
+$labels['public'] = 'publique';
+$labels['private'] = 'privée';
+$labels['confidential'] = 'Confidentiel';
+$labels['links'] = 'Référence';
+$labels['alarms'] = 'Rappel';
+$labels['comment'] = 'Commentaire';
+$labels['created'] = 'Créé';
+$labels['changed'] = 'Dernière modification';
+$labels['unknown'] = 'Inconnu';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'généré à';
+$labels['eventhistory'] = 'Historique';
+$labels['removelink'] = 'Enlever référence d\'e-mail';
+$labels['printdescriptions'] = 'Imprimer les descriptions';
+$labels['parentcalendar'] = 'Ajouter à l\'intérieur';
+$labels['searchearlierdates'] = '« Chercher des événements plus anciens';
+$labels['searchlaterdates'] = 'Chercher des événement plus récents »';
+$labels['andnmore'] = '$nr de plus...';
+$labels['togglerole'] = 'Cliquez pour changer de rôle';
+$labels['createfrommail'] = 'Enregistrer comme un événement';
+$labels['importevents'] = 'Importer des événements';
+$labels['importrange'] = 'Événements depuis';
+$labels['onemonthback'] = '1 mois auparavant';
+$labels['nmonthsback'] = '$nr mois précédents';
+$labels['showurl'] = 'Afficher l\'URL du calendrier';
+$labels['showurldescription'] = 'Utilisez l\'adresse suivante pour accéder (lecture seule) à votre calendrier depuis d\'autres applications. Vous pouvez copier/coller celle-ci dans n\'importe quel calendrier électronique gérant le format iCal.';
+$labels['caldavurldescription'] = 'Copiez cette adresse vers une application client (comme Evolution ou Mozilla Thunderbird) compatible <a href="http://fr.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> pour synchroniser pleinement ce calendrier avec votre ordinateur ou votre smartphone.';
+$labels['findcalendars'] = 'Recherche de calendriers...';
+$labels['searchterms'] = 'Critères de recherche';
+$labels['calsearchresults'] = 'Calendriers disponibles';
+$labels['calendarsubscribe'] = 'Lister définitivement';
+$labels['nocalendarsfound'] = 'Aucun calendrier trouvé';
+$labels['nrcalendarsfound'] = '$nr calendriers trouvés';
+$labels['quickview'] = 'Voir uniquement ce calendrier';
+$labels['invitationspending'] = 'Invitations en attente';
+$labels['invitationsdeclined'] = 'Invitations refusées';
+$labels['changepartstat'] = 'Changer le statut du participant';
+$labels['rsvpcomment'] = 'Texte d\'invitation';
+$labels['listrange'] = 'Intervalle à afficher :';
+$labels['listsections'] = 'Diviser en :';
+$labels['smartsections'] = 'Sections intelligentes';
+$labels['until'] = 'jusqu\'à';
+$labels['today'] = 'Aujourd\'hui';
+$labels['tomorrow'] = 'Demain';
+$labels['thisweek'] = 'Cette semaine';
+$labels['nextweek'] = 'Semaine prochaine';
+$labels['prevweek'] = 'Semaine précédente';
+$labels['thismonth'] = 'Ce mois-ci';
+$labels['nextmonth'] = 'Mois prochain';
+$labels['weekofyear'] = 'Semaine';
+$labels['pastevents'] = 'Passé';
+$labels['futureevents'] = 'Futur';
+$labels['showalarms'] = 'Afficher les rappels';
+$labels['defaultalarmtype'] = 'Paramètre de rappel par défaut';
+$labels['defaultalarmoffset'] = 'Durée de rappel par défaut';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Rôle';
+$labels['availability'] = 'Dispo.';
+$labels['confirmstate'] = 'Statut';
+$labels['addattendee'] = 'Ajouter participant';
+$labels['roleorganizer'] = 'Organisateur';
+$labels['rolerequired'] = 'Requis';
+$labels['roleoptional'] = 'Optionnel';
+$labels['rolechair'] = 'Présidence';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individuel';
+$labels['cutypegroup'] = 'Groupe';
+$labels['cutyperesource'] = 'Ressource';
+$labels['cutyperoom'] = 'Salle';
+$labels['availfree'] = 'Disponible';
+$labels['availbusy'] = 'Occupé';
+$labels['availunknown'] = 'Inconnu';
+$labels['availtentative'] = 'Provisoire';
+$labels['availoutofoffice'] = 'Absent';
+$labels['delegatedto'] = 'Délégué à :';
+$labels['delegatedfrom'] = 'Délégué de :';
+$labels['scheduletime'] = 'Trouver les disponibilités';
+$labels['sendinvitations'] = 'Envoyer les invitations';
+$labels['sendnotifications'] = 'Informer les participants des modifications';
+$labels['sendcancellation'] = 'Informer les participants de l\'annulation';
+$labels['onlyworkinghours'] = 'Trouver des disponibilités en fonction de mes heures de travail';
+$labels['reqallattendees'] = 'Demandé/tous les participants';
+$labels['prevslot'] = 'Créneau précédent';
+$labels['nextslot'] = 'Créneau suivant';
+$labels['suggestedslot'] = 'Emplacement suggéré';
+$labels['noslotfound'] = 'Impossible de trouver un créneau disponible';
+$labels['invitationsubject'] = 'Vous avez été invité à "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees\n\nVous trouverez ci-joint un fichier iCalendar avec tous les détails de l'événement que vous pourrez importer dans votre calendrier électronique.";
+$labels['invitationattendlinks'] = "Dans le cas où votre application de messagerie ne gère pas les demandes \"iTip\", vous pouvez utiliser ce lien pour accepter ou refuser l'invitation : \n\$url";
+$labels['eventupdatesubject'] = '"$title" a été modifié';
+$labels['eventupdatesubjectempty'] = 'Un événement vous concernant a été modifié';
+$labels['eventupdatemailbody'] = "*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees\n\nVous trouverez ci-joint un fichier iCalendar avec toutes les modifications à l'événement que vous pourrez importer dans votre calendrier électronique.";
+$labels['eventcancelsubject'] = '"$title" a été annulé';
+$labels['eventcancelmailbody'] = "*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees\n\nL'événement a été annulé par \$organizer.\n\nVous trouverez en pièce jointe un fichier iCalendar avec les modifications de l'événement que vous pourrez importer dans votre calendrier électronique.";
+$labels['itipobjectnotfound'] = 'L\'événement lié à ce message n\'a pas été trouvé dans votre calendrier.';
+$labels['itipmailbodyaccepted'] = "\$sender a accepté l'invitation à l'événement suivant :\n\n*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender a accepté provisoirement l'invitation à l'événement suivant :\n\n*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender a refusé l'invitation à l'événement suivant :\n\n*\$title*\n\nQuand: \$date\n\nParticipants: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender a rejeté votre participation à l’événement suivant :\n\n*\$title*\n\nLe: \$date";
+$labels['itipmailbodydelegated'] = "\$sender a délégué la participation à l'événement suivant : \n\n*\$title*\n\nQuand: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender vous a délégué la participation à l'événement suivant : \n\n*\$title*\n\nQuand : \$date";
+$labels['itipdeclineevent'] = 'Voulez-vous refuser l\'invitation à cet événement?';
+$labels['declinedeleteconfirm'] = 'Voulez-vous aussi supprimer cet événement annulé de votre calendrier ?';
+$labels['itipcomment'] = 'Commentaire d’invitation ou de notification';
+$labels['itipcommenttitle'] = 'Ce commentaire sera inséré dans le message d\'invitation ou de notification envoyé aux participants';
+$labels['notanattendee'] = 'Vous n\'êtes pas dans la liste des participants à cet événement';
+$labels['eventcancelled'] = 'L\'événement a été annulé';
+$labels['saveincalendar'] = 'enregistrer sous';
+$labels['updatemycopy'] = 'Mise à jour dans mon calendrier';
+$labels['savetocalendar'] = 'Sauvegarde dans le calendrier';
+$labels['openpreview'] = 'Consulter le calendrier';
+$labels['noearlierevents'] = 'Aucun événement passé';
+$labels['nolaterevents'] = 'Aucun événement futur';
+$labels['resource'] = 'Ressource';
+$labels['addresource'] = 'Carnet de ressources';
+$labels['findresources'] = 'Recherche des ressources';
+$labels['resourcedetails'] = 'Détails';
+$labels['resourceavailability'] = 'Disponibilité';
+$labels['resourceowner'] = 'Propriétaire';
+$labels['resourceadded'] = 'Cette ressource a été ajoutée à l\'événement';
+$labels['tabsummary'] = 'Résumé';
+$labels['tabrecurrence'] = 'Récurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Ressources';
+$labels['tabattachments'] = 'Pièces jointes';
+$labels['tabsharing'] = 'Partage';
+$labels['deleteobjectconfirm'] = 'Voulez-vous vraiment supprimer cet événement?';
+$labels['deleteventconfirm'] = 'Voulez-vous vraiment supprimer cet événement?';
+$labels['deletecalendarconfirm'] = 'Voulez-vous vraiment supprimer ce calendrier et tous ses événements?';
+$labels['deletecalendarconfirmrecursive'] = 'Voulez-vous vraiment supprimer ce calendrier avec tous ses événements et ses sous-calendriers ?';
+$labels['savingdata'] = 'Enregistrer...';
+$labels['errorsaving'] = 'Échec lors de l\'enregistrement des changements';
+$labels['operationfailed'] = 'L\'opération demandée a échoué';
+$labels['invalideventdates'] = 'Dates invalides ! Veuillez vérifier votre saisie.';
+$labels['invalidcalendarproperties'] = 'Propriétés du calendrier invalides ! Veuillez saisir un nom valide.';
+$labels['searchnoresults'] = 'Pas d\'événement trouvé dans les calendriers sélectionnés.';
+$labels['successremoval'] = 'L\'événement a été supprimé.';
+$labels['successrestore'] = 'L\'événement a été restauré.';
+$labels['errornotifying'] = 'Échec de l\'envoi de notifications aux participants ';
+$labels['errorimportingevent'] = 'Échec de l\'import de l\'événement';
+$labels['importwarningexists'] = 'Une copie de cet événement existe déjà dans votre calendrier.';
+$labels['newerversionexists'] = 'Une nouvelle version de cet événement existe ! Abandon.';
+$labels['nowritecalendarfound'] = 'Pas de calendrier trouvé pour enregistrer l\'événement';
+$labels['importedsuccessfully'] = 'L\'événement a été ajouté à l\'agenda \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Cet événement a été modifié avec succès dans \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Le statut des participants a été modifié';
+$labels['itipsendsuccess'] = 'Invitation envoyée aux participants.';
+$labels['itipresponseerror'] = 'Échec de l\'envoi d\'une réponse à cette invitation';
+$labels['itipinvalidrequest'] = 'C\'est invitation n\'est plus valide';
+$labels['sentresponseto'] = 'La réponse à l\'invitation a été envoyée à $mailto';
+$labels['localchangeswarning'] = 'Vous êtes sur le point d\'effectuer des modifications qui seront effectives dans votre calendrier mais qui ne seront pas envoyées à l\'organisateur de l’événement.';
+$labels['importsuccess'] = '$nr événements importés.';
+$labels['importnone'] = 'Pas d\'événement à importer';
+$labels['importerror'] = 'Une erreur s\'est produite lors de l\'importation';
+$labels['aclnorights'] = 'Vous ne disposez pas des droits d\'administrateur sur ce calendrier.';
+$labels['changeeventconfirm'] = 'Modifier l\'événement';
+$labels['removeeventconfirm'] = 'Supprimer l\'événement';
+$labels['changerecurringeventwarning'] = 'Ceci est un événement récurrent. Voulez-vous éditer seulement cette occurrence, celle-ci et toutes les suivantes, toutes les occurrences ou l\'enregistrer comme un nouvel événement ? ';
+$labels['removerecurringeventwarning'] = 'Ceci est un événement récurrent. Voulez-vous supprimer l\'événement courant uniquement, l’événement courant et toutes les occurrences futures, ou toutes les occurrences ?';
+$labels['removerecurringallonly'] = 'Ceci est un événement récurrent. En tant que participant, vous pouvez seulement supprimer l\'événement entier avec toutes les occurrences.';
+$labels['currentevent'] = 'Cette occurrence';
+$labels['futurevents'] = 'Cette occurrence et toutes les suivantes';
+$labels['allevents'] = 'Toutes les occurrences';
+$labels['saveasnew'] = 'Enregistrer comme un nouvel événement';
+$labels['birthdays'] = 'Anniversaires';
+$labels['birthdayscalendar'] = 'Calendrier des anniversaires';
+$labels['displaybirthdayscalendar'] = 'Afficher le calendrier des anniversaires';
+$labels['birthdayscalendarsources'] = 'Depuis ces carnets d\'adresses';
+$labels['birthdayeventtitle'] = 'Anniversaire de $name';
+$labels['birthdayage'] = 'Âge $age';
+$labels['objectchangelog'] = 'Historique des modifications';
+$labels['objectdiff'] = 'Modifications de $rev1 à $rev2';
+$labels['objectnotfound'] = 'Impossible de charger les données de l’événement';
+$labels['objectchangelognotavailable'] = 'Il n\'y a pas d\'historique des modifications pour cet événement';
+$labels['objectdiffnotavailable'] = 'La comparaison des versions sélectionnées est impossible';
+$labels['revisionrestoreconfirm'] = 'Voulez-vous vraiment restaurer le version $rev de cet événement ? Cette action va remplacer l\'événement actuel par l\'ancienne version.';
+$labels['objectrestoresuccess'] = 'La révision $rev a été restaurée avec succès';
+$labels['objectrestoreerror'] = 'Échec lors de la restauration de la précédente révision';
+$labels['arialabelminical'] = 'Sélection de la date du calendrier';
+$labels['arialabelcalendarview'] = 'Vue du calendrier';
+$labels['arialabelsearchform'] = 'Recherche d\'événements depuis';
+$labels['arialabelquicksearchbox'] = 'Saisie de le recherche d\'événements';
+$labels['arialabelcalsearchform'] = 'Recherche de calendriers';
+$labels['calendaractions'] = 'Actions calendrier';
+$labels['arialabeleventattendees'] = 'Liste des participants à l\'événement';
+$labels['arialabeleventresources'] = 'Liste des ressources de l\'événement';
+$labels['arialabelresourcesearchform'] = 'Formulaire de recherche des ressources';
+$labels['arialabelresourceselection'] = 'Ressources disponibles';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/he.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['eventchangelog'] = 'Change History';
+$labels['eventdiff'] = 'Changes from revisions $rev';
+$labels['revision'] = 'Revision';
+$labels['user'] = 'User';
+$labels['operation'] = 'Action';
+$labels['actionappend'] = 'Saved';
+$labels['actionmove'] = 'Moved';
+$labels['actiondelete'] = 'Deleted';
+$labels['compare'] = 'Compare';
+$labels['showrevision'] = 'Show this version';
+$labels['restore'] = 'Restore this version';
+$labels['eventnotfound'] = 'Failed to load event data';
+$labels['eventchangelognotavailable'] = 'Change history is not available for this event';
+$labels['eventdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['eventrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/hr.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Format datuma';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'Prvi dan tjedna';
+$labels['first_hour'] = 'Prvi sat za prikaz';
+$labels['workinghours'] = 'Radni sati';
+$labels['add_category'] = 'Dodaj kategoriju';
+$labels['remove_category'] = 'Obriši kategoriju';
+$labels['defaultcalendar'] = 'Kreiraj nove događaje u';
+$labels['eventcoloring'] = 'Bojanje događaja';
+$labels['coloringmode0'] = 'Prema kalendaru';
+$labels['coloringmode1'] = 'Prema kategoriji';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+$labels['calendar'] = 'Kalendar';
+$labels['calendars'] = 'Kalendari';
+$labels['category'] = 'Kategorija';
+$labels['categories'] = 'Kategorije';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['eventchangelog'] = 'Change History';
+$labels['eventdiff'] = 'Changes from revisions $rev';
+$labels['revision'] = 'Revision';
+$labels['user'] = 'User';
+$labels['operation'] = 'Action';
+$labels['actionappend'] = 'Saved';
+$labels['actionmove'] = 'Moved';
+$labels['actiondelete'] = 'Deleted';
+$labels['compare'] = 'Compare';
+$labels['showrevision'] = 'Show this version';
+$labels['restore'] = 'Restore this version';
+$labels['eventnotfound'] = 'Failed to load event data';
+$labels['eventchangelognotavailable'] = 'Change history is not available for this event';
+$labels['eventdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['eventrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/hu_HU.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,215 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Alapnézett nézet';
+$labels['time_format'] = 'Idő formátum';
+$labels['timeslots'] = 'Órák felbontása időrésekre';
+$labels['first_day'] = 'A hét első napja';
+$labels['first_hour'] = 'Kezdő óra';
+$labels['workinghours'] = 'Munkaidő';
+$labels['add_category'] = 'Kategória hozzáadása';
+$labels['remove_category'] = 'Kategória törlése';
+$labels['defaultcalendar'] = 'Új események alapértelmezett helye';
+$labels['eventcoloring'] = 'Események színezése';
+$labels['coloringmode0'] = 'Naptár szerint';
+$labels['coloringmode1'] = 'Kategória szerint';
+$labels['coloringmode2'] = 'Naptár színe körvonal, kategória színe belsőrész';
+$labels['coloringmode3'] = 'Kategória színe körvonal, naptár színe belsőrész';
+$labels['calendar'] = 'Naptár';
+$labels['calendars'] = 'Naptárak';
+$labels['category'] = 'Kategória';
+$labels['categories'] = 'Kategóriák';
+$labels['createcalendar'] = 'Új naptár létrehozása';
+$labels['editcalendar'] = 'Naptár tulajdonságai';
+$labels['name'] = 'Név';
+$labels['color'] = 'Szín';
+$labels['day'] = 'Nap';
+$labels['week'] = 'Hét';
+$labels['month'] = 'Hónap';
+$labels['agenda'] = 'Napirend';
+$labels['new'] = 'Új';
+$labels['new_event'] = 'Új esemény';
+$labels['edit_event'] = 'Esemény módosítása';
+$labels['edit'] = 'Módosítás';
+$labels['save'] = 'Mentés';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Mégse';
+$labels['select'] = 'Jelölés';
+$labels['print'] = 'Nyomtatás';
+$labels['printtitle'] = 'Naptárak nyomtatása';
+$labels['title'] = 'Tárgy';
+$labels['description'] = 'Leírás';
+$labels['all-day'] = 'Egész nap';
+$labels['export'] = 'Exportálás';
+$labels['exporttitle'] = 'Exportálás iCalendar-ba';
+$labels['exportrange'] = 'Visszamenőleg';
+$labels['exportattachments'] = 'Csatolmányokkal';
+$labels['customdate'] = 'Megadott dátumig';
+$labels['location'] = 'Hol';
+$labels['url'] = 'URL';
+$labels['date'] = 'Dátum';
+$labels['start'] = 'Kezdet';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'Vég';
+$labels['repeat'] = 'Ismétlődés';
+$labels['selectdate'] = 'Válasszon dátumot';
+$labels['freebusy'] = 'Foglaltság';
+$labels['free'] = 'Szabad';
+$labels['busy'] = 'Foglalt';
+$labels['outofoffice'] = 'Házon kívűl';
+$labels['tentative'] = 'Feltételes';
+$labels['status'] = 'Stát.';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Prioritás';
+$labels['sensitivity'] = 'Manánszféra';
+$labels['public'] = 'publikus';
+$labels['private'] = 'privát';
+$labels['confidential'] = 'titkos';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Emlékeztető';
+$labels['comment'] = 'Megjegyzés';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Ismeretlen foglaltság';
+$labels['generated'] = 'készítve:';
+$labels['removelink'] = 'Remove email reference';
+$labels['printdescriptions'] = 'Leírás nyomtatása';
+$labels['parentcalendar'] = 'Szülőnaptár';
+$labels['searchearlierdates'] = '< Korábbi események keresése';
+$labels['searchlaterdates'] = 'Későbbi események keresése >';
+$labels['andnmore'] = 'még $nr ...';
+$labels['togglerole'] = 'Kattintson a szerepváltáshoz';
+$labels['createfrommail'] = 'Mentés naptári eseményként';
+$labels['importevents'] = 'Események importálása';
+$labels['importrange'] = 'Visszamenőleg';
+$labels['onemonthback'] = '1 hónapra';
+$labels['nmonthsback'] = '$nr hónapra';
+$labels['showurl'] = 'Naptár URL címe';
+$labels['showurldescription'] = 'Ezen a címen érhető el (csak olvasásra!) a naptár más alkalmazások számára, iCal formátumban.';
+$labels['caldavurldescription'] = 'Másolja be ezt a címet egy <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-kompatibilis kliensbe (pl. Evolution vagy Mozilla Thunderbird) hogy kétirányú szinkronizációval tudjon a naptárához hozzáférni.';
+$labels['searchterms'] = 'Search terms';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['listrange'] = 'Megjelenítés:';
+$labels['listsections'] = 'Felosztás:';
+$labels['smartsections'] = 'Ãttekintve';
+$labels['until'] = 'eddig';
+$labels['today'] = 'Ma';
+$labels['tomorrow'] = 'Holnap';
+$labels['thisweek'] = 'Ezen a héten';
+$labels['nextweek'] = 'Jövő héten';
+$labels['thismonth'] = 'Ebben a hónapban';
+$labels['nextmonth'] = 'Jövő hónapban';
+$labels['weekofyear'] = 'Hét:';
+$labels['pastevents'] = 'Múltban';
+$labels['futureevents'] = 'Jövőben';
+$labels['showalarms'] = 'Emlékeztetők megjelenítése';
+$labels['defaultalarmtype'] = 'Alapértelmezett emlékeztető';
+$labels['defaultalarmoffset'] = 'Alapértelmezett emlékeztető ideje';
+$labels['attendee'] = 'Résztvevő';
+$labels['role'] = 'Szerepkör';
+$labels['availability'] = 'Elérh.';
+$labels['confirmstate'] = 'Stát.';
+$labels['addattendee'] = 'Résztvevő meghívása';
+$labels['roleorganizer'] = 'Szervező';
+$labels['rolerequired'] = 'Kötelező';
+$labels['roleoptional'] = 'Opcionális';
+$labels['rolechair'] = 'Elnöklő';
+$labels['rolenonparticipant'] = 'Hiányzó';
+$labels['cutypeindividual'] = 'Egyén';
+$labels['cutypegroup'] = 'Csoport';
+$labels['cutyperesource'] = 'Eszköz';
+$labels['cutyperoom'] = 'Helyiség';
+$labels['availfree'] = 'Szabad';
+$labels['availbusy'] = 'Foglalt';
+$labels['availunknown'] = 'Ismeretlen foglaltság';
+$labels['availtentative'] = 'Feltételes';
+$labels['availoutofoffice'] = 'Házon kívül';
+$labels['delegatedto'] = 'Beosztva ide: ';
+$labels['delegatedfrom'] = 'Beosztva innen: ';
+$labels['scheduletime'] = 'Elérhetőség';
+$labels['sendinvitations'] = 'Meghívók küldése';
+$labels['sendnotifications'] = 'Résztvevők értesítése a változásokról';
+$labels['sendcancellation'] = 'Résztvevők értesítése a lemondásról';
+$labels['onlyworkinghours'] = 'Elérhetőség figyelembevétele csak az én munkaidőmben';
+$labels['reqallattendees'] = 'Mindenki részére';
+$labels['prevslot'] = 'Előző idősáv';
+$labels['nextslot'] = 'Következő idősáv';
+$labels['noslotfound'] = 'Nem sikerült szabad idősávot találni';
+$labels['invitationsubject'] = '$title';
+$labels['invitationmailbody'] = "Meghívó érkezett '\$title' eseményre.\n\nIdőpont: \$date\nSzervező: \$organizer\nRésztvevők: \$attendees\n\n\nMellékletben egy iCalendar naptárbejegyzés, mely tetszőleges naptárprogramba importálható.";
+$labels['invitationattendlinks'] = "Amennyiben a levelezőben nem lát elfogadó/elutasító gombokat (a levelező program nem támogatja az iTip üzeneteket), kattintson ide a meghívó elfogadására, vagy elutasítására:\n\$url";
+$labels['eventupdatesubject'] = '$title - módosítva';
+$labels['eventupdatesubjectempty'] = 'Egy Önt érintő esemény módosítva lett';
+$labels['eventupdatemailbody'] = "Módosítás érkezett '\$title' eseményre vonatkozóan.\n\nIdőpont: \$date\nSzervező: \$organizer\nRésztvevők: \$attendees\n\n\nMellékletben egy frissített iCalendar naptárbejegyzés, mely tetszőleges naptárprogramba importálható.";
+$labels['eventcancelmailbody'] = "'\$title' eseményre \$organizer szervező visszavonta a meghívást.\n\nIdőpont: \$date\nRésztvevők: \$attendees\n\n\nMellékletben egy frissített iCalendar naptárbejegyzés, mely tetszőleges naptárprogramba importálható.";
+$labels['itipobjectnotfound'] = 'Az üzenetben hivatkozott esemény nem található a naptárban.';
+$labels['itipmailbodyaccepted'] = "\$sender elfogadta a meghívást '\$title' eseményre.\n\nIdőpont: \$date\nRésztvevők: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender feltételesen elfogadta a meghívást '\$title' eseményre.\n\nIdőpont: \$date\nRésztvevők: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender elutasította a meghívást '\$title' eseményre.\n\nIdőpont: \$date\nRésztvevők: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender elutasította a részvételét '\$title' eseményen, \$date időpontra";
+$labels['itipdeclineevent'] = 'Biztos benne, hogy el szeretné utasítani ezt a meghívást?';
+$labels['declinedeleteconfirm'] = 'Ki is szeretné törölni a saját naptárából ezt az időpontot?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['notanattendee'] = 'Ön nem szerepel az esemény meghívottai között';
+$labels['eventcancelled'] = 'Az esemény le lett mondva';
+$labels['saveincalendar'] = 'Mentés naptárba';
+$labels['updatemycopy'] = 'Naptárbejegyzés frissítése';
+$labels['resource'] = 'Eszközök';
+$labels['addresource'] = 'Eszköz foglalása';
+$labels['findresources'] = 'Eszközök keresése';
+$labels['resourcedetails'] = 'Részletek';
+$labels['resourceavailability'] = 'Elérhetőség';
+$labels['resourceowner'] = 'Tulajdonos';
+$labels['resourceadded'] = 'Az eszköz le lett foglalva az eseményhez';
+$labels['tabsummary'] = 'Részletek';
+$labels['tabrecurrence'] = 'Ismétlődés';
+$labels['tabattendees'] = 'Résztvevők';
+$labels['tabresources'] = 'Eszközök';
+$labels['tabattachments'] = 'Csatolmányok';
+$labels['tabsharing'] = 'Megosztás';
+$labels['deleteobjectconfirm'] = 'Biztos benne, hogy törölni szeretné ezt az eseményt?';
+$labels['deleteventconfirm'] = 'Biztos benne, hogy törölni szeretné ezt az eseményt?';
+$labels['deletecalendarconfirm'] = 'Biztos benne, hogy törölni szeretné ezt a naptárat, az összes benne lévő eseménnyel?';
+$labels['deletecalendarconfirmrecursive'] = 'Biztos benne, hogy törölni szeretné ezt a naptárat, az összes benne lévő al-naptárral és eseménnyel?';
+$labels['savingdata'] = 'Adatok mentése...';
+$labels['errorsaving'] = 'A módosításokat nem sikerült elmenteni.';
+$labels['operationfailed'] = 'A műveletet nem sikerült elvégezni.';
+$labels['invalideventdates'] = 'Helytelen dátumokoat adott meg! Kérem, ellenörizze a bevírt adatokat.';
+$labels['invalidcalendarproperties'] = 'Helytelen naptár tulajdonságok! Kérem, adjon meg egy helyes nevet.';
+$labels['searchnoresults'] = 'Nem található esemény a kiválasztott naptárakban.';
+$labels['successremoval'] = 'Az esemény törlése sikerült.';
+$labels['successrestore'] = 'Az esemény sikeresen vissza lett állítva.';
+$labels['errornotifying'] = 'Nem sikerült meghívókat küldeni a résztvevőknek.';
+$labels['errorimportingevent'] = 'Az esemény importálása sikertelen volt.';
+$labels['newerversionexists'] = 'Egy újabb verzió már van ebből az eseményből! Itt abbahagytam.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'Az esemény a \'$calendar\' naptárhoz hozzá lett adva';
+$labels['attendeupdateesuccess'] = 'A résztvevők adatai sikeresen frissítve';
+$labels['itipsendsuccess'] = 'Értesítés küldve a résztvevőknek';
+$labels['itipresponseerror'] = 'Nem sikerült választ küldeni erre a meghívásra';
+$labels['itipinvalidrequest'] = 'Ez a meghívás már érvénytelen';
+$labels['sentresponseto'] = 'Értesítésre válasz küldve: $mailto';
+$labels['localchangeswarning'] = 'Ezek a változások csak az ön naptárában fognak megjelenni, az esemény szervezőjénél nem.';
+$labels['importsuccess'] = '$nr esemény sikeresen importálva lett';
+$labels['importnone'] = 'Nem található importálható esemény.';
+$labels['importerror'] = 'Hiba importálás közben';
+$labels['aclnorights'] = 'Nincs adminisztrátori joga ehhez a naptárhoz';
+$labels['changeeventconfirm'] = 'Bejegyzés módosítása';
+$labels['changerecurringeventwarning'] = 'Ez egy ismétlődő esemény. Csak ezt az előfordulást szeretné módosítani, esetleg ezt az előfordulást az összes következővel, netán a teljes sorozatot, vagy új eseményként legyen mentve?';
+$labels['currentevent'] = 'Csak ezt';
+$labels['futurevents'] = 'Ezt és a következőeket';
+$labels['allevents'] = 'Egész sorozatot';
+$labels['saveasnew'] = 'Mentés újként';
+$labels['birthdays'] = 'Születésnapok';
+$labels['birthdayscalendar'] = 'Születésnapi naptár';
+$labels['displaybirthdayscalendar'] = 'Születésnapok megjelenítése';
+$labels['birthdayscalendarsources'] = 'Alábbi címjegyzékekből:';
+$labels['birthdayeventtitle'] = '$name születésnapja';
+$labels['birthdayage'] = '$age éves';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/it_IT.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Visualizzazione predefinita';
+$labels['time_format'] = 'Formato ora';
+$labels['timeslots'] = 'Timeslots per ora';
+$labels['first_day'] = 'Inizio settimana';
+$labels['first_hour'] = 'Prima ora da mostrare';
+$labels['workinghours'] = 'Orario lavorativo';
+$labels['add_category'] = 'Aggiungi categoria';
+$labels['remove_category'] = 'Rimuovi categoria';
+$labels['defaultcalendar'] = 'Crea nuovi eventi in';
+$labels['eventcoloring'] = 'Colorazione evento';
+$labels['coloringmode0'] = 'Secondo il calendario';
+$labels['coloringmode1'] = 'Secondo la categoria';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Nessuna azione';
+$labels['aftertrash'] = 'Sposta nel cestino';
+$labels['afterdelete'] = 'Cancella il messaggio';
+$labels['afterflagdeleted'] = 'Segna come cancellato';
+$labels['aftermoveto'] = 'Sposta in...';
+$labels['itipoptions'] = 'Inviti all\'evento';
+$labels['afteraction'] = 'Dopo un invito o un aggiornamento il messaggio è processato';
+$labels['calendar'] = 'Calendario';
+$labels['calendars'] = 'Calendari';
+$labels['category'] = 'Categoria';
+$labels['categories'] = 'Categorie';
+$labels['createcalendar'] = 'Crea nuovo calendario';
+$labels['editcalendar'] = 'Modifica proprietà calendario';
+$labels['name'] = 'Nome';
+$labels['color'] = 'Colore';
+$labels['day'] = 'Giorno';
+$labels['week'] = 'Settimana';
+$labels['month'] = 'Mese';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nuovo';
+$labels['new_event'] = 'Nuovo evento';
+$labels['edit_event'] = 'Modifica evento';
+$labels['edit'] = 'Modifica';
+$labels['save'] = 'Salva';
+$labels['removelist'] = 'Rimuovi dalla lista';
+$labels['cancel'] = 'Annulla';
+$labels['select'] = 'Seleziona';
+$labels['print'] = 'Stampa';
+$labels['printtitle'] = 'Stampa calendari';
+$labels['title'] = 'Oggetto';
+$labels['description'] = 'Descrizione';
+$labels['all-day'] = 'Tutto il giorno';
+$labels['export'] = 'Esporta';
+$labels['exporttitle'] = 'Esporta come iCalendar';
+$labels['exportrange'] = 'Eventi di';
+$labels['exportattachments'] = 'Con allegati';
+$labels['customdate'] = 'Data personalizzata';
+$labels['location'] = 'Luogo';
+$labels['url'] = 'URL';
+$labels['date'] = 'Data';
+$labels['start'] = 'Inizio';
+$labels['starttime'] = 'Ora di inizio';
+$labels['end'] = 'Fine';
+$labels['endtime'] = 'Ora di fine';
+$labels['repeat'] = 'Ricorrenza';
+$labels['selectdate'] = 'Scegliere la data';
+$labels['freebusy'] = 'Mostrami come';
+$labels['free'] = 'Libero';
+$labels['busy'] = 'Occupato';
+$labels['outofoffice'] = 'Fuori Ufficio';
+$labels['tentative'] = 'Provvisorio';
+$labels['mystatus'] = 'Il mio stato';
+$labels['status'] = 'Stato';
+$labels['status-confirmed'] = 'Confermato';
+$labels['status-cancelled'] = 'Cancellato';
+$labels['priority'] = 'Priorità';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'pubblico';
+$labels['private'] = 'privato';
+$labels['confidential'] = 'confidenziale';
+$labels['links'] = 'Riferimento';
+$labels['alarms'] = 'Promemoria';
+$labels['comment'] = 'Commento';
+$labels['created'] = 'Creato';
+$labels['changed'] = 'Ultima modifica';
+$labels['unknown'] = 'Sconosciuto';
+$labels['eventoptions'] = 'Opzioni';
+$labels['generated'] = 'generato il';
+$labels['eventhistory'] = 'Storico';
+$labels['removelink'] = 'Rimuovi riferimento email';
+$labels['printdescriptions'] = 'Stampa descrizioni';
+$labels['parentcalendar'] = 'Inserisci dentro';
+$labels['searchearlierdates'] = '« Cerca eventi precedenti';
+$labels['searchlaterdates'] = 'Cerca eventi successivi »';
+$labels['andnmore'] = 'Altri $nr...';
+$labels['togglerole'] = 'Fare clic per cambiare il ruolo';
+$labels['createfrommail'] = 'Salva come evento';
+$labels['importevents'] = 'Importa eventi';
+$labels['importrange'] = 'Eventi di';
+$labels['onemonthback'] = '1 mese prima';
+$labels['nmonthsback'] = '$nr mesi prima';
+$labels['showurl'] = 'Mostra URL calendario';
+$labels['showurldescription'] = 'Usare il seguente indirizzo per accedere (in sola lettura) al calendario da altre applicazioni. È possibile copiarlo e incollarlo in qualsiasi software che supporta il formato iCal.';
+$labels['caldavurldescription'] = 'Copiare questo indirizzo in un\'applicazione client <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> (es. Evolution o Mozilla Thunderbird) per sincronizzare completamente questo specifico calendario con il proprio computer o dispositivo mobile.';
+$labels['findcalendars'] = 'Trova calendari...';
+$labels['searchterms'] = 'Cerca elemento';
+$labels['calsearchresults'] = 'Calendari disponibili';
+$labels['calendarsubscribe'] = 'Elenca sempre';
+$labels['nocalendarsfound'] = 'Nessun calendario trovato';
+$labels['nrcalendarsfound'] = '$nr calendari trovati';
+$labels['quickview'] = 'Visualizza solo questo calendario';
+$labels['invitationspending'] = 'Inviti in sospeso';
+$labels['invitationsdeclined'] = 'Inviti scartati';
+$labels['changepartstat'] = 'Cambia lo stato del partecipante';
+$labels['rsvpcomment'] = 'Testo dell\'invito';
+$labels['listrange'] = 'Intervallo da visualizzare:';
+$labels['listsections'] = 'Dividi in:';
+$labels['smartsections'] = 'Sezioni intelligenti';
+$labels['until'] = 'fino a';
+$labels['today'] = 'Oggi';
+$labels['tomorrow'] = 'Domani';
+$labels['thisweek'] = 'Questa settimana';
+$labels['nextweek'] = 'Prossima settimana';
+$labels['prevweek'] = 'Settimana precedente';
+$labels['thismonth'] = 'Questo mese';
+$labels['nextmonth'] = 'Prossimo mese';
+$labels['weekofyear'] = 'Settimana';
+$labels['pastevents'] = 'Passato';
+$labels['futureevents'] = 'Futuro';
+$labels['showalarms'] = 'Mostra promemoria';
+$labels['defaultalarmtype'] = 'Impostazioni predefinite dei promemoria';
+$labels['defaultalarmoffset'] = 'Tempo predefinito per i promemoria';
+$labels['attendee'] = 'Partecipante';
+$labels['role'] = 'Ruolo';
+$labels['availability'] = 'Dispon.';
+$labels['confirmstate'] = 'Stato';
+$labels['addattendee'] = 'Aggiungi partecipante';
+$labels['roleorganizer'] = 'Organizzatore';
+$labels['rolerequired'] = 'Necessario';
+$labels['roleoptional'] = 'Facoltativo';
+$labels['rolechair'] = 'Presidente';
+$labels['rolenonparticipant'] = 'Assente';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Gruppo';
+$labels['cutyperesource'] = 'Risorsa';
+$labels['cutyperoom'] = 'Stanza';
+$labels['availfree'] = 'Libero';
+$labels['availbusy'] = 'Occupato';
+$labels['availunknown'] = 'Sconosciuto';
+$labels['availtentative'] = 'Provvisorio';
+$labels['availoutofoffice'] = 'Fuori sede';
+$labels['delegatedto'] = 'Delegato a:';
+$labels['delegatedfrom'] = 'Delegato da:';
+$labels['scheduletime'] = 'Trova disponibilità';
+$labels['sendinvitations'] = 'Manda inviti';
+$labels['sendnotifications'] = 'Notifica le modifiche ai partecipanti';
+$labels['sendcancellation'] = 'Notifica ai partecipanti la cancellazione dell\'evento';
+$labels['onlyworkinghours'] = 'Trova disponibilità durante le ore lavorative';
+$labels['reqallattendees'] = 'Necessario/tutti i partecipanti';
+$labels['prevslot'] = 'Spazio precedente';
+$labels['nextslot'] = 'Spazio successivo';
+$labels['suggestedslot'] = 'Spazio suggerito';
+$labels['noslotfound'] = 'Impossibile trovare uno spazio di tempo libero';
+$labels['invitationsubject'] = 'Sei stato invitato a "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees\n\nIn allegato un file iCalendar con tutti i dettagli dell'evento, che puoi importare nella tua applicazione calendario.";
+$labels['invitationattendlinks'] = "Se il tuo client di posta elettronica non supporta le richieste iTip, puoi seguire il seguente collegamento per accettare o rifiutare l'invito:\n\$url";
+$labels['eventupdatesubject'] = '"$title" è stato aggiornato';
+$labels['eventupdatesubjectempty'] = 'Un evento che ti riguarda è stato aggiornato';
+$labels['eventupdatemailbody'] = "*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees\n\nIn allegato un file iCalendar con i dettagli aggiornati dell'evento che puoi importare nella tua applicazione calendario.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees\n\nL'evento è stato cancellato da \$organizer.\n\nIn allegato un file iCalendar con i dettagli aggiornati dell'evento .";
+$labels['itipobjectnotfound'] = 'L\'evento al quale questo messaggio fa riferimento non è stato trovato nel tuo calendario.';
+$labels['itipmailbodyaccepted'] = "\$sender ha accettato l'invito al seguente evento:\n\n*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender ha accettato con riserva l'invito al seguente evento:\n\n*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ha rifiutato l'invito al seguente evento:\n\n*\$title*\n\nQuando: \$date\n\nInvitati: \$attendees";
+$labels['itipmailbodycancel'] = "\$Il mittente ha rifiutato la tua partecipazione nel seguente evento:\n\n*\$title*\n\nQuando: \$date";
+$labels['itipmailbodydelegated'] = "\$Il mittente ha delegato la partecipazione nel seguente evento:\n\n*\$title*\n\nQuando: \$date";
+$labels['itipmailbodydelegatedto'] = "\$Il mittente ha delegato a te la partecipazione nel seguente evento:\n\n*\$title*\n\nQuando:\$date";
+$labels['itipdeclineevent'] = 'Vuoi rifiutare l\'invito a questo evento?';
+$labels['declinedeleteconfirm'] = 'Vuoi anche cancellare dal calendario l\'evento rifiutato?';
+$labels['itipcomment'] = 'Commento all\'invito/notifica';
+$labels['itipcommenttitle'] = 'Questo commento verrà allegato al messaggio di invito/notifica spedito ai partecipanti';
+$labels['notanattendee'] = 'Non sei elencato tra i partecipanti a questo evento';
+$labels['eventcancelled'] = 'L\'evento è stato annullato';
+$labels['saveincalendar'] = 'salva in';
+$labels['updatemycopy'] = 'Aggiorna nel mio calendario';
+$labels['savetocalendar'] = 'Salva sul calendario';
+$labels['openpreview'] = 'Controlla calendario';
+$labels['noearlierevents'] = 'Non ci sono eventi precedenti';
+$labels['nolaterevents'] = 'Non ci sono eventi successivi';
+$labels['resource'] = 'Risorsa';
+$labels['addresource'] = 'Prenota risorsa';
+$labels['findresources'] = 'Trova risorse';
+$labels['resourcedetails'] = 'Dettagli';
+$labels['resourceavailability'] = 'Disponibilità';
+$labels['resourceowner'] = 'Proprietario';
+$labels['resourceadded'] = 'La risorsa è stata aggiunta al tuo evento';
+$labels['tabsummary'] = 'Riepilogo';
+$labels['tabrecurrence'] = 'Ricorrenza';
+$labels['tabattendees'] = 'Partecipanti';
+$labels['tabresources'] = 'Risorse';
+$labels['tabattachments'] = 'Allegati';
+$labels['tabsharing'] = 'Condivisione';
+$labels['deleteobjectconfirm'] = 'Cancellare davvero questo evento?';
+$labels['deleteventconfirm'] = 'Cancellare davvero questo evento?';
+$labels['deletecalendarconfirm'] = 'Cancellare davvero questo calendario con tutti i suoi eventi?';
+$labels['deletecalendarconfirmrecursive'] = 'Vuoi veramente eliminare questo calendario con tutti i suoi eventi e i suoi sotto-calendari?';
+$labels['savingdata'] = 'Salvataggio dati...';
+$labels['errorsaving'] = 'Impossibile salvare le modifiche.';
+$labels['operationfailed'] = 'L\'operazione richiesta è fallita.';
+$labels['invalideventdates'] = 'Le date inserite non sono valide. Controllare l\'inserimento.';
+$labels['invalidcalendarproperties'] = 'Proprietà del calendario non valide. Impostare un nome valido.';
+$labels['searchnoresults'] = 'Nessun evento trovato nel calendario selezionato.';
+$labels['successremoval'] = 'L\'evento è stato cancellato correttamente.';
+$labels['successrestore'] = 'L\'evento è stato ripristinato correttamente.';
+$labels['errornotifying'] = 'Spedizione delle notifiche ai partecipanti dell\'evento fallita';
+$labels['errorimportingevent'] = 'Importazione evento fallita';
+$labels['importwarningexists'] = 'Una copia di questo evento esiste già nel tuo calendario';
+$labels['newerversionexists'] = 'Esiste già una versione più recente di questo evento. Abortito.';
+$labels['nowritecalendarfound'] = 'Non c\'è nessun calendario dove salvare l\'evento';
+$labels['importedsuccessfully'] = 'Evento aggiunto correttamente a \'$calendar\'';
+$labels['updatedsuccessfully'] = 'L\'evento è stato aggiornato con successo su \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Stato dei partecipanti aggiornato correttamente';
+$labels['itipsendsuccess'] = 'Invito spedito ai partecipanti.';
+$labels['itipresponseerror'] = 'Spedizione della risposta all\'invito fallita';
+$labels['itipinvalidrequest'] = 'Questo invito non è più valido';
+$labels['sentresponseto'] = 'Risposta all\'invito inviata correttamente a $mailto';
+$labels['localchangeswarning'] = 'Stai per fare dei cambiamenti che compariranno solo nel tuo calendario e non saranno spediti all\'organizzatore dell\'evento.';
+$labels['importsuccess'] = '$nr eventi importati correttamente';
+$labels['importnone'] = 'Nessun evento trovato da importare';
+$labels['importerror'] = 'Si è verificato un errore durante l\'importazione';
+$labels['aclnorights'] = 'Non hai i diritti di amministratore per questo calendario.';
+$labels['changeeventconfirm'] = 'Cambia evento';
+$labels['removeeventconfirm'] = 'Cancella evento';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'Questo è un evento ricorrente. Vuoi cancellare solamente l\'evento corrente, quest\'ultimo e tutte le future ricorrenze oppure tutte le ricorrenze di questo evento?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Futuro';
+$labels['allevents'] = 'Tutto';
+$labels['saveasnew'] = 'Salva come nuovo';
+$labels['birthdays'] = 'Compleanni';
+$labels['birthdayscalendar'] = 'Calendario compleanni';
+$labels['displaybirthdayscalendar'] = 'Mostra il calendario compleanni';
+$labels['birthdayscalendarsources'] = 'Da queste rubriche';
+$labels['birthdayeventtitle'] = 'Compleanno di $name';
+$labels['birthdayage'] = 'Età: $age anni';
+$labels['objectchangelog'] = 'Storico modifiche';
+$labels['objectnotfound'] = 'Caricamento dati dell\'evento fallito';
+$labels['objectchangelognotavailable'] = 'Lo storico modifiche non è disponibile per questo evento';
+$labels['objectdiffnotavailable'] = 'Nessun confronto possibile tra le revisioni selezionate';
+$labels['revisionrestoreconfirm'] = 'Vuoi veramente ripristinare la revisione $rev di questo evento? L\'evento corrente verrà sostituito dalla vecchia versione.';
+$labels['arialabelminical'] = 'Selezione della data del calendario';
+$labels['arialabelcalendarview'] = 'Vista calendario';
+$labels['arialabelsearchform'] = 'Modulo ricerca evento';
+$labels['arialabelquicksearchbox'] = 'Inserimento ricerca evento';
+$labels['arialabelcalsearchform'] = 'Modulo ricerca calendari';
+$labels['calendaractions'] = 'Azione calendari';
+$labels['arialabeleventattendees'] = 'Lista partecipanti all\'evento';
+$labels['arialabeleventresources'] = 'Lista risorse dell\'evento';
+$labels['arialabelresourcesearchform'] = 'Modulo ricerca risorse';
+$labels['arialabelresourceselection'] = 'Risorse disponibili';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/ja_JP.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,267 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'デフォルトビュー';
+$labels['time_format'] = '時刻表示形å¼';
+$labels['timeslots'] = '一時間毎ã®ã‚¿ã‚¤ãƒ ã‚¹ãƒ­ãƒƒãƒˆ';
+$labels['first_day'] = '最åˆã®å¹³æ—¥';
+$labels['first_hour'] = '最åˆã®æ™‚間を表示';
+$labels['workinghours'] = '労åƒæ™‚é–“';
+$labels['add_category'] = 'カテゴリ追加';
+$labels['remove_category'] = 'カテゴリ削除';
+$labels['defaultcalendar'] = 'æ–°ã—ã„イベントã®ä½œæˆ';
+$labels['eventcoloring'] = 'イベントカラー';
+$labels['coloringmode0'] = 'カレンダーã®èª¬æ˜Ž';
+$labels['coloringmode1'] = 'カテゴリã®èª¬æ˜Ž';
+$labels['coloringmode2'] = 'アウトライン用カレンダーã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„用カテゴリ';
+$labels['coloringmode3'] = 'アウトライン用カレンダーã€ã‚³ãƒ³ãƒ†ãƒ³ãƒ„用カテゴリ';
+$labels['afternothing'] = '何もã—ãªã„';
+$labels['aftertrash'] = 'ゴミ箱ã¸ç§»å‹•';
+$labels['afterdelete'] = 'メッセージを削除';
+$labels['afterflagdeleted'] = '削除フラグ';
+$labels['aftermoveto'] = '移動...';
+$labels['itipoptions'] = 'イベント招待';
+$labels['afteraction'] = '招待もã—ãã¯æ›´æ–°ã®å¾Œã«ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã¯é€é”ã•ã‚Œã¾ã™';
+$labels['calendar'] = 'カレンダー';
+$labels['calendars'] = 'カレンダー';
+$labels['category'] = 'カテゴリ';
+$labels['categories'] = 'カテゴリ';
+$labels['createcalendar'] = 'æ–°ã—ã„カレンダーã®ä½œæˆ';
+$labels['editcalendar'] = 'カレンダーã®ãƒ—ロパティ編集';
+$labels['name'] = 'åå‰';
+$labels['color'] = '色';
+$labels['day'] = 'æ—¥';
+$labels['week'] = '週';
+$labels['month'] = '月';
+$labels['agenda'] = '予定表';
+$labels['new'] = 'æ–°è¦';
+$labels['new_event'] = 'æ–°è¦ã‚¤ãƒ™ãƒ³ãƒˆ';
+$labels['edit_event'] = 'イベント編集';
+$labels['edit'] = '編集';
+$labels['save'] = 'ä¿å­˜';
+$labels['removelist'] = 'リストã‹ã‚‰å‰Šé™¤';
+$labels['cancel'] = 'キャンセル';
+$labels['select'] = 'é¸æŠž';
+$labels['print'] = 'å°åˆ·';
+$labels['printtitle'] = 'カレンダーå°åˆ·';
+$labels['title'] = 'è¦ç´„';
+$labels['description'] = '説明';
+$labels['all-day'] = '全日';
+$labels['export'] = 'エクスãƒãƒ¼ãƒˆ';
+$labels['exporttitle'] = 'iカレンダーã¸ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆ';
+$labels['exportrange'] = 'イベント元';
+$labels['exportattachments'] = '添付ã™ã‚‹';
+$labels['customdate'] = 'カスタム日時';
+$labels['location'] = '場所';
+$labels['url'] = 'URL';
+$labels['date'] = '期日';
+$labels['start'] = '開始';
+$labels['starttime'] = '開始時間';
+$labels['end'] = '終了';
+$labels['endtime'] = '終了日';
+$labels['repeat'] = 'ç¹°è¿”ã—';
+$labels['selectdate'] = '日付é¸æŠž';
+$labels['freebusy'] = '表示ã™ã‚‹';
+$labels['free'] = '空';
+$labels['busy'] = 'ビジー';
+$labels['outofoffice'] = '外出';
+$labels['tentative'] = 'ä»®';
+$labels['mystatus'] = 'マイ ステータス';
+$labels['status'] = '状態';
+$labels['status-confirmed'] = '確èªæ¸ˆ';
+$labels['status-cancelled'] = 'キャンセル済';
+$labels['priority'] = '優先度';
+$labels['sensitivity'] = 'プライãƒã‚·ãƒ¼';
+$labels['public'] = 'パブリック';
+$labels['private'] = 'プライベート';
+$labels['confidential'] = '親展';
+$labels['links'] = 'å‚ç…§';
+$labels['alarms'] = '通知';
+$labels['comment'] = 'コメント';
+$labels['created'] = '作æˆæ¸ˆ';
+$labels['changed'] = '最終変更';
+$labels['unknown'] = 'ä¸æ˜Ž';
+$labels['eventoptions'] = 'オプション';
+$labels['generated'] = '生æˆ';
+$labels['eventhistory'] = '履歴';
+$labels['removelink'] = 'Eメール引用文ã®å‰Šé™¤';
+$labels['printdescriptions'] = '説明å°åˆ·';
+$labels['parentcalendar'] = '内ã«æŒ¿å…¥';
+$labels['searchearlierdates'] = '<< 以å‰ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’検索';
+$labels['searchlaterdates'] = '今後ã®ã‚¤ãƒ™ãƒ³ãƒˆã®æ¤œç´¢ >>';
+$labels['andnmore'] = '$nr ã•ã‚‰ã«â€¦';
+$labels['togglerole'] = 'クリックã§ãƒ­ãƒ¼ãƒ«ã‚’トグル';
+$labels['createfrommail'] = 'イベントã¨ã—ã¦ä¿å­˜';
+$labels['importevents'] = 'イベントã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆ';
+$labels['importrange'] = 'イベント元';
+$labels['onemonthback'] = '1 ヶ月戻る';
+$labels['nmonthsback'] = '$nr ヶ月戻る';
+$labels['showurl'] = 'カレンダーURL表示';
+$labels['showurldescription'] = '以下ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’使用ã—ã¦ä»–ã®ã‚¢ãƒ—リケーションã‹ã‚‰ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã‚¢ã‚¯ã‚»ã‚¹(読込ã®ã¿)ã§ãã¾ã™ã€‚iCalå½¢å¼ã‚’サãƒãƒ¼ãƒˆã—ãŸã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚½ãƒ•ãƒˆã‚¦ã‚§ã‚¢ã¸ã‚³ãƒ”ーアンドペーストãŒã§ãã¾ã™ã€‚';
+$labels['caldavurldescription'] = 'ã“ã®æŒ‡å®šã—ãŸã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚’コンピュータもã—ãã¯ãƒ¢ãƒã‚¤ãƒ«ãƒ‡ãƒã‚¤ã‚¹ã¨å…¨åŒæœŸã™ã‚‹ãŸã‚ã«ã¯ã“ã®ã‚¢ãƒ‰ãƒ¬ã‚¹ã‚’ <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> クライアントアプリケーション(ãŸã¨ãˆã°ã‚¨ãƒœãƒªãƒ¥ãƒ¼ã‚·ãƒ§ãƒ³ã‚„Mozilla サンダーãƒãƒ¼ãƒ‰)ã¸ã‚³ãƒ”ーã—ã¦ãã ã•ã„。';
+$labels['findcalendars'] = 'カレンダーã®æ¤œç´¢';
+$labels['searchterms'] = '用語検索';
+$labels['calsearchresults'] = '有効ãªã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼';
+$labels['calendarsubscribe'] = 'æŒç¶šçš„ãªã‚‚ã®ã®ãƒªã‚¹ãƒˆ';
+$labels['nocalendarsfound'] = 'カレンダーãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“ã§ã—ãŸ';
+$labels['nrcalendarsfound'] = '$nr カレンダーãŒè¦‹ã¤ã‹ã‚Šã¾ã—ãŸ';
+$labels['quickview'] = 'ã“ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã¯é–²è¦§ã®ã¿';
+$labels['invitationspending'] = 'ä¿ç•™ä¸­ã®æ‹›å¾…';
+$labels['invitationsdeclined'] = 'æ–­ã£ãŸæ‹›å¾…';
+$labels['changepartstat'] = 'å‚加者ã®ã‚¹ãƒ†ãƒ¼ã‚¿ã‚¹å¤‰æ›´';
+$labels['rsvpcomment'] = '招待テキスト';
+$labels['listrange'] = '表示範囲:';
+$labels['listsections'] = '分割:';
+$labels['smartsections'] = 'スマートセクション';
+$labels['until'] = 'ã„ã¤ã¾ã§ã«';
+$labels['today'] = '今日';
+$labels['tomorrow'] = '明日';
+$labels['thisweek'] = '今週';
+$labels['nextweek'] = 'æ¥é€±';
+$labels['prevweek'] = 'å‰é€±';
+$labels['thismonth'] = '今月';
+$labels['nextmonth'] = 'æ¥æœˆ';
+$labels['weekofyear'] = '週';
+$labels['pastevents'] = '以å‰';
+$labels['futureevents'] = '以é™';
+$labels['showalarms'] = 'リマインド表示';
+$labels['defaultalarmtype'] = 'デフォルト通知設定';
+$labels['defaultalarmoffset'] = 'デフォルト通知時間';
+$labels['attendee'] = 'å‚加者';
+$labels['role'] = 'ロール';
+$labels['availability'] = '利用å¯';
+$labels['confirmstate'] = '状態';
+$labels['addattendee'] = 'å‚加者追加';
+$labels['roleorganizer'] = 'ç·¨æˆè€…';
+$labels['rolerequired'] = 'è¦ä»¶';
+$labels['roleoptional'] = 'オプション';
+$labels['rolechair'] = 'è­°é•·';
+$labels['rolenonparticipant'] = '欠席';
+$labels['cutypeindividual'] = '個人';
+$labels['cutypegroup'] = 'グループ';
+$labels['cutyperesource'] = 'リソース';
+$labels['cutyperoom'] = 'ルーム';
+$labels['availfree'] = '空';
+$labels['availbusy'] = 'ビジー';
+$labels['availunknown'] = 'ä¸æ˜Ž';
+$labels['availtentative'] = 'ä»®';
+$labels['availoutofoffice'] = '外出';
+$labels['delegatedto'] = '委任先:';
+$labels['delegatedfrom'] = '委任元:';
+$labels['scheduletime'] = '利用å¯æ¤œç´¢';
+$labels['sendinvitations'] = '招待をé€ã‚‹';
+$labels['sendnotifications'] = '変更をå‚加者ã¸é€šçŸ¥ã™ã‚‹';
+$labels['sendcancellation'] = '中止をå‚加者ã¸é€šçŸ¥ã™ã‚‹';
+$labels['onlyworkinghours'] = '労åƒæ™‚間内ã®åˆ©ç”¨å¯æ¤œç´¢';
+$labels['reqallattendees'] = 'è¦ä»¶/å…¨å‚加者';
+$labels['prevslot'] = 'å‰ã®ã‚¹ãƒ­ãƒƒãƒˆ';
+$labels['nextslot'] = '次ã®ã‚¹ãƒ­ãƒƒãƒˆ';
+$labels['suggestedslot'] = '指示ã•ã‚ŒãŸã‚¹ãƒ­ãƒƒãƒˆ';
+$labels['noslotfound'] = '空スロットを見ã¤ã‘られã¾ã›ã‚“';
+$labels['invitationsubject'] = '"$title" ã«æ‹›å¾…ã•ã‚Œã¾ã—ãŸ';
+$labels['invitationmailbody'] = "*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees\n\nã‚ãªãŸã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚¢ãƒ—リケーションã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãる全イベントã®è©³ç´°ãŒã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãる添付ã•ã‚ŒãŸiカレンダーファイルを見ã¤ã‘ã¦ãã ã•ã„。";
+$labels['invitationattendlinks'] = "ã“ã®å ´åˆã‚ãªãŸã®ãƒ¡ãƒ¼ãƒ«ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã¯iTip リクエストをサãƒãƒ¼ãƒˆã—ã¦ã¾ã›ã‚“ã€ä»¥ä¸‹ã®ãƒªãƒ³ã‚¯ã‹ã‚‰ã“ã®æ‹›å¾…を承諾もã—ãã¯è¾žé€€ã—ã¦ãã ã•ã„:\n\$url";
+$labels['eventupdatesubject'] = '"$title" ã¯ã‚¢ãƒƒãƒ—デートã•ã‚Œã¾ã—ãŸ';
+$labels['eventupdatesubjectempty'] = 'ã‚ãªãŸã«é–¢é€£ã™ã‚‹ã‚¤ãƒ™ãƒ³ãƒˆãŒæ›´æ–°ã•ã‚Œã¾ã—ãŸ';
+$labels['eventupdatemailbody'] = "*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees\n\nã‚ãªãŸã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚¢ãƒ—リケーションã«ã‚¤ãƒ³ãƒãƒ¼ãƒˆã§ãるアップデートã•ã‚ŒãŸå…¨ã‚¤ãƒ™ãƒ³ãƒˆã®è©³ç´°ãŒæ·»ä»˜ã•ã‚ŒãŸiカレンダーファイルを見ã¤ã‘ã¦ãã ã•ã„。";
+$labels['eventcancelmailbody'] = "*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees\n\nイベント㌠\$organizer ã«ã‚ˆã£ã¦ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•ã‚Œã¾ã—ãŸã€‚\n\næ›´æ–°ã•ã‚ŒãŸã‚¤ãƒ™ãƒ³ãƒˆã®è©³ç´°ã¨ã¨ã‚‚ã«æ·»ä»˜ã•ã‚ŒãŸiカレンダーファイルを見ã¤ã‘ã¦ãã ã•ã„。";
+$labels['itipobjectnotfound'] = 'ã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‹ã‚‰å‚ç…§ã•ã‚Œã‚‹ã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。';
+$labels['itipmailbodyaccepted'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®æ‹›å¾…を承諾ã—ã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®æ‹›å¾…を仮承諾ã—ã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®æ‹›å¾…を辞退ã—ã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date\n\n招待者: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®å‚加を断りã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date";
+$labels['itipmailbodydelegated'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®å‚加を委任ã—ã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender ã¯ä»¥ä¸‹ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®å‚加をã‚ãªãŸã¸å§”ä»»ã—ã¾ã—ãŸ:\n\n*\$title*\n\nã„ã¤: \$date";
+$labels['itipdeclineevent'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã¸ã®æ‹›å¾…を辞退ã—ã¾ã™ã‹?';
+$labels['declinedeleteconfirm'] = 'ã“ã®æ–­ã£ãŸã‚¤ãƒ™ãƒ³ãƒˆã‚‚カレンダーã‹ã‚‰å‰Šé™¤ã—ã¾ã™ã‹?';
+$labels['itipcomment'] = '招待/通知コメント';
+$labels['itipcommenttitle'] = 'ã“ã®ã‚³ãƒ¡ãƒ³ãƒˆã¯å‚加者ã¸é€ã‚‰ã‚ŒãŸæ‹›å¾…/通知メッセージã¸æ·»ä»˜ã•ã‚Œã¾ã™';
+$labels['notanattendee'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã®å‡ºå¸­è€…ã¨ã—ã¦ä¸€è¦§ã«ã‚ã‚Šã¾ã›ã‚“';
+$labels['eventcancelled'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚­ãƒ£ãƒ³ã‚»ãƒ«ã•ã‚Œã¾ã—ãŸ';
+$labels['saveincalendar'] = 'ä¿å­˜';
+$labels['updatemycopy'] = 'マイカレンダーã®æ›´æ–°';
+$labels['savetocalendar'] = 'カレンダーã¸ä¿å­˜';
+$labels['openpreview'] = 'カレンダーã«ãƒã‚§ãƒƒã‚¯';
+$labels['noearlierevents'] = '以å‰ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“';
+$labels['nolaterevents'] = '以é™ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“';
+$labels['resource'] = 'リソース';
+$labels['addresource'] = 'リソースã®è¨˜å¸³';
+$labels['findresources'] = 'リソースã®æ¤œç´¢';
+$labels['resourcedetails'] = '詳細';
+$labels['resourceavailability'] = '利用å¯';
+$labels['resourceowner'] = '所有者';
+$labels['resourceadded'] = 'リソースã¯ã‚¤ãƒ™ãƒ³ãƒˆã¸è¿½åŠ ã•ã‚Œã¾ã—ãŸ';
+$labels['tabsummary'] = 'è¦ç´„';
+$labels['tabrecurrence'] = 'ç¹°è¿”ã—';
+$labels['tabattendees'] = 'å‚加者';
+$labels['tabresources'] = 'リソース';
+$labels['tabattachments'] = '添付';
+$labels['tabsharing'] = '共有';
+$labels['deleteobjectconfirm'] = '本当ã«ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹?';
+$labels['deleteventconfirm'] = '本当ã«ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹?';
+$labels['deletecalendarconfirm'] = '本当ã«ã“ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã‚’全イベントã¨ã¨ã‚‚ã«å‰Šé™¤ã—ã¾ã™ã‹?';
+$labels['deletecalendarconfirmrecursive'] = 'ã“ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã¨ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼å†…ã®ã™ã¹ã¦ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚„サブカレンダーを削除ã—ã¾ã™ã‹?';
+$labels['savingdata'] = 'データをä¿å­˜ä¸­â€¦';
+$labels['errorsaving'] = '変更ãŒä¿å­˜ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚';
+$labels['operationfailed'] = 'è¦æ±‚ã•ã‚ŒãŸæ“作ãŒã§ãã¾ã›ã‚“ã§ã—ãŸã€‚';
+$labels['invalideventdates'] = '無効ãªæ—¥ä»˜ãŒå…¥åŠ›ã•ã‚Œã¾ã—ãŸ! 入力を確èªã—ã¦ãã ã•ã„。';
+$labels['invalidcalendarproperties'] = '無効ãªã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã®ãƒ—ロパティã§ã™! 有効ãªåå‰ã‚’設定ã—ã¦ãã ã•ã„。';
+$labels['searchnoresults'] = 'é¸æŠžã•ã‚ŒãŸã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã‚¤ãƒ™ãƒ³ãƒˆã¯è¦‹ã¤ã‹ã‚Šã¾ã›ã‚“。';
+$labels['successremoval'] = 'イベントを削除ã—ã¾ã—ãŸ';
+$labels['successrestore'] = 'イベントを復旧ã—ã¾ã—ãŸ';
+$labels['errornotifying'] = 'イベントå‚加者ã¸ã®é€šçŸ¥ãŒé€ä¿¡ã§ãã¾ã›ã‚“ã§ã—ãŸ';
+$labels['errorimportingevent'] = 'イベントã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆãŒã§ãã¾ã›ã‚“ã§ã—ãŸ';
+$labels['importwarningexists'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ã‚³ãƒ”ーã¯ã™ã§ã«ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã‚ã‚Šã¾ã™ã€‚';
+$labels['newerversionexists'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ã‚ˆã‚Šæ–°ã—ã„ヴァージョンãŒã™ã§ã«ã‚ã‚Šã¾ã™ã€‚中断ã•ã‚Œã¾ã—ãŸã€‚';
+$labels['nowritecalendarfound'] = 'イベントをä¿å­˜ã™ã‚‹ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ãŒè¦‹ã¤ã‹ã‚Šã¾ã›ã‚“';
+$labels['importedsuccessfully'] = '\'$calendar\' ã¸ã‚¤ãƒ™ãƒ³ãƒˆã‚’追加ã—ã¾ã—ãŸ';
+$labels['updatedsuccessfully'] = '\'$calendar\' 内ã®ã‚¤ãƒ™ãƒ³ãƒˆã®æ›´æ–°ã«æˆåŠŸã—ã¾ã—ãŸ';
+$labels['attendeupdateesuccess'] = '出席者状æ³ã‚’æ›´æ–°ã—ã¾ã—ãŸ';
+$labels['itipsendsuccess'] = '出席者ã¸æ‹›å¾…ã‚’é€ä¿¡ã—ã¾ã—ãŸã€‚';
+$labels['itipresponseerror'] = 'ã“ã®æ‹›å¾…ã®è¿”ä¿¡ã§ãã¾ã›ã‚“ã§ã—ãŸ';
+$labels['itipinvalidrequest'] = 'ã“ã®æ‹›å¾…ã¯é–“ã‚‚ãªã無効ã«ãªã‚Šã¾ã™';
+$labels['sentresponseto'] = '$mailto ã¸ã®æ‹›å¾…ã®è¿”ä¿¡ã—ã¾ã—ãŸ';
+$labels['localchangeswarning'] = 'ã‚ãªãŸã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã«ã®ã¿å映ã•ã‚Œã‚¤ãƒ™ãƒ³ãƒˆã®ã¾ã¨ã‚å½¹ã«é€šçŸ¥ã•ã‚Œãªã„変更を行ãŠã†ã¨ã—ã¦ã„ã¾ã™';
+$labels['importsuccess'] = '$nr イベントをインãƒãƒ¼ãƒˆã—ã¾ã—ãŸ';
+$labels['importnone'] = 'インãƒãƒ¼ãƒˆã•ã‚ŒãŸã‚¤ãƒ™ãƒ³ãƒˆã¯ã‚ã‚Šã¾ã›ã‚“';
+$labels['importerror'] = 'インãƒãƒ¼ãƒˆä¸­ã«ã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã—ã¾ã—ãŸã€‚';
+$labels['aclnorights'] = 'ã“ã®ã‚«ãƒ¬ãƒ³ãƒ€ãƒ¼ã®ç®¡ç†æ¨©é™ãŒã‚ã‚Šã¾ã›ã‚“。';
+$labels['changeeventconfirm'] = 'イベント変更';
+$labels['removeeventconfirm'] = 'イベントを削除';
+$labels['changerecurringeventwarning'] = 'ã“ã‚Œã¯ç¹°è¿”ã—イベントã§ã™ã€‚ç¾åœ¨ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ã¿ã€ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã¨ä»Šå¾Œã®å…¨ã‚¤ãƒ™ãƒ³ãƒˆã€å…¨ã‚¤ãƒ™ãƒ³ãƒˆã€ç·¨é›†ã—ãŸã„ã€ã‚‚ã—ãã¯æ–°ã—ã„イベントã¨ã—ã¦ä¿å­˜ã—ãŸã„?';
+$labels['removerecurringeventwarning'] = 'ã“ã‚Œã¯ç¹°è¿”ã—ã®ã‚¤ãƒ™ãƒ³ãƒˆã§ã™ã€‚ç¾åœ¨ã®ã‚¤ãƒ™ãƒ³ãƒˆã®ã¿å‰Šé™¤ã—ã¾ã™ã‹? ã“ã‚Œã¨å°†æ¥ã®ã™ã¹ã¦ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’削除ã—ã¾ã™ã‹? ã¾ãŸã¯å…¨ã¦ã‚’削除ã—ã¾ã™ã‹?';
+$labels['removerecurringallonly'] = 'ã“ã‚Œã¯ç¹°è¿”ã—イベントã§ã™ã€‚å‚加者ã¯å…¨ã¦ã®å‡ºæ¥äº‹ã«å¯¾ã—ã¦å‚加ã®ã¿ã‚’å–り消ã›ã¾ã™ã€‚';
+$labels['currentevent'] = 'ç¾åœ¨';
+$labels['futurevents'] = '今後';
+$labels['allevents'] = 'å…¨ã¦';
+$labels['saveasnew'] = 'æ–°è¦ä¿å­˜';
+$labels['birthdays'] = '誕生日';
+$labels['birthdayscalendar'] = '誕生日カレンダー';
+$labels['displaybirthdayscalendar'] = '誕生日をカレンダーã«è¡¨ç¤º';
+$labels['birthdayscalendarsources'] = 'ã“れらã®ã‚¢ãƒ‰ãƒ¬ã‚¹å¸³ã‹ã‚‰';
+$labels['birthdayeventtitle'] = '$name ã•ã‚“ã®èª•ç”Ÿæ—¥';
+$labels['birthdayage'] = 'å¹´é½¢ $age';
+$labels['objectchangelog'] = '変更履歴';
+$labels['objectdiff'] = '$rev1 ã‹ã‚‰ $rev2 ã¸ã®å¤‰æ›´';
+$labels['objectnotfound'] = 'イベントデータã®ãƒ­ãƒ¼ãƒ‰ã«å¤±æ•—ã—ã¾ã—ãŸ';
+$labels['objectchangelognotavailable'] = 'ã“ã®ã‚¤ãƒ™ãƒ³ãƒˆã®æ›´æ–°å±¥æ­´ã¯åˆ©ç”¨ã§ãã¾ã›ã‚“';
+$labels['objectdiffnotavailable'] = 'é¸æŠžã•ã‚ŒãŸãƒªãƒ“ジョンã§ã¯æ¯”較ã§ãã¾ã›ã‚“';
+$labels['revisionrestoreconfirm'] = '本当ã«ãƒªãƒ“ジョン $rev ã®ã‚¤ãƒ™ãƒ³ãƒˆã‚’復旧ã—ã¾ã™ã‹? ç¾åœ¨ã®ã‚¤ãƒ™ãƒ³ãƒˆã¯å¤ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®ã‚¤ãƒ™ãƒ³ãƒˆã«ç½®æ›ãˆã‚‰ã‚Œã¾ã™ã€‚';
+$labels['objectrestoresuccess'] = 'リビジョン $rev ã¯å¾©æ—§ã•ã‚Œã¾ã—ãŸ';
+$labels['objectrestoreerror'] = 'å¤ã„ãƒãƒ¼ã‚¸ãƒ§ãƒ³ã®å¾©æ—§ã«å¤±æ•—ã—ã¾ã—ãŸ';
+$labels['arialabelminical'] = 'カレンダー日付é¸æŠž';
+$labels['arialabelcalendarview'] = 'カレンダー表示';
+$labels['arialabelsearchform'] = 'イベント検索元';
+$labels['arialabelquicksearchbox'] = 'イベント検索入力';
+$labels['arialabelcalsearchform'] = 'カレンダー検索元';
+$labels['calendaractions'] = '行事カレンダー';
+$labels['arialabeleventattendees'] = 'イベントå‚加者リスト';
+$labels['arialabeleventresources'] = 'イベントリソースリスト';
+$labels['arialabelresourcesearchform'] = 'リソース検索元';
+$labels['arialabelresourceselection'] = '利用å¯èƒ½ãªãƒªã‚½ãƒ¼ã‚¹';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/ku_IQ.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/nl_NL.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Standaardweergave';
+$labels['time_format'] = 'Tijdnotatie';
+$labels['timeslots'] = 'Tijdvakken per uur';
+$labels['first_day'] = 'Eerste weekdag';
+$labels['first_hour'] = 'Eerste uur om weer te geven';
+$labels['workinghours'] = 'Werkuren';
+$labels['add_category'] = 'Categorie toevoegen';
+$labels['remove_category'] = 'Categorie verwijderen';
+$labels['defaultcalendar'] = 'Nieuwe activiteiten maken in';
+$labels['eventcoloring'] = 'Kleuren voor activiteiten';
+$labels['coloringmode0'] = 'Volgens agenda';
+$labels['coloringmode1'] = 'Volgens categorie';
+$labels['coloringmode2'] = 'Agenda voor overzicht, categorie voor inhoud';
+$labels['coloringmode3'] = 'Categorie voor overzicht, agenda voor inhoud';
+$labels['afternothing'] = 'Niets doen';
+$labels['aftertrash'] = 'Verplaatsen naar Prullenbak';
+$labels['afterdelete'] = 'Bericht verwijderen';
+$labels['afterflagdeleted'] = 'Markeren als verwijderd';
+$labels['aftermoveto'] = 'Verplaatsen naar...';
+$labels['itipoptions'] = 'Uitnodigingen voor activiteiten';
+$labels['afteraction'] = 'Nadat een uitnodiging of updatebericht is verwerkt';
+$labels['calendar'] = 'Agenda';
+$labels['calendars'] = 'Agenda\'s';
+$labels['category'] = 'Categorie';
+$labels['categories'] = 'Categorieën';
+$labels['createcalendar'] = 'Nieuwe agenda maken';
+$labels['editcalendar'] = 'Agenda-eigenschappen bewerken';
+$labels['name'] = 'Naam';
+$labels['color'] = 'Kleur';
+$labels['day'] = 'Dag';
+$labels['week'] = 'Week';
+$labels['month'] = 'Maand';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nieuw';
+$labels['new_event'] = 'Nieuwe activiteit';
+$labels['edit_event'] = 'Activiteit bewerken';
+$labels['edit'] = 'Bewerken';
+$labels['save'] = 'Opslaan';
+$labels['removelist'] = 'Verwijderen uit lijst';
+$labels['cancel'] = 'Annuleren';
+$labels['select'] = 'Selecteren';
+$labels['print'] = 'Afdrukken';
+$labels['printtitle'] = 'Agenda\'s afdrukken';
+$labels['title'] = 'Samenvatting';
+$labels['description'] = 'Beschrijving';
+$labels['all-day'] = 'hele dag';
+$labels['export'] = 'Exporteren';
+$labels['exporttitle'] = 'Exporteren naar iCalendar';
+$labels['exportrange'] = 'Activiteiten uit';
+$labels['exportattachments'] = 'Met bijlagen';
+$labels['customdate'] = 'Aangepaste datum';
+$labels['location'] = 'Locatie';
+$labels['url'] = 'URL';
+$labels['date'] = 'Datum';
+$labels['start'] = 'Begin';
+$labels['starttime'] = 'Begintijd';
+$labels['end'] = 'Einde';
+$labels['endtime'] = 'Eindtijd';
+$labels['repeat'] = 'Herhalen';
+$labels['selectdate'] = 'Datum kiezen';
+$labels['freebusy'] = 'Toon mij als';
+$labels['free'] = 'Beschikbaar';
+$labels['busy'] = 'Bezet';
+$labels['outofoffice'] = 'Niet aanwezig';
+$labels['tentative'] = 'Misschien';
+$labels['mystatus'] = 'Mijn status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Bevestigd';
+$labels['status-cancelled'] = 'Geannuleerd';
+$labels['priority'] = 'Prioriteit';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'openbaar';
+$labels['private'] = 'privé';
+$labels['confidential'] = 'vertrouwelijk';
+$labels['links'] = 'Referentie';
+$labels['alarms'] = 'Herinnering';
+$labels['comment'] = 'Opmerking';
+$labels['created'] = 'Gemaakt op';
+$labels['changed'] = 'Laatst gewijzigd op';
+$labels['unknown'] = 'Onbekend';
+$labels['eventoptions'] = 'Opties';
+$labels['generated'] = 'gegenereerd op';
+$labels['eventhistory'] = 'Geschiedenis';
+$labels['removelink'] = 'E-mailreferentie verwijderen';
+$labels['printdescriptions'] = 'Beschrijvingen afdrukken';
+$labels['parentcalendar'] = 'Invoegen in';
+$labels['searchearlierdates'] = '« Zoek eerdere activiteiten';
+$labels['searchlaterdates'] = 'Zoek latere activiteiten »';
+$labels['andnmore'] = 'Nog $nr...';
+$labels['togglerole'] = 'Klik om van rol te wisselen';
+$labels['createfrommail'] = 'Opslaan als activiteit';
+$labels['importevents'] = 'Activiteiten importeren';
+$labels['importrange'] = 'Activiteiten van';
+$labels['onemonthback'] = 'afgelopen maand';
+$labels['nmonthsback'] = 'afgelopen $nr maanden';
+$labels['showurl'] = 'URL van agenda tonen';
+$labels['showurldescription'] = 'Gebruik het volgende adres om uw agenda in andere programma\'s te gebruiken (alleen-lezen). U kunt dit knippen en plakken in elk agendaprogramma dat de iCal-indeling ondersteunt.';
+$labels['caldavurldescription'] = 'Kopieer dit adres naar een <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a>-client (bijv. Evolution of Mozilla Thunderbird) om deze specifieke agenda volledig te synchroniseren met uw computer of mobiele apparaat.';
+$labels['findcalendars'] = 'Agenda\'s zoeken...';
+$labels['searchterms'] = 'Zoektermen';
+$labels['calsearchresults'] = 'Beschikbare agenda\'s';
+$labels['calendarsubscribe'] = 'Definitief weergeven';
+$labels['nocalendarsfound'] = 'Geen agenda\'s gevonden';
+$labels['nrcalendarsfound'] = '$nr agenda\'s gevonden';
+$labels['quickview'] = 'Alleen deze agenda bekijken';
+$labels['invitationspending'] = 'Uitnodigingen in behandeling';
+$labels['invitationsdeclined'] = 'Afgeslagen uitnodigingen';
+$labels['changepartstat'] = 'Status van deelnemer wijzigen';
+$labels['rsvpcomment'] = 'Tekst van uitnodiging';
+$labels['listrange'] = 'Weer te geven bereik:';
+$labels['listsections'] = 'Verdelen in:';
+$labels['smartsections'] = 'Slimme secties';
+$labels['until'] = 'tot';
+$labels['today'] = 'Vandaag';
+$labels['tomorrow'] = 'Morgen';
+$labels['thisweek'] = 'Deze week';
+$labels['nextweek'] = 'Volgende week';
+$labels['prevweek'] = 'Vorige week';
+$labels['thismonth'] = 'Deze maand';
+$labels['nextmonth'] = 'Volgende maand';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Verleden';
+$labels['futureevents'] = 'Toekomst';
+$labels['showalarms'] = 'Herinneringen tonen';
+$labels['defaultalarmtype'] = 'Standaardinstelling herinnering';
+$labels['defaultalarmoffset'] = 'Standaardtijd herinnering';
+$labels['attendee'] = 'Deelnemer';
+$labels['role'] = 'Rol';
+$labels['availability'] = 'Beschikbaar';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Deelnemer toevoegen';
+$labels['roleorganizer'] = 'Organisator';
+$labels['rolerequired'] = 'Verplicht';
+$labels['roleoptional'] = 'Optioneel';
+$labels['rolechair'] = 'Voorzitter';
+$labels['rolenonparticipant'] = 'Afwezig';
+$labels['cutypeindividual'] = 'Persoon';
+$labels['cutypegroup'] = 'Groep';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Ruimte';
+$labels['availfree'] = 'Beschikbaar';
+$labels['availbusy'] = 'Bezet';
+$labels['availunknown'] = 'Onbekend';
+$labels['availtentative'] = 'Misschien';
+$labels['availoutofoffice'] = 'Niet aanwezig';
+$labels['delegatedto'] = 'Gedelegeerd aan:';
+$labels['delegatedfrom'] = 'Gedelegeerd door:';
+$labels['scheduletime'] = 'Beschikbaarheid zoeken';
+$labels['sendinvitations'] = 'Uitnodigingen versturen';
+$labels['sendnotifications'] = 'Wijzigingen melden aan deelnemers';
+$labels['sendcancellation'] = 'Annulering van activiteiten melden aan deelnemers';
+$labels['onlyworkinghours'] = 'Beschikbaarheid binnen mijn werkuren zoeken';
+$labels['reqallattendees'] = 'Verplichte/alle deelnemers';
+$labels['prevslot'] = 'Vorig tijdstip';
+$labels['nextslot'] = 'Volgend tijdstip';
+$labels['suggestedslot'] = 'Voorgesteld tijdstip';
+$labels['noslotfound'] = 'Geen beschikbaar tijdstip gevonden';
+$labels['invitationsubject'] = 'U bent uitgenodigd voor "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees\n\nAls bijlage vindt u een iCalendar-bestand met alle gegevens van de activiteit die u in uw agendaprogramma kunt importeren.";
+$labels['invitationattendlinks'] = "In het geval dat uw e-mailprogramma geen iTip-verzoeken ondersteunt, kunt u de volgende koppeling gebruiken om deze uitnodiging te accepteren or af te slaan:\n\$url";
+$labels['eventupdatesubject'] = '"$title" is bijgewerkt';
+$labels['eventupdatesubjectempty'] = 'Een activiteit die u aangaat is bijgewerkt';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees\n\nAls bijlage vindt u een iCalendar-bestand met de bijgewerkte gegevens van de activiteit die u in uw agendaprogramma kunt importeren.";
+$labels['eventcancelsubject'] = '"$title" is geannuleerd';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees\n\nDe activiteit is geannuleerd door \$organizer.\n\nAls bijlage vindt u een iCalendar-bestand met de bijgewerkte gegevens van de activiteit.";
+$labels['itipobjectnotfound'] = 'De activiteit die in dit bericht wordt vermeld staat niet in uw agenda.';
+$labels['itipmailbodyaccepted'] = "\$sender heeft de uitnodiging voor de volgende activiteit geaccepteerd:\n\n*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender heeft de uitnodiging voor de volgende activiteit onder voorbehoud geaccepteerd:\n\n*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender heeft de uitnodiging voor de volgende activiteit afgeslagen:\n\n*\$title*\n\nWanneer: \$date\n\nGenodigden: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender heeft uw deelname aan de volgende activiteit afgewezen:\n\n*\$title*\n\nWanneer: \$date";
+$labels['itipmailbodydelegated'] = "\$sender heeft de deelname aan de volgende activiteit gedelegeerd:\n\n*\$title*\n\nWanneer: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender heeft de deelname aan de volgende activiteit gedelegeerd aan u:\n\n*\$title*\n\nWanneer: \$date";
+$labels['itipdeclineevent'] = 'Wil u de uitnodiging voor deze activiteit afslaan?';
+$labels['declinedeleteconfirm'] = 'Wilt u deze afgewezen activiteit ook verwijderen uit uw agenda?';
+$labels['itipcomment'] = 'Opmerking bij uitnodiging/melding';
+$labels['itipcommenttitle'] = 'Deze opmerking wordt toegevoegd aan de uitnodiging/melding die naar de deelnemers wordt verstuurd';
+$labels['notanattendee'] = 'U staat niet op de lijst met deelnemers aan deze activiteit';
+$labels['eventcancelled'] = 'De activiteit is geannuleerd';
+$labels['saveincalendar'] = 'opslaan in';
+$labels['updatemycopy'] = 'Bijwerken in mijn agenda';
+$labels['savetocalendar'] = 'Opslaan in agenda';
+$labels['openpreview'] = 'Agenda controleren';
+$labels['noearlierevents'] = 'Geen eerdere activiteiten';
+$labels['nolaterevents'] = 'Geen latere activiteiten';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Resource boeken';
+$labels['findresources'] = 'Resources zoeken';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Beschikbaarheid';
+$labels['resourceowner'] = 'Eigenaar';
+$labels['resourceadded'] = 'De resource is toegevoegd aan uw activiteit';
+$labels['tabsummary'] = 'Samenvatting';
+$labels['tabrecurrence'] = 'Herhaling';
+$labels['tabattendees'] = 'Deelnemers';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Bijlagen';
+$labels['tabsharing'] = 'Delen';
+$labels['deleteobjectconfirm'] = 'Weet u zeker dat u deze activiteit wilt verwijderen?';
+$labels['deleteventconfirm'] = 'Weet u zeker dat u deze activiteit wilt verwijderen?';
+$labels['deletecalendarconfirm'] = 'Weet u zeker dat u deze agenda en alle activiteiten erin wilt verwijderen?';
+$labels['deletecalendarconfirmrecursive'] = 'Weet u zeker dat u deze agenda en alle activiteiten erin alsook de subagenda\'s wilt verwijderen?';
+$labels['savingdata'] = 'Gegevens opslaan...';
+$labels['errorsaving'] = 'Opslaan van wijzigingen is mislukt.';
+$labels['operationfailed'] = 'De gevraagde bewerking is mislukt.';
+$labels['invalideventdates'] = 'Ongeldige datums ingevoerd! Controleer de invoer.';
+$labels['invalidcalendarproperties'] = 'Ongeldige agenda-eigenschappen! Stel een geldige naam in.';
+$labels['searchnoresults'] = 'Geen activiteiten gevonden in de geselecteerde agenda\'s.';
+$labels['successremoval'] = 'De activiteit is met succes verwijderd.';
+$labels['successrestore'] = 'De activiteit is met succes hersteld.';
+$labels['errornotifying'] = 'Versturen van meldingen naar deelnemers van activiteit is mislukt';
+$labels['errorimportingevent'] = 'Importeren van activiteit is mislukt';
+$labels['importwarningexists'] = 'Een exemplaar van deze activiteit staat al in uw agenda.';
+$labels['newerversionexists'] = 'Er bestaat al een nieuwere versie van deze activiteit! Bewerking afgebroken.';
+$labels['nowritecalendarfound'] = 'Geen agenda gevonden om de activiteit op te slaan';
+$labels['importedsuccessfully'] = 'De activiteit is met succes toegevoegd aan \'$calendar\'';
+$labels['updatedsuccessfully'] = 'De activiteit is met succes bijgewerkt in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Status van deelnemer is met succes bijgewerkt';
+$labels['itipsendsuccess'] = 'Uitnodiging is naar deelnemers verstuurd.';
+$labels['itipresponseerror'] = 'Versturen van antwoord op uitnodiging voor activiteit is mislukt';
+$labels['itipinvalidrequest'] = 'Deze uitnodiging is niet meer geldig';
+$labels['sentresponseto'] = 'Antwoord op uitnodiging is met succes verstuurd naar $mailto';
+$labels['localchangeswarning'] = 'U gaat wijzigingen maken die alleen zichtbaar zullen zijn in uw eigen agenda en die niet naar de organisator van de activiteit zullen worden verstuurd.';
+$labels['importsuccess'] = '$nr activiteiten met succes geïmporteerd';
+$labels['importnone'] = 'Geen activiteiten gevonden om te importeren';
+$labels['importerror'] = 'Er is een fout tijdens het importeren opgetreden';
+$labels['aclnorights'] = 'U hebt geen administratorrechten voor deze agenda.';
+$labels['changeeventconfirm'] = 'Activiteit wijzigen';
+$labels['removeeventconfirm'] = 'Activiteit verwijderen';
+$labels['changerecurringeventwarning'] = 'Dit is een herhaalde activiteit. Wilt u alleen de huidige activiteit, deze en alle toekomstige herhalingen of alle herhalingen bewerken of wilt u deze opslaan als een nieuwe activiteit?';
+$labels['removerecurringeventwarning'] = 'Dit is een herhaalde activiteit. Wilt u alleen de huidige activiteit, deze en alle toekomstige herhalingen of alle herhalingen van deze activiteit verwijderen?';
+$labels['removerecurringallonly'] = 'Dit is een herhaalde activiteit. Als deelnemer kunt u alleen de volledige activiteit met alle herhalingen verwijderen.';
+$labels['currentevent'] = 'Huidige';
+$labels['futurevents'] = 'Toekomstige';
+$labels['allevents'] = 'Alle';
+$labels['saveasnew'] = 'Opslaan als nieuw';
+$labels['birthdays'] = 'Verjaardagen';
+$labels['birthdayscalendar'] = 'Verjaardagskalender';
+$labels['displaybirthdayscalendar'] = 'Verjaardagskalender weergeven';
+$labels['birthdayscalendarsources'] = 'Uit deze adresboeken';
+$labels['birthdayeventtitle'] = 'verjaardag van $name';
+$labels['birthdayage'] = '$age jaar';
+$labels['objectchangelog'] = 'Geschiedenis wijzigen';
+$labels['objectdiff'] = 'Wijzigingen van $rev1 tot $rev2';
+$labels['objectnotfound'] = 'Laden van activiteitsgegevens is mislukt';
+$labels['objectchangelognotavailable'] = 'Voor deze activiteit kunt u de geschiedenis niet wijzigen';
+$labels['objectdiffnotavailable'] = 'Geen vergelijking mogelijk voor de geselecteerde revisies';
+$labels['revisionrestoreconfirm'] = 'Weet u zeker dat u revisie $rev van deze activiteit wilt herstellen? Met deze actie vervangt u de huidige activiteit door de oude versie.';
+$labels['objectrestoresuccess'] = 'Revisie $rev met succes hersteld';
+$labels['objectrestoreerror'] = 'Herstellen van oude revisie is mislukt';
+$labels['arialabelminical'] = 'Datum in agenda selecteren';
+$labels['arialabelcalendarview'] = 'Agendaweergave';
+$labels['arialabelsearchform'] = 'Zoekformulier voor activiteiten';
+$labels['arialabelquicksearchbox'] = 'Zoekopdracht voor activiteiten';
+$labels['arialabelcalsearchform'] = 'Zoekformulier voor agenda\'s';
+$labels['calendaractions'] = 'Acties voor agenda';
+$labels['arialabeleventattendees'] = 'Lijst met deelnemers van activiteit';
+$labels['arialabeleventresources'] = 'Lijst met resources voor activiteit';
+$labels['arialabelresourcesearchform'] = 'Zoekformulier voor resources';
+$labels['arialabelresourceselection'] = 'Beschikbare resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/pl.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Domyślny widok';
+$labels['time_format'] = 'Format czasu';
+$labels['first_day'] = 'Pierwszy dzień tygodnia';
+$labels['first_hour'] = 'Pierwsza godzina do wyświetlenia';
+$labels['workinghours'] = 'Godziny pracy';
+$labels['add_category'] = 'Dodaj kategoriÄ™';
+$labels['remove_category'] = 'Usuń kategorię';
+$labels['afternothing'] = 'Nie rób nic';
+$labels['aftertrash'] = 'PrzenieÅ› do kosza';
+$labels['afterdelete'] = 'Usuń wiadomość';
+$labels['afterflagdeleted'] = 'Oznacz jako usunięte';
+$labels['aftermoveto'] = 'PrzenieÅ› do...';
+$labels['calendar'] = 'Kalendarz';
+$labels['calendars'] = 'Kalendarze';
+$labels['category'] = 'Kategoria';
+$labels['categories'] = 'Kategorie';
+$labels['createcalendar'] = 'Utwórz nowy kalendarz';
+$labels['editcalendar'] = 'Edytuj właściwości kalendarza';
+$labels['name'] = 'Nazwa';
+$labels['color'] = 'Kolor';
+$labels['day'] = 'Dzień';
+$labels['week'] = 'Tydzień';
+$labels['month'] = 'MiesiÄ…c';
+$labels['new'] = 'Nowy';
+$labels['new_event'] = 'Nowe zdarzenie';
+$labels['edit_event'] = 'Edytuj zdarzenie';
+$labels['edit'] = 'Edytuj';
+$labels['save'] = 'Zapisz';
+$labels['removelist'] = 'Usuń z listy';
+$labels['cancel'] = 'Anuluj';
+$labels['print'] = 'Drukuj';
+$labels['printtitle'] = 'Drukuj kalendarze';
+$labels['title'] = 'Podsumowanie';
+$labels['description'] = 'Opis';
+$labels['export'] = 'Eksport';
+$labels['exporttitle'] = 'Eksport do iCalendar';
+$labels['url'] = 'URL';
+$labels['date'] = 'Data';
+$labels['start'] = 'Start';
+$labels['end'] = 'Koniec';
+$labels['repeat'] = 'Powtórz';
+$labels['selectdate'] = 'Wybierz datÄ™';
+$labels['free'] = 'Wolny';
+$labels['busy'] = 'Zajęty';
+$labels['outofoffice'] = 'Poza biurem';
+$labels['status-cancelled'] = 'Anulowane';
+$labels['priority'] = 'Priorytet';
+$labels['sensitivity'] = 'Prywatność';
+$labels['created'] = 'Utworzone';
+$labels['changed'] = 'Ostatnia modyfikacja';
+$labels['unknown'] = 'Nieznane';
+$labels['eventoptions'] = 'Opcje';
+$labels['eventhistory'] = 'Historia';
+$labels['andnmore'] = '$nr więcej...';
+$labels['createfrommail'] = 'Zapisz jako zdarzenie';
+$labels['importevents'] = 'Importuj zdarzenia';
+$labels['showurl'] = 'Pokaż URL kalendarza';
+$labels['calsearchresults'] = 'Dostępne Kalendarze';
+$labels['nocalendarsfound'] = 'Nie znaleziono kalendarzy';
+$labels['quickview'] = 'Pokaż tylko ten kalendarz';
+$labels['today'] = 'Dzisiaj';
+$labels['tomorrow'] = 'Jutro';
+$labels['thisweek'] = 'Ten tydzień';
+$labels['nextweek'] = 'Następny tydzień';
+$labels['prevweek'] = 'Poprzedni tydzień';
+$labels['thismonth'] = 'Ten miesiÄ…c';
+$labels['nextmonth'] = 'Następny miesiąc';
+$labels['weekofyear'] = 'Tydzień';
+$labels['rolerequired'] = 'Wymagane';
+$labels['availfree'] = 'Wolny';
+$labels['availbusy'] = 'Zajęty';
+$labels['availunknown'] = 'Nieznane';
+$labels['availoutofoffice'] = 'Poza biurem';
+$labels['saveincalendar'] = 'zapisz w';
+$labels['savetocalendar'] = 'Zapisz w kalendarzu';
+$labels['openpreview'] = 'Sprawdź kalendarz';
+$labels['noearlierevents'] = 'Brak wcześniejszych zdarzeń';
+$labels['nolaterevents'] = 'Brak późniejszych zdarzeń';
+$labels['resourcedetails'] = 'Szczegóły';
+$labels['resourceavailability'] = 'Dostępność';
+$labels['resourceowner'] = 'Właściciel';
+$labels['tabsummary'] = 'Podsumowanie';
+$labels['deleteobjectconfirm'] = 'Czy na pewno chcesz usunąć to zdarzenie?';
+$labels['deleteventconfirm'] = 'Czy na pewno chcesz usunąć to zdarzenie?';
+$labels['deletecalendarconfirm'] = 'Czy na pewno chcesz usunąć ten kalendarz i wszystkie jego zdarzenia?';
+$labels['changeeventconfirm'] = 'Zmodyfikuj zdarzenie';
+$labels['removeeventconfirm'] = 'Usuń zdarzenie';
+$labels['allevents'] = 'Wszystko';
+$labels['saveasnew'] = 'Zapisz jako nowy';
+$labels['birthdays'] = 'Urodziny';
+$labels['birthdayscalendar'] = 'Kalendarz urodzin';
+$labels['displaybirthdayscalendar'] = 'Wyświetl kalendarz urodzin';
+$labels['birthdayage'] = 'Wiek $age';
+$labels['user'] = 'Użytkownik';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/pl_PL.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Domyślny widok';
+$labels['time_format'] = 'Format czasu';
+$labels['timeslots'] = 'Przedziały czasowe w ciągu godziny';
+$labels['first_day'] = 'Pierwszy dzień tygodnia';
+$labels['first_hour'] = 'Pierwsza godzina';
+$labels['workinghours'] = 'Godziny robocze';
+$labels['add_category'] = 'Dodaj kategoriÄ™';
+$labels['remove_category'] = 'Usuń kategorię';
+$labels['defaultcalendar'] = 'Twórz nowe zdarzenia w';
+$labels['eventcoloring'] = 'Kolor zdarzenia';
+$labels['coloringmode0'] = 'Zgodnie z kalendarzem';
+$labels['coloringmode1'] = 'Zgodnie z kategoriÄ…';
+$labels['coloringmode2'] = 'Kalendarz dla obramowania, kategoria dla środka';
+$labels['coloringmode3'] = 'Kategoria dla obramowania, kalendarz dla środka';
+$labels['afternothing'] = 'Nie rób nic';
+$labels['aftertrash'] = 'PrzenieÅ› do Kosza';
+$labels['afterdelete'] = 'Usuń wiadomość';
+$labels['afterflagdeleted'] = 'Oznacz jako usunięta';
+$labels['aftermoveto'] = 'PrzenieÅ› do...';
+$labels['itipoptions'] = 'Zaproszenia';
+$labels['afteraction'] = 'Wiadomość jest przetwarzana po zaproszeniu lub aktualizacji';
+$labels['calendar'] = 'Kalendarz';
+$labels['calendars'] = 'Kalendarze';
+$labels['category'] = 'Kategoria';
+$labels['categories'] = 'Kategorie';
+$labels['createcalendar'] = 'Utwórz nowy kalendarz';
+$labels['editcalendar'] = 'Edytuj właściwości kalendarza';
+$labels['name'] = 'Nazwa';
+$labels['color'] = 'Kolor';
+$labels['day'] = 'Dzień';
+$labels['week'] = 'Tydzień';
+$labels['month'] = 'MiesiÄ…c';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nowy';
+$labels['new_event'] = 'Nowe zdarzenie';
+$labels['edit_event'] = 'Edytuj zdarzenie';
+$labels['edit'] = 'Edytuj';
+$labels['save'] = 'Zapisz';
+$labels['removelist'] = 'Usuń z listy';
+$labels['cancel'] = 'Anuluj';
+$labels['select'] = 'Wybierz';
+$labels['print'] = 'Drukuj';
+$labels['printtitle'] = 'Drukuj kalendarze';
+$labels['title'] = 'Podsumowanie';
+$labels['description'] = 'Opis';
+$labels['all-day'] = 'cały dzień';
+$labels['export'] = 'Eksport';
+$labels['exporttitle'] = 'Eksport w formacie iCalendar';
+$labels['exportrange'] = 'Zdarzenia z';
+$labels['exportattachments'] = 'Z załącznikami';
+$labels['customdate'] = 'WÅ‚asna data';
+$labels['location'] = 'Położenie';
+$labels['url'] = 'Adres URL';
+$labels['date'] = 'Data';
+$labels['start'] = 'PoczÄ…tek';
+$labels['starttime'] = 'PoczÄ…tek';
+$labels['end'] = 'Koniec';
+$labels['endtime'] = 'Koniec';
+$labels['repeat'] = 'Powtórz';
+$labels['selectdate'] = 'Wybierz datÄ™';
+$labels['freebusy'] = 'Pokaż mnie jako';
+$labels['free'] = 'Wolny';
+$labels['busy'] = 'Zajęty';
+$labels['outofoffice'] = 'Poza biurem';
+$labels['tentative'] = 'Niepewny';
+$labels['mystatus'] = 'Mój status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Potwierdzony';
+$labels['status-cancelled'] = 'Anulowany';
+$labels['priority'] = 'Priorytet';
+$labels['sensitivity'] = 'Poufność';
+$labels['public'] = 'publiczny';
+$labels['private'] = 'prywatny';
+$labels['confidential'] = 'poufny';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Przypomnienie';
+$labels['comment'] = 'Komentarz';
+$labels['created'] = 'Utworzono';
+$labels['changed'] = 'Ostatnia modyfikacja';
+$labels['unknown'] = 'Nieznany';
+$labels['eventoptions'] = 'Opcje';
+$labels['generated'] = 'wygenerowano';
+$labels['eventhistory'] = 'Historia';
+$labels['removelink'] = 'Usuń odnośnik e-mail';
+$labels['printdescriptions'] = 'Drukuj opisy';
+$labels['parentcalendar'] = 'Wstaw wewnÄ…trz';
+$labels['searchearlierdates'] = '« Szukaj wcześniejszych zdarzeń';
+$labels['searchlaterdates'] = 'Szukaj późniejszych zdarzeń »';
+$labels['andnmore'] = '$nr więcej...';
+$labels['togglerole'] = 'Kliknuj aby przestawić rolę';
+$labels['createfrommail'] = 'Zapisz jako zdarzenie';
+$labels['importevents'] = 'Importuj zdarzenia';
+$labels['importrange'] = 'Zdarzenia z';
+$labels['onemonthback'] = '1 miesiÄ…c wstecz';
+$labels['nmonthsback'] = '$nr miesięcy wstecz';
+$labels['showurl'] = 'Pokaż adres URL kalendarza';
+$labels['showurldescription'] = 'Używaj tego adresu aby dostać się do kalendarza z innych programów (w trybie tylko-do-odczytu). Możesz wkleić go do dowolnej aplikacji obsługującej format iCal.';
+$labels['caldavurldescription'] = 'Skopiuj ten adres do aplikacji obsługującej format <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> (np. Evolution lub Mozilla Thunderbird) aby zsynchronizować wybrany kalendarz z twoim komputerem lub urządzeniem przenośnym.';
+$labels['findcalendars'] = 'Wyszukaj kalendarze...';
+$labels['searchterms'] = 'Szukana fraza';
+$labels['calsearchresults'] = 'Dostępne kalendarze';
+$labels['calendarsubscribe'] = 'Dodaj do listy na stałe';
+$labels['nocalendarsfound'] = 'Nie znaleziono żadych kalendarzy';
+$labels['nrcalendarsfound'] = 'znaleziono $nr kalendarzy';
+$labels['quickview'] = 'Pokaż tylko ten kalendarz';
+$labels['invitationspending'] = 'OczekujÄ…ce zaproszenia';
+$labels['invitationsdeclined'] = 'Odrzucone zaproszenia';
+$labels['changepartstat'] = 'Zmień status uczestnika';
+$labels['rsvpcomment'] = 'Treść zaproszenia';
+$labels['listrange'] = 'Zakres do pokazania:';
+$labels['listsections'] = 'Podziel na:';
+$labels['smartsections'] = 'Inteligentne sekcje';
+$labels['until'] = 'dopóki';
+$labels['today'] = 'Dzisiaj';
+$labels['tomorrow'] = 'Jutro';
+$labels['thisweek'] = 'Bieżący tydzień';
+$labels['nextweek'] = 'Następny tydzień';
+$labels['prevweek'] = 'Poprzedni tydzień';
+$labels['thismonth'] = 'Bieżący miesiąc';
+$labels['nextmonth'] = 'Następny miesiąc';
+$labels['weekofyear'] = 'Tydzień';
+$labels['pastevents'] = 'Przeszłe';
+$labels['futureevents'] = 'Przyszłe';
+$labels['showalarms'] = 'Pokaż powiadomienia';
+$labels['defaultalarmtype'] = 'Domyślne powiadomienie';
+$labels['defaultalarmoffset'] = 'Domyślny czas powiadomienia';
+$labels['attendee'] = 'Uczestnik';
+$labels['role'] = 'Rola';
+$labels['availability'] = 'Dostępny';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Dodaj uczestnika';
+$labels['roleorganizer'] = 'Organizator';
+$labels['rolerequired'] = 'Wymagany';
+$labels['roleoptional'] = 'Opcjonalny';
+$labels['rolechair'] = 'PrzewodniczÄ…cy';
+$labels['rolenonparticipant'] = 'Nieobecny';
+$labels['cutypeindividual'] = 'Osoba';
+$labels['cutypegroup'] = 'Grupa';
+$labels['cutyperesource'] = 'Zasób';
+$labels['cutyperoom'] = 'Pokój';
+$labels['availfree'] = 'Wolny';
+$labels['availbusy'] = 'Zajęty';
+$labels['availunknown'] = 'Nieznany';
+$labels['availtentative'] = 'Niepewny';
+$labels['availoutofoffice'] = 'Poza biurem';
+$labels['delegatedto'] = 'Oddelegowany do:';
+$labels['delegatedfrom'] = 'Oddelegowany z:';
+$labels['scheduletime'] = 'Sprawdź dostępność';
+$labels['sendinvitations'] = 'Wyślij zaproszenia';
+$labels['sendnotifications'] = 'Powiadom uczestników o zmianach';
+$labels['sendcancellation'] = 'Powiadom uczestników o anulowaniu zdarzenia';
+$labels['onlyworkinghours'] = 'Sprawdź dostępność w moich godzinach pracy';
+$labels['reqallattendees'] = 'Wymagany/wszyscy uczestnicy';
+$labels['prevslot'] = 'Poprzedni przedział';
+$labels['nextslot'] = 'Następny przedział';
+$labels['suggestedslot'] = 'Sugerowany przedział';
+$labels['noslotfound'] = 'Nie znaleziono wolnego przedziału czasu';
+$labels['invitationsubject'] = 'Zostałeś zaproszony do "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees\n\nW załączeniu plik w formacie iCalendar ze szczegółami zdarzenia, który możesz zaimportować do twojej aplikacji kalendarza.";
+$labels['invitationattendlinks'] = "W przypadku gdy klient poczty elektronicznej nie obsługuje rządań w formacie iTip, aby zaakceptować lub odrzucić to zaproszenie, można skorzystać z następującego linku:\n\$url ";
+$labels['eventupdatesubject'] = '"$title" zostało zaktualizowane';
+$labels['eventupdatesubjectempty'] = 'Zdarzenie które cię dotyczy zostało zaktualizowane';
+$labels['eventupdatemailbody'] = "*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees\n\nW załączeniu plik w formacie iCalendar zawierający zaktualizowane szczegóły zdarzenia, które możesz zaimportować do swojej aplikacji kalendarza.";
+$labels['eventcancelsubject'] = '"$title" zostało anulowane';
+$labels['eventcancelmailbody'] = "*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees\n\nZdarzenie zostało anulowane przez \$organizer.\n\nW załączeniu plik w formacie iCalendar ze zaktualizowanymi szczegółami zdarzenia.";
+$labels['itipobjectnotfound'] = 'W twoim kalendarzu nie znaleziono zdarzenia związanego z tą wiadomością.';
+$labels['itipmailbodyaccepted'] = "\$sender zaakceptował zaproszenie do następującego zdarzenia:\n\n*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender warunkowo zaakceptował zaproszenie do następującego zdarzenia:\n\n*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender odrzucił zaproszenie na następujące zdarzenie:\n\n*\$title*\n\nKiedy: \$date\n\nZaproszeni: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender odrzucił twój udział w zastępującym zdarzeniu:\n\n*\$title*\n\nKiedy: \$date";
+$labels['itipmailbodydelegated'] = "\$sender oddelegował udział w następującym wydarzeniu:\n\n*\$title*\n\nKiedy: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender oddelegował do ciebie udział w następującym wydarzeniu:\n\n*\$title*\n\nKiedy: \$date";
+$labels['itipdeclineevent'] = 'Czy chcesz odrzucić zaproszenie na to zdarzenie?';
+$labels['declinedeleteconfirm'] = 'Czy chcesz także usunąć to odrzucone zdarzenie ze swojego kalendarza?';
+$labels['itipcomment'] = 'Komentarz zaproszenia/powiadomienia';
+$labels['itipcommenttitle'] = 'Komentarz ten będzie dołączony do wiadomości wysłanej do uczestników zdarzenia';
+$labels['notanattendee'] = 'Nie jesteś na liście uczestników tego zdarzenia';
+$labels['eventcancelled'] = 'Zdarzenie zostało anulowane';
+$labels['saveincalendar'] = 'zapisz w';
+$labels['updatemycopy'] = 'Uaktualnij w moim kalendarzu';
+$labels['savetocalendar'] = 'Zapisz do kalendarza';
+$labels['openpreview'] = 'Sprawdź kalendarz';
+$labels['noearlierevents'] = 'Brak wcześniejszych zdarzeń';
+$labels['nolaterevents'] = '« Brak póżniejszych zdarzeń';
+$labels['resource'] = 'Zasób';
+$labels['addresource'] = 'Rezerwuj zasób';
+$labels['findresources'] = 'Wyszukaj zasoby';
+$labels['resourcedetails'] = 'Szczegóły';
+$labels['resourceavailability'] = 'Dostępność';
+$labels['resourceowner'] = 'Właściciel';
+$labels['resourceadded'] = 'Zasób został dodany do twojego zdarzenia';
+$labels['tabsummary'] = 'Podsumowanie';
+$labels['tabrecurrence'] = 'Powtarzalność';
+$labels['tabattendees'] = 'Uczestnicy';
+$labels['tabresources'] = 'Zasoby';
+$labels['tabattachments'] = 'Załączniki';
+$labels['tabsharing'] = 'Udostępnianie';
+$labels['deleteobjectconfirm'] = 'Czy na pewno chcesz usunąć to zdarzenie?';
+$labels['deleteventconfirm'] = 'Czy na pewno chcesz usunąć to zdarzenie?';
+$labels['deletecalendarconfirm'] = 'Czy na pewno chcesz usunąć ten kalendarz z wszystkimi zadaniami?';
+$labels['deletecalendarconfirmrecursive'] = 'Czy na pewno chcesz usunąć ten kalendarz ze wszystkimi zdarzeniami i pod-kalendarzami?';
+$labels['savingdata'] = 'ZapisujÄ™ dane...';
+$labels['errorsaving'] = 'BÅ‚Ä…d podczas zapisu danych.';
+$labels['operationfailed'] = 'Żądana operacja nie powiodła się.';
+$labels['invalideventdates'] = 'Błędna data! Proszę sprawdzić wprowadzone dane.';
+$labels['invalidcalendarproperties'] = 'Błędna właściwość kalendarza! Proszę podać poprawną nazwę.';
+$labels['searchnoresults'] = 'Nie znaleziono zdarzeń w wybranym kalendarzu.';
+$labels['successremoval'] = 'Zdarzenie zostało usunięte.';
+$labels['successrestore'] = 'Zdarzenie zostało przywrócone.';
+$labels['errornotifying'] = 'Nie udało się wysłać powiadomień do uczestników zdarzenia';
+$labels['errorimportingevent'] = 'Nie udało się zaimportować zdarzenia';
+$labels['importwarningexists'] = 'Kopia tego zdarzenia już istnieje w twoim kalendarzu.';
+$labels['newerversionexists'] = 'Istnieje nowsza wersja tego zdarzenia ! Przerwano.';
+$labels['nowritecalendarfound'] = 'Nie znaleziono kalendarza aby zapisać zdarzenie.';
+$labels['importedsuccessfully'] = 'Zdarzenie dodano do \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Zdarzenie zostało pomyślnie zaktualizowane w \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Zaktualizowano status uczestnika.';
+$labels['itipsendsuccess'] = 'Wysłano zaproszenia do uczestników.';
+$labels['itipresponseerror'] = 'Nie udało się wysłać odpowiedzi na to zaproszenie.';
+$labels['itipinvalidrequest'] = 'To zaproszenie nie jest już aktualne.';
+$labels['sentresponseto'] = 'Wysłano odpowiedź na zaproszenie do $mailto.';
+$labels['localchangeswarning'] = 'Zamierzasz dokonać zmian, które mogą zostać wykonane tylko w twoim kalendarzu i nie zostaną wysłane do organizatora zdarzenia.';
+$labels['importsuccess'] = 'Zaimportowano $nr zdarzeń.';
+$labels['importnone'] = 'Nie znaleziono zdarzeń do zaimportowania.';
+$labels['importerror'] = 'Wystąpił błąd podczas importu.';
+$labels['aclnorights'] = 'Nie masz uprawnień administracyjnych dla tego kalendarza.';
+$labels['changeeventconfirm'] = 'Zmień zdarzenie';
+$labels['removeeventconfirm'] = 'Usuń zdarzenie';
+$labels['changerecurringeventwarning'] = 'To jest zdarzenie powtarzalne. Czy chcesz zmienić bieżące zdarzenie, bieżące i przyszłe, wszystkie, a może zapisać je jako nowe zdarzenie?';
+$labels['removerecurringeventwarning'] = 'Jest to zdarzenie cykliczne. Czy chcesz usunąć wyłącznie bieżące zdarzenie i jego przyszłe wystąpienia, czy wszystkie wystąpienia tego zdarzenia?';
+$labels['removerecurringallonly'] = 'Jest to zdarzenie cykliczne. Jako uczestnik, możesz jedynie usunąć całe zdarzenie ze wszystkimi jego wystąpieniami.';
+$labels['currentevent'] = 'Bieżące';
+$labels['futurevents'] = 'Przyszłe';
+$labels['allevents'] = 'Wszystkie';
+$labels['saveasnew'] = 'Zapisz jako nowe';
+$labels['birthdays'] = 'Uruodziny';
+$labels['birthdayscalendar'] = 'Kalendarz Urodzin';
+$labels['displaybirthdayscalendar'] = 'Wyświetl kalendarz urodzin';
+$labels['birthdayscalendarsources'] = 'Z tych książek adresowych';
+$labels['birthdayeventtitle'] = 'Urodziny $name\'s';
+$labels['birthdayage'] = 'Wiek $age';
+$labels['objectchangelog'] = 'Historia zmian';
+$labels['objectdiff'] = 'Zmiany od $rev1 do $rev2';
+$labels['objectnotfound'] = 'Nie udało się wczytać zdarzenia';
+$labels['objectchangelognotavailable'] = 'Historia zmian jest niedostępna dla tego zdarzenia';
+$labels['objectdiffnotavailable'] = 'Nie można porównać wybranych wersji';
+$labels['revisionrestoreconfirm'] = 'Czy na pewno chcesz przywrócić wersję $rev tego zdarzenia? Bierzące zdarzenie zostanie zastąpione starszą wersją.';
+$labels['objectrestoresuccess'] = 'Wersja $rev została pomyślnie przywrócona';
+$labels['objectrestoreerror'] = 'Nie udało się przywrócić starej wersji';
+$labels['arialabelminical'] = 'Wybór daty kalendarza';
+$labels['arialabelcalendarview'] = 'PodglÄ…d kalendarza';
+$labels['arialabelsearchform'] = 'Formularz wyszukiwania zdarzeń';
+$labels['arialabelquicksearchbox'] = 'Fraza wyszukiwania zdarzeń';
+$labels['arialabelcalsearchform'] = 'Formularz wyszukiwania kalendarzy';
+$labels['calendaractions'] = 'Akcje kalendarzy';
+$labels['arialabeleventattendees'] = 'Lista uczestników zdarzenia';
+$labels['arialabeleventresources'] = 'Lista zasobów zdarzenia';
+$labels['arialabelresourcesearchform'] = 'Formularz wyszukiwania zasobów';
+$labels['arialabelresourceselection'] = 'Dostępne zasoby';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/pt_BR.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,220 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Visualização padrão';
+$labels['time_format'] = 'Formato da hora';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'Primeiro dia da semana';
+$labels['first_hour'] = 'Primeira hora a mostrar';
+$labels['workinghours'] = 'Horário de trabalho';
+$labels['add_category'] = 'Adicionar categoria';
+$labels['remove_category'] = 'Remover categoria';
+$labels['defaultcalendar'] = 'Criar novos eventos em';
+$labels['eventcoloring'] = 'Coloração de evento';
+$labels['coloringmode0'] = 'De acordo com o calendário';
+$labels['coloringmode1'] = 'De acordo com a categoria';
+$labels['coloringmode2'] = 'Calendário para esboço, categoria para conteúdo';
+$labels['coloringmode3'] = 'Categoria para esboço, calendário para conteúdo';
+$labels['calendar'] = 'Calendário';
+$labels['calendars'] = 'Calendários';
+$labels['category'] = 'Categoria';
+$labels['categories'] = 'Categorias';
+$labels['createcalendar'] = 'Criar novo calendário';
+$labels['editcalendar'] = 'Editar propriedades do calendário';
+$labels['name'] = 'Nome';
+$labels['color'] = 'Cor';
+$labels['day'] = 'Dia';
+$labels['week'] = 'Semana';
+$labels['month'] = 'Mês';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Novo';
+$labels['new_event'] = 'Novo evento';
+$labels['edit_event'] = 'Editar evento';
+$labels['edit'] = 'Editar';
+$labels['save'] = 'Gravar';
+$labels['removelist'] = 'Remover da lista';
+$labels['cancel'] = 'Cancelar';
+$labels['select'] = 'Selecionar';
+$labels['print'] = 'Imprimir';
+$labels['printtitle'] = 'Imprimir calendários';
+$labels['title'] = 'Sumário';
+$labels['description'] = 'Descrição';
+$labels['all-day'] = 'dia todo';
+$labels['export'] = 'Exportar';
+$labels['exporttitle'] = 'Exportar para iCalendar';
+$labels['exportrange'] = 'Eventos de';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Local';
+$labels['url'] = 'URL';
+$labels['date'] = 'Data';
+$labels['start'] = 'Início';
+$labels['starttime'] = 'Hora de início';
+$labels['end'] = 'Término';
+$labels['repeat'] = 'Repetir';
+$labels['selectdate'] = 'Escolha a data';
+$labels['freebusy'] = 'Mostrar me como';
+$labels['free'] = 'Disponível';
+$labels['busy'] = 'Ocupado';
+$labels['outofoffice'] = 'Fora de escritório';
+$labels['tentative'] = 'Tentativa';
+$labels['status'] = 'Situação';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancalado';
+$labels['priority'] = 'Prioridade';
+$labels['sensitivity'] = 'Privacidade';
+$labels['public'] = 'público';
+$labels['private'] = 'privado';
+$labels['confidential'] = 'confidencial';
+$labels['links'] = 'Reference';
+$labels['alarms'] = 'Lembrete';
+$labels['comment'] = 'Comentário';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Última modificação';
+$labels['unknown'] = 'Desconhecido';
+$labels['eventoptions'] = 'Opções';
+$labels['generated'] = 'gerado em';
+$labels['eventhistory'] = 'História';
+$labels['removelink'] = 'Remover referência do email';
+$labels['printdescriptions'] = 'Descrições de impressão';
+$labels['parentcalendar'] = 'Inserir dentro';
+$labels['searchearlierdates'] = '« Procurar por eventos anteriores';
+$labels['searchlaterdates'] = 'Procurar por eventos posteriores »';
+$labels['andnmore'] = '$nr mais...';
+$labels['togglerole'] = 'Clique para alternar o papel';
+$labels['createfrommail'] = 'Salvar como evento';
+$labels['importevents'] = 'Importar eventos';
+$labels['importrange'] = 'Eventos de';
+$labels['onemonthback'] = '1 mês atrás';
+$labels['nmonthsback'] = '$nr meses atrás';
+$labels['showurl'] = 'Mostrar URL do calendário';
+$labels['showurldescription'] = 'Use o seguinte endereço para acessar (somente leitura) seu calendário em outras aplicações. Você pode copiar e colar este endereço em qualquer software de calendário que suporte o formato iCal.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Buscar calendários...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Calendários disponíveis';
+$labels['calendarsubscribe'] = 'Listar permanentemente';
+$labels['listrange'] = 'Intervalo para exibir:';
+$labels['listsections'] = 'Dividir em:';
+$labels['smartsections'] = 'Seções inteligentes';
+$labels['until'] = 'até';
+$labels['today'] = 'Hoje';
+$labels['tomorrow'] = 'Amanhã';
+$labels['thisweek'] = 'Esta semana';
+$labels['nextweek'] = 'Próxima semana';
+$labels['thismonth'] = 'Este mês';
+$labels['nextmonth'] = 'Próximo mês';
+$labels['weekofyear'] = 'Semana';
+$labels['pastevents'] = 'Passado';
+$labels['futureevents'] = 'Futuro';
+$labels['showalarms'] = 'Mostrar lembrentes';
+$labels['defaultalarmtype'] = 'Configuração de lembrete padrão';
+$labels['defaultalarmoffset'] = 'Horário padrão de lembrete';
+$labels['attendee'] = 'Participante';
+$labels['role'] = 'Papel';
+$labels['availability'] = 'Disp.';
+$labels['confirmstate'] = 'Situação';
+$labels['addattendee'] = 'Adicionar participante';
+$labels['roleorganizer'] = 'Organizador';
+$labels['rolerequired'] = 'Obrigatório';
+$labels['roleoptional'] = 'Opcional';
+$labels['rolechair'] = 'Cadeira';
+$labels['cutypegroup'] = 'Grupo';
+$labels['cutyperesource'] = 'Recurso';
+$labels['availfree'] = 'Disponível';
+$labels['availbusy'] = 'Ocupado';
+$labels['availunknown'] = 'Desconhecido';
+$labels['availtentative'] = 'Tentativa';
+$labels['availoutofoffice'] = 'Fora de escritório';
+$labels['delegatedto'] = 'Delegado para:';
+$labels['delegatedfrom'] = 'Delegado de:';
+$labels['scheduletime'] = 'Procurar disponibilidade';
+$labels['sendinvitations'] = 'Enviar convites';
+$labels['sendnotifications'] = 'Avisar os participantes sobre as modificações';
+$labels['sendcancellation'] = 'Avisar os participantes sobre o cancelamento do evento';
+$labels['onlyworkinghours'] = 'Procurar disponibilidade dentro do meu horário de trabalho';
+$labels['reqallattendees'] = 'Obrigatório/todos os participantes';
+$labels['prevslot'] = 'Espaço anterior';
+$labels['nextslot'] = 'Próximo espaço';
+$labels['noslotfound'] = 'Incapaz de encontrar um horário disponível';
+$labels['invitationsubject'] = 'Você foi convidado para "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nSegue em anexo um arquivo iCalendar com todos os detalhes do evento na qual você pode importar para sua aplicação de calendário.";
+$labels['invitationattendlinks'] = "No caso do seu cliente de e-mail não suportar requisições iTIP você pode usar o link a seguir para aceitar ou recusar este convite:\n\$url";
+$labels['eventupdatesubject'] = '"$title" foi atualizado';
+$labels['eventupdatesubjectempty'] = 'Um evento do seu interesse foi atualizado';
+$labels['eventupdatemailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nSegue em anexo um arquivo iCalendar com os detalhes atualizados do evento na qual você pode importar para sua aplicação de calendário.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nO evento foi cancelado por \$organizer.\n\nSegue em anexo um arquivo iCalendar com os detalhes atualizados do evento.";
+$labels['itipobjectnotfound'] = 'O evento referenciado por esta mensagem não foi encontrado em seu calendário.';
+$labels['itipmailbodyaccepted'] = "\$sender aceitou o convite para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender aceitou como tentativa o convite para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender recusou o convite para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender rejeitou sua participação no seguinte evento:\n\n*\$title*\n\nQuando: \$date";
+$labels['itipdeclineevent'] = 'Você deseja recusar o convite para este evento?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['notanattendee'] = 'Você não está listado como um participante deste evento';
+$labels['eventcancelled'] = 'O evento foi cancelado';
+$labels['saveincalendar'] = 'salvar em';
+$labels['updatemycopy'] = 'Atualize em meu calendário';
+$labels['savetocalendar'] = 'Gravar no calendário';
+$labels['resource'] = 'Recurso';
+$labels['addresource'] = 'Livro de recursos';
+$labels['findresources'] = 'Encontrar recursos';
+$labels['resourcedetails'] = 'Detalhes';
+$labels['resourceavailability'] = 'Disponibilidade';
+$labels['resourceowner'] = 'Dono';
+$labels['resourceadded'] = 'O recurso foi adicionado ao seu evento';
+$labels['tabsummary'] = 'Sumário';
+$labels['tabrecurrence'] = 'Repetição';
+$labels['tabattendees'] = 'Participantes';
+$labels['tabresources'] = 'Recursos';
+$labels['tabattachments'] = 'Anexos';
+$labels['tabsharing'] = 'Compartilhamento';
+$labels['deleteobjectconfirm'] = 'Você realmente deseja remover este evento?';
+$labels['deleteventconfirm'] = 'Você realmente deseja remover este evento?';
+$labels['deletecalendarconfirm'] = 'Você realmente deseja excluir este calendário com todos os seus eventos?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Salvando dados...';
+$labels['errorsaving'] = 'Falha ao salvar as modificações.';
+$labels['operationfailed'] = 'A operação requisitada falhou.';
+$labels['invalideventdates'] = 'Datas inválidas inseridas! Por favor verifique a inserção.';
+$labels['invalidcalendarproperties'] = 'Propriedades de calendário inválidas! Por favor defina um nome válido.';
+$labels['searchnoresults'] = 'Nenhum evento encontrado nos calendários selecionados.';
+$labels['successremoval'] = 'O evento foi excluído com sucesso.';
+$labels['successrestore'] = 'O evento foi restaurado com sucesso.';
+$labels['errornotifying'] = 'Falha ao enviar notificações para os participantes do evento.';
+$labels['errorimportingevent'] = 'Falha ao importar evento';
+$labels['importwarningexists'] = 'Uma cópia deste evento já existe em seu calendário.';
+$labels['newerversionexists'] = 'Já existe uma nova versão deste evento! Abortado.';
+$labels['nowritecalendarfound'] = 'Nenhum calendário encontrado para salvar o evento';
+$labels['importedsuccessfully'] = 'O evento foi adicionado com sucesso em \'$calendar\'';
+$labels['updatedsuccessfully'] = 'O evento foi atualizado com sucesso em \'$calendar\'.';
+$labels['attendeupdateesuccess'] = 'O status do participante foi atualizado com sucesso.';
+$labels['itipsendsuccess'] = 'Convite enviado aos participantes.';
+$labels['itipresponseerror'] = 'Falha ao enviar a resposta para este convite de evento';
+$labels['itipinvalidrequest'] = 'Este convite não é mais válido';
+$labels['sentresponseto'] = 'Resposta de convite enviada com sucesso para $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Importado com sucesso $nr eventos';
+$labels['importnone'] = 'Não há eventos a serem importados';
+$labels['importerror'] = 'Ocorreu um erro na importação';
+$labels['aclnorights'] = 'Você não tem permissão de administrador neste calendário.';
+$labels['changeeventconfirm'] = 'Trocar evento';
+$labels['changerecurringeventwarning'] = 'Este é um evento com repetição. Você gostaria de editar o evento atual somente, estas e todas as futuras ocorrências ou salvar este como um novo evento?';
+$labels['currentevent'] = 'Atual';
+$labels['futurevents'] = 'Futuro';
+$labels['allevents'] = 'Todos';
+$labels['saveasnew'] = 'Salvar como novo';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['objectchangelog'] = 'Mudar Histórico';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/pt_PT.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Visualização predefinida';
+$labels['time_format'] = 'Formato da hora';
+$labels['timeslots'] = 'Entradas por hora';
+$labels['first_day'] = 'Primeiro dia da semana';
+$labels['first_hour'] = 'Primeira hora a mostrar';
+$labels['workinghours'] = 'Horário de trabalho';
+$labels['add_category'] = 'Adicionar categoria';
+$labels['remove_category'] = 'Remover categoria';
+$labels['defaultcalendar'] = 'Criar novos eventos em';
+$labels['eventcoloring'] = 'Cores dos eventos';
+$labels['coloringmode0'] = 'De acordo com o calendário';
+$labels['coloringmode1'] = 'De acordo com a categoria';
+$labels['coloringmode2'] = 'Calendário para esboço, categoria para conteúdo';
+$labels['coloringmode3'] = 'Categoria para esboço, calendário para conteúdo';
+$labels['afternothing'] = 'Manter';
+$labels['aftertrash'] = 'Enviar para o lixo';
+$labels['afterdelete'] = 'Eliminar a mensagem';
+$labels['afterflagdeleted'] = 'Marcar como eliminada';
+$labels['aftermoveto'] = 'Mover para...';
+$labels['itipoptions'] = 'Convites de eventos';
+$labels['afteraction'] = 'Depois do processamento de um convite ou mensagem de alteração';
+$labels['calendar'] = 'Calendário';
+$labels['calendars'] = 'Calendários';
+$labels['category'] = 'Categoria';
+$labels['categories'] = 'Categorias';
+$labels['createcalendar'] = 'Criar um novo calendário';
+$labels['editcalendar'] = 'Alterar propriedades do calendário';
+$labels['name'] = 'Nome';
+$labels['color'] = 'Cor';
+$labels['day'] = 'Dia';
+$labels['week'] = 'Semana';
+$labels['month'] = 'Mês';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Novo';
+$labels['new_event'] = 'Novo evento';
+$labels['edit_event'] = 'Alterar evento';
+$labels['edit'] = 'Alterar';
+$labels['save'] = 'Guardar';
+$labels['removelist'] = 'Remover da lista';
+$labels['cancel'] = 'Cancelar';
+$labels['select'] = 'Selecionar';
+$labels['print'] = 'Imprimir';
+$labels['printtitle'] = 'Imprimir calendários';
+$labels['title'] = 'Sumário';
+$labels['description'] = 'Descrição';
+$labels['all-day'] = 'dia todo';
+$labels['export'] = 'Exportar';
+$labels['exporttitle'] = 'Exportar para iCalendar';
+$labels['exportrange'] = 'Eventos de';
+$labels['exportattachments'] = 'Incluir anexos';
+$labels['customdate'] = 'Definir data';
+$labels['location'] = 'Local';
+$labels['url'] = 'Endereço web';
+$labels['date'] = 'Data';
+$labels['start'] = 'Início';
+$labels['starttime'] = 'Hora de início';
+$labels['end'] = 'Fim';
+$labels['endtime'] = 'Hora de fim';
+$labels['repeat'] = 'Repetir';
+$labels['selectdate'] = 'Escolher data';
+$labels['freebusy'] = 'Mostrar-me como';
+$labels['free'] = 'Livre';
+$labels['busy'] = 'Ocupado';
+$labels['outofoffice'] = 'Ausente';
+$labels['tentative'] = 'Tentativa';
+$labels['mystatus'] = 'O meu estado';
+$labels['status'] = 'Estado';
+$labels['status-confirmed'] = 'Confirmado';
+$labels['status-cancelled'] = 'Cancelado';
+$labels['priority'] = 'Prioridade';
+$labels['sensitivity'] = 'Privacidade';
+$labels['public'] = 'público';
+$labels['private'] = 'privado';
+$labels['confidential'] = 'confidencial';
+$labels['links'] = 'Referência';
+$labels['alarms'] = 'Lembrete';
+$labels['comment'] = 'Comentário';
+$labels['created'] = 'Criado em';
+$labels['changed'] = 'Alterado em';
+$labels['unknown'] = 'Desconhecido';
+$labels['eventoptions'] = 'Opções';
+$labels['generated'] = 'produzido a';
+$labels['eventhistory'] = 'Histórico';
+$labels['removelink'] = 'Remover referência de email ';
+$labels['printdescriptions'] = 'Descrições de impressão';
+$labels['parentcalendar'] = 'Inserir dentro';
+$labels['searchearlierdates'] = '« Procurar eventos anteriores';
+$labels['searchlaterdates'] = 'Procurar eventos posteriores »';
+$labels['andnmore'] = '$nr mais...';
+$labels['togglerole'] = 'Clique para alternar o papel';
+$labels['createfrommail'] = 'Salvar como evento';
+$labels['importevents'] = 'Importar eventos';
+$labels['importrange'] = 'Eventos de';
+$labels['onemonthback'] = '1 mês atrás';
+$labels['nmonthsback'] = '$nr meses atrás';
+$labels['showurl'] = 'Mostrar URL do calendário';
+$labels['showurldescription'] = 'Use o seguinte endereço para obter acesso (somente leitura) ao seu calendário com outras aplicações. Para isso pode copiar e colar este endereço em qualquer software que suporte o formato iCal.';
+$labels['caldavurldescription'] = 'Para sincronizar este calendário com o seu computador ou dispositivos móveis deverá copiar este endereço <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> para a aplicação cliente. (ex. Evolution ou Mozilla Thunderbird)';
+$labels['findcalendars'] = 'Procurar calendários...';
+$labels['searchterms'] = 'Procurar termos';
+$labels['calsearchresults'] = 'Calendários disponíveis';
+$labels['calendarsubscribe'] = 'Listar sempre';
+$labels['nocalendarsfound'] = 'Não foram encontrados calendários';
+$labels['nrcalendarsfound'] = '$nr calendários encontrados';
+$labels['quickview'] = 'Só mostrar este calendário';
+$labels['invitationspending'] = 'Convites pendentes';
+$labels['invitationsdeclined'] = 'Convites recusados';
+$labels['changepartstat'] = 'Mudar estado de participante';
+$labels['rsvpcomment'] = 'Mensagem de convite';
+$labels['listrange'] = 'Intervalo para exibir:';
+$labels['listsections'] = 'Dividir em:';
+$labels['smartsections'] = 'Seções inteligentes';
+$labels['until'] = 'até';
+$labels['today'] = 'Hoje';
+$labels['tomorrow'] = 'Amanhã';
+$labels['thisweek'] = 'Esta semana';
+$labels['nextweek'] = 'Próxima semana';
+$labels['prevweek'] = 'Semana anterior';
+$labels['thismonth'] = 'Este mês';
+$labels['nextmonth'] = 'Próximo mês';
+$labels['weekofyear'] = 'Semana';
+$labels['pastevents'] = 'Passado';
+$labels['futureevents'] = 'Futuro';
+$labels['showalarms'] = 'Mostrar lembretes';
+$labels['defaultalarmtype'] = 'Notificação predefinida dos lembretes';
+$labels['defaultalarmoffset'] = 'Agendamento predefinido das notificações';
+$labels['attendee'] = 'Participante';
+$labels['role'] = 'Papel';
+$labels['availability'] = 'Disp.';
+$labels['confirmstate'] = 'Estado';
+$labels['addattendee'] = 'Adicionar participante';
+$labels['roleorganizer'] = 'Organizador';
+$labels['rolerequired'] = 'Obrigatório';
+$labels['roleoptional'] = 'Facultativo';
+$labels['rolechair'] = 'Responsável';
+$labels['rolenonparticipant'] = 'Ausente';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Grupo';
+$labels['cutyperesource'] = 'Recurso';
+$labels['cutyperoom'] = 'Sala';
+$labels['availfree'] = 'Disponível';
+$labels['availbusy'] = 'Ocupado';
+$labels['availunknown'] = 'Desconhecido';
+$labels['availtentative'] = 'Tentativa';
+$labels['availoutofoffice'] = 'Ausente';
+$labels['delegatedto'] = 'Delegado a:';
+$labels['delegatedfrom'] = 'Delegado de:';
+$labels['scheduletime'] = 'Procurar disponibilidade';
+$labels['sendinvitations'] = 'Enviar convites';
+$labels['sendnotifications'] = 'Avisar os participantes sobre as alterações';
+$labels['sendcancellation'] = 'Avisar os participantes sobre o cancelamento do evento';
+$labels['onlyworkinghours'] = 'Procurar disponibilidade dentro do meu horário de trabalho';
+$labels['reqallattendees'] = 'Necessário/todos os participantes';
+$labels['prevslot'] = 'Espaço anterior';
+$labels['nextslot'] = 'Próximo espaço';
+$labels['suggestedslot'] = 'Espaço sugerido';
+$labels['noslotfound'] = 'Incapaz de encontrar um horário disponível';
+$labels['invitationsubject'] = 'Foi convidado para "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nSegue em anexo um arquivo iCalendar com todos os detalhes de um evento, o qual pode importar para o seu calendário.";
+$labels['invitationattendlinks'] = "No caso do seu cliente de e-mail não suportar pedidos do tipo iTIP, pode usar o seguinte link para aceitar ou recusar este convite:\n\$url";
+$labels['eventupdatesubject'] = '"$title" foi atualizado.';
+$labels['eventupdatesubjectempty'] = 'Um evento do seu interesse foi atualizado';
+$labels['eventupdatemailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nSegue em anexo um arquivo iCalendar atualizado com os detalhes de um evento, o qual pode importar para o seu calendário.";
+$labels['eventcancelsubject'] = '"$title" foi cancelado';
+$labels['eventcancelmailbody'] = "*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees\n\nO evento foi cancelado por \$organizer.\n\nSegue em anexo um arquivo iCalendar com os detalhes atualizados do evento.";
+$labels['itipobjectnotfound'] = 'O evento citado nesta mensagem não foi encontrado no seu calendário.';
+$labels['itipmailbodyaccepted'] = "\$sender aceitou o convite para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender aceitou o convite como \"tentativa\" para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender recusou o convite para o seguinte evento:\n\n*\$title*\n\nQuando: \$date\n\nConvidados: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender rejeitou a sua participação no seguinte evento:\n\n*\$title*\n\nQuando: \$date";
+$labels['itipmailbodydelegated'] = "\$sender delegou a participação no seguinte evento:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender delegou a sua participação no seguinte evento:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Deseja recusar o convite para este evento?';
+$labels['declinedeleteconfirm'] = 'Também deseja apagar o evento recusado do seu calendário?';
+$labels['itipcomment'] = 'Observações — Convite/notificação';
+$labels['itipcommenttitle'] = 'Estas observações serão enviadas com o convite/notificação aos participantes.';
+$labels['notanattendee'] = 'Não está listado como participante neste evento';
+$labels['eventcancelled'] = 'O evento foi cancelado';
+$labels['saveincalendar'] = 'guardar em';
+$labels['updatemycopy'] = 'Atualizar no meu calendário';
+$labels['savetocalendar'] = 'Guardar no calendário';
+$labels['openpreview'] = 'Verificar calendário';
+$labels['noearlierevents'] = 'Sem eventos anteriores';
+$labels['nolaterevents'] = 'Sem eventos posteriores';
+$labels['resource'] = 'Recurso';
+$labels['addresource'] = 'Livro de recursos';
+$labels['findresources'] = 'Encontrar recursos';
+$labels['resourcedetails'] = 'Detalhes';
+$labels['resourceavailability'] = 'Disponibilidade';
+$labels['resourceowner'] = 'Dono';
+$labels['resourceadded'] = 'O recurso foi adicionado ao seu evento';
+$labels['tabsummary'] = 'Sumário';
+$labels['tabrecurrence'] = 'Repetição';
+$labels['tabattendees'] = 'Participantes';
+$labels['tabresources'] = 'Recursos';
+$labels['tabattachments'] = 'Anexos';
+$labels['tabsharing'] = 'Partilha';
+$labels['deleteobjectconfirm'] = 'Tem a certeza que quer eliminar este evento?';
+$labels['deleteventconfirm'] = 'Tem a certeza que quer eliminar este evento?';
+$labels['deletecalendarconfirm'] = 'Tem a certeza que quer eliminar este calendário e todos os seus eventos?';
+$labels['deletecalendarconfirmrecursive'] = 'Tem a certeza que quer eliminar este calendário com todos os seus eventos e sub-calendários?';
+$labels['savingdata'] = 'A guardar os dados...';
+$labels['errorsaving'] = 'Falha ao guardar as alterações.';
+$labels['operationfailed'] = 'A operação pedida falhou.';
+$labels['invalideventdates'] = 'As datas são inválidas! Por favor, verifique novamente.';
+$labels['invalidcalendarproperties'] = 'Propriedades de calendário inválidas! Por favor defina um nome válido.';
+$labels['searchnoresults'] = 'Nenhum evento encontrado nos calendários selecionados.';
+$labels['successremoval'] = 'O evento foi excluído com sucesso.';
+$labels['successrestore'] = 'O evento foi restaurado com sucesso.';
+$labels['errornotifying'] = 'Falha ao enviar notificações para os participantes do evento.';
+$labels['errorimportingevent'] = 'Falha ao importar evento';
+$labels['importwarningexists'] = 'Uma cópia deste evento já existe em seu calendário.';
+$labels['newerversionexists'] = 'Já existe uma nova versão deste evento! Abortado.';
+$labels['nowritecalendarfound'] = 'Nenhum calendário encontrado para salvar o evento';
+$labels['importedsuccessfully'] = 'O evento foi adicionado com sucesso em \'$calendar\'';
+$labels['updatedsuccessfully'] = 'O evento foi atualizado com sucesso em \'$calendar\'.';
+$labels['attendeupdateesuccess'] = 'O status do participante foi atualizado com sucesso.';
+$labels['itipsendsuccess'] = 'Convite enviado aos participantes.';
+$labels['itipresponseerror'] = 'Falha ao enviar a resposta para este convite de evento';
+$labels['itipinvalidrequest'] = 'Este convite já não é válido';
+$labels['sentresponseto'] = 'Resposta de convite enviada com sucesso para $mailto';
+$labels['localchangeswarning'] = 'As alterações que pretende efetuar só serão válidas no seu calendário e não serão enviadas ao organizador do evento.';
+$labels['importsuccess'] = 'Importado com sucesso $nr eventos';
+$labels['importnone'] = 'Não há eventos a serem importados';
+$labels['importerror'] = 'Ocorreu um erro na importação';
+$labels['aclnorights'] = 'Não tem permissão de administrador neste calendário.';
+$labels['changeeventconfirm'] = 'Alterar evento';
+$labels['removeeventconfirm'] = 'Eliminar evento';
+$labels['changerecurringeventwarning'] = 'Este evento é recorrente. Deseja alterar a ocorrência atual, esta e todas as futuras ocorrências ou guardar como um novo evento?';
+$labels['removerecurringeventwarning'] = 'Este evento é recorrente. Deseja eliminar a ocorrência atual, esta e todas as futuras ocorrências ou todas as ocorrências do evento?';
+$labels['removerecurringallonly'] = 'Este evento é recorrente. Como participante, só pode apagar apagar o evento com todas as ocorrências.';
+$labels['currentevent'] = 'Atual';
+$labels['futurevents'] = 'Futuro';
+$labels['allevents'] = 'Todos';
+$labels['saveasnew'] = 'Guardar como';
+$labels['birthdays'] = 'Aniversários';
+$labels['birthdayscalendar'] = 'Calendário de aniversários';
+$labels['displaybirthdayscalendar'] = 'Mostrar calendário de aniversários';
+$labels['birthdayscalendarsources'] = 'Agendas a incluir:';
+$labels['birthdayeventtitle'] = 'Aniversário de $name';
+$labels['birthdayage'] = 'Idade $age';
+$labels['objectchangelog'] = 'Alterar histórico';
+$labels['objectdiff'] = 'Diferenças entre $rev1 e $rev2';
+$labels['objectnotfound'] = 'Falha ao ler os dados do evento';
+$labels['objectchangelognotavailable'] = 'Não é possível alterar o histórico deste evento';
+$labels['objectdiffnotavailable'] = 'Não é possível comparar as versões selecionadas';
+$labels['revisionrestoreconfirm'] = 'Confirma a reposição das alterações $rev deste evento? Os dados atuais serão substituídos pela versão anterior.';
+$labels['objectrestoresuccess'] = 'A versão $rev foi reposta com sucesso.';
+$labels['objectrestoreerror'] = 'Não foi possível repor a versão anterior.';
+$labels['arialabelminical'] = 'Seleção da data do calendário';
+$labels['arialabelcalendarview'] = 'Vista do calendário';
+$labels['arialabelsearchform'] = 'Quadro de pesquisa de eventos';
+$labels['arialabelquicksearchbox'] = 'Pesquisa de eventos';
+$labels['arialabelcalsearchform'] = 'Quadro de pesquisa de calendários';
+$labels['calendaractions'] = 'Ações do calendário';
+$labels['arialabeleventattendees'] = 'Lista de participantes do evento';
+$labels['arialabeleventresources'] = 'Lista de recursos para eventos';
+$labels['arialabelresourcesearchform'] = 'Quadro de pesquisa de recursos';
+$labels['arialabelresourceselection'] = 'Recursos disponíveis';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/ro.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/ru_RU.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,268 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Вид по умолчанию';
+$labels['time_format'] = 'Формат времени';
+$labels['timeslots'] = 'Промежутков в чаÑ';
+$labels['first_day'] = 'Первый день недели';
+$labels['first_hour'] = 'Показывать Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ';
+$labels['workinghours'] = 'Рабочие чаÑÑ‹';
+$labels['add_category'] = 'Добавить категорию';
+$labels['remove_category'] = 'Удалить категорию';
+$labels['defaultcalendar'] = 'Создавать новое Ñобытие в';
+$labels['eventcoloring'] = 'Цвет ÑобытиÑ';
+$labels['coloringmode0'] = 'СоглаÑно цвета календарÑ';
+$labels['coloringmode1'] = 'СоглаÑно цвета категории';
+$labels['coloringmode2'] = 'Цвет ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð´Ð»Ñ Ñ€Ð°Ð¼ÐºÐ¸, цвет категории Ð´Ð»Ñ Ñ„Ð¾Ð½Ð°';
+$labels['coloringmode3'] = 'Цвет категории Ð´Ð»Ñ Ñ€Ð°Ð¼ÐºÐ¸, цвет ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð´Ð»Ñ Ñ„Ð¾Ð½Ð°';
+$labels['afternothing'] = 'Ðичего не делать';
+$labels['aftertrash'] = 'ПеремеÑтить в корзину';
+$labels['afterdelete'] = 'Удалить Ñообщение';
+$labels['afterflagdeleted'] = 'Пометить как удалённое';
+$labels['aftermoveto'] = 'ПеремеÑтить в...';
+$labels['itipoptions'] = 'ÐŸÑ€Ð¸Ð³Ð»Ð°ÑˆÐµÐ½Ð¸Ñ Ð½Ð° ÑобытиÑ';
+$labels['afteraction'] = 'ПоÑле того, как приглашение или Ñообщение о его изменении обработано';
+$labels['calendar'] = 'Календарь';
+$labels['calendars'] = 'Календари';
+$labels['category'] = 'КатегориÑ';
+$labels['categories'] = 'Категории';
+$labels['createcalendar'] = 'Создать новый календарь';
+$labels['editcalendar'] = 'Редактировать ÑвойÑтва календарÑ';
+$labels['name'] = 'ИмÑ';
+$labels['color'] = 'Цвет';
+$labels['day'] = 'День';
+$labels['week'] = 'ÐеделÑ';
+$labels['month'] = 'МеÑÑц';
+$labels['agenda'] = 'СпиÑок';
+$labels['new'] = 'Ðовый';
+$labels['new_event'] = 'Ðовое Ñобытие';
+$labels['edit_event'] = 'Изменить Ñобытие';
+$labels['edit'] = 'Редактировать';
+$labels['save'] = 'Сохранить';
+$labels['removelist'] = 'Удалить из ÑпиÑка';
+$labels['cancel'] = 'Отмена';
+$labels['select'] = 'Выбрать';
+$labels['print'] = 'РаÑпечатать';
+$labels['printtitle'] = 'РаÑпечатать календарь';
+$labels['title'] = 'Сводка';
+$labels['description'] = 'ОпиÑание';
+$labels['all-day'] = 'веÑÑŒ день';
+$labels['export'] = 'ЭкÑпорт';
+$labels['exporttitle'] = 'ЭкÑпорт в iCalendar';
+$labels['exportrange'] = 'Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ';
+$labels['exportattachments'] = 'С вложениÑми';
+$labels['customdate'] = 'Ð¡Ð¿ÐµÑ†Ð¸Ð°Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð°';
+$labels['location'] = 'МеÑто';
+$labels['url'] = 'URL';
+$labels['date'] = 'Дата';
+$labels['start'] = 'Ðачало';
+$labels['starttime'] = 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°';
+$labels['end'] = 'Конец';
+$labels['endtime'] = 'Ð’Ñ€ÐµÐ¼Ñ Ð¾ÐºÐ¾Ð½Ñ‡Ð°Ð½Ð¸Ñ';
+$labels['repeat'] = 'Повторить';
+$labels['selectdate'] = 'Выберите дату';
+$labels['freebusy'] = 'Показать как';
+$labels['free'] = 'Свободен';
+$labels['busy'] = 'ЗанÑÑ‚';
+$labels['outofoffice'] = 'Вне офиÑа';
+$labels['tentative'] = 'Ðеопределённо';
+$labels['mystatus'] = 'Мой ÑтатуÑ';
+$labels['status'] = 'СтатуÑ';
+$labels['status-confirmed'] = 'Подтвеждённый';
+$labels['status-cancelled'] = 'Отмененные';
+$labels['priority'] = 'Приоритет';
+$labels['sensitivity'] = 'СекретноÑÑ‚ÑŒ';
+$labels['public'] = 'общедоÑтупнаÑ';
+$labels['private'] = 'личнаÑ';
+$labels['confidential'] = 'конфиденциальнаÑ';
+$labels['links'] = 'СÑылка';
+$labels['alarms'] = 'Ðапоминание';
+$labels['comment'] = 'Комментарий';
+$labels['created'] = 'Создана';
+$labels['changed'] = 'Изменена';
+$labels['unknown'] = 'ÐеизвеÑтно';
+$labels['eventoptions'] = 'Опции';
+$labels['generated'] = 'Ñоздан';
+$labels['eventhistory'] = 'ИÑториÑ';
+$labels['removelink'] = 'Удалить ÑÑылку на пиÑьмо';
+$labels['printdescriptions'] = 'Печатать опиÑаниÑ';
+$labels['parentcalendar'] = 'Ð’Ñтавить внутри';
+$labels['searchearlierdates'] = '« ИÑкать ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ñ€Ð°Ð½ÑŒÑˆÐµ';
+$labels['searchlaterdates'] = 'ИÑкать ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¿Ð¾Ð·Ð¶Ðµ »';
+$labels['andnmore'] = '$nr больше...';
+$labels['togglerole'] = 'Кликните Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ñ Ñ€Ð¾Ð»Ð¸';
+$labels['createfrommail'] = 'Сохранить как Ñобытие';
+$labels['importevents'] = 'Импортировать ÑобытиÑ';
+$labels['importrange'] = 'Ð¡Ð¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ';
+$labels['onemonthback'] = '1 меÑÑц назад';
+$labels['nmonthsback'] = '$nr меÑÑца(ев) назад';
+$labels['showurl'] = 'Показать URL календарÑ';
+$labels['showurldescription'] = 'ИÑпользуйте Ñледующий Ð°Ð´Ñ€ÐµÑ Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñмотра Вашего ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð¸Ð· других приложений. Ð’Ñ‹ можете Ñкопировать и вÑтавить Ñто в любое приложение которое поддерживает формат iCal.';
+$labels['caldavurldescription'] = 'Скопируйте Ñтот Ð°Ð´Ñ€ÐµÑ Ð² клиент, <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">поддерживающий CalDAV</a> (например, Evolution или Mozilla Thunderbird) Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð¹ Ñинхронизации данного ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ñо Ñвоим компьютером или мобильным уÑтройÑтвом.';
+$labels['findcalendars'] = 'Ðайти календари...';
+$labels['searchterms'] = 'УÑÐ»Ð¾Ð²Ð¸Ñ Ð¿Ð¾Ð¸Ñка';
+$labels['calsearchresults'] = 'ДоÑтупные календари';
+$labels['calendarsubscribe'] = 'Ð’Ñегда показывать';
+$labels['nocalendarsfound'] = 'Календарей не найдено';
+$labels['nrcalendarsfound'] = '$nr календарей найдено';
+$labels['quickview'] = 'ПроÑмотреть только Ñтот календарь';
+$labels['invitationspending'] = 'Ожидающие приглашениÑ';
+$labels['invitationsdeclined'] = 'Отклонённые приглашениÑ';
+$labels['changepartstat'] = 'Изменить ÑÑ‚Ð°Ñ‚ÑƒÑ ÑƒÑ‡Ð°Ñтника';
+$labels['rsvpcomment'] = 'ТекÑÑ‚ приглашениÑ';
+$labels['listrange'] = 'Диапазон:';
+$labels['listsections'] = 'Разделить на:';
+$labels['smartsections'] = 'Умные Ñекции';
+$labels['until'] = 'до';
+$labels['today'] = 'СегоднÑ';
+$labels['tomorrow'] = 'Завтра';
+$labels['thisweek'] = 'Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð½ÐµÐ´ÐµÐ»Ñ';
+$labels['nextweek'] = 'Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð½ÐµÐ´ÐµÐ»Ñ';
+$labels['prevweek'] = 'ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ð½ÐµÐ´ÐµÐ»Ñ';
+$labels['thismonth'] = 'Этот меÑÑц';
+$labels['nextmonth'] = 'Следующий меÑÑц';
+$labels['weekofyear'] = 'ÐеделÑ';
+$labels['pastevents'] = 'Прошедшее';
+$labels['futureevents'] = 'Будущее';
+$labels['showalarms'] = 'Показывать напоминаниÑ';
+$labels['defaultalarmtype'] = 'ÐаÑтройки Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾ умолчанию';
+$labels['defaultalarmoffset'] = 'Ð’Ñ€ÐµÐ¼Ñ Ð½Ð°Ð¿Ð¾Ð¼Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ð¾ умолчанию';
+$labels['attendee'] = 'УчаÑтник';
+$labels['role'] = 'Роль';
+$labels['availability'] = 'ДоÑтупноÑÑ‚ÑŒ';
+$labels['confirmstate'] = 'СтатуÑ';
+$labels['addattendee'] = 'Добавить учаÑтника';
+$labels['roleorganizer'] = 'Организатор';
+$labels['rolerequired'] = 'ОбÑзательный';
+$labels['roleoptional'] = 'ÐеобÑзательный';
+$labels['rolechair'] = 'МеÑто';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Индивидуум';
+$labels['cutypegroup'] = 'Группа';
+$labels['cutyperesource'] = 'РеÑурÑ';
+$labels['cutyperoom'] = 'Комната';
+$labels['availfree'] = 'Свободен';
+$labels['availbusy'] = 'ЗанÑÑ‚';
+$labels['availunknown'] = 'ÐеизвеÑтно';
+$labels['availtentative'] = 'Предварительно';
+$labels['availoutofoffice'] = 'Вне офиÑа';
+$labels['delegatedto'] = 'Поручено:';
+$labels['delegatedfrom'] = 'Поручено от:';
+$labels['scheduletime'] = 'Ðайти доÑтупноÑÑ‚ÑŒ';
+$labels['sendinvitations'] = 'Отправить приглашениÑ';
+$labels['sendnotifications'] = 'Уведомить учаÑтников об изменениÑÑ…';
+$labels['sendcancellation'] = 'Уведомить учаÑтников об отмене ÑобытиÑ';
+$labels['onlyworkinghours'] = 'Ðайти доÑтупноÑÑ‚ÑŒ в мои рабочие чаÑÑ‹';
+$labels['reqallattendees'] = 'Ðеобходимые/вÑе учаÑтники';
+$labels['prevslot'] = 'Предыдущее времÑ';
+$labels['nextslot'] = 'Следующее времÑ';
+$labels['suggestedslot'] = 'Предлагаемое времÑ';
+$labels['noslotfound'] = 'Ðевозможно найти Ñвободное времÑ';
+$labels['invitationsubject'] = 'Вы приглашены на "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees\n\nВо вложении вы найдёте файл iCalendar Ñо вÑеми деталÑми ÑобытиÑ, который Ð’Ñ‹ можете импортировать в Вашу программу-ежедневник.";
+$labels['invitationattendlinks'] = "Ð’ Ñлучае, еÑли Ваш почтовый клиент не поддерживает запроÑÑ‹ iTip, Ð’Ñ‹ можете иÑпользовать ÑÑылку данную ниже, чтобы принÑÑ‚ÑŒ или отклонить Ñто приглашение:\n\$url";
+$labels['eventupdatesubject'] = '"$title" было обновлено';
+$labels['eventupdatesubjectempty'] = 'Событие, которое каÑаетÑÑ Ð’Ð°Ñ, было обновлено';
+$labels['eventupdatemailbody'] = "*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees\n\nВо вложении вы найдёте файл iCalendar Ñо вÑеми изменениÑми в Ñобытии, который Ð’Ñ‹ можете импортировать в Вашу программу-ежедневник.";
+$labels['eventcancelsubject'] = '"$title" было отменено';
+$labels['eventcancelmailbody'] = "*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees\n\nЭто Ñобытие отменено \$organizer.\n\nВо вложении вы найдёте файл iCalendar Ñо вÑеми изменениÑми в Ñобытии.";
+$labels['itipobjectnotfound'] = 'Событие, упомÑнутое в Ñтом Ñообщении, не найдено в вашем календаре.';
+$labels['itipmailbodyaccepted'] = "\$sender принÑл(а) приглашение на Ñледующее Ñобытие:\n\n*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender предварительно принÑл(а) приглашение на Ñледующее Ñобытие:\n\n*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender отклонил(а) приглашение на Ñледующее Ñобытие:\n\n*\$title*\n\nКогда: \$date\n\nПриглашенные: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender отклонил ваше учаÑтие в Ñобытии:\n\n*\$title*\n\nДата: \$date";
+$labels['itipmailbodydelegated'] = "\$sender перепоручил(а) учеÑтие в Ñобытии:\n\n*\$title*\n\nДата: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender перепоручил(а) Вам учаÑтие в Ñобытии:\n\n*\$title*\n\nДата: \$date";
+$labels['itipdeclineevent'] = 'Ð’Ñ‹ хотите отклонить приглашение на Ñто Ñобытие?';
+$labels['declinedeleteconfirm'] = 'Хотите ли вы так же удалить Ñто отклонённое Ñобытие из вашего календарÑ?';
+$labels['itipcomment'] = 'Комментарий к приглашению/извещению';
+$labels['itipcommenttitle'] = 'Этот комментарий будет прикреплён к приглашению/оповещению, отправленному учаÑтникам';
+$labels['notanattendee'] = 'Ð’Ñ‹ не в ÑпиÑке учаÑтников Ñтого ÑобытиÑ';
+$labels['eventcancelled'] = 'Это Ñобытие отменено';
+$labels['saveincalendar'] = 'Ñохранить в';
+$labels['updatemycopy'] = 'Обновить в моём календаре';
+$labels['savetocalendar'] = 'Сохранить в календарь';
+$labels['openpreview'] = 'Проверить календарь';
+$labels['noearlierevents'] = 'Ðет предыдущих Ñобытий';
+$labels['nolaterevents'] = 'Ðет поÑледующих Ñобытий';
+$labels['resource'] = 'РеÑурÑ';
+$labels['addresource'] = 'Зарезервировать реÑурÑ';
+$labels['findresources'] = 'Ðайти реÑурÑÑ‹';
+$labels['resourcedetails'] = 'Подробнее';
+$labels['resourceavailability'] = 'ДоÑтупноÑÑ‚ÑŒ';
+$labels['resourceowner'] = 'Владелец';
+$labels['resourceadded'] = 'РеÑÑƒÑ€Ñ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½ в ваше Ñобытие';
+$labels['tabsummary'] = 'Сводка';
+$labels['tabrecurrence'] = 'Повторение';
+$labels['tabattendees'] = 'УчаÑтники';
+$labels['tabresources'] = 'РеÑурÑÑ‹';
+$labels['tabattachments'] = 'ВложениÑ';
+$labels['tabsharing'] = 'СовмеÑтное иÑпользование';
+$labels['deleteobjectconfirm'] = 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñто Ñобытие?';
+$labels['deleteventconfirm'] = 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñто Ñобытие?';
+$labels['deletecalendarconfirm'] = 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñтот календарь Ñо вÑеми его ÑобытиÑми?';
+$labels['deletecalendarconfirmrecursive'] = 'Ð’Ñ‹ дейÑтвительно хотите удалить Ñтот календарь Ñо вÑеми его ÑобытиÑми и вложенными календарÑми?';
+$labels['savingdata'] = 'Сохранение данных...';
+$labels['errorsaving'] = 'Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹.';
+$labels['operationfailed'] = 'Ðе удалоÑÑŒ выполнить запрошенную операцию.';
+$labels['invalideventdates'] = 'ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð´Ð°Ñ‚Ð°! ПожалуйÑта проверьте данные.';
+$labels['invalidcalendarproperties'] = 'Ðеверные ÑвойÑтва календарÑ! ПожалуйÑта введите допуÑтимые данные.';
+$labels['searchnoresults'] = 'Событие не найдено в выбранных календарÑÑ….';
+$labels['successremoval'] = 'Событие уÑпешно удалено.';
+$labels['successrestore'] = 'Событие уÑпешно воÑÑтановлено.';
+$labels['errornotifying'] = 'Ðе удалоÑÑŒ отправить ÑƒÐ²ÐµÐ´Ð¾Ð¼Ð»ÐµÐ½Ð¸Ñ ÑƒÑ‡Ð°Ñтникам Ñобытий';
+$labels['errorimportingevent'] = 'Ðе удалоÑÑŒ импортировать Ñобытие';
+$labels['importwarningexists'] = 'ÐšÐ¾Ð¿Ð¸Ñ Ñтого ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ ÑƒÐ¶Ðµ еÑÑ‚ÑŒ в вашем календаре.';
+$labels['newerversionexists'] = 'ÐžÐ±Ð½Ð¾Ð²Ð»Ñ‘Ð½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ñтого ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ ÑƒÐ¶Ðµ ÑущеÑтвует! Отменено.';
+$labels['nowritecalendarfound'] = 'Ðе найден календарь Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñи Ñтого ÑобытиÑ';
+$labels['importedsuccessfully'] = 'Событие уÑпешно добавлено в \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Событие уÑпешно обновлено в \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'УÑпешно обновлен ÑÑ‚Ð°Ñ‚ÑƒÑ ÑƒÑ‡Ð°Ñтника';
+$labels['itipsendsuccess'] = 'ÐŸÑ€Ð¸Ð³Ð»Ð°ÑˆÐ°Ð½Ð¸Ñ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ñ‹ учаÑтникам.';
+$labels['itipresponseerror'] = 'Ðе удалоÑÑŒ поÑлать ответ на Ñто приглашение';
+$labels['itipinvalidrequest'] = 'Это приглашение больше не дейÑтвительно';
+$labels['sentresponseto'] = 'УÑпешно отправлен ответ на приглашение на $mailto';
+$labels['localchangeswarning'] = 'Ð’Ñ‹ ÑобираетеÑÑŒ внеÑти изменениÑ, которые отразÑÑ‚ÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ на Вашем личном календаре и не будут отправлены организатору ÑобытиÑ.';
+$labels['importsuccess'] = 'УÑпешно импортировано $nr Ñобытий';
+$labels['importnone'] = 'Ðе найдено Ñобытий Ð´Ð»Ñ Ð¸Ð¼Ð¿Ð¾Ñ€Ñ‚Ð°';
+$labels['importerror'] = 'Ошибка при импорте';
+$labels['aclnorights'] = 'Ð’Ñ‹ не имеете прав админиÑтратора Ð´Ð»Ñ Ñтого календарÑ.';
+$labels['changeeventconfirm'] = 'Изменить Ñобытие';
+$labels['removeeventconfirm'] = 'Удалить Ñобытие';
+$labels['changerecurringeventwarning'] = 'Это - повторÑющееÑÑ Ñобытие. Хотели бы Ð’Ñ‹ редактировать только текущее Ñобытие, Ñто и вÑе будущие повторениÑ, вÑе ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð»Ð¸ ÑохранÑÑ‚ÑŒ его как новое Ñобытие?';
+$labels['removerecurringeventwarning'] = 'Это - повторÑющееÑÑ Ñобытие. Хотели бы Ð’Ñ‹ удалить только текущее Ñобытие, Ñто и вÑе будущие ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð»Ð¸ вÑе Ñти ÑобытиÑ?';
+$labels['removerecurringallonly'] = 'Это - повторÑющееÑÑ Ñобытие. Как учаÑтник, Ð’Ñ‹ можете удалить вÑÑ‘ Ñобытие вмеÑте Ñ Ð²Ñеми повторениÑми.';
+$labels['currentevent'] = 'Текущее';
+$labels['futurevents'] = 'Будущие';
+$labels['allevents'] = 'Ð’Ñе';
+$labels['saveasnew'] = 'Сохранить как новое';
+$labels['birthdays'] = 'Дни рождениÑ';
+$labels['birthdayscalendar'] = 'Календарь Дней РождениÑ';
+$labels['displaybirthdayscalendar'] = 'Показывать календарь Дней РождениÑ';
+$labels['birthdayscalendarsources'] = 'Из Ñтих адреÑных книг';
+$labels['birthdayeventtitle'] = 'День Ñ€Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ $name';
+$labels['birthdayage'] = 'ВозраÑÑ‚ $age';
+$labels['objectchangelog'] = 'ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹';
+$labels['objectdiff'] = 'Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ $rev1 до $rev2';
+$labels['objectnotfound'] = 'Ðе удалоÑÑŒ загрузить информацию о мероприÑтиÑÑ…';
+$labels['objectchangelognotavailable'] = 'ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹ Ð´Ð»Ñ Ñтого ÑÐ¾Ð±Ñ‹Ñ‚Ð¸Ñ Ð½ÐµÐ´Ð¾Ñтупна';
+$labels['objectdiffnotavailable'] = 'Ðевозможно провеÑти Ñравнение выбранных ревизий ';
+$labels['revisionrestoreconfirm'] = 'Ð’Ñ‹ уверенны, что хотите воÑÑтановить Ñто Ñобытие из ревизии $rev? Оно заменит текущее Ñобытие Ñтарой верÑией. ';
+$labels['objectrestoresuccess'] = 'Ð ÐµÐ²Ð¸Ð·Ð¸Ñ $rev уÑпешно воÑÑтановлена';
+$labels['objectrestoreerror'] = 'Ðе удалоÑÑŒ воÑÑтановить Ñтарую ревизию';
+$labels['arialabelminical'] = 'Выбор даты';
+$labels['arialabelcalendarview'] = 'Вид календарÑ';
+$labels['arialabelsearchform'] = 'Форма поиÑка Ñобытий';
+$labels['arialabelquicksearchbox'] = 'ПоиÑк Ñобытий';
+$labels['arialabelcalsearchform'] = 'Форма поиÑка календарей';
+$labels['calendaractions'] = 'ДейÑÑ‚Ð²Ð¸Ñ Ñ ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñми';
+$labels['arialabeleventattendees'] = 'УчаÑтники ÑобытиÑ';
+$labels['arialabeleventresources'] = 'РеÑурÑÑ‹ ÑобытиÑ';
+$labels['arialabelresourcesearchform'] = 'Форма поиÑка реÑурÑов';
+$labels['arialabelresourceselection'] = 'ДоÑтупные реÑурÑÑ‹';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/sk_SK.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Prednastavené zobrazenie';
+$labels['time_format'] = 'Formát Äasu';
+$labels['timeslots'] = 'Časových úsekov za hodinu';
+$labels['first_day'] = 'Prvý deň v týždni';
+$labels['first_hour'] = 'Prvá hodina na zobrazenie';
+$labels['workinghours'] = 'Pracovný Äas';
+$labels['add_category'] = 'Pridať kategóriu';
+$labels['remove_category'] = 'Odstrániť kategóriu';
+$labels['defaultcalendar'] = 'Vytvoriť novú udalosť v';
+$labels['eventcoloring'] = 'Farba udalosti';
+$labels['afternothing'] = 'NeurobiÅ¥ niÄ';
+$labels['aftertrash'] = 'Presunúť do koša';
+$labels['afterdelete'] = 'Vymazať správu';
+$labels['afterflagdeleted'] = 'OznaÄiÅ¥ ako vymazané';
+$labels['aftermoveto'] = 'Presunúť do...';
+$labels['itipoptions'] = 'Pozvánky na udalosť';
+$labels['calendar'] = 'Kalendár';
+$labels['calendars'] = 'Kalendáre';
+$labels['category'] = 'Kategória';
+$labels['categories'] = 'Kategórie';
+$labels['createcalendar'] = 'Vytvoriť nový kalendár';
+$labels['editcalendar'] = 'Upraviť nastavenie kalendára';
+$labels['name'] = 'Meno';
+$labels['color'] = 'Farba';
+$labels['day'] = 'Deň';
+$labels['week'] = 'Týždeň';
+$labels['month'] = 'Mesiac';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Nový';
+$labels['new_event'] = 'Nová udalosť';
+$labels['edit_event'] = 'Upraviť udalosť';
+$labels['edit'] = 'Upraviť';
+$labels['save'] = 'Uložiť';
+$labels['removelist'] = 'Odstrániť zo zoznamu';
+$labels['cancel'] = 'Zrušiť';
+$labels['select'] = 'Vybrať';
+$labels['print'] = 'VytlaÄiÅ¥';
+$labels['printtitle'] = 'VytlaÄiÅ¥ kalendáre';
+$labels['title'] = 'Sumár';
+$labels['description'] = 'Popis';
+$labels['all-day'] = 'celý deň';
+$labels['export'] = 'Exportovať';
+$labels['exporttitle'] = 'Exportovať do iCalendar';
+$labels['exportrange'] = 'Udalosti z';
+$labels['exportattachments'] = 'S prílohami';
+$labels['customdate'] = 'Používateľský dátum';
+$labels['location'] = 'Poloha';
+$labels['url'] = 'URL';
+$labels['date'] = 'Dátum';
+$labels['start'] = 'ZaÄiatok';
+$labels['starttime'] = 'ÄŒas zaÄiatku';
+$labels['end'] = 'Koniec';
+$labels['endtime'] = 'ÄŒas konca';
+$labels['repeat'] = 'Opakovať';
+$labels['selectdate'] = 'Vyberte dátum';
+$labels['freebusy'] = 'Zobraziť ako';
+$labels['free'] = 'Voľný';
+$labels['busy'] = 'Zaneprázdnený';
+$labels['outofoffice'] = 'Mimo kancelárie';
+$labels['tentative'] = 'Nezáväzne';
+$labels['mystatus'] = 'Môj stav';
+$labels['status'] = 'Stav';
+$labels['status-confirmed'] = 'Potvrdené';
+$labels['status-cancelled'] = 'Zrušené';
+$labels['priority'] = 'Priorita';
+$labels['importrange'] = 'Udalosti z';
+$labels['weekofyear'] = 'Týždeň';
+$labels['confirmstate'] = 'Stav';
+$labels['availfree'] = 'Voľný';
+$labels['availbusy'] = 'Zaneprázdnený';
+$labels['availtentative'] = 'Nezáväzne';
+$labels['availoutofoffice'] = 'Mimo kancelárie';
+$labels['tabsummary'] = 'Sumár';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/sl_SI.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Privzeti pogled';
+$labels['time_format'] = 'Format Äasa';
+$labels['timeslots'] = 'ÄŒasovnih oken na uro';
+$labels['first_day'] = 'Prvi dan v tednu';
+$labels['first_hour'] = 'Prva ura za prikaz';
+$labels['workinghours'] = 'Ure dela';
+$labels['add_category'] = 'Dodaj kategorijo';
+$labels['remove_category'] = 'Odstrani kategorijo';
+$labels['defaultcalendar'] = 'Ustvari nove dogodke v';
+$labels['eventcoloring'] = 'Barva dogodka';
+$labels['coloringmode0'] = 'Po koledarju';
+$labels['coloringmode1'] = 'Po kategoriji';
+$labels['coloringmode2'] = 'Koledar za prikaz, kategorija za vsebino';
+$labels['coloringmode3'] = 'Kategorija za prikaz, koledar za vsebino';
+$labels['afternothing'] = 'Ne naredi niÄesar';
+$labels['aftertrash'] = 'Premakni v koš';
+$labels['afterdelete'] = 'IzbriÅ¡i sporoÄilo';
+$labels['afterflagdeleted'] = 'OznaÄi kot izbrisano';
+$labels['aftermoveto'] = 'Premakni v...';
+$labels['itipoptions'] = 'Vabila za dogodek';
+$labels['afteraction'] = 'Po obdelavi vabila ali posodobljenega sporoÄila';
+$labels['calendar'] = 'Koledar';
+$labels['calendars'] = 'Koledarji';
+$labels['category'] = 'Kategorija';
+$labels['categories'] = 'Kategorije';
+$labels['createcalendar'] = 'Ustvari nov koledar';
+$labels['editcalendar'] = 'Uredi nastavitve koledarja';
+$labels['name'] = 'Ime';
+$labels['color'] = 'Barva';
+$labels['day'] = 'Dan';
+$labels['week'] = 'Teden';
+$labels['month'] = 'Mesec';
+$labels['agenda'] = 'Urnik';
+$labels['new'] = 'Nov';
+$labels['new_event'] = 'Nov dogodek';
+$labels['edit_event'] = 'Uredi dogodek';
+$labels['edit'] = 'Uredi';
+$labels['save'] = 'Shrani';
+$labels['removelist'] = 'Odstrani iz seznama';
+$labels['cancel'] = 'PrekliÄi';
+$labels['select'] = 'Izberi';
+$labels['print'] = 'Natisni';
+$labels['printtitle'] = 'Natisni koledarje';
+$labels['title'] = 'Pregled';
+$labels['description'] = 'Opis';
+$labels['all-day'] = 'cel dan';
+$labels['export'] = 'Izvozi';
+$labels['exporttitle'] = 'Izvozi v iCalendar';
+$labels['exportrange'] = 'Dogodki od';
+$labels['exportattachments'] = 'S priponkami';
+$labels['customdate'] = 'Poljubni datum';
+$labels['location'] = 'Lokacija';
+$labels['url'] = 'URL';
+$labels['date'] = 'Datum';
+$labels['start'] = 'ZaÄetek';
+$labels['starttime'] = 'ÄŒas zaÄetka';
+$labels['end'] = 'Konec';
+$labels['endtime'] = 'ÄŒas konca';
+$labels['repeat'] = 'Ponovi';
+$labels['selectdate'] = 'Izberi datum';
+$labels['freebusy'] = 'Prikaži me kot';
+$labels['free'] = 'Prost';
+$labels['busy'] = 'Zaseden';
+$labels['outofoffice'] = 'Izven pisarne';
+$labels['tentative'] = 'Pogojno';
+$labels['mystatus'] = 'Moj status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Potrjeno';
+$labels['status-cancelled'] = 'Preklicano';
+$labels['priority'] = 'Prednost';
+$labels['sensitivity'] = 'Zasebnost';
+$labels['public'] = 'javno';
+$labels['private'] = 'zasebno';
+$labels['confidential'] = 'zaupno';
+$labels['links'] = 'Sklic';
+$labels['alarms'] = 'Opomnik';
+$labels['comment'] = 'Komentar';
+$labels['created'] = 'Ustvarjeno';
+$labels['changed'] = 'Nazadnje urejeno';
+$labels['unknown'] = 'Neznano';
+$labels['eventoptions'] = 'Nastavitve';
+$labels['generated'] = 'generirano ob';
+$labels['eventhistory'] = 'Zgodovina';
+$labels['removelink'] = 'Odstrani email povezavo';
+$labels['printdescriptions'] = 'Opis za tisk';
+$labels['parentcalendar'] = 'Vstavi';
+$labels['searchearlierdates'] = '« IÅ¡Äi po prejÅ¡njih dogodkih';
+$labels['searchlaterdates'] = 'IÅ¡Äi po kasnejÅ¡ih dogodkih »';
+$labels['andnmore'] = '$nr veÄ...';
+$labels['togglerole'] = 'Klikni za prikaz vloge';
+$labels['createfrommail'] = 'Shrani kot dogodek';
+$labels['importevents'] = 'Uvozi dogodke';
+$labels['importrange'] = 'Dogodki od';
+$labels['onemonthback'] = '1 mesec nazaj';
+$labels['nmonthsback'] = '$nr mesecev nazaj';
+$labels['showurl'] = 'Prikaži URL koledarja';
+$labels['showurldescription'] = 'Za dostop do koledarja (samo za branje) iz drugih aplikacij uporabi naslednji naslov. Funkcija kopiraj in prilepi deluje z vsakim koledarjem v iCal formatu.';
+$labels['caldavurldescription'] = 'Za sinhronizacijo tega koledarja z vaÅ¡im raÄunalnikom ali mobilno napravo, v podprto aplikacijo (npr. Evolution ali Mozilla Thunderbird) kopirajte ta naslov <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> .';
+$labels['findcalendars'] = 'IÅ¡Äi koledarje...';
+$labels['searchterms'] = 'Iskalni pogoji';
+$labels['calsearchresults'] = 'Razpoložljivi koledarji';
+$labels['calendarsubscribe'] = 'OznaÄi za vedno';
+$labels['nocalendarsfound'] = 'Ni najdenih koledarjev';
+$labels['nrcalendarsfound'] = '$nr najdenih koledarjev';
+$labels['quickview'] = 'Prikaži samo ta koledar';
+$labels['invitationspending'] = 'Vabila za dogodek';
+$labels['invitationsdeclined'] = 'Zavrnjena vabila';
+$labels['changepartstat'] = 'Spremeni status sodelujoÄega';
+$labels['rsvpcomment'] = 'SporoÄilo vabila';
+$labels['listrange'] = 'Prikaži v razponu:';
+$labels['listsections'] = 'Razdeli v:';
+$labels['smartsections'] = 'Pametni razdelki';
+$labels['until'] = 'do';
+$labels['today'] = 'Danes';
+$labels['tomorrow'] = 'Jutri';
+$labels['thisweek'] = 'Ta teden';
+$labels['nextweek'] = 'Naslednji teden';
+$labels['prevweek'] = 'Prejšnji teden';
+$labels['thismonth'] = 'Ta mesec';
+$labels['nextmonth'] = 'Naslednji mesec';
+$labels['weekofyear'] = 'Teden';
+$labels['pastevents'] = 'Pretekli';
+$labels['futureevents'] = 'Prihodnji';
+$labels['showalarms'] = 'Prikaži opomnike';
+$labels['defaultalarmtype'] = 'Privzeta nastavitev opomnika';
+$labels['defaultalarmoffset'] = 'Privzeti Äas opomnika';
+$labels['attendee'] = 'Udeleženec';
+$labels['role'] = 'Vloga';
+$labels['availability'] = 'Razpol.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Dodaj udeleženca';
+$labels['roleorganizer'] = 'Organizator';
+$labels['rolerequired'] = 'Zahtevano';
+$labels['roleoptional'] = 'Neobvezno';
+$labels['rolechair'] = 'Vodja sestanka';
+$labels['rolenonparticipant'] = 'Odsoten';
+$labels['cutypeindividual'] = 'Osebni';
+$labels['cutypegroup'] = 'Skupina';
+$labels['cutyperesource'] = 'Vir';
+$labels['cutyperoom'] = 'Soba';
+$labels['availfree'] = 'Prost';
+$labels['availbusy'] = 'Zaseden';
+$labels['availunknown'] = 'Neznano';
+$labels['availtentative'] = 'Pogojno';
+$labels['availoutofoffice'] = 'Izven pisarne';
+$labels['delegatedto'] = 'Preneseno na:';
+$labels['delegatedfrom'] = 'Preneseno od:';
+$labels['scheduletime'] = 'Najdi razpoložljivost';
+$labels['sendinvitations'] = 'Pošlji vabila';
+$labels['sendnotifications'] = 'SporoÄi udeležencem spremembe';
+$labels['sendcancellation'] = 'SporoÄi udeležencem odpoved dogodka';
+$labels['onlyworkinghours'] = 'Najdi razpoložljivost med mojim delavnikom';
+$labels['reqallattendees'] = 'Zahtevano/vsi udeleženci';
+$labels['prevslot'] = 'Prejšnje mesto';
+$labels['nextslot'] = 'Naslednje mesto';
+$labels['suggestedslot'] = 'Predlagano mesto';
+$labels['noslotfound'] = 'Ne najdem prostega mesta';
+$labels['invitationsubject'] = 'Vabljeni ste v "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nProsim preglejte pripeto iCalendar datoteko z vsemi informacijami o dogodku. Datoteko lahko uvozite v vašo koledar aplikacijo.";
+$labels['invitationattendlinks'] = "V kolikor vaš email klient ne podpira iTip zahtevkov lahko uporabite naslednjo povezavo za sprejem ali zavrnitev vabila:\n\$url";
+$labels['eventupdatesubject'] = '"$title" je bil posodobljen';
+$labels['eventupdatesubjectempty'] = 'Dogodek, ki vas zadeva je bil posodobljen';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nProsim preglejte pripeto iCalendar datoteko s posodobljenimi informacijami o dogodku. Datoteko lahko uvozite v vašo koledar aplikacijo.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nDogodek je bil preklican s strani \$organizer.\n\nProsim preglejte pripeto iCalendar datoteko s posodobljenimi informacijami o dogodku.";
+$labels['itipobjectnotfound'] = 'Dogodek, na katerega se nanaÅ¡a to sporoÄilo, ni bil najden v vaÅ¡em koledarju.';
+$labels['itipmailbodyaccepted'] = "\$sender je sprejel vabilo na dogodek:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender je okvirno sprejel vabilo na dogodek:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ni sprejel vabila na dogodek:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender je zavrnil vaše sodelovanje pri dogodku:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender je prenesel sodelovanje na dogodku:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender je na vas prenesel sodelovanje na dogodku:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Želite zavrniti vabilo na ta dogodek?';
+$labels['declinedeleteconfirm'] = 'Ali želite tudi izbrisati zavrnjeni dogodek iz vašega koledarja?';
+$labels['itipcomment'] = 'Komentar vabila/obvestila';
+$labels['itipcommenttitle'] = 'Komentar bo dodan k vabilu/obvestilu, ki bo poslano sodelujoÄim';
+$labels['notanattendee'] = 'Niste oznaÄeni kot sodelujoÄi na tem sestanku';
+$labels['eventcancelled'] = 'Ta dogodek je bil preklican';
+$labels['saveincalendar'] = 'shrani v';
+$labels['updatemycopy'] = 'Posodobi v mojem koledarju';
+$labels['savetocalendar'] = 'Shrani v koledar';
+$labels['openpreview'] = 'Preveri Koledar';
+$labels['noearlierevents'] = 'Ni predhodnjih dogodkov';
+$labels['nolaterevents'] = 'Ni kasnejših dogodkov';
+$labels['resource'] = 'Vir';
+$labels['addresource'] = 'OznaÄi vir';
+$labels['findresources'] = 'PoiÅ¡Äi vire';
+$labels['resourcedetails'] = 'Podrobnosti';
+$labels['resourceavailability'] = 'Razpoložljivost';
+$labels['resourceowner'] = 'Lastnik';
+$labels['resourceadded'] = 'Vir je bil dodan v vašem dogodku';
+$labels['tabsummary'] = 'Pregled';
+$labels['tabrecurrence'] = 'Ponovitev';
+$labels['tabattendees'] = 'SodelujoÄi';
+$labels['tabresources'] = 'Viri';
+$labels['tabattachments'] = 'Priponke';
+$labels['tabsharing'] = 'Deli z ostalimi';
+$labels['deleteobjectconfirm'] = 'Ali želite potrditi brisanje tega dogodka?';
+$labels['deleteventconfirm'] = 'Ali želite potrditi brisanje tega dogodka?';
+$labels['deletecalendarconfirm'] = 'Ali želite izbrisati ta koledar z vsemi dogodki?';
+$labels['deletecalendarconfirmrecursive'] = 'Ali želite izbrisati to koledar z vsemi dogodki in pod-koledarji?';
+$labels['savingdata'] = 'Shranjujem...';
+$labels['errorsaving'] = 'Napaka pri shranjevanju sprememb.';
+$labels['operationfailed'] = 'Zahtevana operacija ni uspela.';
+$labels['invalideventdates'] = 'Vnos datumov napaÄen! Prosim preverite vaÅ¡ vnos.';
+$labels['invalidcalendarproperties'] = 'NapaÄne nastavitve koledarja! Prosim nastavite pravilno ime.';
+$labels['searchnoresults'] = 'V izbranih koledarjih ni dogodkov.';
+$labels['successremoval'] = 'Dogodek je bil uspešno izbrisan.';
+$labels['successrestore'] = 'Dogodek je bil uspešno obnovljen.';
+$labels['errornotifying'] = 'Napaka. PoÅ¡iljanje obvestil sodelujoÄim ni bilo uspeÅ¡no.';
+$labels['errorimportingevent'] = 'Napaka pri uvozu dogodka';
+$labels['importwarningexists'] = 'RazliÄica tega dogodka že obstaja v vaÅ¡em koledarju.';
+$labels['newerversionexists'] = 'Obstaja novejša verzija tega dogodka!';
+$labels['nowritecalendarfound'] = 'Za shranjevanje dogodka ni na voljo nobenega koledarja';
+$labels['importedsuccessfully'] = 'Dogodek je bil uspešno dodan v \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Dogodek je bil uspešno posodobljen v \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Posodabljanje statusa sodelujoÄega uspeÅ¡no';
+$labels['itipsendsuccess'] = 'Vabilo sodelujoÄim poslano';
+$labels['itipresponseerror'] = 'Napaka. Pošiljanje odgovora na vabilo ni bilo uspešno';
+$labels['itipinvalidrequest'] = 'To vabilo ni veÄ veljavno';
+$labels['sentresponseto'] = 'Odgovor je bil uspešno poslan na naslov $mailto';
+$labels['localchangeswarning'] = 'Spremembe bodo vidne samo v vašem koledarju in ne bodo poslane organizatorju dogodka';
+$labels['importsuccess'] = 'Uspešno uvoženih $nr dogodkov';
+$labels['importnone'] = 'Ne najdem dogodkov za uvoz';
+$labels['importerror'] = 'Pri uvozu je prišlo do napake';
+$labels['aclnorights'] = 'Na tem koledarju nimate administratorskih pravic';
+$labels['changeeventconfirm'] = 'Spremeni dogodek';
+$labels['removeeventconfirm'] = 'Izbriši dogodek';
+$labels['changerecurringeventwarning'] = 'To je ponavljajoÄ dogodek. Ali želite urediti samo trenutni dogodek, trenutni in vse prihodnje dogodke, vse ponavljajoÄe dogodke ali shraniti kot nov dogodek?';
+$labels['removerecurringeventwarning'] = 'To je ponavljajoÄ dogodek. Ali želite izbrisati trenutni dogodek, trenutni in vse prihodnje dogodke ali vsa ponavljanja tega dogodka?';
+$labels['currentevent'] = 'Trenutni';
+$labels['futurevents'] = 'Prihodnji';
+$labels['allevents'] = 'Vsi';
+$labels['saveasnew'] = 'Shrani kot nov';
+$labels['birthdays'] = 'Rojstni dnevi';
+$labels['birthdayscalendar'] = 'Koledar rojstnih dnevov';
+$labels['displaybirthdayscalendar'] = 'Prikaži koledar rojstnih dnevov';
+$labels['birthdayscalendarsources'] = 'Iz teh imenikov';
+$labels['birthdayeventtitle'] = 'Rojstni dan osebe $name';
+$labels['birthdayage'] = 'Starost $age';
+$labels['objectchangelog'] = 'Spremeni Zgodovino';
+$labels['objectnotfound'] = 'Napaka pri nalaganju podatkov o dogodku';
+$labels['objectchangelognotavailable'] = 'Sprememba zgodovine za ta dogodek ni na voljo';
+$labels['objectdiffnotavailable'] = 'Primerjava za izbrane verzije ni na voljo';
+$labels['revisionrestoreconfirm'] = 'Ali želite obnoviti verzijo $rev tega dogodka? To bo nadomestilo trenutni dogodek s starejšo verzijo.';
+$labels['arialabelminical'] = 'Izbira datuma v koledarju';
+$labels['arialabelcalendarview'] = 'Prikaz koledarja';
+$labels['arialabelsearchform'] = 'Obrazec za iskanje dogodkov';
+$labels['arialabelquicksearchbox'] = 'Vnos iskanja dogodkov';
+$labels['arialabelcalsearchform'] = 'Obrazec za iskanje koledarjev';
+$labels['calendaractions'] = 'Dejanja koledarja';
+$labels['arialabeleventattendees'] = 'Seznam sodelujoÄih na dogodku';
+$labels['arialabeleventresources'] = 'Seznam virov za dogodek';
+$labels['arialabelresourcesearchform'] = 'Obrazec za iskanje virov';
+$labels['arialabelresourceselection'] = 'Viri na voljo';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/sv.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/sv_SE.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,263 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Standardvy';
+$labels['time_format'] = 'Tidformat';
+$labels['timeslots'] = 'Tidsluckor per timme';
+$labels['first_day'] = 'Första veckodag';
+$labels['first_hour'] = 'Dagen börjar';
+$labels['workinghours'] = 'Arbetstid';
+$labels['add_category'] = 'Lägg till kategori';
+$labels['remove_category'] = 'Ta bort kategori';
+$labels['defaultcalendar'] = 'Skapa nya händelser i';
+$labels['eventcoloring'] = 'Händelsefärg';
+$labels['coloringmode0'] = 'Enligt kalender';
+$labels['coloringmode1'] = 'Enligt kategori';
+$labels['coloringmode2'] = 'Kalender för översikt, kategori för innehåll';
+$labels['coloringmode3'] = 'Kategori för översikt, kalender för innehåll';
+$labels['afternothing'] = 'Gör inget';
+$labels['aftertrash'] = 'Flytta till papperskorgen';
+$labels['afterdelete'] = 'Ta bort meddelandet';
+$labels['afterflagdeleted'] = 'Märk som borttaget';
+$labels['aftermoveto'] = 'Flytta till ...';
+$labels['itipoptions'] = 'Händelseinbjudningar';
+$labels['afteraction'] = 'Efter en inbjudan eller uppdatering bearbetats meddelande';
+$labels['calendar'] = 'Kalender';
+$labels['calendars'] = 'Kalendrar';
+$labels['category'] = 'Kategori';
+$labels['categories'] = 'Kategorier';
+$labels['createcalendar'] = 'Skapa ny kalender';
+$labels['editcalendar'] = 'Redigera kalenderegenskaper';
+$labels['name'] = 'Namn';
+$labels['color'] = 'Färg';
+$labels['day'] = 'Dag';
+$labels['week'] = 'Vecka';
+$labels['month'] = 'MÃ¥nad';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'Ny';
+$labels['new_event'] = 'Ny händelse';
+$labels['edit_event'] = 'Redigera händelse';
+$labels['edit'] = 'Redigera';
+$labels['save'] = 'Spara';
+$labels['removelist'] = 'Ta bort från lista';
+$labels['cancel'] = 'Avbryt';
+$labels['select'] = 'Välj';
+$labels['print'] = 'Skriv ut';
+$labels['printtitle'] = 'Skriv ut kalendrar';
+$labels['title'] = 'Sammanfattning';
+$labels['description'] = 'Beskrivning';
+$labels['all-day'] = 'heldag';
+$labels['export'] = 'Exportera';
+$labels['exporttitle'] = 'Exportera till iCalendar';
+$labels['exportrange'] = 'Händelser från';
+$labels['exportattachments'] = 'Med bilagor';
+$labels['customdate'] = 'Anpassat datum';
+$labels['location'] = 'Plats';
+$labels['url'] = 'URL';
+$labels['date'] = 'Datum';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Starttid';
+$labels['end'] = 'Slut';
+$labels['endtime'] = 'Sluttid';
+$labels['repeat'] = 'Upprepa';
+$labels['selectdate'] = 'Välj datum';
+$labels['freebusy'] = 'Visa mig som';
+$labels['free'] = 'Ledig';
+$labels['busy'] = 'Upptagen';
+$labels['outofoffice'] = 'Frånvarande';
+$labels['tentative'] = 'Preliminärt';
+$labels['mystatus'] = 'Min status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Bekräftad';
+$labels['status-cancelled'] = 'Inställd';
+$labels['priority'] = 'Prioritet';
+$labels['sensitivity'] = 'Integritet';
+$labels['public'] = 'publik';
+$labels['private'] = 'privat';
+$labels['confidential'] = 'konfidentiell';
+$labels['links'] = 'Referens';
+$labels['alarms'] = 'PÃ¥minnelse';
+$labels['comment'] = 'Kommentar';
+$labels['created'] = 'Skapad';
+$labels['changed'] = 'Senast ändrad';
+$labels['unknown'] = 'Okänd';
+$labels['eventoptions'] = 'Alternativ';
+$labels['generated'] = 'genererad vid';
+$labels['eventhistory'] = 'Historik';
+$labels['removelink'] = 'Ta bort e-postreferens';
+$labels['printdescriptions'] = 'Skriv ut beskrivning';
+$labels['parentcalendar'] = 'Infoga inuti';
+$labels['searchearlierdates'] = '« Sök efter tidigare händelser';
+$labels['searchlaterdates'] = 'Sök efter senare händelser »';
+$labels['andnmore'] = '$nr fler ...';
+$labels['togglerole'] = 'Klicka för att växla roll';
+$labels['createfrommail'] = 'Spara som händelse';
+$labels['importevents'] = 'Importera händelser';
+$labels['importrange'] = 'Händelser från';
+$labels['onemonthback'] = '1 månad bakåt';
+$labels['nmonthsback'] = '$nr månader bakåt';
+$labels['showurl'] = 'Visa kalender-URL';
+$labels['showurldescription'] = 'Använd följande adress för åtkomst (endast läsbar) till din kalender från andra applikationer. Du kan kopiera och klistra in adressen in i ett kalenderprogram som stöder iCal-formatet.';
+$labels['caldavurldescription'] = 'Kopiera denna adress till ett <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> klientprogram (t.ex. Evolution eller Mozilla Thunderbird) för att helt synkronisera denna specifika kalender med din dator eller mobila enhet.';
+$labels['findcalendars'] = 'Hitta kalendrar ...';
+$labels['searchterms'] = 'Sökord';
+$labels['calsearchresults'] = 'Tillgängliga kalendrar';
+$labels['calendarsubscribe'] = 'Lista permanent';
+$labels['nocalendarsfound'] = 'Inga kalendrar hittades';
+$labels['nrcalendarsfound'] = '$nr kalendrar hittades';
+$labels['quickview'] = 'Visa endast denna kalender';
+$labels['invitationspending'] = 'Avvaktade inbjudningar';
+$labels['invitationsdeclined'] = 'Avböjda inbjudningar';
+$labels['changepartstat'] = 'Ändra deltagarstatus';
+$labels['rsvpcomment'] = 'Inbjudningstext';
+$labels['listrange'] = 'Intervall att visa';
+$labels['listsections'] = 'Dela upp i';
+$labels['smartsections'] = 'Smarta sektioner';
+$labels['until'] = 'tills';
+$labels['today'] = 'Idag';
+$labels['tomorrow'] = 'Imorgon';
+$labels['thisweek'] = 'Denna vecka';
+$labels['nextweek'] = 'Nästa vecka';
+$labels['prevweek'] = 'Föregående vecka';
+$labels['thismonth'] = 'Denna månad';
+$labels['nextmonth'] = 'Nästa månad';
+$labels['weekofyear'] = 'Vecka';
+$labels['pastevents'] = 'Förflutna';
+$labels['futureevents'] = 'Framtida';
+$labels['showalarms'] = 'Visa påminnelser';
+$labels['defaultalarmtype'] = 'Standardinställning för påminnelser';
+$labels['defaultalarmoffset'] = 'Standartid för påminnelser';
+$labels['attendee'] = 'Deltagare';
+$labels['role'] = 'Roll';
+$labels['availability'] = 'Tillg.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Lägg till deltagare';
+$labels['roleorganizer'] = 'Organisatör';
+$labels['rolerequired'] = 'Obligatorisk';
+$labels['roleoptional'] = 'Valfritt';
+$labels['rolechair'] = 'Stol';
+$labels['rolenonparticipant'] = 'Frånvarande';
+$labels['cutypeindividual'] = 'Individuell';
+$labels['cutypegroup'] = 'Grupp';
+$labels['cutyperesource'] = 'Resurs';
+$labels['cutyperoom'] = 'Rum';
+$labels['availfree'] = 'Ledig';
+$labels['availbusy'] = 'Upptagen';
+$labels['availunknown'] = 'Okänd';
+$labels['availtentative'] = 'Preliminärt';
+$labels['availoutofoffice'] = 'Frånvarande';
+$labels['delegatedto'] = 'Delegerad till:';
+$labels['delegatedfrom'] = 'Delegerad från:';
+$labels['scheduletime'] = 'Hitta tillgänglighet';
+$labels['sendinvitations'] = 'Skicka inbjudningar';
+$labels['sendnotifications'] = 'Meddela deltagare om ändringar';
+$labels['sendcancellation'] = 'Meddela deltagare om att händelsen ställts in';
+$labels['onlyworkinghours'] = 'Sök tillgänglighet inom min arbetstid';
+$labels['reqallattendees'] = 'Obligatorisk/alla deltagare';
+$labels['prevslot'] = 'Föregående lucka';
+$labels['nextslot'] = 'Nästa lucka';
+$labels['suggestedslot'] = 'Föreslagen lucka';
+$labels['noslotfound'] = 'Det gick inte att hitta en ledig tidslucka';
+$labels['invitationsubject'] = 'Du har blivit inbjuden till "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nNär: \$date\n\nInbjudna: \$attendees\n\nHärmed bifogas en iCalendar-fil med alla detaljer om händelsen som du kan importera till din kalenderapplikation.";
+$labels['invitationattendlinks'] = "Om din e-postklient inte stöder iTip-förfrågningar kan du använda följande länk för att antingen tacka ja eller eller nej till denna inbjudan:\n\$url";
+$labels['eventupdatesubject'] = '"$title" har uppdaterats';
+$labels['eventupdatesubjectempty'] = 'En händelse som berör dig har uppdaterats';
+$labels['eventupdatemailbody'] = "*\$title*\n\nNär: \$date\n\nInbjudna: \$attendees\n\nHärmed bifogas en iCalendar-fil med uppdaterad information som du kan importera till din kalenderapplikation.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nNär: \$date\n\nInbjudna: \$attendees\n\nHändelsen har ställts in av \$organizer.\n\nHärmed bifogas en iCalendar-fil med uppdaterad information om händelsen.";
+$labels['itipobjectnotfound'] = 'Den händelse som avses i detta meddelande hittades inte i din kalender.';
+$labels['itipmailbodyaccepted'] = "\$sender har accepterat inbjudan till följande händelse:\n\n*\$title*\n\nNär: \$date\n\nInbjudna: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender har preliminärt accepterat inbjudan till följande evenemang:\n\n*\$title*\n\nNär: \$date\n\nInbjudna: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender har tackat nej till inbjudan till följande händelse:\n\n*\$title*\n\nNär: \$date\n\nIbjudna: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender har avvisat ditt deltagande i följande händelse:\n\n*\$title*\n\nNär: \$dat";
+$labels['itipmailbodydelegated'] = "\$sender har delegerat deltagandet i följande händelse:\n\n*\$title*\n\nNär: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender har delegerat deltagandet i följande händelse till dig:\n\n*\$title*\n\nNär: \$date";
+$labels['itipdeclineevent'] = 'Vill du tacka nej till inbjudan till denna händelse?';
+$labels['declinedeleteconfirm'] = 'Vill du även ta bort denna händelse, som du tackat nej till, från din kalender?';
+$labels['itipcomment'] = 'Kommentar till inbjudan/meddelande';
+$labels['itipcommenttitle'] = 'Denna kommentar kommer att bifogas i inbjudan/meddelandet som skickas till deltagarna';
+$labels['notanattendee'] = 'Du är inte listad som en deltagare i denna händelse';
+$labels['eventcancelled'] = 'Händelsen har ställts in';
+$labels['saveincalendar'] = 'Spara i';
+$labels['updatemycopy'] = 'Uppdatera i min kalender';
+$labels['savetocalendar'] = 'Spara till kalender';
+$labels['openpreview'] = 'Kontrollera kalender';
+$labels['noearlierevents'] = 'Inga tidigare händelser';
+$labels['nolaterevents'] = 'Inga senare händelser';
+$labels['resource'] = 'Resurs';
+$labels['addresource'] = 'Boka resurs';
+$labels['findresources'] = 'Hitta resurser';
+$labels['resourcedetails'] = 'Detaljer';
+$labels['resourceavailability'] = 'Tillgänglighet';
+$labels['resourceowner'] = 'Ägare';
+$labels['resourceadded'] = 'Resursen har kopplats till din händelser ';
+$labels['tabsummary'] = 'Sammanfattning';
+$labels['tabrecurrence'] = 'Ã…terkommande';
+$labels['tabattendees'] = 'Deltagare';
+$labels['tabresources'] = 'Resurser';
+$labels['tabattachments'] = 'Bilagor';
+$labels['tabsharing'] = 'Delning';
+$labels['deleteobjectconfirm'] = 'Vill du verkligen ta bort denna händelse';
+$labels['deleteventconfirm'] = 'Vill du verkligen ta bort denna händelse';
+$labels['deletecalendarconfirm'] = 'Vill du verkligen ta bort denna kalender med alla dess händelser?';
+$labels['deletecalendarconfirmrecursive'] = 'Vill du verkligen ta bort denna kalender med alla händelser och delkalendrar?';
+$labels['savingdata'] = 'Sparar data ...';
+$labels['errorsaving'] = 'Misslyckades att spara ändringar.';
+$labels['operationfailed'] = 'Den begärda åtgärden misslyckades.';
+$labels['invalideventdates'] = 'Ogiltiga datum angivna! Kontrollera din inmatning.';
+$labels['invalidcalendarproperties'] = 'Ogiltiga kalenderegenskaper! Var god ange ett giltigt namn.';
+$labels['searchnoresults'] = 'Inga händelser hittades i de valda kalendrarna.';
+$labels['successremoval'] = 'Händelsen har tagits bort.';
+$labels['successrestore'] = 'Händelsen har återställts.';
+$labels['errornotifying'] = 'Det gick inte att skicka meddelande till händelsens deltagare';
+$labels['errorimportingevent'] = 'Det gick inte att importera händelsen';
+$labels['importwarningexists'] = 'En kopia av denna händelse finns redan i din kalender.';
+$labels['newerversionexists'] = 'En nyare version av denna händelse finns redan! Åtgärd avbruten.';
+$labels['nowritecalendarfound'] = 'Ingen kalender hittades att spara händelsen i';
+$labels['importedsuccessfully'] = 'Händelsen har lagts till i \'$calendar\'';
+$labels['updatedsuccessfully'] = 'Händelsen uppdaterades i \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Uppdaterade deltagarens status';
+$labels['itipsendsuccess'] = 'Inbjudan har skickats till deltagare.';
+$labels['itipresponseerror'] = 'Det gick inte att skicka ett svar på inbjudan till denna händelse';
+$labels['itipinvalidrequest'] = 'Denna inbjudan är inte längre giltig';
+$labels['sentresponseto'] = 'Skickade svar på inbjudan till $mailto';
+$labels['localchangeswarning'] = 'Du är på väg att göra ändringar som endast kommer att återspeglas i din kalender och inte skickas till organisatören av händelsen.';
+$labels['importsuccess'] = 'Importerade $nr händelser';
+$labels['importnone'] = 'Hittade inga händelser att importera';
+$labels['importerror'] = 'Ett fel uppstod vid import';
+$labels['aclnorights'] = 'Du har inga administratörsrättigheter i denna kalender.';
+$labels['changeeventconfirm'] = 'Ändra händelse';
+$labels['removeeventconfirm'] = 'Ta bort händelse';
+$labels['changerecurringeventwarning'] = 'Detta är en återkommande händelse. Vill du redigera endast den aktuella händelsen, denna och alla framtida händelser, alla händelser eller spara den som en ny händelse?';
+$labels['removerecurringeventwarning'] = 'etta är en återkommande händelse. Vill du ta bort endast den aktuella händelsen, denna och alla framtida händelser eller alla förekomster av denna händelse';
+$labels['currentevent'] = 'Nuvarande';
+$labels['futurevents'] = 'Framtida';
+$labels['allevents'] = 'Alla';
+$labels['saveasnew'] = 'Spara som ny';
+$labels['birthdays'] = 'Födelsedagar';
+$labels['birthdayscalendar'] = 'Födelsedagskalender';
+$labels['displaybirthdayscalendar'] = 'Visa födelsedagskalender';
+$labels['birthdayscalendarsources'] = 'Från dessa adressböcker';
+$labels['birthdayeventtitle'] = '$name\s födelsedag';
+$labels['birthdayage'] = '$age år';
+$labels['objectchangelog'] = 'Ändringshistorik';
+$labels['objectnotfound'] = 'Det gick inte att läsa in data för händelsen';
+$labels['objectchangelognotavailable'] = 'Ändringshistorik är inte tillgänglig för denna händelse';
+$labels['objectdiffnotavailable'] = 'Ingen jämförelse möjlig för valda revisioner';
+$labels['revisionrestoreconfirm'] = 'Vill du verkligen återställa revision $rev för denna händelse? Det kommer att ersätta den aktuella händelsen med den gamla versionen.';
+$labels['arialabelminical'] = 'Kalender datumurval';
+$labels['arialabelcalendarview'] = 'Kalender vy';
+$labels['arialabelsearchform'] = 'Händelse sökformulär';
+$labels['arialabelquicksearchbox'] = 'Händelse sökinmatning';
+$labels['arialabelcalsearchform'] = 'Kalender sökformulär';
+$labels['calendaractions'] = 'Kalender åtgärder';
+$labels['arialabeleventattendees'] = 'Händelse deltagarlista';
+$labels['arialabeleventresources'] = 'Händelse resurslista';
+$labels['arialabelresourcesearchform'] = 'Resurser sökformulär';
+$labels['arialabelresourceselection'] = 'Tillgängliga resurser';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/th_TH.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,247 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'มุมมองเริ่มต้น';
+$labels['time_format'] = 'รูปà¹à¸šà¸šà¸‚องà¸à¸²à¸£à¹à¸ªà¸”งเวลา';
+$labels['timeslots'] = 'จำนวนช่องเวลาต่อชั่วโมง';
+$labels['first_day'] = 'วันà¹à¸£à¸à¸‚องสัปดาห์';
+$labels['first_hour'] = 'ชั่วโมงà¹à¸£à¸à¸—ี่เริ่มà¹à¸ªà¸”งผล';
+$labels['workinghours'] = 'ชั่วโมงทำงาน';
+$labels['add_category'] = 'เพิ่มหมวดหมู่';
+$labels['remove_category'] = 'ลบหมวดหมู่';
+$labels['defaultcalendar'] = 'เพิ่มนัดหมายใหม่ใน';
+$labels['eventcoloring'] = 'à¸à¸²à¸£à¹ƒà¸«à¹‰à¸ªà¸µà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ต่างๆ';
+$labels['coloringmode0'] = 'ตามปฎิทิน';
+$labels['coloringmode1'] = 'ตามหมวดหมู่';
+$labels['afternothing'] = 'ไม่ต้องทำอะไร';
+$labels['aftertrash'] = 'ย้ายลงถังขยะ';
+$labels['afterdelete'] = 'ลบข้อความ';
+$labels['afterflagdeleted'] = 'ติดธงว่าลบà¹à¸¥à¹‰à¸§';
+$labels['aftermoveto'] = 'ย้ายไปยัง...';
+$labels['itipoptions'] = 'เชิà¸à¹€à¸‚้าร่วมนัดหมาย';
+$labels['afteraction'] = 'ภายหลังà¸à¸²à¸£à¸›à¸£à¸°à¸¡à¸§à¸¥à¸œà¸¥à¸‚้อความ คำเชิภหรือ ปรับปรุงสถานภาพ';
+$labels['calendar'] = 'ปฎิทิน';
+$labels['calendars'] = 'ปฎิทิน';
+$labels['category'] = 'หมวดหมู่';
+$labels['categories'] = 'หมวดหมู่';
+$labels['createcalendar'] = 'สร้างปฎิทินฉบับใหม่';
+$labels['editcalendar'] = 'à¹à¸à¹‰à¹„ขคุณสมบัติของปฎิทิน';
+$labels['name'] = 'ชื่อ';
+$labels['color'] = 'สี';
+$labels['day'] = 'วัน';
+$labels['week'] = 'สัปดาห์';
+$labels['month'] = 'เดือน';
+$labels['new'] = 'เพิ่ม';
+$labels['new_event'] = 'เพิ่มนัดหมาย';
+$labels['edit_event'] = 'à¹à¸à¹‰à¹„ขนัดหมาย';
+$labels['edit'] = 'à¹à¸à¹‰à¹„ข';
+$labels['save'] = 'บันทึà¸';
+$labels['removelist'] = 'นำออà¸à¸ˆà¸²à¸à¸£à¸²à¸¢à¸à¸²à¸£';
+$labels['cancel'] = 'ยà¸à¹€à¸¥à¸´à¸';
+$labels['select'] = 'เลือà¸';
+$labels['print'] = 'พิมพ์';
+$labels['printtitle'] = 'พิมพ์ปฎิทิน';
+$labels['title'] = 'สรุป';
+$labels['description'] = 'คำอธิบาย';
+$labels['all-day'] = 'ทั้งวัน';
+$labels['export'] = 'ส่งออà¸';
+$labels['exporttitle'] = 'ส่งออà¸à¹„ปยัง iCalendar';
+$labels['exportrange'] = 'เหตุà¸à¸²à¸£à¸“์จาà¸';
+$labels['exportattachments'] = 'พร้อมสิ่งที่à¹à¸™à¸šà¸¡à¸²à¸”้วย';
+$labels['location'] = 'สถานที่';
+$labels['date'] = 'วันที่';
+$labels['start'] = 'เริ่ม';
+$labels['starttime'] = 'เวลาเริ่ม';
+$labels['end'] = 'จบ';
+$labels['endtime'] = 'à¸à¸³à¸«à¸™à¸”เสร็จ';
+$labels['repeat'] = 'เà¸à¸´à¸”ซ้ำ';
+$labels['selectdate'] = 'เลือà¸à¸§à¸±à¸™à¸—ี่';
+$labels['free'] = 'ว่าง';
+$labels['busy'] = 'ติดธุระ';
+$labels['outofoffice'] = 'อยู่นอà¸à¸­à¸­à¸Ÿà¸Ÿà¸´à¸¨';
+$labels['tentative'] = 'à¹à¸™à¸§à¹‚น้ม';
+$labels['mystatus'] = 'สถานะของฉัน';
+$labels['status'] = 'สถานะ';
+$labels['status-confirmed'] = 'ยืนยัน';
+$labels['status-cancelled'] = 'ยà¸à¹€à¸¥à¸´à¸';
+$labels['priority'] = 'ความสำคัà¸';
+$labels['sensitivity'] = 'ความเป็นส่วนตัว';
+$labels['public'] = 'สาธารณะ';
+$labels['private'] = 'ส่วนตัว';
+$labels['confidential'] = 'ลับเฉพาะ';
+$labels['links'] = 'อ้างอิง';
+$labels['alarms'] = 'คำà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™';
+$labels['comment'] = 'ความคิดเห็น';
+$labels['created'] = 'สร้างเมื่อ';
+$labels['changed'] = 'à¹à¸à¹‰à¹„ขครั้งสุดท้ายเมื่อ';
+$labels['unknown'] = 'ไม่ทราบ';
+$labels['eventoptions'] = 'ทางเลือà¸';
+$labels['eventhistory'] = 'ประวัติ';
+$labels['printdescriptions'] = 'พิมพ์คำอธิบาย';
+$labels['parentcalendar'] = 'เพิ่มเข้าภายใต้';
+$labels['searchearlierdates'] = '« ค้นหาà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸“์ที่เà¸à¸´à¸”ชึ้นà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²';
+$labels['searchlaterdates'] = 'ค้นหาà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸“์ที่เà¸à¸´à¸”ขึ้นหลังจาภ»';
+$labels['andnmore'] = 'มีอีภ$nr';
+$labels['togglerole'] = 'à¸à¸”เพื่อเปลี่ยนสลับบทบาท';
+$labels['createfrommail'] = 'บันทึà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์';
+$labels['importevents'] = 'นำเข้าเหตุà¸à¸²à¸£à¸“์';
+$labels['importrange'] = 'เหตุà¸à¸²à¸£à¸“์จาà¸';
+$labels['onemonthback'] = 'ย้อนหลัง 1 เดือน';
+$labels['nmonthsback'] = 'ย้อนหลัง $nr เดือน';
+$labels['showurl'] = 'à¹à¸ªà¸”งลิงค์ปฎิทิน';
+$labels['showurldescription'] = 'ใช้ลิงค์ที่อยู่ต่อไปนี้เพื่อเข้าถึง (อ่านเท่านั้น) ปฎิทินของคุณจาà¸à¹‚ปรà¹à¸à¸£à¸¡à¸­à¸·à¹ˆà¸™  คุณสามารถคัดลอà¸à¹à¸¥à¸°à¸™à¸³à¹„ปวางไว้ในซอฟท์à¹à¸§à¸£à¹Œà¸›à¸Žà¸´à¸—ินที่รับรองรูปà¹à¸šà¸š iCal';
+$labels['caldavurldescription'] = 'คัดลอà¸à¸¥à¸´à¸‡à¸„์ที่อยู่นี้ไปยัง <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> โปรà¹à¸à¸£à¸¡à¹ƒà¸™à¹€à¸„รื่องลูà¸à¸‚่าย (เช่น Evolution หรือ Mozilla Thunderbird) เพื่อซิงค์ข้อมูลปฎิทินฉบับนี้à¸à¸±à¸šà¸„อมพิวเตอร์หรืออุปà¸à¸£à¸“์มือถือของคุณ';
+$labels['findcalendars'] = 'ค้นหาปฎิทิน...';
+$labels['searchterms'] = 'ข้อความที่ต้องà¸à¸²à¸£à¸„้นหา';
+$labels['calsearchresults'] = 'ปฎิทินที่มีให้เลือà¸';
+$labels['calendarsubscribe'] = 'à¹à¸ªà¸”งเป็นรายà¸à¸²à¸£à¸–าวร';
+$labels['nocalendarsfound'] = 'ไม่พบปฎิทิน';
+$labels['nrcalendarsfound'] = 'พบปฎิทิน $nr ฉบับ';
+$labels['quickview'] = 'ดูเฉพาะปฎิทินฉบับนี้';
+$labels['invitationspending'] = 'จดหมายเชิà¸à¸—ี่ยังค้างอยู่';
+$labels['invitationsdeclined'] = 'ปฎิเสธคำเชิà¸';
+$labels['changepartstat'] = 'เปลี่ยนสถานะของผู้เข้าร่วม';
+$labels['rsvpcomment'] = 'คำเชิà¸';
+$labels['listrange'] = 'ขอบเขตุที่à¹à¸ªà¸”ง';
+$labels['listsections'] = 'à¹à¸šà¹ˆà¸‡à¹€à¸›à¹‡à¸™:';
+$labels['until'] = 'จนถึง';
+$labels['today'] = 'วันนี้';
+$labels['tomorrow'] = 'พรุ่งนี้';
+$labels['thisweek'] = 'สัปดาห์นี้';
+$labels['nextweek'] = 'สัปดาห์หน้า';
+$labels['prevweek'] = 'สัปดาห์ที่à¹à¸¥à¹‰à¸§';
+$labels['thismonth'] = 'เดือนนี้';
+$labels['nextmonth'] = 'เดือนหน้า';
+$labels['weekofyear'] = 'สัปดาห์';
+$labels['pastevents'] = 'อดีต';
+$labels['futureevents'] = 'อนาคต';
+$labels['showalarms'] = 'à¹à¸ªà¸”งข้อความเตือน';
+$labels['defaultalarmtype'] = 'ค่าà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™à¹€à¸£à¸´à¹ˆà¸¡à¸•à¹‰à¸™';
+$labels['attendee'] = 'ผู้เข้าร่วม';
+$labels['role'] = 'บทบาท';
+$labels['confirmstate'] = 'สถานะ';
+$labels['addattendee'] = 'เพิ่มผู้เข้าร่วม';
+$labels['roleorganizer'] = 'ผู้จัดงาน';
+$labels['rolerequired'] = 'บังคับ';
+$labels['roleoptional'] = 'เลือà¸à¹„ด้';
+$labels['rolenonparticipant'] = 'ขาด';
+$labels['cutypeindividual'] = 'บุคคล';
+$labels['cutypegroup'] = 'à¸à¸¥à¸¸à¹ˆà¸¡';
+$labels['cutyperesource'] = 'ทรัพยาà¸à¸£';
+$labels['cutyperoom'] = 'ห้อง';
+$labels['availfree'] = 'ว่าง';
+$labels['availbusy'] = 'ติดธุระ';
+$labels['availunknown'] = 'ไม่ทราบ';
+$labels['availtentative'] = 'à¹à¸™à¸§à¹‚น้ม';
+$labels['availoutofoffice'] = 'ไม่อยู่ออฟฟิศ';
+$labels['delegatedto'] = 'มอบหมายให้';
+$labels['delegatedfrom'] = 'รับมอบจาà¸';
+$labels['scheduletime'] = 'ค้นหาส่วนที่ว่าง';
+$labels['sendinvitations'] = 'ส่งคำเชิà¸';
+$labels['sendnotifications'] = 'à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™à¸œà¸¹à¹‰à¹€à¸‚้าร่วมสำหรับà¸à¸²à¸£à¹à¸à¹‰à¹„ข';
+$labels['sendcancellation'] = 'à¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™à¸œà¸¹à¹‰à¹€à¸‚้าร่วมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸à¸²à¸£à¸¢à¸à¹€à¸¥à¸´à¸';
+$labels['onlyworkinghours'] = 'ค้นหาเวลาว่างในช่วงชั่วโมงà¸à¸²à¸£à¸—ำงานของฉัน';
+$labels['reqallattendees'] = 'บังคับ/ผู้เข้าร่วมทุà¸à¸„น';
+$labels['prevslot'] = 'ช่องว่างà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸™à¸µà¹‰';
+$labels['nextslot'] = 'ช่องว่า่งถัดจาà¸à¸™à¸µà¹‰';
+$labels['suggestedslot'] = 'à¹à¸™à¸°à¸™à¸³à¸Šà¹ˆà¸­à¸‡à¸§à¹ˆà¸²à¸‡';
+$labels['noslotfound'] = 'ไม่สามารถหาช่วงเวลาที่ว่าง';
+$labels['invitationsubject'] = 'คุณได้รับเชิà¸à¹„ปยัง "$title"';
+$labels['invitationattendlinks'] = "ในà¸à¸£à¸“ีที่โปรà¹à¸à¸£à¸¡à¸­à¸µà¹€à¸¡à¸¥à¹Œà¸‚องคุณไม่รองรับ 'à¸à¸²à¸£à¸£à¹‰à¸­à¸‡à¸‚อ iTip' คุณสามารถใช้ลิงค์ต่อไปนี้ในà¸à¸²à¸£à¸•à¸­à¸šà¸£à¸±à¸šà¸«à¸£à¸·à¸­à¸›à¸Žà¸´à¹€à¸ªà¸˜à¸ˆà¸”หมายเชิà¸à¸‰à¸šà¸±à¸šà¸™à¸µà¹‰ :\n\$url";
+$labels['eventupdatesubject'] = '"$title" ได้รับà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¸ªà¸–านะ';
+$labels['eventupdatesubjectempty'] = 'เหตุà¸à¸²à¸£à¸“์ที่คุณเป็นห่วงได้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¸ªà¸–านะà¹à¸¥à¹‰à¸§';
+$labels['eventcancelmailbody'] = "*\$title*\n\nเมื่อ: \$date\n\nInvitees: \$attendees\n\n เหตุà¸à¸²à¸£à¸“์ได้ถูà¸à¸¢à¸à¹€à¸¥à¸´à¸à¹‚ดย \$organizer.\n\n ไฟล์ iCalendar ที่à¹à¸™à¸šà¸¡à¸²à¸”้วย ได้รับà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”เรียบร้อยà¹à¸¥à¹‰à¸§";
+$labels['itipobjectnotfound'] = 'เหตุà¸à¸²à¸£à¸“์ที่อ้างถึงโดยข้อความนี้ไม่ถูà¸à¸•à¸£à¸§à¸ˆà¸žà¸šà¹ƒà¸™à¸›à¸Žà¸´à¸—ินของคุณ';
+$labels['itipmailbodyaccepted'] = "\$sender ได้ตอบรับคำเชิà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ต่อไปนี้:\n\n*\$title*\n\nเมื่อ: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender มีà¹à¸™à¸§à¹‚น้มที่จะตอบรับคำเชิà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ต่อไปนี้:\n\n*\$title*\n\nเมื่อ: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender ได้ปฎิเสธคำเชิà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ต่อไปนี้ :\n\n*\$title*\n\nเมื่อ: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender ปฎิเสธà¸à¸²à¸£à¹€à¸‚้าร่วมของคุณในเหตุà¸à¸²à¸£à¸“์ต่อไปนี้:\n\n*\$title*\n\nเมื่อ: \$date";
+$labels['itipmailbodydelegated'] = "\$sender ได้มอบหมายà¸à¸²à¸£à¹€à¸‚้าร่วมเหตุà¸à¸²à¸£à¸“์ต่อไปนี้:\n\n*\$title*\n\nเมื่อ: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender ได้มอบหมายให้คุณเข้าร่วมเหตุà¸à¸²à¸£à¸“์ต่อไปนี้:\n\n*\$title*\n\nเมื่อ: \$date";
+$labels['itipdeclineevent'] = 'คุณต้องà¸à¸²à¸£à¸›à¸Žà¸´à¹€à¸ªà¸˜à¸„ำเชิà¸à¸ªà¸³à¸«à¸£à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้หรือไม่';
+$labels['declinedeleteconfirm'] = 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ที่ปฎิเสธนี้ออà¸à¸ˆà¸²à¸à¸›à¸Žà¸´à¸—ินของคุณหรือไม่';
+$labels['itipcomment'] = 'ความคิดเห็น คำเชิà¸/คำà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™';
+$labels['itipcommenttitle'] = 'ความคิดเห็นนี้จะถูà¸à¹à¸™à¸šà¹„ปพร้อมà¸à¸±à¸šà¸‚้อความ คำเชิà¸/คำà¹à¸ˆà¹‰à¸‡à¹€à¸•à¸·à¸­à¸™ ไปยังผู้เข้าร่วม';
+$labels['notanattendee'] = 'คุณไม่อยู่ในรายชื่อผุ้เข้าร่วมสำหรับเหตุà¸à¸²à¸£à¸“์นี้';
+$labels['eventcancelled'] = 'เหตุà¸à¸²à¸£à¸“์ได้ถูà¸à¸¢à¸à¹€à¸¥à¸´à¸';
+$labels['saveincalendar'] = 'บันทึà¸à¹ƒà¸™';
+$labels['updatemycopy'] = 'ปรับปรุงปฎิทินของฉัน';
+$labels['savetocalendar'] = 'บันทึà¸à¹„ปยังปฎิทินของฉัน';
+$labels['openpreview'] = 'ตรวจสอบปฎิทิน';
+$labels['noearlierevents'] = 'ไม่มีเหตุà¸à¸²à¸£à¸“์à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²';
+$labels['nolaterevents'] = 'ไม่มีเหตุà¸à¸²à¸£à¸“์หลังจาà¸à¸™à¸±à¹‰à¸™';
+$labels['resource'] = 'ทรัพยาà¸à¸£';
+$labels['addresource'] = 'จองทรัพยาà¸à¸£';
+$labels['findresources'] = 'ค้นหาทรัพยาà¸à¸£';
+$labels['resourcedetails'] = 'รายละเอียด';
+$labels['resourceavailability'] = 'ยังว่างอยู่';
+$labels['resourceowner'] = 'เจ้าของ';
+$labels['resourceadded'] = 'ทรัพยาà¸à¸£à¹„ด้ถูà¸à¹€à¸žà¸´à¹ˆà¸¡à¹„ปยังเหตุà¸à¸²à¸£à¸“์ของคุณ';
+$labels['tabsummary'] = 'สรุป';
+$labels['tabrecurrence'] = 'à¸à¸²à¸£à¹€à¸à¸´à¸”ซ้ำ';
+$labels['tabattendees'] = 'ผู้เข้าร่วม';
+$labels['tabresources'] = 'ทรัพยาà¸à¸£';
+$labels['tabattachments'] = 'สิ่งที่à¹à¸™à¸šà¸¡à¸²à¸”้วย';
+$labels['tabsharing'] = 'à¹à¸šà¹ˆà¸‡à¸›à¸±à¸™';
+$labels['deleteobjectconfirm'] = 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้หรือไม่';
+$labels['deleteventconfirm'] = 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้หรือไม่';
+$labels['deletecalendarconfirm'] = 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸›à¸Žà¸´à¸—ินฉบับนี้พร้อมà¸à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ทั้งหมดหรือไม่';
+$labels['deletecalendarconfirmrecursive'] = 'คุณต้องà¸à¸²à¸£à¸¥à¸šà¸›à¸Žà¸´à¸—ินฉบับนี้พร้อมเหตุà¸à¸²à¸£à¸“์à¹à¸¥à¸°à¸›à¸Žà¸´à¸—ินย่อยทั้งหมดหรือไม่';
+$labels['savingdata'] = 'บันทึà¸à¸‚้อมูล';
+$labels['errorsaving'] = 'à¸à¸²à¸£à¸šà¸±à¸™à¸—ึà¸à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸¥à¹‰à¸¡à¹€à¸«à¸¥à¸§';
+$labels['operationfailed'] = 'à¸à¸²à¸£à¸—ำตามคำร้องล้มเหลว';
+$labels['invalideventdates'] = 'วันที่ที่ป้อนเข้ามาไม่ถูà¸à¸•à¹‰à¸­à¸‡  โปรดตรวจสอบà¸à¸²à¸£à¸›à¹‰à¸­à¸™à¸‚้อมูลของท่าน';
+$labels['invalidcalendarproperties'] = 'à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸„่าคุณสมบัติปฎิทินไม่ถูà¸à¸•à¹‰à¸­à¸‡ โปรดตั้งชื่อให้ถูà¸à¸•à¹‰à¸­à¸‡';
+$labels['searchnoresults'] = 'ไม่พบเหตุà¸à¸²à¸£à¸“์ในปฎิทินฉบับที่เลือà¸';
+$labels['successremoval'] = 'เหตุà¸à¸²à¸£à¸“์ได้ถูà¸à¸¥à¸šà¹€à¸£à¸µà¸¢à¸šà¸£à¹‰à¸­à¸¢à¹à¸¥à¹‰à¸§';
+$labels['successrestore'] = 'à¸à¸²à¸£à¸à¸¹à¹‰à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์เรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['errornotifying'] = 'เà¸à¸´à¸”ข้อผิดพลาดในà¸à¸²à¸£à¸ªà¹ˆà¸‡à¸‚้อความเตือนไปยังผู้เข้าร่วมเหตุà¸à¸²à¸£à¸“์';
+$labels['errorimportingevent'] = 'à¸à¸²à¸£à¸™à¸³à¹€à¸‚้าเหตุà¸à¸²à¸£à¸“์เà¸à¸´à¸”ข้อผิดพลาด';
+$labels['importwarningexists'] = 'มีเหตุà¸à¸²à¸£à¸“์นี้อยู่ในปฎิทินของคุณอยู่à¹à¸¥à¹‰à¸§';
+$labels['newerversionexists'] = 'มีเหตุà¸à¸²à¸£à¸“์ที่มีรุ่นควบคุมที่ใหม่à¸à¸§à¹ˆà¸²à¸­à¸¢à¸¹à¹ˆà¹à¸¥à¹‰à¸§ ยà¸à¹€à¸¥à¸´à¸à¸à¸²à¸£à¸™à¸³à¹€à¸‚้า';
+$labels['nowritecalendarfound'] = 'ไม่พบปฎิทินที่จะบันทึà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้';
+$labels['importedsuccessfully'] = 'เหตุà¸à¸²à¸£à¸“์ได้ถูà¸à¹€à¸žà¸´à¹ˆà¸¡à¹„ปยัง \'$calendar\' เรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['updatedsuccessfully'] = 'เหตุà¸à¸²à¸£à¸“์ได้ถูà¸à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹„ปยัง \'$calendar\' เรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['attendeupdateesuccess'] = 'à¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¸ªà¸–านะของผู้เข้าร่วมเรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['itipsendsuccess'] = 'คำเชิà¸à¸–ูà¸à¸ªà¹ˆà¸‡à¹„ปยังผู้เข้าร่วมà¹à¸¥à¹‰à¸§';
+$labels['itipresponseerror'] = 'à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸„ำตอบรับสำหรับคำเชิà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้ล้มเหลว';
+$labels['itipinvalidrequest'] = 'คำเชิà¸à¸™à¸µà¹‰à¹„ม่มีผลà¹à¸¥à¹‰à¸§';
+$labels['sentresponseto'] = 'à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸„ำตอบรับสำหรับคำเชิà¸à¹„ปยัง $mailto เรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['localchangeswarning'] = 'คุณà¸à¸³à¸¥à¸±à¸‡à¸›à¸£à¸±à¸šà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”ซึ่งมีผลà¸à¸±à¸šà¸›à¸Žà¸´à¸—ินของคุณเท่านั้น à¹à¸¥à¸° à¸à¸²à¸£à¸›à¸£à¸±à¸šà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸ˆà¸°à¹„ม่ถูà¸à¸ªà¹ˆà¸‡à¹„ปยังผู้จัดของเหตุà¸à¸²à¸£à¸“์นี้';
+$labels['importsuccess'] = 'นำเข้า $nr เหตุà¸à¸²à¸£à¸“์เรียบร้อยà¹à¸¥à¹‰à¸§';
+$labels['importnone'] = 'ไม่พบเหตุà¸à¸²à¸£à¸“์ที่จะนำเข้า';
+$labels['importerror'] = 'พบข้อผิดพลาดในระหว่างนำเข้า';
+$labels['aclnorights'] = 'คุณไม่มีสิทธิของผู้ดูà¹à¸¥à¸£à¸°à¸šà¸šà¸‚องปฎิทินฉบับนี้';
+$labels['changeeventconfirm'] = 'เปลี่ยนเหตุà¸à¸²à¸£à¸“์';
+$labels['removeeventconfirm'] = 'ลบเหตุà¸à¸²à¸£à¸“์';
+$labels['changerecurringeventwarning'] = 'เหตุà¸à¸²à¸£à¸“์นี้เป็นเหตุà¸à¸²à¸£à¸“์ที่เà¸à¸´à¸”ซ้ำ  คุณต้องà¸à¸²à¸£à¹à¸à¹‰à¹„ขเฉพาะเหตุà¸à¸²à¸£à¸“์ปัจจุบันเท่านั้น  เหตุà¸à¸²à¸£à¸“์ปัจจุบันรวมทั้งในอนาคต หรือ เหตุà¸à¸²à¸£à¸“์ทั้งหมด หรือ บันทึà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ใหม่';
+$labels['removerecurringeventwarning'] = 'เหตุà¸à¸²à¸£à¸“์นี้เป็นเหตุà¸à¸²à¸£à¸“์ที่เà¸à¸´à¸”ซ้ำ  คุณต้องà¸à¸²à¸£à¸¥à¸šà¹€à¸‰à¸žà¸²à¸°à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ปัจจุบันเท่านั้น  เหตุà¸à¸²à¸£à¸“์ปัจจุบันรวมทั้งในอนาคต หรือ เหตุà¸à¸²à¸£à¸“์ทั้งหมด';
+$labels['removerecurringallonly'] = 'เหตุà¸à¸²à¸£à¸“์นี้เป็นเหตุà¸à¸²à¸£à¸“์ที่เà¸à¸´à¸”ซ้ำ ในà¸à¸²à¸™à¸°à¸œà¸¹à¹‰à¹€à¸‚้าร่วม คุณสามารถทำà¸à¸²à¸£à¸¥à¸šà¹„ด้เฉพาะà¹à¸šà¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ทั้งหมด พร้อม à¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸—ี่จะเà¸à¸´à¸”ขึ้นทั้งหมดเท่านั้น';
+$labels['currentevent'] = 'ปัจจุบัน';
+$labels['futurevents'] = 'อนาคต';
+$labels['allevents'] = 'ทั้งหมด';
+$labels['saveasnew'] = 'บันทึà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ใหม่';
+$labels['birthdays'] = 'วันเà¸à¸´à¸”';
+$labels['birthdayscalendar'] = 'ปฎิทินวันเà¸à¸´à¸”';
+$labels['displaybirthdayscalendar'] = 'à¹à¸ªà¸”งปฎิทินวันเà¸à¸´à¸”';
+$labels['birthdayscalendarsources'] = 'จาà¸à¸ªà¸¡à¸¸à¸”ที่อยู่เหล่านี้';
+$labels['birthdayeventtitle'] = 'วันเà¸à¸´à¸”ของ $name';
+$labels['birthdayage'] = 'อายุ $age ปี';
+$labels['objectchangelog'] = 'ประวัติà¸à¸²à¸£à¸›à¸£à¸±à¸šà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™';
+$labels['objectnotfound'] = 'à¸à¸²à¸£à¹‚หลดข้อมูลของเหตุà¸à¸²à¸£à¸“์ล้มเหลว';
+$labels['objectchangelognotavailable'] = 'ไม่มีประวัติà¸à¸²à¸£à¸›à¸£à¸±à¸šà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸ªà¸³à¸«à¸£à¸±à¸šà¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์นี้';
+$labels['objectdiffnotavailable'] = 'ไม่สามารถเปรียบเทียบรุ่นà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¸—ี่เลือà¸à¹„ด้';
+$labels['revisionrestoreconfirm'] = 'คุณต้องà¸à¸²à¸£à¸à¸¹à¹‰à¸„ืนรุ่นà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡ $rev ของเหตุà¸à¸²à¸£à¸“์นี้หรือ  นี่จะเป็นà¸à¸²à¸£à¹€à¸‚ียนทับข้อมูลปัจจุบันด้วยข้อมูลที่เà¸à¹ˆà¸²à¸à¸§à¹ˆà¸²';
+$labels['arialabelcalendarview'] = 'มุมมองปฎิทิน';
+$labels['arialabelquicksearchbox'] = 'ป้อนข้อมูลเพื่อค้นหาเหตุà¸à¸²à¸£à¸“์';
+$labels['arialabelcalsearchform'] = 'ฟอร์มค้นหาปฎิทิน';
+$labels['arialabeleventattendees'] = 'รายชื่อผู้เข้าร่วมเหตุà¸à¸²à¸£à¸“์';
+$labels['arialabeleventresources'] = 'รายà¸à¸²à¸£à¸—รัพยาà¸à¸£à¸—ี่ใช้ในเหตุà¸à¸²à¸£à¸“์';
+$labels['arialabelresourcesearchform'] = 'ฟอร์มค้นหาทรัพยาà¸à¸£';
+$labels['arialabelresourceselection'] = 'ทรัพยาà¸à¸£à¸—ี่ยังว่าง';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/tr_TR.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/uk_UA.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,218 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Початковий виглÑд';
+$labels['time_format'] = 'Формат чаÑу';
+$labels['timeslots'] = 'Проміжків на годину';
+$labels['first_day'] = 'Перший день тижнÑ';
+$labels['first_hour'] = 'Показувати починаючи з';
+$labels['workinghours'] = 'Робочі години';
+$labels['add_category'] = 'Додати категорію';
+$labels['remove_category'] = 'Вилучити категорію';
+$labels['defaultcalendar'] = 'Створити нову подію в';
+$labels['eventcoloring'] = 'Колір події';
+$labels['coloringmode0'] = 'Згідно кольору календарÑ';
+$labels['coloringmode1'] = 'Згідно кольору категорії';
+$labels['coloringmode2'] = 'Колір ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð´Ð»Ñ Ñ€Ð°Ð¼ÐºÐ¸, колір категорії Ð´Ð»Ñ Ñ„Ð¾Ð½Ñƒ';
+$labels['coloringmode3'] = 'Колір категорії Ð´Ð»Ñ Ñ€Ð°Ð¼ÐºÐ¸, колір ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð´Ð»Ñ Ñ„Ð¾Ð½Ñƒ';
+$labels['afternothing'] = 'Ðічого не робити';
+$labels['aftertrash'] = 'ПереміÑтити до Ñмітника';
+$labels['afterdelete'] = 'Вилучити повідомленнÑ';
+$labels['afterflagdeleted'] = 'Відмітити Ñк видалене';
+$labels['aftermoveto'] = 'ПереміÑтити в ...';
+$labels['itipoptions'] = 'Ð—Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð½Ð° події';
+$labels['afteraction'] = 'ПіÑÐ»Ñ Ñ‚Ð¾Ð³Ð¾, Ñк Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð°Ð±Ð¾ Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ його зміну оброблено';
+$labels['calendar'] = 'Календар';
+$labels['calendars'] = 'Календарі';
+$labels['category'] = 'КатегоріÑ';
+$labels['categories'] = 'Категорії';
+$labels['createcalendar'] = 'Створити новий календар';
+$labels['editcalendar'] = 'Змінити влаÑтивоÑÑ‚Ñ– календарÑ';
+$labels['name'] = 'Ðазва';
+$labels['color'] = 'Колір';
+$labels['day'] = 'День';
+$labels['week'] = 'Тиждень';
+$labels['month'] = 'МіÑÑць';
+$labels['agenda'] = 'РозпорÑдок днÑ';
+$labels['new'] = 'Ðовий';
+$labels['new_event'] = 'Ðова подіÑ';
+$labels['edit_event'] = 'Редагувати подію';
+$labels['edit'] = 'Редагувати';
+$labels['save'] = 'Зберегти';
+$labels['removelist'] = 'Вилучити зі ÑпиÑку';
+$labels['cancel'] = 'Відмінити';
+$labels['select'] = 'Вибрати';
+$labels['print'] = 'Друк';
+$labels['printtitle'] = 'Друкувати календар';
+$labels['title'] = 'Резюме';
+$labels['description'] = 'ОпиÑ';
+$labels['all-day'] = 'веÑÑŒ день';
+$labels['export'] = 'ЕкÑпорт';
+$labels['exporttitle'] = 'ЕкÑпорт в iCalendar';
+$labels['exportrange'] = 'Події починаючи з';
+$labels['exportattachments'] = 'Із вкладеннÑми';
+$labels['customdate'] = 'Спеціальна дата';
+$labels['location'] = 'РозташуваннÑ';
+$labels['url'] = 'URL';
+$labels['date'] = 'Дата';
+$labels['start'] = 'Початок';
+$labels['starttime'] = 'Ð§Ð°Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ';
+$labels['end'] = 'ЗакінченнÑ';
+$labels['endtime'] = 'Ð§Ð°Ñ Ð·Ð°ÐºÑ–Ð½Ñ‡ÐµÐ½Ð½Ñ';
+$labels['repeat'] = 'Повторити';
+$labels['selectdate'] = 'Виберіть дату';
+$labels['freebusy'] = 'Показати Ñк';
+$labels['free'] = 'Вільний';
+$labels['busy'] = 'ЗайнÑтий';
+$labels['outofoffice'] = 'Поза офіÑом';
+$labels['tentative'] = 'Ðевизначений';
+$labels['mystatus'] = 'Мій ÑтатуÑ';
+$labels['status'] = 'СтатуÑ';
+$labels['status-confirmed'] = 'Підтверджений';
+$labels['status-cancelled'] = 'Відмінений';
+$labels['priority'] = 'Пріоритет';
+$labels['sensitivity'] = 'КонфіденційніÑÑ‚ÑŒ';
+$labels['public'] = 'публічна';
+$labels['private'] = 'приватна';
+$labels['confidential'] = 'конфіденційна';
+$labels['alarms'] = 'ÐагадуваннÑ';
+$labels['comment'] = 'Коментарій';
+$labels['created'] = 'Створена';
+$labels['changed'] = 'Змінена';
+$labels['unknown'] = 'Ðевідомо';
+$labels['eventoptions'] = 'Опції';
+$labels['generated'] = 'Ñтворений';
+$labels['eventhistory'] = 'ІÑторіÑ';
+$labels['printdescriptions'] = 'Друк опиÑу';
+$labels['parentcalendar'] = 'Ð’Ñтавити вÑередину';
+$labels['searchearlierdates'] = '« Шукати події раніше';
+$labels['searchlaterdates'] = 'Шукати події пізніше »';
+$labels['andnmore'] = '$nr більше...';
+$labels['togglerole'] = 'Клікніть Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¸ÐºÐ°Ð½Ð½Ñ Ñ€Ð¾Ð»Ñ–';
+$labels['createfrommail'] = 'Зберегти Ñк подію';
+$labels['importevents'] = 'Імпортувати подію';
+$labels['importrange'] = 'Події починаючи з';
+$labels['onemonthback'] = '1 міÑÑць назад';
+$labels['nmonthsback'] = '$nr міÑÑÑ†Ñ (ів) тому';
+$labels['showurl'] = 'Показати URL календарÑ';
+$labels['showurldescription'] = 'ВикориÑтовуйте наÑтупну адреÑу Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Вашого ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð· інших додатків. Ви можете Ñкопіювати Ñ– вÑтавити це в будь-Ñкий додаток Ñкий підтримує формат iCal.';
+$labels['caldavurldescription'] = 'Скопіюйте цей Ð°Ð´Ñ€ÐµÑ Ð² клієнт, Ñкий підтримує <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> (наприклад, Evolution або Mozilla Thunderbird) Ð´Ð»Ñ Ð¿Ð¾Ð²Ð½Ð¾Ñ— Ñинхронізації даного ÐºÐ°Ð»ÐµÐ½Ð´Ð°Ñ€Ñ Ð·Ñ– Ñвоїм комп\'ютером або мобільним приÑтроєм.';
+$labels['findcalendars'] = 'Знайти календарі...';
+$labels['searchterms'] = 'Умови пошуку';
+$labels['calsearchresults'] = 'ДоÑтупні календарі';
+$labels['calendarsubscribe'] = 'Завжди показувати';
+$labels['nocalendarsfound'] = 'Ðе знайдено календарів';
+$labels['nrcalendarsfound'] = '$nr календарів знайдено';
+$labels['quickview'] = 'ПодивитиÑÑ Ñ‚Ñ–Ð»ÑŒÐºÐ¸ цей календар';
+$labels['invitationspending'] = 'Очікуючі запрошеннÑ';
+$labels['invitationsdeclined'] = 'Відхилені запрошеннÑ';
+$labels['changepartstat'] = 'Змінити ÑÑ‚Ð°Ñ‚ÑƒÑ ÑƒÑ‡Ð°Ñника';
+$labels['rsvpcomment'] = 'ТекÑÑ‚ запрошеннÑ';
+$labels['listrange'] = 'Діапазон:';
+$labels['listsections'] = 'Розділити на:';
+$labels['smartsections'] = 'Розумні Ñекції';
+$labels['until'] = 'до';
+$labels['today'] = 'Сьогодні';
+$labels['tomorrow'] = 'Завтра';
+$labels['thisweek'] = 'Поточний тиждень';
+$labels['nextweek'] = 'ÐаÑтупний тиждень';
+$labels['prevweek'] = 'Попередній тиждень';
+$labels['thismonth'] = 'Поточний міÑÑць';
+$labels['nextmonth'] = 'ÐаÑтупний міÑÑць';
+$labels['weekofyear'] = 'Тиждень';
+$labels['pastevents'] = 'Минуле';
+$labels['futureevents'] = 'Майбутнє';
+$labels['showalarms'] = 'Показувати нагадуваннÑ';
+$labels['defaultalarmtype'] = 'Типові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½ÑŒ';
+$labels['defaultalarmoffset'] = 'Типовий Ñ‡Ð°Ñ Ð½Ð°Ð³Ð°Ð´ÑƒÐ²Ð°Ð½ÑŒ';
+$labels['attendee'] = 'УчаÑний';
+$labels['role'] = 'Роль';
+$labels['availability'] = 'ДоÑтупніÑÑ‚ÑŒ';
+$labels['confirmstate'] = 'СтатуÑ';
+$labels['addattendee'] = 'Додати учаÑника';
+$labels['roleorganizer'] = 'Організатор';
+$labels['rolerequired'] = 'Обов\'Ñзковий';
+$labels['roleoptional'] = 'Ðеобов\'Ñзковий';
+$labels['cutypeindividual'] = 'Окремий';
+$labels['cutypegroup'] = 'Група';
+$labels['cutyperesource'] = 'РеÑурÑ';
+$labels['cutyperoom'] = 'Кімната';
+$labels['availfree'] = 'Вільний';
+$labels['availbusy'] = 'ЗайнÑтий';
+$labels['availunknown'] = 'Ðевідомо';
+$labels['availtentative'] = 'Ðевизначений';
+$labels['availoutofoffice'] = 'Поза офіÑом';
+$labels['delegatedto'] = 'Доручено:';
+$labels['delegatedfrom'] = 'Доручено від:';
+$labels['scheduletime'] = 'Знайти доÑтупних';
+$labels['sendinvitations'] = 'ЗапроÑити';
+$labels['sendnotifications'] = 'Повідомити учаÑників про зміни';
+$labels['sendcancellation'] = 'Повідомити учаÑників про ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾Ð´Ñ–Ñ—';
+$labels['reqallattendees'] = 'Ðеобхідні/вÑÑ– учаÑники';
+$labels['prevslot'] = 'Попередній чаÑ';
+$labels['nextslot'] = 'ÐаÑтупний чаÑ';
+$labels['suggestedslot'] = 'Пропонований чаÑ';
+$labels['noslotfound'] = 'Ðеможливо знайти вільний чаÑ';
+$labels['invitationsubject'] = 'Ви запрошені на ';
+$labels['invitationmailbody'] = "*\$title*\n\nКоли: \$date\n\nЗапрошені: \$attendees\n\nУ вкладенні Ви знайдете файл iCalendar з уÑіма деталÑми події, Ñкий Ви можете імпортувати у Вашу програму-щоденник.";
+$labels['invitationattendlinks'] = "У разі, Ñкщо Ваш поштовий клієнт не підтримує запити iTip, Ви можете викориÑтати дане нижче поÑиланнÑ, щоб прийнÑти або відхилити це запрошеннÑ:\n\$url";
+$labels['eventupdatesubject'] = '"$title" була оновлена';
+$labels['eventupdatesubjectempty'] = 'ПодіÑ, Ñка ÑтоÑуєтьÑÑ Ð’Ð°Ñ, була оновленна ';
+$labels['eventupdatemailbody'] = "*\$title*\n\nКоли: \$date\n\nЗапрошені: \$attendees\n\nУ вкладенні Ви знайдете файл iCalendar з уÑіма змінами у події, Ñкий Ви можете імпортувати у Вашу програму-щоденник.";
+$labels['eventcancelmailbody'] = "*\$title*\n\nКоли: \$date\n\nЗапрошені: \$attendees\n\nÐ¦Ñ Ð¿Ð¾Ð´Ñ–Ñ ÑкаÑована \$organizer.\n\nУ вкладенні Ви знайдете файл iCalendar з уÑіма змінами у події.";
+$labels['itipobjectnotfound'] = 'Згадану в даному повідомленні подію, не знайдено у Вашому календарі.';
+$labels['itipdeclineevent'] = 'Ви хочете відхилити Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð½Ð° дану подію?';
+$labels['declinedeleteconfirm'] = 'Чи хочете Ви також видалити відхилену подію з вашого календарÑ?';
+$labels['itipcomment'] = 'Коментар до запрошеннÑ/повідомленнÑ';
+$labels['itipcommenttitle'] = 'Даний коментар буде прикріплений до запрошеннÑ/повідомленнÑм, надіÑланого учаÑникам';
+$labels['notanattendee'] = 'Ви не в ÑпиÑку учаÑників цієї події';
+$labels['eventcancelled'] = 'Дана Ð¿Ð¾Ð´Ñ–Ñ Ð²Ñ–Ð´Ð¼Ñ–Ð½ÐµÐ½Ð°';
+$labels['saveincalendar'] = 'зберегти в';
+$labels['updatemycopy'] = 'Оновити в моєму календарі';
+$labels['savetocalendar'] = 'Зберегти в календар';
+$labels['openpreview'] = 'Перевірте календар';
+$labels['noearlierevents'] = 'Ðемає попередніх подій';
+$labels['nolaterevents'] = 'Ðемає наÑтупних подій';
+$labels['resource'] = 'РеÑурÑ';
+$labels['addresource'] = 'Зарезервувати реÑурÑ';
+$labels['findresources'] = 'Знайти реÑурÑ';
+$labels['resourcedetails'] = 'Подробиці';
+$labels['resourceavailability'] = 'ДоÑтупніÑÑ‚ÑŒ';
+$labels['resourceowner'] = 'ВлаÑник';
+$labels['resourceadded'] = 'РеÑÑƒÑ€Ñ Ð´Ð¾Ð´Ð°Ð½Ð¸Ð¹ у Вашу подію';
+$labels['tabsummary'] = 'Резюме';
+$labels['tabrecurrence'] = 'ПовтореннÑ';
+$labels['tabattendees'] = 'УчаÑники';
+$labels['tabresources'] = 'РеÑурÑи';
+$labels['tabattachments'] = 'ВкладеннÑ';
+$labels['tabsharing'] = 'Спільне викориÑтаннÑ';
+$labels['savingdata'] = 'Ð—Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ…...';
+$labels['errorsaving'] = 'Помилка Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½.';
+$labels['changeeventconfirm'] = 'Змінити подію';
+$labels['removeeventconfirm'] = 'Вилучити подію';
+$labels['currentevent'] = 'Поточну';
+$labels['futurevents'] = 'Майбутню';
+$labels['allevents'] = 'Ð’ÑÑ–';
+$labels['birthdays'] = 'Дні ÐародженнÑ';
+$labels['birthdayscalendar'] = 'Календар Днів Ðароджень';
+$labels['displaybirthdayscalendar'] = 'Показувати календар Днів Ðароджень';
+$labels['birthdayscalendarsources'] = 'З даних адреÑних книг';
+$labels['birthdayeventtitle'] = 'День ÐÐ°Ñ€Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ $name';
+$labels['birthdayage'] = 'Вік $age';
+$labels['objectchangelog'] = 'ІÑÑ‚Ð¾Ñ€Ñ–Ñ Ð·Ð¼Ñ–Ð½';
+$labels['arialabelminical'] = 'Вибір дати';
+$labels['arialabelcalendarview'] = 'ВиглÑд календарÑ';
+$labels['arialabelsearchform'] = 'Форма пошуку подій';
+$labels['arialabelquicksearchbox'] = 'Пошук подій';
+$labels['arialabelcalsearchform'] = 'Форма пошуку календарів';
+$labels['calendaractions'] = 'Дії з календарÑми';
+$labels['arialabeleventattendees'] = 'УчаÑники події';
+$labels['arialabeleventresources'] = 'РеÑурÑи подій';
+$labels['arialabelresourcesearchform'] = 'Форма пошуку реÑурÑів';
+$labels['arialabelresourceselection'] = 'ДоÑтупні реÑурÑи';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/vi.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['eventchangelog'] = 'Change History';
+$labels['eventdiff'] = 'Changes from revisions $rev';
+$labels['revision'] = 'Revision';
+$labels['user'] = 'User';
+$labels['operation'] = 'Action';
+$labels['actionappend'] = 'Saved';
+$labels['actionmove'] = 'Moved';
+$labels['actiondelete'] = 'Deleted';
+$labels['compare'] = 'Compare';
+$labels['showrevision'] = 'Show this version';
+$labels['restore'] = 'Restore this version';
+$labels['eventnotfound'] = 'Failed to load event data';
+$labels['eventchangelognotavailable'] = 'Change history is not available for this event';
+$labels['eventdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['eventrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/vi_VN.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['eventchangelog'] = 'Change History';
+$labels['eventdiff'] = 'Changes from revisions $rev';
+$labels['revision'] = 'Revision';
+$labels['user'] = 'User';
+$labels['operation'] = 'Action';
+$labels['actionappend'] = 'Saved';
+$labels['actionmove'] = 'Moved';
+$labels['actiondelete'] = 'Deleted';
+$labels['compare'] = 'Compare';
+$labels['showrevision'] = 'Show this version';
+$labels['restore'] = 'Restore this version';
+$labels['eventnotfound'] = 'Failed to load event data';
+$labels['eventchangelognotavailable'] = 'Change history is not available for this event';
+$labels['eventdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['eventrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/zh_CN.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,119 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = '默认视图';
+$labels['time_format'] = '时间格å¼';
+$labels['timeslots'] = 'æ¯å°æ—¶åˆ†æˆå‡ æ®µ';
+$labels['first_day'] = '周几排列在å‰';
+$labels['first_hour'] = '几点最å‰æ˜¾ç¤º';
+$labels['workinghours'] = '工作时间';
+$labels['add_category'] = '增加分类';
+$labels['remove_category'] = '移除分类';
+$labels['defaultcalendar'] = '创建事件于';
+$labels['eventcoloring'] = '事件标色';
+$labels['coloringmode0'] = 'æ ¹æ®æ—¥åŽ†';
+$labels['aftertrash'] = '移到回收站';
+$labels['afterdelete'] = '删除此信æ¯';
+$labels['afterflagdeleted'] = '被删除标签';
+$labels['aftermoveto'] = '移到...';
+$labels['category'] = '分类';
+$labels['categories'] = '类别';
+$labels['name'] = 'å称';
+$labels['color'] = '颜色';
+$labels['day'] = '天';
+$labels['week'] = '周';
+$labels['month'] = '月';
+$labels['agenda'] = '议程表';
+$labels['new'] = '新建';
+$labels['new_event'] = '新建事件';
+$labels['edit_event'] = '编辑事件';
+$labels['edit'] = '编辑';
+$labels['save'] = 'ä¿å­˜';
+$labels['removelist'] = '从列表中移除';
+$labels['cancel'] = 'å–消';
+$labels['select'] = '选择';
+$labels['print'] = '打å°';
+$labels['title'] = '汇总';
+$labels['description'] = 'æè¿°';
+$labels['export'] = '导出';
+$labels['exporttitle'] = '导出到ICalendar';
+$labels['location'] = '地点';
+$labels['url'] = '网å€';
+$labels['date'] = '日期';
+$labels['start'] = '开始';
+$labels['starttime'] = '开始时间';
+$labels['end'] = '结æŸ';
+$labels['endtime'] = '结æŸæ—¶é—´';
+$labels['repeat'] = '循环';
+$labels['selectdate'] = '选择日期';
+$labels['free'] = '空闲';
+$labels['busy'] = '忙碌';
+$labels['outofoffice'] = '外出';
+$labels['tentative'] = '临时';
+$labels['mystatus'] = '我的状æ€';
+$labels['status'] = '状æ€';
+$labels['status-confirmed'] = '已确认';
+$labels['status-cancelled'] = 'å·²å–消';
+$labels['priority'] = '优先级';
+$labels['sensitivity'] = 'éšç§';
+$labels['public'] = '公开';
+$labels['private'] = 'ç§äºº';
+$labels['confidential'] = 'ä¿å¯†çš„';
+$labels['links'] = 'å‚考';
+$labels['alarms'] = 'æ醒';
+$labels['comment'] = '注释';
+$labels['created'] = '已创建';
+$labels['changed'] = '最åŽæ›´æ”¹';
+$labels['unknown'] = '未知';
+$labels['eventoptions'] = '选项';
+$labels['eventhistory'] = '历å²';
+$labels['searchearlierdates'] = '<< 查找以å‰çš„事件';
+$labels['searchlaterdates'] = '查找以åŽçš„时间 >>';
+$labels['invitationspending'] = '未决邀请';
+$labels['invitationsdeclined'] = '已拒ç»é‚€è¯·';
+$labels['rsvpcomment'] = '邀请文字';
+$labels['until'] = '直到';
+$labels['today'] = '今天';
+$labels['tomorrow'] = '明天';
+$labels['thisweek'] = '本周';
+$labels['nextweek'] = '下周';
+$labels['prevweek'] = '上周';
+$labels['thismonth'] = '本月';
+$labels['nextmonth'] = '下月';
+$labels['weekofyear'] = '周';
+$labels['pastevents'] = '过去';
+$labels['futureevents'] = '未æ¥';
+$labels['showalarms'] = '显示æ醒';
+$labels['defaultalarmtype'] = '默认æ醒设置';
+$labels['defaultalarmoffset'] = '默认æ醒时间';
+$labels['attendee'] = 'å‚与者';
+$labels['role'] = '身份';
+$labels['availability'] = '有空';
+$labels['confirmstate'] = '状æ€';
+$labels['addattendee'] = '添加å‚与者';
+$labels['roleorganizer'] = '组织者';
+$labels['rolerequired'] = 'å¿…é¡»';
+$labels['roleoptional'] = 'å¯é€‰';
+$labels['rolenonparticipant'] = '缺席';
+$labels['cutypeindividual'] = '个人';
+$labels['cutypegroup'] = '组织';
+$labels['cutyperesource'] = '资æº';
+$labels['availfree'] = '空闲';
+$labels['availbusy'] = '忙碌';
+$labels['availunknown'] = '未知';
+$labels['availtentative'] = '临时';
+$labels['availoutofoffice'] = '外出';
+$labels['scheduletime'] = '查找是å¦æœ‰ç©º';
+$labels['sendinvitations'] = 'å‘é€é‚€è¯·';
+$labels['sendnotifications'] = '通知å‚加者修改事项';
+$labels['resource'] = '资æº';
+$labels['tabsummary'] = '汇总';
+$labels['tabsharing'] = '分享';
+$labels['savingdata'] = 'ä¿å­˜æ•°æ®...';
+$labels['futurevents'] = '未æ¥';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/localization/zh_TW.inc	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,272 @@
+<?php
+/**
+ * Localizations for Kolab Calendar plugin
+ *
+ * Copyright (C) 2014, Kolab Systems AG
+ *
+ * For translation see https://www.transifex.com/projects/p/kolab/resource/calendar/
+ */
+$labels['default_view'] = 'Default view';
+$labels['time_format'] = 'Time format';
+$labels['timeslots'] = 'Time slots per hour';
+$labels['first_day'] = 'First weekday';
+$labels['first_hour'] = 'First hour to show';
+$labels['workinghours'] = 'Working hours';
+$labels['add_category'] = 'Add category';
+$labels['remove_category'] = 'Remove category';
+$labels['defaultcalendar'] = 'Create new events in';
+$labels['eventcoloring'] = 'Event coloring';
+$labels['coloringmode0'] = 'According to calendar';
+$labels['coloringmode1'] = 'According to category';
+$labels['coloringmode2'] = 'Calendar for outline, category for content';
+$labels['coloringmode3'] = 'Category for outline, calendar for content';
+$labels['afternothing'] = 'Do nothing';
+$labels['aftertrash'] = 'Move to Trash';
+$labels['afterdelete'] = 'Delete the message';
+$labels['afterflagdeleted'] = 'Flag as deleted';
+$labels['aftermoveto'] = 'Move to...';
+$labels['itipoptions'] = 'Event Invitations';
+$labels['afteraction'] = 'After an invitation or update message is processed';
+$labels['calendar'] = 'Calendar';
+$labels['calendars'] = 'Calendars';
+$labels['category'] = 'Category';
+$labels['categories'] = 'Categories';
+$labels['createcalendar'] = 'Create new calendar';
+$labels['editcalendar'] = 'Edit calendar properties';
+$labels['name'] = 'Name';
+$labels['color'] = 'Color';
+$labels['day'] = 'Day';
+$labels['week'] = 'Week';
+$labels['month'] = 'Month';
+$labels['agenda'] = 'Agenda';
+$labels['new'] = 'New';
+$labels['new_event'] = 'New event';
+$labels['edit_event'] = 'Edit event';
+$labels['edit'] = 'Edit';
+$labels['save'] = 'Save';
+$labels['removelist'] = 'Remove from list';
+$labels['cancel'] = 'Cancel';
+$labels['select'] = 'Select';
+$labels['print'] = 'Print';
+$labels['printtitle'] = 'Print calendars';
+$labels['title'] = 'Summary';
+$labels['description'] = 'Description';
+$labels['all-day'] = 'all-day';
+$labels['export'] = 'Export';
+$labels['exporttitle'] = 'Export to iCalendar';
+$labels['exportrange'] = 'Events from';
+$labels['exportattachments'] = 'With attachments';
+$labels['customdate'] = 'Custom date';
+$labels['location'] = 'Location';
+$labels['url'] = 'URL';
+$labels['date'] = 'Date';
+$labels['start'] = 'Start';
+$labels['starttime'] = 'Start time';
+$labels['end'] = 'End';
+$labels['endtime'] = 'End time';
+$labels['repeat'] = 'Repeat';
+$labels['selectdate'] = 'Choose date';
+$labels['freebusy'] = 'Show me as';
+$labels['free'] = 'Free';
+$labels['busy'] = 'Busy';
+$labels['outofoffice'] = 'Out of Office';
+$labels['tentative'] = 'Tentative';
+$labels['mystatus'] = 'My status';
+$labels['status'] = 'Status';
+$labels['status-confirmed'] = 'Confirmed';
+$labels['status-cancelled'] = 'Cancelled';
+$labels['priority'] = 'Priority';
+$labels['sensitivity'] = 'Privacy';
+$labels['public'] = 'public';
+$labels['private'] = 'private';
+$labels['confidential'] = 'confidential';
+$labels['alarms'] = 'Reminder';
+$labels['comment'] = 'Comment';
+$labels['created'] = 'Created';
+$labels['changed'] = 'Last Modified';
+$labels['unknown'] = 'Unknown';
+$labels['eventoptions'] = 'Options';
+$labels['generated'] = 'generated at';
+$labels['eventhistory'] = 'History';
+$labels['printdescriptions'] = 'Print descriptions';
+$labels['parentcalendar'] = 'Insert inside';
+$labels['searchearlierdates'] = '« Search for earlier events';
+$labels['searchlaterdates'] = 'Search for later events »';
+$labels['andnmore'] = '$nr more...';
+$labels['togglerole'] = 'Click to toggle role';
+$labels['createfrommail'] = 'Save as event';
+$labels['importevents'] = 'Import events';
+$labels['importrange'] = 'Events from';
+$labels['onemonthback'] = '1 month back';
+$labels['nmonthsback'] = '$nr months back';
+$labels['showurl'] = 'Show calendar URL';
+$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
+$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
+$labels['findcalendars'] = 'Find calendars...';
+$labels['searchterms'] = 'Search terms';
+$labels['calsearchresults'] = 'Available Calendars';
+$labels['calendarsubscribe'] = 'List permanently';
+$labels['nocalendarsfound'] = 'No calendars found';
+$labels['nrcalendarsfound'] = '$nr calendars found';
+$labels['quickview'] = 'View only this calendar';
+$labels['invitationspending'] = 'Pending invitations';
+$labels['invitationsdeclined'] = 'Declined invitations';
+$labels['changepartstat'] = 'Change participant status';
+$labels['rsvpcomment'] = 'Invitation text';
+$labels['listrange'] = 'Range to display:';
+$labels['listsections'] = 'Divide into:';
+$labels['smartsections'] = 'Smart sections';
+$labels['until'] = 'until';
+$labels['today'] = 'Today';
+$labels['tomorrow'] = 'Tomorrow';
+$labels['thisweek'] = 'This week';
+$labels['nextweek'] = 'Next week';
+$labels['prevweek'] = 'Previous week';
+$labels['thismonth'] = 'This month';
+$labels['nextmonth'] = 'Next month';
+$labels['weekofyear'] = 'Week';
+$labels['pastevents'] = 'Past';
+$labels['futureevents'] = 'Future';
+$labels['showalarms'] = 'Show reminders';
+$labels['defaultalarmtype'] = 'Default reminder setting';
+$labels['defaultalarmoffset'] = 'Default reminder time';
+$labels['attendee'] = 'Participant';
+$labels['role'] = 'Role';
+$labels['availability'] = 'Avail.';
+$labels['confirmstate'] = 'Status';
+$labels['addattendee'] = 'Add participant';
+$labels['roleorganizer'] = 'Organizer';
+$labels['rolerequired'] = 'Required';
+$labels['roleoptional'] = 'Optional';
+$labels['rolechair'] = 'Chair';
+$labels['rolenonparticipant'] = 'Absent';
+$labels['cutypeindividual'] = 'Individual';
+$labels['cutypegroup'] = 'Group';
+$labels['cutyperesource'] = 'Resource';
+$labels['cutyperoom'] = 'Room';
+$labels['availfree'] = 'Free';
+$labels['availbusy'] = 'Busy';
+$labels['availunknown'] = 'Unknown';
+$labels['availtentative'] = 'Tentative';
+$labels['availoutofoffice'] = 'Out of Office';
+$labels['delegatedto'] = 'Delegated to: ';
+$labels['delegatedfrom'] = 'Delegated from: ';
+$labels['scheduletime'] = 'Find availability';
+$labels['sendinvitations'] = 'Send invitations';
+$labels['sendnotifications'] = 'Notify participants about modifications';
+$labels['sendcancellation'] = 'Notify participants about event cancellation';
+$labels['onlyworkinghours'] = 'Find availability within my working hours';
+$labels['reqallattendees'] = 'Required/all participants';
+$labels['prevslot'] = 'Previous Slot';
+$labels['nextslot'] = 'Next Slot';
+$labels['suggestedslot'] = 'Suggested Slot';
+$labels['noslotfound'] = 'Unable to find a free time slot';
+$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the event details which you can import to your calendar application.";
+$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
+$labels['eventupdatesubject'] = '"$title" has been updated';
+$labels['eventupdatesubjectempty'] = 'An event that concerns you has been updated';
+$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated event details which you can import to your calendar application.";
+$labels['eventcancelsubject'] = '"$title" has been canceled';
+$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe event has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated event details.";
+$labels['itipobjectnotfound'] = 'The event referred by this message was not found in your calendar.';
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegated'] = "\$sender has delegated the participation in the following event:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodydelegatedto'] = "\$sender has delegated the participation in the following event to you:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?';
+$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined event from your calendar?';
+$labels['itipcomment'] = 'Invitation/notification comment';
+$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
+$labels['notanattendee'] = 'You\'re not listed as an attendee of this event';
+$labels['eventcancelled'] = 'The event has been cancelled';
+$labels['saveincalendar'] = 'save in';
+$labels['updatemycopy'] = 'Update in my calendar';
+$labels['savetocalendar'] = 'Save to calendar';
+$labels['openpreview'] = 'Check Calendar';
+$labels['noearlierevents'] = 'No earlier events';
+$labels['nolaterevents'] = 'No later events';
+$labels['resource'] = 'Resource';
+$labels['addresource'] = 'Book resource';
+$labels['findresources'] = 'Find resources';
+$labels['resourcedetails'] = 'Details';
+$labels['resourceavailability'] = 'Availability';
+$labels['resourceowner'] = 'Owner';
+$labels['resourceadded'] = 'The resource was added to your event';
+$labels['tabsummary'] = 'Summary';
+$labels['tabrecurrence'] = 'Recurrence';
+$labels['tabattendees'] = 'Participants';
+$labels['tabresources'] = 'Resources';
+$labels['tabattachments'] = 'Attachments';
+$labels['tabsharing'] = 'Sharing';
+$labels['deleteobjectconfirm'] = 'Do you really want to delete this event?';
+$labels['deleteventconfirm'] = 'Do you really want to delete this event?';
+$labels['deletecalendarconfirm'] = 'Do you really want to delete this calendar with all its events?';
+$labels['deletecalendarconfirmrecursive'] = 'Do you really want to delete this calendar with all its events and sub-calendars?';
+$labels['savingdata'] = 'Saving data...';
+$labels['errorsaving'] = 'Failed to save changes.';
+$labels['operationfailed'] = 'The requested operation failed.';
+$labels['invalideventdates'] = 'Invalid dates entered! Please check your input.';
+$labels['invalidcalendarproperties'] = 'Invalid calendar properties! Please set a valid name.';
+$labels['searchnoresults'] = 'No events found in the selected calendars.';
+$labels['successremoval'] = 'The event has been deleted successfully.';
+$labels['successrestore'] = 'The event has been restored successfully.';
+$labels['errornotifying'] = 'Failed to send notifications to event participants';
+$labels['errorimportingevent'] = 'Failed to import the event';
+$labels['importwarningexists'] = 'A copy of this event already exists in your calendar.';
+$labels['newerversionexists'] = 'A newer version of this event already exists! Aborted.';
+$labels['nowritecalendarfound'] = 'No calendar found to save the event';
+$labels['importedsuccessfully'] = 'The event was successfully added to \'$calendar\'';
+$labels['updatedsuccessfully'] = 'The event was successfully updated in \'$calendar\'';
+$labels['attendeupdateesuccess'] = 'Successfully updated the participant\'s status';
+$labels['itipsendsuccess'] = 'Invitation sent to participants.';
+$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
+$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
+$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
+$labels['importsuccess'] = 'Successfully imported $nr events';
+$labels['importnone'] = 'No events found to be imported';
+$labels['importerror'] = 'An error occured while importing';
+$labels['aclnorights'] = 'You do not have administrator rights on this calendar.';
+$labels['changeeventconfirm'] = 'Change event';
+$labels['removeeventconfirm'] = 'Delete event';
+$labels['changerecurringeventwarning'] = 'This is a recurring event. Would you like to edit the current event only, this and all future occurences, all occurences or save it as a new event?';
+$labels['removerecurringeventwarning'] = 'This is a recurring event. Would you like to delete the current event only, this and all future occurences or all occurences of this event?';
+$labels['currentevent'] = 'Current';
+$labels['futurevents'] = 'Future';
+$labels['allevents'] = 'All';
+$labels['saveasnew'] = 'Save as new';
+$labels['birthdays'] = 'Birthdays';
+$labels['birthdayscalendar'] = 'Birthdays Calendar';
+$labels['displaybirthdayscalendar'] = 'Display birthdays calendar';
+$labels['birthdayscalendarsources'] = 'From these address books';
+$labels['birthdayeventtitle'] = '$name\'s Birthday';
+$labels['birthdayage'] = 'Age $age';
+$labels['eventchangelog'] = 'Change History';
+$labels['eventdiff'] = 'Changes from revisions $rev';
+$labels['revision'] = 'Revision';
+$labels['user'] = 'User';
+$labels['operation'] = 'Action';
+$labels['actionappend'] = 'Saved';
+$labels['actionmove'] = 'Moved';
+$labels['actiondelete'] = 'Deleted';
+$labels['compare'] = 'Compare';
+$labels['showrevision'] = 'Show this version';
+$labels['restore'] = 'Restore this version';
+$labels['eventnotfound'] = 'Failed to load event data';
+$labels['eventchangelognotavailable'] = 'Change history is not available for this event';
+$labels['eventdiffnotavailable'] = 'No comparison possible for the selected revisions';
+$labels['eventrestoreconfirm'] = 'Do you really want to restore revision $rev of this event? This will replace the current event with the old version.';
+$labels['arialabelminical'] = 'Calendar date selection';
+$labels['arialabelcalendarview'] = 'Calendar view';
+$labels['arialabelsearchform'] = 'Event search form';
+$labels['arialabelquicksearchbox'] = 'Event search input';
+$labels['arialabelcalsearchform'] = 'Calendars search form';
+$labels['calendaractions'] = 'Calendar actions';
+$labels['arialabeleventattendees'] = 'Event participants list';
+$labels['arialabeleventresources'] = 'Event resources list';
+$labels['arialabelresourcesearchform'] = 'Resources search form';
+$labels['arialabelresourceselection'] = 'Available resources';
+?>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/print.js	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,176 @@
+/**
+ * Print view for the Calendar plugin
+ *
+ * @author Thomas Bruederli <bruederli@kolabsys.com>
+ *
+ * @licstart  The following is the entire license notice for the
+ * JavaScript code in this file.
+ *
+ * Copyright (C) 2011, Kolab Systems AG <contact@kolabsys.com>
+ *
+ * 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/>.
+ *
+ * @licend  The above is the entire license notice
+ * for the JavaScript code in this file.
+ */
+
+
+/* calendar plugin printing code */
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+
+  // quote html entities
+  var Q = function(str)
+  {
+    return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
+  };
+  
+  var rc_loading;
+  var showdesc = true;
+  var settings = $.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings);
+  
+  // create list of event sources AKA calendars
+  var src, event_sources = [];
+  var add_url = (rcmail.env.search ? '&q='+escape(rcmail.env.search) : '');
+  for (var id in rcmail.env.calendars) {
+    if (!rcmail.env.calendars[id].active)
+      continue;
+
+    source = $.extend({
+      url: "./?_task=calendar&_action=load_events&source=" + escape(id) + add_url,
+      className: 'fc-event-cal-'+id,
+      id: id
+    }, rcmail.env.calendars[id]);
+
+    event_sources.push(source);
+  }
+  
+  var viewdate = new Date();
+  if (rcmail.env.date)
+    viewdate.setTime(rcmail.env.date * 1000);
+
+  // initalize the fullCalendar plugin
+  var fc = $('#calendar').fullCalendar({
+    header: {
+      left: '',
+      center: 'title',
+      right: 'agendaDay,agendaWeek,month,table'
+    },
+    aspectRatio: 0.85,
+    ignoreTimezone: true,  // will treat the given date strings as in local (browser's) timezone
+    date: viewdate.getDate(),
+    month: viewdate.getMonth(),
+    year: viewdate.getFullYear(),
+    defaultView: rcmail.env.view,
+    eventSources: event_sources,
+    monthNames : settings['months'],
+    monthNamesShort : settings['months_short'],
+    dayNames : settings['days'],
+    dayNamesShort : settings['days_short'],
+    firstDay : settings['first_day'],
+    firstHour : settings['first_hour'],
+    slotMinutes : 60/settings['timeslots'],
+    timeFormat: {
+      '': settings['time_format'],
+      agenda: settings['time_format'] + '{ - ' + settings['time_format'] + '}',
+      list: settings['time_format'] + '{ - ' + settings['time_format'] + '}',
+      table: settings['time_format'] + '{ - ' + settings['time_format'] + '}'
+    },
+    axisFormat : settings['time_format'],
+    columnFormat: {
+      month: 'ddd', // Mon
+      week: 'ddd ' + settings['date_short'], // Mon 9/7
+      day: 'dddd ' + settings['date_short'],  // Monday 9/7
+      list: settings['date_agenda'],
+      table: settings['date_agenda']
+    },
+    titleFormat: {
+      month: 'MMMM yyyy',
+      week: settings['dates_long'],
+      day: 'dddd ' + settings['date_long'],
+      list: settings['dates_long'],
+      table: settings['dates_long']
+    },
+    listSections: rcmail.env.listSections !== undefined ? rcmail.env.listSections : settings['agenda_sections'],
+    listRange: rcmail.env.listRange || settings['agenda_range'],
+    tableCols: ['handle', 'date', 'time', 'title', 'location'],
+    allDayText: rcmail.gettext('all-day', 'calendar'),
+    buttonText: {
+      day: rcmail.gettext('day', 'calendar'),
+      week: rcmail.gettext('week', 'calendar'),
+      month: rcmail.gettext('month', 'calendar'),
+      table: rcmail.gettext('agenda', 'calendar')
+    },
+    listTexts: {
+      until: rcmail.gettext('until', 'calendar'),
+      past: rcmail.gettext('pastevents', 'calendar'),
+      today: rcmail.gettext('today', 'calendar'),
+      tomorrow: rcmail.gettext('tomorrow', 'calendar'),
+      thisWeek: rcmail.gettext('thisweek', 'calendar'),
+      nextWeek: rcmail.gettext('nextweek', 'calendar'),
+      thisMonth: rcmail.gettext('thismonth', 'calendar'),
+      nextMonth: rcmail.gettext('nextmonth', 'calendar'),
+      future: rcmail.gettext('futureevents', 'calendar'),
+      week: rcmail.gettext('weekofyear', 'calendar')
+    },
+    loading: function(isLoading) {
+      rc_loading = rcmail.set_busy(isLoading, 'loading', rc_loading);
+    },
+    // event rendering
+    eventRender: function(event, element, view) {
+      if (view.name != 'month' && view.name != 'table') {
+        var cont = element.find('.fc-event-title');
+        if (event.location) {
+          cont.after('<div class="fc-event-location">@&nbsp;' + Q(event.location) + '</div>');
+          cont = cont.next();
+        }
+        if (event.description && showdesc) {
+          cont.after('<div class="fc-event-description">' + Q(event.description) + '</div>');
+        }
+/* TODO: create icons black on white
+        if (event.recurrence)
+          element.find('.fc-event-time').append('<i class="fc-icon-recurring"></i>');
+        if (event.alarms)
+          element.find('.fc-event-time').append('<i class="fc-icon-alarms"></i>');
+*/
+      }
+      if (view.name == 'table' && event.description && showdesc) {
+        var cols = element.children().css('border', 0).length;
+        element.after('<tr class="fc-event-row-secondary fc-event"><td colspan="'+cols+'" class="fc-event-description">' + Q(event.description) + '</td></tr>');
+      }
+    },
+    viewDisplay: function(view) {
+      // remove hard-coded hight and make contents visible
+      window.setTimeout(function(){
+        if (view.name == 'table') {
+          $('div.fc-list-content').css('overflow', 'visible').height('auto');
+        }
+        else {
+          $('div.fc-agenda-divider')
+            .next().css('overflow', 'visible').height('auto')
+            .children('div').css('overflow', 'visible').height('auto');
+          }
+          // adjust fixed height if vertical day slots
+          var h = $('table.fc-agenda-slots:visible').height() + $('table.fc-agenda-allday:visible').height() + 4;
+          if (h) $('table.fc-agenda-days td.fc-widget-content').children('div').height(h);
+         }, 20);
+    }
+  });
+  
+  // activate settings form
+  $('#propdescription').change(function(){
+    showdesc = this.checked;
+    fc.fullCalendar('render');
+  });
+
+});
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/README	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,5 @@
+Icons by Fugue Icons <http://p.yusukekamiyamane.com/>
+
+Copyright (C) 2010 Yusuke Kamiyamane. All rights reserved.
+The icons are licensed under a Creative Commons Attribution
+3.0 license. <http://creativecommons.org/licenses/by/3.0/>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/calendar.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1790 @@
+/*** Style for Calendar plugin ***/
+
+body.calendarmain {
+	overflow: hidden;
+}
+
+#taskbar a.button-calendar {
+	background: url(images/calendar.png) 0px 1px no-repeat;
+}
+
+/* hack for IE 6/7 */
+* html #taskbar a.button-calendar {
+	background-image: url(images/calendar.gif);
+}
+
+#main {
+	position: absolute;
+	clear: both;
+	top: 72px;
+	left: 0;
+	right: 0;
+	bottom: 10px;
+}
+
+#calendarsidebar {
+	position: absolute;
+	top: 0px;
+	left: 10px;
+	bottom: 0;
+	width: 230px;
+}
+
+#datepicker {
+	position: relative;
+	top: 42px;
+	width: 100%;
+}
+
+#datepicker .ui-datepicker {
+	width: 97% !important;
+	box-shadow: none;
+	-moz-box-shadow: none;
+	-webkit-box-shadow: none;
+}
+
+#datepicker .ui-datepicker-activerange a {
+	border-color: #c33;
+	color: #a22;
+}
+
+#datepicker .ui-datepicker-activerange a.ui-state-active {
+	color: #fff;
+}
+
+#datepicker .ui-priority-secondary {
+	opacity: 0.4;
+}
+
+#datepicker td.ui-datepicker-week-col {
+	cursor: pointer;
+}
+
+#calendarsidebartoggle {
+	position: absolute;
+	left: 244px;
+	width: 8px;
+	top: 4px;
+	bottom: 0;
+	background: url(images/toggle.gif) 0 48% no-repeat transparent;
+	cursor: pointer;
+}
+
+div.sidebarclosed {
+	background-position: -8px 48% !important;
+}
+
+#calendarsidebartoggle:hover {
+	background-color: #ddd;
+}
+
+#calendar {
+	position: absolute;
+	top: 4px;
+	left: 256px;
+	right: 10px;
+	bottom: 0;
+}
+
+#print {
+	width: 680px;
+}
+
+pre {
+ font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+}
+
+#calendars {
+	position: absolute;
+	top: 228px;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	background-color: #F9F9F9;
+	border: 1px solid #999999;
+	overflow: hidden;
+}
+
+#calendars .boxlistcontent {
+	top: 43px;
+}
+
+#calendars .listsearchbox {
+	padding: 2px 4px;
+}
+
+#calendarslist {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+}
+
+#attachmentlist li,
+#calendarslist li {
+	margin: 0;
+	padding: 1px;
+	display: block;
+	background: #fff;
+	border-bottom: 1px solid #EBEBEB;
+	white-space: nowrap;
+	cursor: default;
+}
+
+#calendars .treelist li {
+	margin: 0;
+	padding: 0;
+	position: relative;
+}
+
+#calendars .treelist ul li:last-child {
+	border-bottom: 0;
+}
+
+#calendars .treelist li div.folder,
+#calendars .treelist li div.calendar {
+	position: relative;
+	height: 22px;
+}
+
+#calendars .treelist li span.calname {
+	display: block;
+	padding: 0px 30px 2px 2px;
+	position: absolute;
+	top: 4px;
+	left: 38px;
+	right: 40px;
+	cursor: default;
+	background: url(images/calendars.png) right 20px no-repeat;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+#calendars .treelist li div.virtual > span.calname {
+	color: #aaa;
+	left: 20px;
+}
+
+#calendars .treelist.flat li span.calname {
+	left: 24px;
+	right: 22px;
+}
+
+#calendars .treelist li span.handle {
+	display: inline-block;
+	position: absolute;
+	top: 5px;
+	right: 6px;
+	padding: 0;
+	width: 12px;
+	height: 12px;
+	border-radius: 3px;
+	font-size: 0.8em;
+}
+
+#calendars .treelist li a.subscribed {
+	display: inline-block;
+	position: absolute;
+	top: 2px;
+	right: 22px;
+	height: 16px;
+	width: 16px;
+	padding: 0;
+	background: url(images/calendars.png) -100px 0 no-repeat;
+	overflow: hidden;
+	text-indent: -5000px;
+	cursor: pointer;
+}
+
+#calendars .treelist div:hover > a.subscribed {
+	background-position: 0 -126px;
+}
+
+#calendars .treelist div.subscribed a.subscribed {
+	background-position: 0 -144px;
+}
+
+#calendars .treelist li input {
+	position: absolute;
+	top: 1px;
+	left: 18px;
+}
+
+#calendars .treelist li div.treetoggle {
+	top: -1px;
+	left: 1px !important;
+}
+
+#calendars .treelist ul li div.treetoggle {
+	left: 17px !important;
+}
+
+#calendars .treelist ul ul li div.treetoggle {
+	left: 33px !important;
+}
+
+#calendars .treelist.flat li input {
+	left: 4px;
+}
+
+#calendars .treelist ul li div.folder,
+#calendars .treelist ul li div.calendar {
+	margin-left: 16px;
+}
+
+#calendars .treelist ul ul li div.folder,
+#calendars .treelist ul ul li div.calendar {
+	margin-left: 32px;
+}
+
+#calendars .treelist ul ul ul li div.folder,
+#calendars .treelist ul ul ul li div.calendar {
+	margin-left: 48px;
+}
+
+#calendars .treelist li.selected {
+	background-color: #ccc;
+}
+
+#calendars .treelist li.selected > span.calname {
+	font-weight: bold;
+}
+
+#calendars .treelist div.readonly span.calname {
+	background-position: right -20px;
+}
+
+#calendars .treelist li.user > div > span.calname {
+	background-position: right -38px;
+}
+
+#calendarslist li.virtual span.calname {
+	color: #666;
+}
+
+#calendars .searchresults .boxtitle {
+	border-top: 1px solid #aaa;
+	margin-bottom: 0;
+}
+
+#calfeedurl,
+#caldavurl {
+	width: 98%;
+	background: #fbfbfb;
+	padding: 4px;
+	margin-bottom: 1em;
+	resize: none;
+}
+
+#agendalist {
+	width: 100%;
+	margin: 0 auto;
+	margin-top: 60px;
+	border: 1px solid #C1DAD7;
+	display: none;
+}
+
+#agendalist table {
+	width: 100%;
+}
+
+#agendalist td,
+#agendalist th {
+	border-right: 1px solid #C1DAD7;
+	border-bottom: 1px solid #C1DAD7;
+	background: #fff;
+	padding: 6px 6px 6px 12px;
+}
+
+#agendalist tr {
+	vertical-align: top;
+}
+
+#agendalist th {
+	font-weight: bold;
+}
+
+#calendartoolbar {
+	position: absolute;
+	top: 0px;
+	left: 0px;
+	height: 35px;
+}
+
+#calendartoolbar a {
+	padding-right: 10px;
+}
+
+#calendartoolbar a.button,
+#calendartoolbar a.buttonPas {
+	display: block;
+	float: left;
+	width: 32px;
+	height: 32px;
+	padding: 0;
+	margin-right: 10px;
+	overflow: hidden;
+	background: url(images/toolbar.png) 0 0 no-repeat transparent;
+	opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
+}
+
+#calendartoolbar a.buttonPas {
+	opacity: 0.35;
+}
+
+#calendartoolbar a.addeventSel {
+	background-position: 0 -32px;
+}
+
+#calendartoolbar a.delete {
+  background-position: -32px 0;
+}
+
+#calendartoolbar a.deleteSel {
+  background-position: -32px -32px;
+}
+
+#calendartoolbar a.print {
+  background-position: -64px 0;
+}
+
+#calendartoolbar a.printSel {
+  background-position: -64px -32px;
+}
+
+#calendartoolbar a.import {
+	background-position: -168px 0;
+}
+
+#calendartoolbar a.importSel {
+	background-position: -168px -32px;
+}
+
+#calendartoolbar a.export {
+	background-position: -128px 0;
+}
+
+#calendartoolbar a.exportSel {
+	background-position: -128px -32px;
+}
+
+.calendarmain #quicksearchbar {
+	top: 80px;
+	right: 4px;
+}
+
+#eventedit.uidialog,
+.calendarmain div.uidialog {
+	display: none;
+}
+
+#user {
+	position: absolute;
+	top: 10px;
+	right: 100px;
+	left: 100px;
+	text-align: center;
+}
+
+a.morelink {
+	font-size: 90%;
+	color: #C33;
+	text-decoration: none;
+}
+
+a.morelink:hover {
+	text-decoration: underline;
+}
+
+a.miniColors-trigger {
+	margin-top: -3px;
+}
+
+#attachmentcontainer {
+	position: absolute;
+	top: 80px;
+	left: 20px;
+	right: 20px;
+	bottom: 20px;
+}
+
+#attachmentframe {
+	width: 100%;
+	height: 100%;
+	border: 1px solid #999999;
+	background-color: #F9F9F9;
+}
+
+#partheader {
+	position: absolute;
+	top: 20px;
+	left: 220px;
+	right: 20px;
+	height: 40px;
+}
+
+#partheader table td {
+	padding-left: 2px;
+	padding-right: 4px;
+	vertical-align: middle;
+	font-size: 11px;
+}
+
+#partheader table td.title {
+	color: #666;
+	font-weight: bold;
+}
+
+.attachments-list ul {
+	margin: 0px;
+	padding: 0px;
+	list-style-image: none;
+	list-style-type: none;
+}
+
+.attachments-list ul li {
+	height: 18px;
+	font-size: 12px;
+	padding-top: 2px;
+	padding-right: 8px;
+	white-space: nowrap;
+}
+
+.attachments-list ul li img {
+	padding-right: 2px;
+	vertical-align: middle;
+}
+
+.attachments-list ul li a {
+	text-decoration: none;
+}
+
+.attachments-list ul li a:hover {
+	text-decoration: underline;
+}
+
+#attachmentlist {
+	margin: 0 -0.8em;
+}
+
+#attachmentlist li {
+	padding: 2px 2px 3px 0.8em;
+}
+
+#eventshow .attachments-list ul li {
+	float: left;
+}
+
+#edit-attachments-form {
+	padding-top: 1.2em;
+}
+
+#edit-attachments-form .formbuttons {
+	margin: 0.5em 0;
+}
+
+.event-attendees span.attendee {
+	padding-right: 18px;
+	margin-right: 0.5em;
+	background: url(images/attendee-status.gif) right 0 no-repeat;
+}
+
+.event-attendees span.attendee a.mailtolink {
+	text-decoration: none;
+	white-space: nowrap;
+}
+
+.event-attendees span.attendee a.mailtolink:hover {
+	text-decoration: underline;
+}
+
+.event-attendees span.accepted {
+	background-position: right -20px;
+}
+
+.event-attendees span.declined {
+	background-position: right -40px;
+}
+
+.event-attendees span.tentative {
+	background-position: right -60px;
+}
+
+.event-attendees span.delegated {
+	background-position: right -180px;
+}
+
+.event-attendees span.organizer {
+	background-position: right -80px;
+}
+
+#all-event-attendees span.attendee {
+	display: block;
+	margin-bottom: 4px;
+	padding-bottom: 3px;
+	border-bottom: 1px solid #ddd;
+}
+
+/* jQuery UI overrides */
+
+.calendarmain .ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset {
+	float: left;
+}
+
+#eventshow h1 {
+	font-size: 20px;
+	margin: 0.1em 0 0.4em 0;
+}
+
+#eventshow label,
+#eventshow h5.label {
+	font-weight: normal;
+	font-size: 0.9em;
+	color: #999;
+	margin: 0 0 0.2em 0;
+}
+
+#eventshow {
+	margin: 0 -0.2em;
+}
+
+#event-status-badge {
+	width: 100px;
+	height: 100px;
+	position: absolute;
+	top: 0;
+	right: 0;
+	overflow: hidden;
+}
+
+#event-status-badge span {
+	display: none;
+	text-transform: uppercase;
+	width: 150px;
+	height: 20px;
+	line-height: 20px;
+	position: absolute;
+	left: -20px;
+	top: 35px;
+	padding-left: 10px;
+	text-align: center;
+	font-weight: bold;
+	font-size: 12px;
+	color: #fff;
+	box-shadow: 1px 1px 2px #ccc, -1px -1px 2px #ccc;
+	-webkit-transform: rotate(45deg);
+	-moz-transform: rotate(45deg);
+	-ms-transform: rotate(45deg);
+	-o-transform: rotate(45deg);
+	transform: rotate(45deg);
+}
+
+#eventshow.status-cancelled #event-status-badge span {
+	background: url(images/badge.png) 26px -24px no-repeat #cc0000;
+	display: block;
+}
+
+#eventshow.sensitivity-private #event-status-badge span {
+	background: url(images/badge.png) 40px -52px no-repeat #0066ff;
+	display: block;
+}
+
+#eventshow.sensitivity-confidential #event-status-badge span {
+	background: url(images/badge.png) 20px 2px no-repeat #cc0000;
+	display: block;
+}
+
+#eventshow.status-cancelled #event-title,
+#eventshow.sensitivity-private #event-title,
+#eventshow.sensitivity-confidential #event-title {
+	margin-right: 80px;
+}
+
+#eventshow div.event-line {
+	margin-top: 0.1em;
+	margin-bottom: 0.3em;
+}
+
+#eventshow #event-url .event-text {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+#event-rsvp .itip-reply-controls {
+	margin-top: 0.5em;
+}
+
+#eventshow .itip-reply-controls label {
+	font-size: 1em;
+	color: #333;
+}
+
+#event-partstat .changersvp {
+	cursor: pointer;
+	color: #333;
+	text-decoration: none;
+}
+
+#event-partstat:hover .changersvp {
+	text-decoration: underline;
+}
+
+#event-partstat .changersvp.accepted {
+	color: #589b1e;
+}
+
+#event-partstat .changersvp.tentative {
+	color: #f0bb1d;
+}
+
+#event-partstat .changersvp.declined {
+	color: #ea0000;
+}
+
+#event-partstat .changersvp.delegated {
+	color: #018be9;
+}
+
+#eventedit {
+	position: relative;
+	padding: 0.5em 0.1em;
+}
+
+#eventedit input.text,
+#eventedit textarea {
+	width: 97%;
+}
+
+#eventtabs {
+	position: relative;
+	padding: 0;
+	border: 0;
+	border-radius: 0;
+}
+
+div.form-section,
+#eventshow div.event-section,
+#eventtabs div.event-section {
+	margin-top: 0.2em;
+	margin-bottom: 0.8em;
+}
+
+#eventtabs .tabsbar {
+	position: absolute;
+	top: 0;
+}
+
+#eventtabs .ui-tabs-panel {
+	padding: 1em 0.8em;
+	border: 1px solid #aaa;
+	border-width: 0 1px 1px 1px;
+}
+
+#eventtabs .ui-tabs-nav {
+	background: none;
+	padding: 0;
+	border-width: 0 0 1px 0;
+	border-radius: 0;
+}
+
+#eventtabs .border-after {
+	padding-bottom: 0.6em;
+	margin-bottom: 0.6em;
+	border-bottom: 1px solid #999;
+}
+
+#eventshow label,
+#eventedit label,
+.form-section label {
+	display: inline-block;
+	min-width: 7em;
+	padding-right: 0.5em;
+}
+
+#eventedit .formtable td.label {
+	min-width: 6em;
+}
+
+td.topalign {
+	vertical-align: top;
+}
+
+#eventedit .edit-alarm-item {
+	position: relative;
+	padding-right: 30px;
+	margin-bottom: 2px;
+}
+
+#eventedit .edit-alarm-buttons {
+	position: absolute;
+	top: 2px;
+	right: 0;
+}
+
+#eventedit .edit-alarm-buttons a.iconlink {
+	display: none;
+	width: 18px;
+	height: 17px;
+	padding: 1px;
+	text-indent: -5000px;
+	overflow: hidden;
+}
+
+#eventedit .edit-alarm-buttons a.add-alarm {
+	background: url(images/plus.png) 1px 1px no-repeat;
+}
+
+#eventedit .edit-alarm-buttons a.delete-alarm {
+	background: url(images/delete.png) 1px 1px no-repeat;
+}
+
+#eventedit .edit-alarm-buttons a.delete-alarm,
+#eventedit .first .edit-alarm-buttons a.add-alarm {
+	display: inline-block;
+}
+
+#eventedit .first .edit-alarm-buttons a.delete-alarm {
+	display: none;
+}
+
+#eventedit label.weekday,
+#eventedit label.monthday {
+	min-width: 3em;
+}
+
+#eventedit label.month {
+	min-width: 5em;
+}
+
+#edit-recurrence-yearly-bymonthblock {
+	margin-left: 7.5em;
+}
+
+#edit-recurrence-rdates {
+	display: block;
+	list-style: none;
+	margin: 0 0 0.8em 0;
+	padding: 0;
+	max-height: 300px;
+	overflow: auto;
+}
+
+#edit-recurrence-rdates li {
+	display: block;
+	position: relative;
+	width: 14em;
+	padding: 1px;
+}
+
+#edit-recurrence-rdates li a.delete {
+	position: absolute;
+	top: 1px;
+	right: 0;
+}
+
+#eventedit .recurrence-form {
+	display: none;
+}
+
+#eventedit .formtable td {
+	padding: 0.2em 0;
+}
+
+.ui-dialog .event-update-confirm {
+	padding: 0 0.5em 0.5em 0.5em;
+}
+
+.event-dialog-message,
+.event-update-confirm .message {
+	margin-top: 0.5em;
+	padding: 0.8em;
+	background-color: #F7FDCB;
+	border: 1px solid #C2D071;
+}
+
+.event-dialog-message .message,
+.event-update-confirm .message {
+	margin-bottom: 0.5em;
+}
+
+.edit-recurring-warning .savemode {
+	padding-left: 20px;
+}
+
+.event-update-confirm .savemode {
+	padding-left: 30px;
+}
+
+.event-dialog-message span.ui-icon,
+.event-update-confirm span.ui-icon {
+	float: left;
+	margin: 0 7px 20px 0;
+}
+
+.event-dialog-message label,
+.event-update-confirm label {
+	min-width: 3em;
+	padding-right: 1em;
+}
+
+.event-update-confirm a.button {
+	margin: 0 0.5em 0 0.2em;
+	min-width: 5em;
+}
+
+#event-rsvp,
+#edit-attendees-notify {
+	margin: 0.3em 0;
+	padding: 0.5em;
+	background-color: #F7FDCB;
+	border: 1px solid #C2D071;
+}
+
+.edit-attendees-table {
+	width: 100%;
+	display: table;
+	table-layout: fixed;
+	border-collapse: collapse;
+	border: 1px solid #ccc;
+}
+
+.edit-attendees-table th,
+.edit-attendees-table td {
+	padding: 3px;
+	border-bottom: 1px solid #ccc;
+	text-align: left;
+}
+
+.edit-attendees-table th.role,
+.edit-attendees-table td.role {
+	width: 8em;
+}
+
+.edit-attendees-table th.availability,
+.edit-attendees-table th.confirmstate,
+.edit-attendees-table td.availability,
+.edit-attendees-table td.confirmstate {
+	width: 4em;
+}
+
+.edit-attendees-table th.options,
+.edit-attendees-table td.options {
+	width: 2em;
+	text-align: right;
+	padding-right: 4px;
+}
+
+.edit-attendees-table th.invite,
+.edit-attendees-table td.invite {
+	width: 24px;
+	padding: 2px;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: hidden;
+}
+
+#eventedit .edit-attendees-table th.invite label {
+	display: none;
+}
+
+.edit-attendees-table th.name,
+.edit-attendees-table td.name {
+	width: auto;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.edit-attendees-table thead th,
+.edit-attendees-table thead td {
+	background: url(images/listheader.gif) top left repeat-x #CCC;
+}
+
+#edit-attendees-form,
+#edit-resources-form {
+	position: relative;
+	margin-top: 1em;
+}
+
+#edit-attendees-form #edit-attendee-schedule,
+#edit-resources-form #edit-resource-find {
+	position: absolute;
+	top: 0;
+	right: 0;
+}
+
+.edit-attendees-table select.edit-attendee-role {
+	border: 0;
+	padding: 2px;
+	background: white;
+}
+
+.availability img.availabilityicon {
+	margin: 1px;
+	width: 14px;
+	height: 14px;
+	border-radius: 4px;
+	-moz-border-radius: 4px;
+}
+
+.availability img.availabilityicon.loading {
+	background: url(images/loading_blue.gif) center no-repeat;
+}
+
+#schedule-freebusy-times td.unknown,
+.availability img.availabilityicon.unknown {
+	background: #ddd;
+}
+
+#schedule-freebusy-times td.free,
+.availability img.availabilityicon.free {
+	background: #0c0;
+}
+
+#schedule-freebusy-times td.busy,
+.availability img.availabilityicon.busy {
+	background: #c00;
+}
+
+#schedule-freebusy-times td.tentative,
+.availability img.availabilityicon.tentative {
+	background: #66d;
+}
+
+#schedule-freebusy-times td.out-of-office,
+.availability img.availabilityicon.out-of-office {
+	background: #f0b400;
+}
+
+#schedule-freebusy-times td.all-busy,
+#schedule-freebusy-times td.all-tentative,
+#schedule-freebusy-times td.all-out-of-office {
+	background-image: url(images/freebusy-colors.png);
+	background-position: top right;
+	background-repeat: no-repeat;
+}
+
+#schedule-freebusy-times td.all-tentative {
+	background-position: right -40px;
+}
+
+#schedule-freebusy-times td.all-out-of-office {
+	background-position: right -80px;
+}
+
+#edit-attendees-legend {
+	margin-top: 3em;
+	margin-bottom: 0.5em;
+}
+
+#edit-attendees-legend .legend {
+	margin-right: 2em;
+	white-space: nowrap;
+}
+
+#edit-attendees-legend img.availabilityicon {
+	vertical-align: middle;
+}
+
+.edit-attendees-table tbody td.confirmstate {
+	overflow: hidden;
+	white-space: nowrap;
+	text-indent: -2000%;
+}
+
+.edit-attendees-table td.confirmstate span {
+	display: block;
+	width: 20px;
+	background: url(images/attendee-status.gif) 5px 0 no-repeat;
+}
+
+.edit-attendees-table td.confirmstate span.needs-action {
+}
+
+.edit-attendees-table td.confirmstate span.accepted {
+	background-position: 5px -20px;
+}
+
+.edit-attendees-table td.confirmstate span.declined {
+	background-position: 5px -40px;
+}
+
+.edit-attendees-table td.confirmstate span.tentative {
+	background-position: 5px -60px;
+}
+
+.edit-attendees-table td.confirmstate span.delegated {
+	background-position: 5px -180px;
+}
+
+#attendees-freebusy-table {
+	width: 100%;
+	table-layout: fixed;
+	border-collapse: collapse;
+	margin: 0.5em 0;
+}
+
+#attendees-freebusy-table td.attendees {
+	width: 18em;
+	border: 1px solid #ccc;
+	vertical-align: top;
+	overflow: hidden;
+}
+
+#attendees-freebusy-table td.times {
+	width: auto;
+	vertical-align: top;
+	border: 1px solid #ccc;
+}
+
+#attendees-freebusy-table div.scroll {
+	position: relative;
+	overflow: auto;
+}
+
+#attendees-freebusy-table h3.boxtitle {
+	margin: 0;
+	height: auto !important;
+	border-color: #ccc;
+}
+
+.attendees-list .attendee {
+	padding: 3px 4px 3px 1px;
+	background: url(images/attendee-status.gif) 2px -97px no-repeat;
+	white-space: nowrap;
+}
+
+.attendees-list a.attendee-role-toggle {
+	display: inline-block;
+	width: 16px;
+	margin-right: 3px;
+	cursor: pointer;
+}
+
+.attendees-list div.attendee {
+	border-top: 1px solid #ccc;
+}
+
+.attendees-list span.attendee {
+	padding-left: 20px;
+	margin-right: 2em;
+}
+
+.attendees-list .organizer {
+	background-position: 3px -77px;
+}
+
+.attendees-list .opt-participant {
+	background-position: 2px -117px;
+}
+
+.attendees-list .non-participant {
+	background-position: 2px -137px;
+}
+
+.attendees-list .chair {
+	background-position: 2px -157px;
+}
+
+.attendees-list .loading {
+	background: url(images/loading_blue.gif) 1px 50% no-repeat;
+}
+
+.attendees-list .total {
+	background: none;
+	padding-left: 4px;
+	font-weight: bold;
+}
+
+.attendees-list .spacer,
+#schedule-freebusy-times tr.spacer td {
+	background: 0;
+	font-size: 50%;
+}
+
+#schedule-freebusy-times {
+	border-collapse: collapse;
+	width: 100%;
+}
+
+#schedule-freebusy-times td {
+	padding: 3px;
+	border: 1px solid #ccc;
+}
+
+#schedule-freebusy-times tr.dates th {
+	border-color: #aaa;
+	border-style: solid;
+	border-width: 0 1px 0 1px;
+	white-space: nowrap;
+}
+
+#attendees-freebusy-table div.timesheader,
+#schedule-freebusy-times tr.times td {
+	min-width: 30px;
+	font-size: 9px;
+	padding: 5px 2px 6px 2px;
+	text-align: center;
+}
+
+#schedule-freebusy-times tr.times td.allday {
+	min-width: 60px;
+}
+
+#schedule-freebusy-times tr.times td {
+	cursor: pointer;
+}
+
+#schedule-event-time {
+	position: absolute;
+	border: 2px solid #333;
+	background: #777;
+	background: rgba(60, 60, 60, 0.6);
+	opacity: 0.5;
+	border-radius: 4px;
+	cursor: move;
+	filter: alpha(opacity=40); /* IE8 */
+}
+
+#eventfreebusy .schedule-options {
+	position: relative;
+	margin-bottom: 1.5em;
+}
+
+#eventfreebusy .schedule-buttons {
+	position: absolute;
+	top: 0;
+	right: 0;
+}
+
+#eventfreebusy .schedule-find-buttons {
+	padding-bottom:0.5em;
+}
+
+#eventfreebusy .schedule-find-buttons button {
+	min-width: 9em;
+	text-align: center;
+}
+
+span.edit-alarm-set {
+	white-space: nowrap;
+}
+
+a.dropdown-link {
+	color: #CC0000;
+	font-size: 12px;
+	text-decoration: none;
+}
+
+a.dropdown-link:after {
+	content: ' â–¼';
+	font-size: 11px;
+	color: #666;
+}
+
+#eventedit .ui-tabs-panel {
+	min-height: 20em;
+}
+
+.alarm-item {
+	margin: 0.4em 0 1em 0;
+}
+
+.alarm-item .event-title {
+	font-size: 14px;
+	margin: 0.1em 0 0.3em 0;
+}
+
+.alarm-item div.event-section {
+	margin-top: 0.1em;
+	margin-bottom: 0.3em;
+}
+
+.alarm-item .alarm-actions {
+	margin-top: 0.4em;
+}
+
+.alarm-item div.alarm-actions a {
+	color: #CC0000;
+	margin-right: 0.8em;
+	text-decoration: none;
+}
+
+a.alarm-action-snooze:after {
+	content: ' â–¼';
+	font-size: 10px;
+	color: #666;
+}
+
+#alarm-snooze-dropdown {
+	z-index: 5000;
+}
+
+.ui-dialog-buttonset a.dropdown-link {
+	margin-right: 1em;
+}
+
+.ui-datepicker-calendar .ui-datepicker-today .ui-state-default {
+	border-color: #cccccc;
+	background: #ffffcc;
+	color: #000;
+}
+
+.ui-datepicker-calendar .ui-datepicker-week-col {
+	text-align: right;
+	padding-right: 0.5em;
+}
+
+.ui-datepicker th {
+    padding: 0.3em 0;
+    font-size: 10px;
+}
+
+.ui-datepicker td span,
+.ui-datepicker td a {
+	padding-left: 0.1em;
+}
+
+.ui-autocomplete {
+	max-height: 160px;
+	overflow-y: auto;
+	overflow-x: hidden;
+}
+
+.ui-autocomplete .ui-menu-item {
+	white-space: nowrap;
+}
+
+* html .ui-autocomplete {
+	height: 160px;
+}
+
+span.spacer {
+	padding-left: 3em;
+}
+
+#agendaoptions {
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	height: auto;
+	z-index: 200;
+	border: 1px solid #ccc;
+	padding: 2px 5px 1px;
+	font-size: 90%;
+}
+
+#agendaoptions label {
+	color: #444;
+	text-shadow: 1px 1px #eee;
+	padding-right: 0.5em;
+}
+
+#calendar-kolabform {
+	position: relative;
+	padding-top: 24px;
+	margin: 0 -8px;
+	min-width: 660px;
+	min-height: 400px;
+}
+
+#calendar-kolabform div.tabsbar {
+	top: 0;
+	right: 2px;
+	left: 2px;
+	height: 24px;
+}
+
+#calendar-kolabform fieldset.tabbed {
+	background-color: #fff;
+	margin-top: 0;
+}
+
+#calendar-kolabform span.tablink {
+	background-color: #e8e8e9;
+	background-image: -moz-linear-gradient(center top, #f4f4f4, #e6e6e6);
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0.0, #f4f4f4), color-stop(1.0, #e6e6e6));
+	filter: progid:DXImageTransform.Microsoft.gradient(enabled='true', startColorstr=#f4f4f4, endColorstr=#e6e6e6, GradientType=0);
+	height: 24px !important;
+}
+
+#calendar-kolabform span.tablink-selected {
+	background: #fff;
+	height: 25px !important;
+}
+
+#calendar-kolabform span.tablink a,
+#calendar-kolabform span.tablink-selected a {
+	background: none;
+	border: 1px solid #AAAAAA;
+	border-top-left-radius: 2px;
+	border-top-right-radius: 2px;
+	padding: 4px 10px 0 10px;
+	margin-left: 0;
+}
+
+#calendar-kolabform table td.title {
+  font-weight: bold;
+  white-space: nowrap;
+  color: #666;
+  padding-right: 10px;
+}
+
+#resource-dialog-right {
+	position: absolute;
+	top: 10px;
+	left: 300px;
+	right: 8px;
+	bottom: 10px;
+}
+
+#resource-info,
+#resource-availability {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	height: 48%;
+	border: 1px solid #999;
+	background-color: #F9F9F9;
+	overflow: auto;
+}
+
+#resource-availability {
+	top: auto;
+	bottom: 0;
+	height: 49%;
+	overflow: hidden;
+}
+
+#resource-info .boxtitle,
+#resource-availability .boxtitle {
+	margin-top: 0;
+}
+
+#resource-freebusy-calendar {
+	position: absolute;
+	top: 20px;
+	left: -1px;
+	right: -1px;
+	bottom: -1px;
+}
+
+#resource-freebusy-calendar .fc-content {
+	top: 0;
+}
+
+#resource-freebusy-calendar .fc-content .fc-event-bg {
+	background: 0;
+}
+
+#resource-freebusy-calendar .fc-event.status-busy,
+#resource-freebusy-calendar .status-busy .fc-event-skin {
+	border-color: #e26569;
+	background-color: #e26569;
+}
+
+#resource-freebusy-calendar .fc-event.status-tentative,
+#resource-freebusy-calendar .status-tentative .fc-event-skin {
+	border-color: #8383fc;
+	background: #8383fc;
+}
+
+#resource-freebusy-calendar .fc-event.status-outofoffice,
+#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
+	border-color: #fbaa68;
+	background: #fbaa68;
+}
+
+#resources-list div.treetoggle {
+	left: 3px !important;
+	top: -2px;
+}
+
+#resources-list li ul div.treetoggle {
+	left: 23px !important;
+}
+
+#resource-selection {
+  position: absolute;
+	top: 10px;
+	bottom: 10px;
+	left: 8px;
+	width: 280px;
+	border: 1px solid #999999;
+	background-color: #F9F9F9;
+	overflow: hidden;
+}
+
+#resource-selection .boxlistcontent {
+	top: 25px;
+	border-top: 1px solid #eee;
+}
+
+#resourcequicksearch {
+	position: absolute;
+	top: 3px;
+	left: 7px;
+	right: 4px;
+	height: 17px;
+	background: #fff;
+	border: 1px solid #888;
+	border-radius: 10px;
+	-webkit-box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
+	-moz-box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
+	box-shadow: inset 1px 1px 1px 0px rgba(0, 0, 0, 0.3);
+}
+
+#resourcesearchbox {
+	position: absolute;
+	top: 1px;
+	left: 24px;
+	width: 140px;
+	height: 15px;
+	font-size: 11px;
+	padding: 0px;
+	border: none;
+	outline: none;
+	background: #fff;
+}
+
+#resourcesearchreset {
+	position: absolute;
+	top: 2px;
+	right: 2px;
+	text-decoration: none;
+}
+
+#resource-details,
+#resource-details-owner {
+	margin: 8px;
+}
+
+#resource-details td.title,
+#resource-details-owner td.title {
+	color: #666;
+	padding-right: 10px;
+	min-width: 10em;
+}
+
+#resource-details-owner thead td {
+	color: #333;
+	font-size: 13px;
+	font-weight: bold;
+}
+
+/* fullcalendar style overrides */
+
+#calendar .fc-header-right {
+	padding-right: 200px;
+	padding-top: 0;
+}
+
+.rcube-fc-content {
+	position: absolute !important;
+	top: 38px;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	overflow: hidden;
+}
+
+.fc-event-title {
+	font-weight: bold;
+}
+
+.cal-event-status-cancelled .fc-event-title {
+	text-decoration: line-through;
+}
+
+.fc-event-hori .fc-event-title {
+	font-weight: normal;
+	white-space: nowrap;
+}
+
+.fc-event-hori .fc-event-time {
+	white-space: nowrap;
+	font-weight: normal !important;
+	font-size: 10px;
+	padding-right: 0.6em;
+}
+
+.fc-event-vert.fc-invitation-needs-action,
+.fc-event-hori.fc-invitation-needs-action {
+	border: 1px dashed #5757c7 !important;
+}
+
+.fc-event-vert.fc-invitation-tentative,
+.fc-event-hori.fc-invitation-tentative {
+	border: 1px dashed #eb8900 !important;
+}
+
+.fc-event-vert.fc-invitation-declined,
+.fc-event-hori.fc-invitation-declined {
+	border: 1px dashed #c00 !important;
+}
+
+.fc-grid .fc-event-time {
+	font-weight: normal !important;
+	padding-right: 0.3em;
+}
+
+.fc-event-cateories {
+	font-style:italic; 
+}
+
+.fc-event-location {
+	font-size: 90%;
+}
+
+.fc-more-link {
+	color: #999;
+	padding-top: 1px;
+	cursor: pointer;
+}
+
+.fc-agenda-slots td div {
+	height: 22px;
+}
+
+.fc-mon, .fc-tue, .fc-wed, .fc-thu, .fc-fri {
+	background-color: #fdfdfd;
+}
+
+.fc-widget-header {
+	background-color: #fff;
+}
+
+.fc-icon-alarms,
+.fc-icon-sensitive,
+.fc-icon-recurring {
+	display: inline-block;
+	width: 11px;
+	height: 11px;
+	background: url(images/eventicons.gif) 0 0 no-repeat;
+	margin-left: 3px;
+	line-height: 10px;
+}
+
+.fc-icon-alarms {
+	background-position: 0 -13px;
+}
+
+.fc-icon-sensitive {
+	background-position: 0 -25px;
+}
+
+.fc-list-section .fc-event {
+	cursor: pointer;
+}
+
+#calendar .fc-event-vert .fc-event-head,
+#calendar .fc-event-vert .fc-event-content {
+	position: relative;
+	z-index: 2;
+	width: 100%;
+	overflow: hidden;
+}
+
+.fc-view-list div.fc-list-header,
+.fc-view-table td.fc-list-header,
+.edit-attendees-table thead td {
+	padding: 3px;
+	background: #dddddd;
+	background-image: -moz-linear-gradient(center top, #f4f4f4, #d2d2d2);
+	background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0.00, #f4f4f4), color-stop(1.00, #d2d2d2));
+	filter: progid:DXImageTransform.Microsoft.gradient(enabled='true', startColorstr=#f4f4f4, endColorstr=#d2d2d2, GradientType=0);
+	font-weight: bold;
+	color: #333;
+}
+
+.fc-view-list .fc-event-skin .fc-event-content {
+	background: #F6F6F6;
+	padding: 2px;
+}
+
+.fc-view-list .fc-event-skin .fc-event-title,
+.fc-view-list .fc-event-skin .fc-event-location {
+	color: #333;
+}
+
+.fc-view-table table.fc-list-smart {
+	table-layout: auto;
+}
+
+.fc-listappend {
+	text-align: center;
+	margin: 1em 0;
+}
+
+.fc-listappend .message {
+	padding: 0.5em;
+	margin-bottom: 0.5em;
+	font-size: 150%;
+	color: #999;
+}
+
+.fc-listappend .formlinks a {
+	font-size: 12px;
+	padding: 0 0.3em;
+}
+
+.fc-event-temp {
+	opacity: 0.4;
+	filter: alpha(opacity=40); /* IE8 */
+}
+
+/* Settings section */
+
+fieldset #calendarcategories div {
+	margin-bottom: 0.3em;
+}
+
+/* Invitation UI in mail */
+
+.messagelist tbody .attachment span.ical {
+	display: inline-block;
+	vertical-align: middle;
+	height: 18px;
+	width: 20px;
+	padding: 0;
+	background: url(images/calendar-small.png) 1px 1px no-repeat;
+}
+
+#messagemenu li a.calendarlink,
+#attachmentmenu li a.calendarlink {
+	background-image: url(images/calendars.png);
+	background-position: 7px -109px;
+	background-repeat: no-repeat;
+}
+
+div.calendar-invitebox {
+	min-height: 20px;
+	margin: 5px 8px;
+	padding: 3px 6px 6px 34px;
+	border: 1px solid #C2D071;
+	background: url(images/calendar.png) 6px 5px no-repeat #F7FDCB;
+}
+
+div.calendar-invitebox td.ititle {
+	font-weight: bold;
+	padding-right: 0.5em;
+}
+
+div.calendar-invitebox td.label {
+	color: #666;
+	padding-right: 1em;
+}
+
+#event-rsvp .rsvp-buttons,
+div.calendar-invitebox .itip-buttons div {
+	margin-top: 0.5em;
+}
+
+#event-rsvp input.button,
+div.calendar-invitebox input.button,
+div.calendar-invitebox select {
+	font-size: 11px;
+	margin-right: 0.5em;
+}
+
+div.calendar-invitebox .folder-select {
+	font-size: 11px;
+	margin-left: 1em;
+}
+
+div.calendar-invitebox .rsvp-status.loading {
+	color: #666;
+	padding: 1px 0 2px 24px;
+	background: url(images/loading_blue.gif) top left no-repeat;
+}
+
+div.calendar-invitebox .rsvp-status.declined,
+div.calendar-invitebox .rsvp-status.tentative,
+div.calendar-invitebox .rsvp-status.delegated,
+div.calendar-invitebox .rsvp-status.accepted {
+	padding: 0 0 1px 22px;
+	background: url(images/attendee-status.gif) 2px -20px no-repeat;
+}
+
+div.calendar-invitebox .rsvp-status.declined {
+	background-position: 2px -40px;
+}
+
+div.calendar-invitebox .rsvp-status.tentative {
+	background-position: 2px -60px;
+}
+
+div.calendar-invitebox .rsvp-status.delegated {
+	background-position: 2px -180px;
+}
+
+div.calendar-invitebox .calendar-agenda-preview {
+	display: none;
+	border-top: 1px solid #dfdfdf;
+	margin-top: 1em;
+	padding-top: 0.6em;
+}
+
+div.calendar-invitebox .calendar-agenda-preview h3.preview-title {
+	margin: 0 0 0.5em 0;
+	font-size: 12px;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row {
+	color: #777;
+	padding: 2px 0;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row.current {
+	color: #000;
+	font-weight: bold;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row.no-event {
+	font-style: italic;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-date {
+	display: inline-block;
+	min-width: 8em;
+	margin-right: 1em;
+	white-space: nowrap;
+}
+
+/* iTIP attend reply page */
+
+.calendaritipattend .centerbox {
+	width: 40em;
+	margin: 80px auto;
+	padding: 10px 10px 10px 90px;
+	border: 1px solid #ccc;
+	box-shadow: 1px 1px 24px #ccc;
+	-moz-box-shadow: 1px 1px 18px #ccc;
+	-webkit-box-shadow: #ccc 1px 1px 18px;
+	background: url(images/invitation.png) 10px 10px no-repeat #fbfbfb;
+}
+
+.calendaritipattend .calendar-invitebox {
+	background: none;
+	padding-left: 0;
+	border: 0;
+	margin: 0 0 2em 0;
+}
+
+.calendaritipattend .calendar-invitebox .rsvp-status {
+	margin-top: 2.5em;
+	font-size: 110%;
+	font-weight: bold;
+}
+
+.calendaritipattend .calendar-invitebox td.title,
+.calendaritipattend .calendar-invitebox td.ititle {
+	font-size: 120%;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/fullcalendar.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,1 @@
+../larry/fullcalendar.css
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/iehacks.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,80 @@
+/* CSS hacks for IE 6/7 */
+
+#main {
+	width: expression(Math.max(300, parseInt(document.documentElement.clientWidth)-10)+'px');
+	height: expression(Math.max(300, parseInt(document.documentElement.clientHeight)-100)+'px');
+}
+
+#calendarsidebar,
+#calendarsidebartoggle {
+	height: expression((parseInt(this.parentNode.offsetHeight)-37)+'px');
+}
+
+#calendar {
+	width: expression((parseInt(this.parentNode.offsetWidth)-parseInt(document.getElementById('calendarsidebartoggle').offsetWidth)-parseInt(document.getElementById('calendarsidebartoggle').offsetLeft)-4)+'px');
+	height: expression(parseInt(this.parentNode.offsetHeight)+'px');
+}
+
+#calendars {
+	height: expression((parseInt(this.parentNode.offsetHeight)-220)+'px');
+}
+
+#agendaoptions {
+	width: expression((parseInt(this.parentNode.offsetWidth)-12)+'px');
+}
+
+#calendartoolbar a.buttonPas {
+	filter: alpha(opacity=35);
+}
+
+html #calendartoolbar a.button,
+html #calendartoolbar a.buttonPas {
+	background-image: url(images/toolbar.gif);
+}
+
+#datepicker a.ui-priority-secondary {
+	filter: alpha(opacity=40);
+}
+
+#calendarslist li span.handle {
+	background-image: url(images/calendars.gif);
+}
+
+#datepicker .ui-widget-header {
+	width: 102%;
+}
+
+.fc-day-content {
+	cursor: default;
+}
+
+.fc-header-title h2 {
+	font-size: 16px;
+}
+
+.fc-event-temp .fc-event-bg {
+	display: none; /* nested opacity filters while dragging don't work */
+}
+
+#schedule-event-time {
+	filter: alpha(opacity=40);
+}
+
+#eventfreebusy .schedule-buttons,
+#edit-attendees-form #edit-attendee-schedule {
+	right: 0.6em;
+}
+
+#schedule-freebusy-times td.all-busy,
+#schedule-freebusy-times td.all-tentative,
+#schedule-freebusy-times td.all-out-of-office {
+	background-image: url('images/freebusy-colors.gif');
+}
+
+#schedule-freebusy-times tr.times td.allday {
+	width: expression(Math.max(60, parseInt(this.offsetWidth))+'px');
+}
+
+.ui-dialog .ui-dialog-titlebar {
+	width: expression((parseInt(this.parentNode.offsetWidth)-26)+'px');
+}
Binary file plugins/calendar/skins/classic/images/attendee-status.gif has changed
Binary file plugins/calendar/skins/classic/images/badge_confidential.gif has changed
Binary file plugins/calendar/skins/classic/images/badge_confidential.png has changed
Binary file plugins/calendar/skins/classic/images/badge_private.gif has changed
Binary file plugins/calendar/skins/classic/images/badge_private.png has changed
Binary file plugins/calendar/skins/classic/images/calendar-blue.png has changed
Binary file plugins/calendar/skins/classic/images/calendar.gif has changed
Binary file plugins/calendar/skins/classic/images/calendar.png has changed
Binary file plugins/calendar/skins/classic/images/calendars.gif has changed
Binary file plugins/calendar/skins/classic/images/calendars.png has changed
Binary file plugins/calendar/skins/classic/images/eventicons.gif has changed
Binary file plugins/calendar/skins/classic/images/export.png has changed
Binary file plugins/calendar/skins/classic/images/freebusy-colors.gif has changed
Binary file plugins/calendar/skins/classic/images/freebusy-colors.png has changed
Binary file plugins/calendar/skins/classic/images/invitation.png has changed
Binary file plugins/calendar/skins/classic/images/listheader.gif has changed
Binary file plugins/calendar/skins/classic/images/loading_blue.gif has changed
Binary file plugins/calendar/skins/classic/images/minicolors-all.png has changed
Binary file plugins/calendar/skins/classic/images/minicolors-handles.gif has changed
Binary file plugins/calendar/skins/classic/images/preview.png has changed
Binary file plugins/calendar/skins/classic/images/print.png has changed
Binary file plugins/calendar/skins/classic/images/spacer.gif has changed
Binary file plugins/calendar/skins/classic/images/toggle.gif has changed
Binary file plugins/calendar/skins/classic/images/toolbar.gif has changed
Binary file plugins/calendar/skins/classic/images/toolbar.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/jquery.miniColors.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,106 @@
+.miniColors-trigger {
+	height: 22px;
+	width: 22px;
+	background: url('images/minicolors-all.png') -170px 0 no-repeat;
+	vertical-align: middle;
+	margin: 0 .25em;
+	display: inline-block;
+	outline: none;
+}
+
+.miniColors-selector {
+	position: absolute;
+	width: 175px;
+	height: 150px;
+	background: #FFF;
+	border: solid 1px #BBB;
+	-moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	-webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+	padding: 5px;
+	z-index: 999999;
+}
+
+.miniColors-selector.black {
+	background: #000;
+	border-color: #000;
+}
+
+.miniColors-colors {
+	position: absolute;
+	top: 5px;
+	left: 5px;
+	width: 150px;
+	height: 150px;
+	background: url('images/minicolors-all.png') top left no-repeat;
+	cursor: crosshair;
+}
+
+.miniColors-hues {
+	position: absolute;
+	top: 5px;
+	left: 160px;
+	width: 20px;
+	height: 150px;
+	background: url('images/minicolors-all.png') -150px 0 no-repeat;
+	cursor: crosshair;
+}
+
+.miniColors-colorPicker {
+	position: absolute;
+	width: 11px;
+	height: 11px;
+	background: url('images/minicolors-all.png') -170px -28px no-repeat;
+}
+
+.miniColors-huePicker {
+	position: absolute;
+	left: -3px;
+	width: 26px;
+	height: 3px;
+	background: url('images/minicolors-all.png') -170px -24px no-repeat;
+	overflow: hidden;
+}
+
+.miniColors-presets {
+	position: absolute;
+	left: 185px;
+	top: 5px;
+	width: 60px;
+}
+
+.miniColors-colorPreset {
+	float: left;
+	width: 18px;
+	height: 15px;
+	margin: 2px;
+	border: 1px solid #333;
+	cursor: pointer;
+}
+
+.miniColors-colorPreset-active {
+	border: 2px dotted #666;
+	margin: 1px;
+}
+
+/* Hacks for IE6/7 */
+
+* html .miniColors-colors {
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='plugins/calendar/skins/classic/images/minicolors-all.png', sizingMethod='crop');
+}
+
+* html .miniColors-colorPicker {
+	background: url('images/minicolors-handles.gif') 0 -28px no-repeat;
+}
+
+* html .miniColors-huePicker {
+	background: url('images/minicolors-handles.gif') 0 -24px no-repeat;
+}
+
+* html .miniColors-trigger {
+	background: url('images/minicolors-handles.gif') 0 0 no-repeat;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/print.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,209 @@
+/*** Printing styles for Calendar plugin ***/
+
+body {
+	margin: 0;
+	color: #000;
+	background: #fff;
+}
+
+body, td, th, div, p, h3, select, input, textarea {
+	font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+	font-size: 8pt;
+}
+
+#calendar {
+	position: relative;
+	top: 0;
+	left: 0;
+	height: auto;
+	margin: 5em auto 0 auto;
+	overflow: visible;
+}
+
+#calendar .fc-header-right {
+	padding-right: 0;
+}
+
+#printconfig {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	z-index: 10000;
+	padding: 0.5em;
+	background: #ebebeb;
+	border-bottom: 1px solid #999;
+	box-shadow: 0 3px 4px #ccc;
+	-moz-box-shadow: 0 3px 4px #ccc;
+	-webkit-box-shadow: 0 3px 4px #ccc;
+}
+
+#printconfig .prop {
+	padding-right: 2em;
+}
+
+#message {
+	position: absolute;
+	top: 5.5em;
+	left: 1em;
+}
+
+#message div.loading {
+	color: #666;
+	font-style: italic;
+}
+
+#calendarlist {
+	list-style-type: square;
+	margin: 2em 0;
+	padding-left: 1em;
+}
+
+#calendarlist li {
+	float: left;
+	padding-left: 0;
+	padding-right: 3em;
+	margin-left: 0;
+	font-weight: bold;
+}
+
+#calendarlist input,
+#calendarlist .handle {
+	display: none;
+}
+
+.calwidth {
+	width: 700px;
+	margin: 0 auto;
+}
+
+.rightalign {
+	float: right;
+	padding-top: 0.3em;
+}
+
+@media print {
+	.noprint,
+	.fc-header-right span {
+		display: none;
+	}
+	
+	#calendar {
+		margin-top: 0;
+	}
+}
+
+/* fullcalendar style overrides */
+
+.fc-view {
+	overflow: visible;
+}
+
+.fc-event-skin,
+.fc-event-inner .fc-event-skin {
+	color: black;
+	background-color: #fff !important;
+}
+
+.fc-event-title {
+	font-weight: bold;
+}
+
+.fc-event-hori .fc-event-title {
+	font-weight: normal;
+	white-space: nowrap;
+}
+
+.fc-event-hori .fc-event-time {
+	white-space: nowrap;
+	font-weight: normal !important;
+	font-size: 10px;
+	padding-right: 0.6em;
+}
+
+.fc-grid .fc-event-time {
+	font-weight: normal !important;
+	padding-right: 0.3em;
+}
+
+.fc-event-cateories {
+	font-style: italic;
+}
+
+.fc-event-location {
+	font-size: 90%;
+}
+
+.fc-agenda-slots td div {
+	height: 1.4em;
+}
+
+.fc-widget-header,
+.fc-mon, .fc-tue, .fc-wed, .fc-thu, .fc-fri {
+	background-color: #fff;
+}
+
+.fc-widget-header, .fc-widget-content {
+	border-color: #ccc;
+}
+
+.fc-icon-alarms,
+.fc-icon-recurring {
+	display: inline-block;
+	width: 11px;
+	height: 11px;
+	background: url('images/eventicons.gif') 0 0 no-repeat;
+	margin-left: 3px;
+	line-height: 10px;
+}
+
+.fc-icon-alarms {
+	background-position: 0 -13px;
+}
+
+.fc-view-list, .fc-view-table {
+	border: 0;
+}
+
+.fc-view-list div.fc-list-header,
+.fc-view-table td.fc-list-header {
+	padding: 0.3em;
+	background: #fff;
+	font-weight: bold;
+	font-size: 1.2em;
+	color: #333;
+	border-color: #333;
+	border-style: solid;
+	border-width: 1px 0;
+	filter: none;
+}
+
+.fc-list-section .fc-event {
+	cursor: auto;
+}
+
+.fc-view-table tr.fc-event td,
+.fc-view-table tr.fc-event td.fc-event-handle {
+	border-color: #999;
+	padding-top: 0.5em;
+	padding-bottom: 0.5em;
+}
+
+.fc-view-table tr.fc-last td {
+	border: 0;
+}
+
+.fc-view-table tr.fc-event .fc-event-description {
+	padding-left: 2em;
+	padding-top: 0em;
+}
+
+.fc-event-vert .fc-event-description {
+	font-size: 90%;
+	font-style: italic;
+}
+
+
+.fc-view-table col.fc-event-location {
+	width: 20%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/print.iehacks.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,25 @@
+/* CSS hacks for IE 6/7 */
+
+#calendar {
+	top: 5em;
+}
+
+.calwidth {
+	width: 172mm;
+}
+
+.fc-header-title h2 {
+	font-size: 16px;
+}
+
+#calendarlist li {
+	float: none;
+	padding: 0;
+	margin-left: 1em;
+}
+
+@media print {
+	#calendar {
+		top: 0;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/attachment.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+</head>
+<body class="extwin">
+
+<roundcube:include file="/includes/header.html" />
+
+<div id="partheader">
+<roundcube:object name="plugin.attachmentcontrols" cellpadding="2" cellspacing="0" downloadlink="true" />
+
+<div style="position:absolute; top:2px; right:0; width:12em; text-align:right">
+  [<a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>]
+</div>
+</div>
+
+
+<div id="attachmentcontainer">
+<roundcube:object name="plugin.attachmentframe" id="attachmentframe" width="100%" height="100%" />
+</div>
+
+</body>
+</html>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/calendar.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,261 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<script type="text/javascript" src="/functions.js"></script>
+<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="plugins/calendar/skins/classic/iehacks.css" /><![endif]-->
+</head>
+<roundcube:if condition="env:extwin" /><body class="calendarmain extwin"><roundcube:else /><body class="calendarmain"><roundcube:endif />
+
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+
+<div id="main">
+  <div id="calendarsidebar">
+    <div id="calendartoolbar">
+      <roundcube:button command="addevent" type="link" class="buttonPas addevent" classAct="button addevent" classSel="button addeventSel" title="calendar.new_event" content=" " />
+      <roundcube:button command="print" type="link" class="buttonPas print" classAct="button print" classSel="button printSel" title="calendar.print" content=" " />
+      <roundcube:button command="events-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="calendar.importevents" content=" " />
+      <roundcube:button command="export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="calendar.export" content=" " />
+      <roundcube:container name="toolbar" id="calendartoolbar" />
+    </div>
+
+    <div id="datepicker"></div>
+    <div id="calendars" style="visibility:hidden">
+      <div class="boxtitle"><roundcube:label name="calendar.calendars" /></div>
+      <div class="listsearchbox">
+        <div class="searchbox">
+          <input type="text" name="q" id="calendarlistsearch" placeholder="<roundcube:label name='calendar.findcalendars' />" />
+          <a class="iconbutton searchicon"></a>
+          <roundcube:button command="reset-listsearch" id="calendarlistsearch-reset" class="reset searchreset" title="resetsearch" content="x" />
+        </div>
+      </div>
+      <div class="boxlistcontent">
+      <roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist" />
+      </div>
+      <div class="boxfooter">
+        <roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="buttonPas addgroup" classAct="button addgroup" content=" " />
+        <roundcube:button name="calendaroptionslink" id="calendaroptionslink" type="link" title="moreactions" class="button groupactions" onclick="rcmail_ui.show_popup('calendaroptions');return false" content=" " />
+      </div>
+    </div>
+  </div>
+  <div id="calendarsidebartoggle"></div>
+  <div id="calendar">
+    <roundcube:object name="plugin.angenda_options" class="boxfooter" id="agendaoptions" />
+  </div>
+</div>
+
+<div id="calendaroptionsmenu" class="popupmenu">
+  <ul>
+    <li><roundcube:button command="calendar-edit" label="calendar.edit" classAct="active" /></li>
+    <li><roundcube:button command="calendar-delete" label="delete" classAct="active" /></li>
+    <roundcube:if condition="env:calendar_driver == 'kolab'" />
+    <li><roundcube:button command="calendar-remove" label="calendar.removelist" classAct="active" /></li>
+    <roundcube:endif />
+    <li><roundcube:button command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
+    <roundcube:if condition="env:calendar_driver == 'kolab'" />
+    <li class="separator_above"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
+    <roundcube:endif />
+  </ul>
+</div>
+
+<div id="eventshow" class="uidialog">
+  <h1 id="event-title">Event Title</h1>
+  <div id="event-status-badge"><span></span></div>
+  <div class="event-section" id="event-location">Location</div>
+  <div class="event-section" id="event-date">From-To</div>
+  <div class="event-section" id="event-description">
+    <h5 class="label"><roundcube:label name="calendar.description" /></h5>
+    <div class="event-text"></div>
+  </div>
+  <div class="event-section" id="event-url">
+    <h5 class="label"><roundcube:label name="calendar.url" /></h5>
+    <div class="event-text"></div>
+  </div>
+  <div class="event-section" id="event-repeat">
+    <h5 class="label"><roundcube:label name="calendar.repeat" /></h5>
+    <div class="event-text"></div>
+  </div>
+  <div class="event-section" id="event-alarm">
+    <h5 class="label"><roundcube:label name="calendar.alarms" /></h5>
+    <div class="event-text"></div>
+  </div>
+  <div class="event-section event-attendees" id="event-attendees">
+    <h5 class="label"><roundcube:label name="calendar.tabattendees" /></h5>
+    <div class="event-text"></div>
+  </div>
+  <div class="event-line" id="event-partstat">
+    <label><roundcube:label name="calendar.mystatus" /></label>
+    <a href="#change" class="changersvp" title="<roundcube:label name='calendar.changepartstat' />">
+      <span class="event-text"></span>
+    </a>
+  </div>
+  <div class="event-line" id="event-calendar">
+    <label><roundcube:label name="calendar.calendar" /></label>
+    <span class="event-text">Default</span>
+  </div>
+  <div class="event-line" id="event-category">
+    <label><roundcube:label name="calendar.category" /></label>
+    <span class="event-text"></span>
+  </div>
+  <div class="event-line" id="event-free-busy">
+    <label><roundcube:label name="calendar.freebusy" /></label>
+    <span class="event-text"></span>
+  </div>
+  <div class="event-line" id="event-priority">
+    <label><roundcube:label name="calendar.priority" /></label>
+    <span class="event-text"></span>
+  </div>
+  <div class="event-line" id="event-sensitivity">
+    <label><roundcube:label name="calendar.sensitivity" /></label>
+    <span class="event-text"></span>
+  </div>
+  <div class="event-section" id="event-attachments">
+    <label><roundcube:label name="attachments" /></label>
+    <div class="event-text attachments-list"></div>
+  </div>
+  
+  <roundcube:object name="plugin.event_rsvp_buttons" id="event-rsvp" style="display:none" />
+</div>
+
+<div id="eventoptionsmenu" class="popupmenu">
+  <ul>
+    <li><roundcube:button command="event-download" label="download" classAct="active" /></li>
+    <li><roundcube:button command="event-sendbymail" label="send" classAct="active" /></li>
+    <li><roundcube:button command="event-copy" label="copy" classAct="active" /></li>
+  </ul>
+</div>
+
+<roundcube:include file="/templates/eventedit.html" />
+
+<div id="eventresourcesdialog" class="uidialog">
+  <div id="resource-dialog-left">
+    <div id="resource-selection" class="">
+      <div id="resourcequicksearch">
+        <roundcube:object name="plugin.resources_searchform" id="resourcesearchbox" />
+        <roundcube:button command="reset-resource-search" id="resourcesearchreset" image="/images/icons/reset.gif" title="resetsearch" width="13" height="13" />
+      </div>
+      <div class="boxlistcontent">
+        <roundcube:object name="plugin.resources_list" id="resources-list" class="treelist" />
+      </div>
+    </div>
+  </div>
+
+  <div id="resource-dialog-right">
+    <div id="resource-info">
+      <h2 class="boxtitle"><roundcube:label name="calendar.resourcedetails" /></h2>
+      <roundcube:object name="plugin.resource_info" id="resource-details" />
+    </div>
+
+    <div id="resource-availability">
+      <h2 class="boxtitle"><roundcube:label name="calendar.resourceavailability" /></h2>
+      <roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
+    </div>
+  </div>
+</div>
+
+<div id="eventfreebusy" class="uidialog">
+  <roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellspacing="0" cellpadding="0" border="0" />
+  
+  <div class="schedule-options">
+    &nbsp;
+    <div class="schedule-buttons">
+      <button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button><button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
+    </div>
+  </div>
+  
+  <div style="float:left; width:28em">
+    <div class="form-section">
+      <label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
+      <input type="text" name="startdate" size="11" id="schedule-startdate" disabled="true" /> &nbsp;
+      <input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
+    </div>
+    <div class="form-section">
+      <label for="schedule-enddate"><roundcube:label name="calendar.end" /></label>
+      <input type="text" name="enddate" size="11" id="schedule-enddate" disabled="true" /> &nbsp;
+      <input type="text" name="endtime" size="6"  id="schedule-endtime" disabled="true" />
+    </div>
+  </div>
+  <div style="float:left">
+    <div class="schedule-find-buttons">
+      <button id="shedule-find-prev">&#9668; <roundcube:label name="calendar.prevslot" /></button>
+      <button id="shedule-find-next"><roundcube:label name="calendar.nextslot" /> &#9658;</button>
+    </div>
+    <div class="schedule-options">
+      <label><input type="checkbox" id="schedule-freebusy-workinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
+    </div>
+  </div>
+  <br style="clear:both;" />
+  
+  <roundcube:include file="/templates/freebusylegend.html" />
+  <div class="attendees-list">
+    <span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
+    <span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
+    <span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
+    <span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
+    <span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
+  </div>
+</div>
+
+<div id="calendarform" class="uidialog">
+  <roundcube:label name="loading" />
+</div>
+
+<div id="eventsimport" class="uidialog">
+  <roundcube:object name="plugin.events_import_form" id="events-import-form" uploadFieldSize="30" />
+</div>
+
+<div id="eventsexport" class="uidialog">
+  <roundcube:object name="plugin.events_export_form" id="events-export-form" />
+</div>
+
+<div id="calendarurlbox" class="uidialog">
+  <p><roundcube:label name="calendar.showurldescription" /></p>
+  <textarea id="calfeedurl" rows="2" readonly="readonly"></textarea>
+  <div id="calendarcaldavurl" style="display:none">
+    <p><roundcube:label name="calendar.caldavurldescription" html="yes" /></p>
+    <textarea id="caldavurl" rows="2" readonly="readonly"></textarea>
+  </div>
+</div>
+
+<div id="quicksearchbar">
+<roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
+<roundcube:object name="plugin.searchform" id="quicksearchbox" />
+<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
+</div>
+
+<roundcube:object name="plugin.calendar_css" />
+
+<script type="text/javascript">
+
+// use skin functions to handle popup-menus
+rcube_init_mail_ui();
+rcmail_ui.popups.calendaroptions = { id:'calendaroptionsmenu', above:1, obj:$('#calendaroptionsmenu') };
+
+$(document).ready(function(e){
+  // initialize sidebar toggle
+  $('#calendarsidebartoggle').click(function() {
+    var width = $(this).data('sidebarwidth');
+    var offset = $(this).data('offset');
+    var $sidebar = $('#calendarsidebar'), time = 250;
+    
+    if ($sidebar.is(':visible')) {
+      $sidebar.animate({ left:'-'+(width+10)+'px' }, time, function(){ $('#calendarsidebar').hide(); });
+      $(this).animate({ left:'8px'}, time, function(){ $('#calendarsidebartoggle').addClass('sidebarclosed') });
+      $('#calendar').animate({ left:'20px'}, time, function(){ $(this).fullCalendar('render'); });
+    }
+    else {
+      $sidebar.show().animate({ left:'10px' }, time);
+      $(this).animate({ left:offset+'px'}, time, function(){ $('#calendarsidebartoggle').removeClass('sidebarclosed'); });
+      $('#calendar').animate({ left:(width+16)+'px'}, time, function(){ $(this).fullCalendar('render'); });
+    }
+  })
+  .data('offset', $('#calendarsidebartoggle').position().left)
+  .data('sidebarwidth', $('#calendarsidebar').width() + $('#calendarsidebar').position().left);
+});
+
+</script>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/eventedit.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,128 @@
+<div id="eventedit" class="uidialog">
+  <form id="eventtabs" action="#" method="post" enctype="multipart/form-data">
+    <ul>
+      <li><a href="#event-panel-1"><roundcube:label name="calendar.tabsummary" /></a></li>
+      <li id="edit-tab-recurrence"><a href="#event-panel-recurrence"><roundcube:label name="calendar.tabrecurrence" /></a></li>
+      <li id="edit-tab-attendees"><a href="#event-panel-attendees"><roundcube:label name="calendar.tabattendees" /></a></li>
+      <li id="edit-tab-resources"><a href="#event-panel-resources"><roundcube:label name="calendar.tabresources" /></a></li>
+      <li id="edit-tab-attachments"><a href="#event-panel-attachments"><roundcube:label name="calendar.tabattachments" /></a></li>
+    </ul>
+    <!-- basic info -->
+    <div id="event-panel-1">
+      <div class="event-section">
+        <label for="edit-title"><roundcube:label name="calendar.title" /></label>
+        <br />
+        <input type="text" class="text" name="title" id="edit-title" size="40" />
+      </div>
+      <div class="event-section">
+        <label for="edit-location"><roundcube:label name="calendar.location" /></label>
+        <br />
+        <input type="text" class="text" name="location" id="edit-location" size="40" />
+      </div>
+      <div class="event-section">
+        <label for="edit-description"><roundcube:label name="calendar.description" /></label>
+        <br />
+        <textarea name="description" id="edit-description" class="text" rows="5" cols="40"></textarea>
+      </div>
+      <div class="event-section">
+        <label for="edit-url"><roundcube:label name="calendar.url" /></label>
+        <br />
+        <input type="text" class="text" name="vurl" id="edit-url" size="40" />
+      </div>
+      <div class="event-section">
+        <label style="float:right;padding-right:0.5em"><input type="checkbox" name="allday" id="edit-allday" value="1" /><roundcube:label name="calendar.all-day" /></label>
+        <label for="edit-startdate"><roundcube:label name="calendar.start" /></label>
+        <input type="text" name="startdate" size="11" id="edit-startdate" /> &nbsp;
+        <input type="text" name="starttime" size="6" id="edit-starttime" />
+      </div>
+      <div class="event-section">
+        <label for="edit-enddate"><roundcube:label name="calendar.end" /></label>
+        <input type="text" name="enddate" size="11" id="edit-enddate" /> &nbsp;
+        <input type="text" name="endtime" size="6"  id="edit-endtime" />
+      </div>
+      <div class="event-section" id="edit-alarms">
+        <div class="edit-alarm-item first">
+          <label for="edit-alarm"><roundcube:label name="calendar.alarms" /></label>
+          <roundcube:object name="plugin.alarm_select" />
+          <span class="edit-alarm-buttons">
+            <a href="#add" class="iconlink add add-alarm">+</a>
+            <a href="#delete" class="iconlink delete delete-alarm">-</a>
+          </span>
+        </div>
+      </div>
+      <div class="event-section" id="calendar-select">
+        <label for="edit-calendar"><roundcube:label name="calendar.calendar" /></label>
+        <roundcube:object name="plugin.calendar_select" id="edit-calendar" />
+      </div>
+      <div class="event-section">
+        <label for="edit-categories"><roundcube:label name="calendar.category" /></label>
+        <roundcube:object name="plugin.category_select" id="edit-categories" />
+      </div>
+      <div class="event-section">
+        <label for="edit-event-status"><roundcube:label name="calendar.status" /></label>
+        <roundcube:object name="plugin.status_select" id="edit-event-status" />
+      </div>
+      <div class="event-section">
+        <label for="edit-free-busy"><roundcube:label name="calendar.freebusy" /></label>
+        <roundcube:object name="plugin.freebusy_select" id="edit-free-busy" />
+      </div>
+      <div class="event-section">
+        <label for="edit-priority"><roundcube:label name="calendar.priority" /></label>
+        <roundcube:object name="plugin.priority_select" id="edit-priority" />
+      </div>
+      <div class="event-section">
+        <label for="edit-sensitivity"><roundcube:label name="calendar.sensitivity" /></label>
+        <roundcube:object name="plugin.sensitivity_select" id="edit-sensitivity" />
+      </div>
+    </div>
+    <!-- recurrence settings -->
+    <div id="event-panel-recurrence">
+      <div class="event-section border-after">
+        <roundcube:object name="plugin.recurrence_form" part="frequency" />
+      </div>
+      <div class="recurrence-form border-after" id="recurrence-form-daily">
+        <roundcube:object name="plugin.recurrence_form" part="daily" class="event-section" />
+      </div>
+      <div class="recurrence-form border-after" id="recurrence-form-weekly">
+        <roundcube:object name="plugin.recurrence_form" part="weekly" class="event-section" />
+      </div>
+      <div class="recurrence-form border-after" id="recurrence-form-monthly">
+        <roundcube:object name="plugin.recurrence_form" part="monthly" class="event-section" />
+      </div>
+      <div class="recurrence-form border-after" id="recurrence-form-yearly">
+        <roundcube:object name="plugin.recurrence_form" part="yearly" class="event-section" />
+      </div>
+      <div class="recurrence-form" id="recurrence-form-until">
+        <roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
+      </div>
+      <div class="recurrence-form" id="recurrence-form-rdate">
+        <roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
+      </div>
+    </div>
+    <!-- attendees list -->
+    <div id="event-panel-attendees">
+      <roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" />
+      <roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
+      <roundcube:include file="/templates/freebusylegend.html" />
+    </div>
+    <!-- resources list -->
+    <div id="event-panel-resources">
+      <roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="edit-attendees-table" cellspacing="0" cellpadding="0" border="0" coltitle="resource" />
+      <roundcube:object name="plugin.resources_form" id="edit-resources-form" />
+      <roundcube:include file="/templates/freebusylegend.html" />
+    </div>
+    <!-- attachments list (with upload form) -->
+    <div id="event-panel-attachments">
+      <div id="edit-attachments" class="attachments-list">
+        <roundcube:object name="plugin.attachments_list" id="attachmentlist" deleteIcon="/images/icons/delete.png" cancelIcon="/images/icons/delete.png" loadingIcon="/images/display/loading_blue.gif" />
+      </div>
+      <div id="edit-attachments-form">
+        <roundcube:object name="plugin.attachments_form" id="calendar-attachment-form" attachmentFieldSize="30" />
+      </div>
+    </div>
+  </form>
+
+  <roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="event-dialog-message" style="display:none" />
+  <roundcube:object name="plugin.edit_recurring_warning" class="event-dialog-message edit-recurring-warning" style="display:none" />
+  <div id="edit-localchanges-warning" class="event-dialog-message" style="display:none"><roundcube:label name="calendar.localchangeswarning" /></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/freebusylegend.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,7 @@
+  <div id="edit-attendees-legend" class="availability">
+    <span class="legend"><img class="availabilityicon free" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availfree" /></span>
+    <span class="legend"><img class="availabilityicon busy" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availbusy" /></span>
+    <span class="legend"><img class="availabilityicon tentative" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availtentative" /></span>
+    <!--<span class="legend"><img class="availabilityicon out-of-office" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availoutofoffice" /></span>-->
+    <span class="legend"><img class="availabilityicon unknown" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span>
+  </div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/itipattend.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+</head>
+<body class="calendaritipattend">
+
+<roundcube:object name="logo" src="/images/roundcube_logo.png" id="logo" border="0" style="margin:0 11px" />
+
+<roundcube:object name="message" id="message" />
+
+<div class="centerbox">
+	<roundcube:object name="plugin.event_inviteform" />
+	<roundcube:object name="plugin.event_invitebox" class="calendar-invitebox" />
+	<roundcube:object name="plugin.event_rsvp_buttons" type="submit" iname="rsvp" id="event-rsvp" delegate="false" />
+	</form>
+</div>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/kolabacl.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<script type="text/javascript" src="/functions.js"></script>
+</head>
+<body class="iframe" style="background:#fff; margin:0">
+
+<roundcube:object name="folderacl" />
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/kolabform.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,7 @@
+<div id="calendar-kolabform">
+  <roundcube:object name="calendarform" />
+</div>
+<script type="text/javascript">rcube_init_tabs('calendar-kolabform');</script>
+<style type="text/css">
+#calendarpropform { min-width:680px }
+</style>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/classic/templates/print.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+</head>
+<body class="calendarprint">
+
+<div id="printconfig" class="noprint">
+  <div class="calwidth">
+    <a href="#close" onclick="window.close()" class="rightalign"><roundcube:label name="close" /></a>
+    <span class="prop"><input type="button" id="printme" value="<roundcube:label name='print' />" onclick="window.print()"></span>
+    <span class="prop"><label><input type="checkbox" id="propdescription" checked="checked" /> <roundcube:label name="calendar.printdescriptions" /></label></span>
+  </div>
+</div>
+
+<roundcube:object name="message" id="message" class="noprint" />
+
+<div id="calendar" class="calwidth"></div>
+
+<div class="calwidth">
+  <roundcube:object name="plugin.calendar_list" activeonly="true" id="calendarlist" />
+</div>
+
+<roundcube:object name="plugin.calendar_css" printmode="true" />
+
+<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="plugins/calendar/skins/classic/print.iehacks.css" /><![endif]-->
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/README	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,11 @@
+Screendesign by FLINT / Büro für Gestaltung, Bern, Switzerland
+http://bueroflint.com
+
+
+LICENSE
+-------
+The contents of this folder are subject to the Creative Commons
+Attribution-ShareAlike License. It is allowed to copy, distribute,
+transmit and to adapt the work by keeping credits to the original
+autors in the README file.
+See http://creativecommons.org/licenses/by-sa/3.0/ for details.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/calendar.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,2326 @@
+/**
+ * Roundcube Calendar plugin styles for skin "Larry"
+ *
+ * Copyright (c) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
+ * Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
+ *
+ * The contents are subject to the Creative Commons Attribution-ShareAlike
+ * License. It is allowed to copy, distribute, transmit and to adapt the work
+ * by keeping credits to the original autors in the README file.
+ * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
+ */
+
+body.calendarmain {
+	overflow: hidden;
+}
+
+body.calendarmain #mainscreen {
+	left: 0;
+}
+
+/* overrides for tablets and mobile phones */
+@media screen and (max-device-width: 1024px){
+	body.calendarmain {
+		overflow: visible;
+	}
+
+	body.calendarmain #mainscreen {
+		min-width: 1000px !important;
+		min-height: 520px !important;
+	}
+
+	body.calendarmain #header {
+		min-width: 1020px !important;
+	}
+}
+
+#calendarsidebar {
+	position: absolute;
+	top: 0;
+	left: 10px;
+	bottom: 0;
+	width: 250px;
+}
+
+#datepicker {
+	position: absolute;
+	top: 40px;
+	left: 0;
+	width: 100%;
+	min-height: 190px;
+}
+
+#datepicker .ui-datepicker {
+	width: 100% !important;
+	box-shadow: none;
+	-moz-box-shadow: none;
+	-webkit-box-shadow: none;
+}
+
+#datepicker .ui-datepicker td a {
+	padding: 5px 4px;
+	font-size: 12px;
+}
+
+#datepicker td.ui-datepicker-activerange {
+	border-color: #69a2b6;
+}
+
+#datepicker .ui-datepicker-activerange a {
+	color: #185d7a;
+	background: #d9f1fb;
+}
+
+#datepicker .ui-datepicker-days-cell-over a.ui-state-default {
+	color: #fff;
+	border-color: #2fa0c0;
+	background: rgba(73,180,210,0.6);
+	filter: none;
+}
+
+#datepicker .ui-datepicker-activerange a.ui-state-active {
+	color: #fff;
+	background: #00acd4;
+}
+
+#datepicker td.ui-datepicker-week-col {
+	cursor: pointer;
+}
+
+#datepicker .ui-datepicker-title {
+	margin: 2px 2.3em 3px 2.3em;
+}
+
+#datepicker .ui-datepicker .ui-datepicker-prev,
+#datepicker .ui-datepicker .ui-datepicker-next {
+	top: 4px;
+}
+
+#calsidebarsplitter {
+	position: absolute;
+	left: 264px;
+	width: 6px;
+	top: 40px !important;
+	bottom: 0;
+	height: auto;
+	background: url(images/toggle.gif) -1px 48% no-repeat transparent;
+}
+
+div.sidebarclosed {
+	background-position: -8px 48% !important;
+	cursor: pointer;
+}
+
+#calsidebarsplitter:hover {
+	background-color: #ddd;
+}
+
+#calendar {
+	position: absolute;
+	top: 0;
+	left: 276px;
+	right: 0;
+	bottom: 0;
+}
+
+.calendarmain #message.statusbar {
+	border: 1px solid #c3c3c3;
+	border-bottom-color: #ababab;
+}
+
+#timezonedisplay {
+	position: absolute;
+	bottom: 5px;
+	right: 12px;
+	font-size: 0.85em;
+	color: #666;
+}
+
+#print {
+	width: 680px;
+}
+
+pre {
+ font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+}
+
+#calendars {
+	position: absolute;
+	top: 276px;
+	left: 0;
+	bottom: 0;
+	right: 0;
+}
+
+#calendars .boxtitle {
+	position: relative;
+}
+
+#calendars .boxtitle a.iconbutton.search {
+	position: absolute;
+	top: 8px;
+	right: 8px;
+	width: 16px;
+	cursor: pointer;
+	background-position: -2px -317px;
+}
+
+#calendars .listsearchbox {
+	display: none;
+}
+
+#calendars .listsearchbox.expanded {
+	display: block;
+}
+
+#calendars .scroller {
+	top: 34px;
+}
+
+#calendars .listsearchbox.expanded + .scroller {
+	top: 68px;
+}
+
+#calendars .treelist li {
+	margin: 0;
+	position: relative;
+}
+
+#calendars .treelist li div.folder,
+#calendars .treelist li div.calendar {
+	position: relative;
+	height: 28px;
+}
+
+#calendars .treelist li div.virtual {
+	height: 22px;
+}
+
+#calendars .treelist li span.calname {
+	display: block;
+	padding: 0px 18px 2px 2px;
+	position: absolute;
+	top: 7px;
+	left: 38px;
+	right: 45px;
+	cursor: default;
+	background: url(images/calendars.png) right 20px no-repeat;
+	overflow-x: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	color: #004458;
+}
+
+.quickview-active #calendars .treelist div input,
+.quickview-active #calendars .treelist div .calname {
+	opacity: 0.35;
+}
+
+.quickview-active #calendars .treelist div.focusview .calname {
+	opacity: 1.0;
+	background-image: none;
+}
+
+#calendars .treelist li div.virtual > span.calname {
+	color: #aaa;
+	top: 4px;
+	left: 20px;
+}
+
+#calendars .treelist li.x-birthdays span.calname,
+#calendars .treelist li.x-invitations span.calname {
+	font-style: italic;
+}
+
+#calendars .treelist.flat li span.calname {
+	left: 24px;
+	right: 42px;
+}
+
+#calendars .treelist li span.handle {
+	display: inline-block;
+	position: absolute;
+	top: 7px;
+	right: 6px;
+	padding: 0;
+	width: 11px;
+	height: 11px;
+	border-radius: 8px;
+	font-size: 0.8em;
+	border: 1px solid rgba(0, 0, 0, 0.4);
+}
+
+#calendars .treelist div span.actions {
+	display: inline-block;
+	position: absolute;
+	top: 2px;
+	right: 22px;
+	padding: 5px 20px 0 6px;
+/*	min-width: 40px; */
+	height: 19px;
+	text-align: right;
+	z-index: 4;
+}
+
+#calendars .treelist div:hover span.actions {
+	top: 1px;
+	right: 21px;
+	border: 1px solid #ababab;
+	border-radius: 4px;
+	background: #f1f1f1;
+}
+
+#calendars .treelist li a.subscribed {
+	display: inline-block;
+	position: absolute;
+	top: 5px;
+	right: 3px;
+	height: 16px;
+	width: 16px;
+	padding: 0;
+	background: url(images/calendars.png) -100px 0 no-repeat;
+	overflow: hidden;
+	text-indent: -5000px;
+	cursor: pointer;
+}
+
+#calendars .treelist div:hover a.subscribed,
+#calendars .treelist div a.subscribed:focus {
+	background-position: 0 -110px;
+}
+
+#calendars .treelist div.subscribed a.subscribed,
+#calendars .treelist div.subscribed a.subscribed:focus {
+	background-position: -16px -110px;
+}
+
+#calendars .treelist div.subscribed.partial a.subscribed,
+#calendars .treelist div.subscribed.partial a.subscribed:focus {
+	background-position: -16px -148px;
+}
+
+#calendars .treelist div a.remove:focus,
+#calendars .treelist div a.quickview:focus,
+#calendars .treelist div a.subscribed:focus {
+	border-radius: 3px;
+	outline: 2px solid rgba(30,150,192, 0.5);
+}
+
+#calendars .treelist div a.remove,
+#calendars .treelist div a.quickview {
+	display: inline-block;
+	width: 16px;
+	height: 16px;
+	margin-right: 4px;
+	padding: 0;
+	background: url(images/calendars.png) -100px 0 no-repeat;
+	overflow: hidden;
+	text-indent: -5000px;
+	cursor: pointer;
+}
+
+#calendars .treelist div a.quickview:focus,
+#calendars .treelist div:hover a.quickview {
+	background-position: 0 -128px;
+	background-color: transparent !important;
+}
+
+#calendars .treelist div.focusview a.quickview {
+	background-position: -16px -128px;
+}
+
+#calendars .treelist div a.remove:focus,
+#calendars .treelist div:hover a.remove {
+	background-position: -16px -168px;
+	background-color: transparent !important;
+}
+
+#calendars .searchresults .treelist div a.remove {
+	display: none;
+}
+
+#calendars .treelist li input {
+	position: absolute;
+	top: 5px;
+	left: 18px;
+}
+
+#calendars .treelist li div.treetoggle {
+	top: 8px;
+}
+
+#calendars .treelist li.virtual div.treetoggle {
+	top: 6px;
+}
+
+#calendars .treelist.flat li input {
+	left: 4px;
+}
+
+#calendars .treelist ul li div.folder,
+#calendars .treelist ul li div.calendar {
+	margin-left: 16px;
+}
+
+#calendars .treelist ul ul li div.folder,
+#calendars .treelist ul ul li div.calendar {
+	margin-left: 32px;
+}
+
+#calendars .treelist ul ul ul li div.folder,
+#calendars .treelist ul ul ul li div.calendar {
+	margin-left: 48px;
+}
+
+#calendars .treelist li.selected > div.calendar {
+	background-color: #c7e3ef;
+}
+
+#calendars .treelist li.selected > span.calname {
+	font-weight: bold;
+}
+
+#calendars .treelist div.readonly span.calname {
+	background-position: right -20px;
+}
+
+#calendars .treelist li.user > div > span.calname {
+	background-position: right -38px;
+}
+/*
+#calendars .treelist div.user.readonly span.calname {
+	background-position: right -56px;
+}
+
+#calendars .treelist div.shared span.calname {
+	background-position: right -74px;
+}
+
+#calendars .treelist div.shared.readonly span.calname {
+	background-position: right -92px;
+}
+*/
+
+#calendars .treelist .calendar .count {
+	position: absolute;
+	display: inline-block;
+	top: 5px;
+	right: 68px;
+	min-width: 1.3em;
+	padding: 2px 4px;
+	background: #005d76;
+	border-radius: 10px;
+	color: #fff;
+	text-align: center;
+	font-style: normal;
+	font-weight: bold;
+	text-shadow: none;
+	z-index: 3;
+}
+
+#calendars .searchresults {
+	background: #b0ccd7;
+	margin-top: 8px;
+}
+
+#calendars .searchresults .boxtitle {
+	background: none;
+	padding: 2px 8px;
+	border-radius: 0;
+}
+
+#calendars .searchresults .listing li {
+	background-color: #c7e3ef;
+}
+
+#calfeedurl,
+#caldavurl {
+	width: 98%;
+	background: #fbfbfb;
+	padding: 4px;
+	margin-bottom: 1em;
+	resize: none;
+}
+
+#agendalist {
+	width: 100%;
+	margin: 0 auto;
+	margin-top: 60px;
+	border: 1px solid #C1DAD7;
+	display: none;
+}
+
+#agendalist table {
+	width: 100%;
+}
+
+#agendalist td,
+#agendalist th {
+	border-right: 1px solid #C1DAD7;
+	border-bottom: 1px solid #C1DAD7;
+	background: #fff;
+	padding: 6px 6px 6px 12px;
+}
+
+#agendalist tr {
+	vertical-align: top;
+}
+
+#agendalist th {
+	font-weight: bold;
+}
+
+#calendartoolbar {
+	position: absolute;
+	top: -6px;
+	left: 0;
+	height: 40px;
+	white-space: nowrap;
+}
+
+#calendartoolbar a.button {
+	background-image: url(images/toolbar.png);
+	padding-left: 0;
+	padding-right: 0;
+	min-width: 50px;
+	max-width: 60px;
+}
+
+#calendartoolbar a.button.addevent {
+	background-position: center 1px;
+	max-width: 70px;
+}
+
+#calendartoolbar a.button.export {
+	background-position: center -40px;
+}
+
+#calendartoolbar a.button.import {
+	background-position: center -440px;
+}
+
+#calendartoolbar a.button.print {
+	background-position: center -80px;
+}
+
+body.calendarmain #quicksearchbar {
+	z-index: 20;
+}
+
+body.calendarmain #searchmenulink {
+	width: 15px;
+}
+
+#eventedit.uidialog,
+.calendarmain div.uidialog {
+	display: none;
+}
+
+#calendarform {
+	overflow: visible;
+}
+
+#user {
+	position: absolute;
+	top: 10px;
+	right: 100px;
+	left: 100px;
+	text-align: center;
+}
+
+a.morelink {
+	font-size: 90%;
+	color: #0069a6;
+	text-decoration: none;
+}
+
+a.morelink:hover {
+	text-decoration: underline;
+}
+
+a.miniColors-trigger {
+	margin-top: -3px;
+}
+
+.calendar.attachmentwin #attachmenttoolbar {
+	position: relative;
+	top: -6px;
+	height: 40px;
+}
+
+.calendar.attachmentwin #attachmentcontainer {
+	position: absolute;
+	top: 0;
+	left: 232px;
+	right: 0;
+	bottom: 0;
+}
+
+.calendar.attachmentwin #attachmentframe {
+	width: 100%;
+	height: 100%;
+	border: 0;
+	background-color: #fff;
+	border-radius: 4px;
+}
+
+.calendar.attachmentwin #partheader {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 220px;
+	bottom: 0;
+}
+
+.calendar.attachmentwin #partheader table {
+	table-layout: fixed;
+	overflow: hidden;
+}
+
+.calendar.attachmentwin #partheader table td {
+	color: #666;
+	padding: 4px 6px;
+	text-overflow: ellipsis;
+	overflow: hidden;
+}
+
+.calendar.attachmentwin #partheader table td.header {
+	font-weight: bold;
+}
+
+.calendar.attachmentwin #partheader table td.title {
+	width: 60px;
+	padding-right: 0;
+}
+
+#edit-attachments {
+	margin: 0.6em 0;
+}
+
+#edit-attachments ul li {
+	display: block;
+	color: #333;
+	font-weight: bold;
+	padding: 4px 4px 3px 30px;
+	text-decoration: none;
+	white-space: nowrap;
+	line-height: 20px;
+}
+
+#edit-attachments ul li a.file {
+	padding: 0;
+}
+
+#edit-attachments-form {
+	margin-top: 1em;
+	padding-top: 0.8em;
+	border-top: 2px solid #fafafa;
+}
+
+#edit-attachments-form .buttons {
+	margin: 0.5em 0;
+}
+
+#eventedit .droptarget {
+	background-image: url(../../../../skins/larry/images/filedrop.png) !important;
+	background-position: center bottom !important;
+	background-repeat: no-repeat !important;
+}
+
+#eventedit .droptarget.hover,
+#eventedit .droptarget.active {
+	border-color: #019bc6;
+	box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
+	-moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
+	-webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
+	-o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
+}
+
+#eventedit .droptarget.hover {
+	background-color: #d9ecf4;
+	box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
+	-moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
+	-webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
+	-o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
+}
+
+#event-attachments .attachmentslist li {
+	float: left;
+	margin-right: 1em;
+}
+
+#event-attachments .attachmentslist li a {
+	outline: none;
+}
+
+#event-panel-attachments.disabled .attachmentslist li a.delete {
+	visibility: hidden;
+}
+
+.event-attendees span.attendee {
+	padding: 1px 18px 1px 0;
+	margin-right: 0.5em;
+	background: url(images/attendee-status.png) right 0 no-repeat;
+}
+
+.event-attendees span.attendee a.mailtolink {
+	text-decoration: none;
+	white-space: nowrap;
+	outline: none;
+}
+
+.event-attendees span.attendee a.mailtolink:hover {
+	text-decoration: underline;
+}
+
+.event-attendees span.accepted {
+	background-position: right -20px;
+}
+
+.event-attendees span.declined {
+	background-position: right -40px;
+}
+
+.event-attendees span.tentative {
+	background-position: right -60px;
+}
+
+.event-attendees span.delegated {
+	background-position: right -180px;
+}
+
+.event-attendees span.organizer {
+	background-position: right -80px;
+}
+
+#all-event-attendees span.attendee {
+	display: block;
+	margin-bottom: 0.4em;
+	padding-bottom: 0.3em;
+	border-bottom: 1px solid #ddd;
+}
+
+.calendarmain .fc-view-table td.fc-list-header,
+#attendees-freebusy-table h3.boxtitle,
+#schedule-freebusy-times thead th,
+.edit-attendees-table thead th
+ {
+	color: #69939e;
+	font-size: 11px;
+	font-weight: bold;
+	background: #d6eaf3;
+	border: 0;
+	border-bottom: 1px solid #ccc;
+	height: 18px;
+	line-height: 18px;
+	padding: 8px 7px 3px 7px;
+}
+
+/* jQuery UI overrides */
+
+.calendarmain .eventdialog h1 {
+	font-size: 18px;
+	margin: -0.3em 0 0.4em 0;
+}
+
+.calendarmain .eventdialog label,
+.calendarmain .eventdialog h5.label {
+	font-weight: normal;
+	font-size: 1em;
+	color: #999;
+	margin: 0 0 0.2em 0;
+}
+
+.calendarmain .eventdialog label span.index,
+.calendarmain .eventdialog h5.label .index {
+	vertical-align: inherit;
+	margin-left: 0.6em;
+}
+
+.calendarmain .eventdialog {
+	margin: 0 -0.2em;
+}
+
+#event-status-badge {
+	width: 100px;
+	height: 100px;
+	position: absolute;
+	top: 0;
+	right: 0;
+	overflow: hidden;
+}
+
+#event-status-badge span {
+	display: none;
+	text-transform: uppercase;
+	width: 150px;
+	height: 20px;
+	line-height: 20px;
+	position: absolute;
+	left: -20px;
+	top: 35px;
+	padding-left: 10px;
+	text-align: center;
+	font-weight: bold;
+	font-size: 12px;
+	color: #fff;
+	box-shadow: 1px 1px 2px #ccc, -1px -1px 2px #ccc;
+	-webkit-transform: rotate(45deg);
+	-moz-transform: rotate(45deg);
+	-ms-transform: rotate(45deg);
+	-o-transform: rotate(45deg);
+	transform: rotate(45deg);
+}
+
+.eventdialog.status-cancelled #event-status-badge span {
+	background: url(images/badge.png) 26px -24px no-repeat #cc0000;
+	display: block;
+}
+
+.eventdialog.sensitivity-private #event-status-badge span {
+	background: url(images/badge.png) 40px -52px no-repeat #0066ff;
+	display: block;
+}
+
+.eventdialog.sensitivity-confidential #event-status-badge span {
+	background: url(images/badge.png) 20px 2px no-repeat #cc0000;
+	display: block;
+}
+
+.calendarmain .status-cancelled #event-title,
+.calendarmain .sensitivity-private #event-title,
+.calendarmain .sensitivity-confidential #event-title {
+	margin-right: 80px;
+}
+
+.calendarmain .eventdialog div.event-line {
+	margin-top: 0.1em;
+	margin-bottom: 0.3em;
+	white-space: nowrap;
+	overflow-x: hidden;
+	text-overflow: ellipsis;
+}
+
+.calendarmain .eventdialog div.event-line a.iconbutton {
+	margin-left: 0.5em;
+	line-height: 17px;
+}
+
+.calendarmain .eventdialog div.event-line span.event-text + label {
+	margin-left: 2em;
+}
+
+#event-description .event-text,
+#event-attendees .event-text {
+	padding-top: 0.4em;
+}
+
+.eventdialog .spacer {
+	font-size: 4px;
+}
+
+.eventdialog .event-text-old,
+.eventdialog .event-text-new,
+.eventdialog .event-text-diff {
+	padding: 2px;
+}
+
+.eventdialog .event-text-diff del,
+.eventdialog .event-text-diff ins {
+	text-decoration: none;
+	color: inherit;
+}
+
+.eventdialog .event-text-old,
+.eventdialog .event-text-diff del {
+	background-color: #fdd;
+	/* text-decoration: line-through; */
+}
+
+.eventdialog .event-text-new,
+.eventdialog .event-text-diff ins {
+	background-color: #dfd;
+}
+
+#eventdiff .attachmentslist li a,
+#eventdiff .attachmentslist li a:hover {
+	cursor: default;
+	text-decoration: none;
+}
+
+.changelog-table .loading {
+	color: #666;
+	margin: 1em 0;
+	padding: 1px 0 2px 24px;
+	background: url(images/loading_blue.gif) top left no-repeat;
+}
+
+.changelog-dialog .compare-button {
+	margin: 4px 0;
+}
+
+.changelog-table tbody td {
+	padding: 4px 7px;
+	vertical-align: middle;
+}
+
+.changelog-table tbody tr:last-child td {
+	border-bottom: 0;
+}
+
+.changelog-table tbody tr.undisclosed td.date,
+.changelog-table tbody tr.undisclosed td.user {
+	font-style: italic;
+}
+
+.changelog-table .diff {
+	width: 4em;
+	padding: 2px;
+}
+
+.changelog-table .revision {
+	width: 6em;
+}
+
+.changelog-table .date {
+	width: 11em;
+}
+
+.changelog-table .user {
+	width: auto;
+}
+
+.changelog-table .operation {
+	width: 15%;
+}
+
+.changelog-table .actions {
+	width: 50px;
+	text-align: right;
+	padding: 4px;
+}
+
+.changelog-table td a.iconbutton.restore,
+.changelog-table td a.iconbutton.preview {
+	width: 16px;
+	margin-right: 2px;
+	background-image: url(images/calendars.png);
+	background-position: -1px -147px;
+}
+
+.changelog-table td a.iconbutton.restore {
+	background-image: url(images/calendars.png);
+	background-position: -1px -167px;
+}
+
+.changelog-table tr.first td a.iconbutton {
+	opacity: 0.3;
+	cursor: default;
+}
+
+#event-partstat .changersvp {
+	cursor: pointer;
+	color: #333;
+	text-decoration: none;
+}
+
+#event-partstat .iconbutton {
+	visibility: hidden;
+}
+
+#event-partstat .changersvp:focus .iconbutton,
+#event-partstat:hover .iconbutton {
+	visibility: visible;
+}
+
+#eventedit {
+	position: relative;
+	top: -1.5em;
+	padding: 0.5em 0.1em;
+	margin: 0 -0.2em;
+}
+
+#eventedit input.text,
+#eventedit textarea {
+	width: 97%;
+}
+
+#eventtabs {
+	position: relative;
+	padding: 0;
+	border: 0;
+	border-radius: 0;
+}
+
+div.form-section,
+.calendarmain .eventdialog div.event-section,
+#eventtabs div.event-section {
+	margin-top: 0.2em;
+	margin-bottom: 0.6em;
+}
+
+#eventtabs .border-after {
+	padding-bottom: 0.8em;
+	margin-bottom: 0.8em;
+	border-bottom: 2px solid #fafafa;
+}
+
+.calendarmain .eventdialog label,
+#eventedit label,
+.form-section label {
+	display: inline-block;
+	min-width: 7em;
+	padding-right: 0.5em;
+}
+
+#event-links label,
+#edit-event-links label {
+	float: left;
+	margin-top: 0.3em;
+	padding-right: 0.75em;
+}
+
+#edit-event-links .event-text {
+	margin-left: 8em;
+	min-height: 22px;
+}
+
+#edit-event-links .attachmentslist li.message a.messagelink,
+#event-links .attachmentslist li.message a.messagelink {
+	padding: 0 0 0 24px;
+}
+
+#edit-event-links .attachmentslist li a.delete {
+	top: 0;
+	background-position: -6px -378px;
+}
+
+#edit-event-links .attachmentslist li.deleted a.messagelink,
+#edit-event-links .attachmentslist li.deleted a.messagelink:hover {
+	text-decoration: line-through;
+}
+
+#eventedit .formtable td.label {
+	min-width: 6em;
+}
+
+td.topalign {
+	vertical-align: top;
+}
+
+#eventedit label.weekday,
+#eventedit label.monthday {
+	min-width: 3em;
+}
+
+#eventedit label.month {
+	min-width: 5em;
+}
+
+#eventedit .formtable td {
+	padding: 0.2em 0;
+}
+
+.ui-dialog .event-update-confirm {
+	padding: 0 0.5em 0.5em 0.5em;
+}
+
+.event-dialog-message,
+.event-update-confirm .message {
+	margin-top: 0.5em;
+	padding: 0.8em;
+	border: 1px solid #ffdf0e;
+	background-color: #fef893;
+}
+
+.event-dialog-message .message,
+.event-update-confirm .message {
+	margin-bottom: 0.5em;
+}
+
+.edit-recurring-warning .savemode {
+	padding-left: 20px;
+}
+
+.event-update-confirm .savemode {
+	padding-left: 30px;
+}
+
+.event-dialog-message span.ui-icon,
+.event-update-confirm span.ui-icon {
+	float: left;
+	margin: 0 7px 20px 0;
+}
+
+.event-dialog-message label,
+.event-update-confirm label {
+	min-width: 3em;
+	padding-right: 1em;
+}
+
+.event-update-confirm a.button {
+	margin: 0 0.5em 0 0.2em;
+	min-width: 5em;
+	text-align: center;
+}
+
+.libcal-rsvp-replymode li a {
+	cursor: default;
+}
+
+#event-rsvp,
+#edit-attendees-notify {
+	margin: 0.6em 0 0.3em 0;
+	padding: 0.5em;
+}
+
+#event-rsvp .itip-reply-controls {
+	margin-top: 0.5em;
+}
+
+#event-rsvp .itip-reply-controls label {
+	color: #333;
+}
+
+#event-rsvp .itip-reply-controls textarea {
+	width: 95%;
+}
+
+#eventedit .edit-attendees-table {
+	width: 100%;
+	margin-top: 0.5em;
+}
+
+#eventedit .edit-attendees-table th.role,
+#eventedit .edit-attendees-table td.role {
+	width: 9em;
+}
+
+#eventedit .edit-attendees-table th.availability,
+#eventedit .edit-attendees-table td.availability,
+#eventedit .edit-attendees-table th.confirmstate,
+#eventedit .edit-attendees-table td.confirmstate {
+	width: 4em;
+}
+
+#eventedit .edit-attendees-table th.options,
+#eventedit .edit-attendees-table td.options {
+	width: 16px;
+	padding: 2px 4px;
+}
+
+#eventedit .edit-attendees-table th.invite,
+#eventedit .edit-attendees-table td.invite {
+	width: 50px;
+	padding: 2px;
+}
+
+#eventedit .edit-attendees-table th.invite label {
+	display: inline-block;
+	position: relative;
+	top: 4px;
+	width: 24px;
+	height: 18px;
+	min-width: 24px;
+	padding: 0;
+	overflow: hidden;
+	text-indent: -5000px;
+	white-space: nowrap;
+	background: url(images/sendinvitation.png) 1px 0 no-repeat;
+}
+
+#eventedit .edit-attendees-table tbody tr:last-child td {
+	border-bottom: 0;
+}
+
+#eventedit .edit-attendees-table th.name,
+#eventedit .edit-attendees-table td.name {
+	width: auto;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+#eventedit .edit-attendees-table td.name select {
+	width: 100%;
+}
+
+#eventedit .edit-attendees-table td.name .attendee-name {
+	display: block;
+	position: relative;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	padding: 5px 7px 6px;
+	margin: -5px -7px -6px;
+}
+
+#eventedit .edit-attendees-table a.deletelink {
+	display: inline-block;
+	width: 17px;
+	height: 17px;
+	padding: 0;
+	overflow: hidden;
+	text-indent: 1000px;
+}
+
+#eventedit .edit-attendees-table a.expandlink {
+	position: absolute;
+	top: 4px;
+	right: 6px;
+	width: 16px;
+	height: 16px;
+}
+
+#edit-attendees-form,
+#edit-resources-form {
+	position: relative;
+	margin-top: 15px;
+}
+
+#edit-attendees-form .attendees-invitebox {
+	text-align: right;
+	margin: 0;
+}
+
+#edit-attendees-form .attendees-invitebox label {
+	padding-right: 3px;
+}
+
+#edit-resources-form #edit-resource-find {
+	position: absolute;
+	top: 0;
+	right: 0;
+}
+
+#edit-attendees-form #edit-attendee-schedule {
+	position: absolute;
+	right: 0;
+	top: 0;
+}
+
+.edit-attendees-table select.edit-attendee-role {
+	border: 0;
+	padding: 2px;
+	background: white;
+	width: 100%;
+}
+
+.availability img.availabilityicon {
+	margin: 1px;
+	width: 14px;
+	height: 14px;
+	border-radius: 4px;
+	-moz-border-radius: 4px;
+	vertical-align: middle;
+}
+
+.availability img.availabilityicon.loading {
+	background: url(images/loading_blue.gif) center no-repeat;
+}
+
+#schedule-freebusy-times td div.unknown,
+.availability img.availabilityicon.unknown {
+	background: #ddd;
+}
+
+#schedule-freebusy-times td div.free,
+.availability img.availabilityicon.free {
+	background: #abd640;
+}
+
+#schedule-freebusy-times td div.busy,
+.availability img.availabilityicon.busy {
+	background: #e26569;
+}
+
+#schedule-freebusy-times td div.tentative,
+.availability img.availabilityicon.tentative {
+	background: #8383fc;
+}
+
+#schedule-freebusy-times td div.out-of-office,
+.availability img.availabilityicon.out-of-office {
+	background: #fbaa68;
+}
+
+#schedule-freebusy-times td div.all-busy,
+#schedule-freebusy-times td div.all-tentative,
+#schedule-freebusy-times td div.all-out-of-office {
+	background-image: url(images/freebusy-colors.png);
+	background-position: top right;
+	background-repeat: no-repeat;
+}
+
+#schedule-freebusy-times td div.all-tentative {
+	background-position: right -40px;
+}
+
+#schedule-freebusy-times td div.all-out-of-office {
+	background-position: right -80px;
+}
+
+#edit-attendees-legend {
+	margin-top: 3em;
+	margin-bottom: 0.5em;
+}
+
+#edit-attendees-legend .legend {
+	margin-right: 2em;
+	white-space: nowrap;
+}
+
+.edit-attendees-table tbody td.confirmstate {
+	overflow: hidden;
+	white-space: nowrap;
+	text-indent: -2000%;
+}
+
+.edit-attendees-table td.confirmstate span {
+	display: block;
+	width: 20px;
+	background: url(images/attendee-status.png) 5px 0 no-repeat;
+}
+
+.edit-attendees-table td.confirmstate span.needs-action {
+	height: 14px;
+}
+
+.edit-attendees-table td.confirmstate span.accepted {
+	background-position: 5px -20px;
+	height: 14px;
+}
+
+.edit-attendees-table td.confirmstate span.declined {
+	background-position: 5px -40px;
+	height: 14px;
+}
+
+.edit-attendees-table td.confirmstate span.tentative {
+	background-position: 5px -60px;
+	height: 14px;
+}
+
+.edit-attendees-table td.confirmstate span.delegated {
+	background-position: 5px -180px;
+	height: 14px;
+}
+
+#attendees-freebusy-table {
+	width: 100%;
+	table-layout: fixed;
+	border: 1px solid #bbd3da;
+}
+
+#attendees-freebusy-table td.attendees {
+	width: 18em;
+	vertical-align: top;
+	overflow: hidden;
+}
+
+#attendees-freebusy-table td.times {
+	width: auto;
+	vertical-align: top;
+}
+
+#attendees-freebusy-table div.scroll {
+	position: relative;
+	overflow: auto;
+}
+
+#attendees-freebusy-table h3.boxtitle {
+	margin: 0;
+	border-color: #ccc;
+}
+
+.attendees-list .attendee {
+	padding: 4px 4px 4px 1px;
+	background: url(images/attendee-status.png) 2px -97px no-repeat;
+	white-space: nowrap;
+}
+
+.attendees-list a.attendee-role-toggle {
+	display: inline-block;
+	width: 16px;
+	margin-right: 3px;
+	cursor: pointer;
+}
+
+.attendees-list div.attendee {
+	border-top: 1px solid #ccc;
+}
+
+.attendees-list span.attendee {
+	padding-left: 20px;
+	margin-right: 2em;
+}
+
+.attendees-list .organizer {
+	background-position: 3px -77px;
+}
+
+.attendees-list .opt-participant {
+	background-position: 2px -117px;
+}
+
+.attendees-list .non-participant {
+	background-position: 2px -137px;
+}
+
+.attendees-list .chair {
+	background-position: 2px -157px;
+}
+
+.attendees-list .loading {
+	background: url(images/loading_blue.gif) 1px 50% no-repeat;
+}
+
+.attendees-list .total {
+	background: none;
+	padding-left: 4px;
+	font-weight: bold;
+}
+
+.attendees-list .spacer,
+#schedule-freebusy-times tr.spacer td {
+	background: 0;
+	padding: 0;
+	height: 10px;
+}
+
+#schedule-freebusy-times {
+	border-collapse: collapse;
+	width: 100%;
+}
+
+#schedule-freebusy-times td {
+	padding: 4px;
+	border: 1px solid #ccc;
+}
+
+#schedule-freebusy-times tbody td {
+	padding: 0;
+	height: 20px;
+}
+
+#schedule-freebusy-times tbody td div {
+	height: 100%;
+}
+
+#attendees-freebusy-table div.timesheader,
+#schedule-freebusy-times tr.times td {
+	min-width: 30px;
+	font-size: 9px;
+	padding: 5px 2px 6px 2px;
+	text-align: center;
+	color: #004658;
+}
+
+#schedule-freebusy-times tr.times td.allday {
+	min-width: 60px;
+}
+
+#schedule-freebusy-times tr.times td {
+	cursor: pointer;
+}
+
+#schedule-freebusy-times #fbrowall td {
+	border-bottom: none;
+}
+
+#schedule-event-time {
+	position: absolute;
+	border: 2px solid #333;
+	background: #777;
+	background: rgba(60, 60, 60, 0.6);
+	opacity: 0.5;
+	border-radius: 4px;
+	cursor: move;
+	filter: alpha(opacity=40); /* IE8 */
+}
+
+#eventfreebusy .schedule-options {
+	position: relative;
+	margin-bottom: 1.5em;
+}
+
+#eventfreebusy .schedule-buttons {
+	position: absolute;
+	top: 0.5em;
+	right: 0;
+	margin-right: 0;
+}
+
+#eventfreebusy .schedule-find-buttons {
+	padding-bottom:0.5em;
+}
+
+#eventfreebusy .schedule-find-buttons button {
+	min-width: 9em;
+	text-align: center;
+}
+
+#eventedit .attendees-commentbox label {
+	display: block;
+}
+
+#eventedit .ui-tabs-panel {
+	min-height: 24em;
+}
+
+#rcmKSearchpane ul li.resource i.icon,
+#rcmKSearchpane ul li.collection i.icon {
+	background-image: url(images/autocomplete.png);
+	background-position: -1px -2px;
+}
+
+#rcmKSearchpane ul li.collection i.icon {
+	background-position: -1px -26px;
+}
+
+a.dropdown-link {
+	font-size: 11px;
+	text-decoration: none;
+}
+
+a.dropdown-link:after {
+	content: ' â–¼';
+	font-size: 10px;
+	color: #666;
+}
+
+.ui-dialog-buttonset a.dropdown-link {
+	position: relative;
+	top: 2px;
+	margin: 0 1em;
+	color: #333;
+}
+
+#calendarsidebar .ui-datepicker-calendar {
+	table-layout: fixed;
+}
+
+.ui-datepicker-calendar .ui-datepicker-week-col {
+	border: 0;
+	color: #999;
+	font-size: 90%;
+	text-align: right;
+	padding-right: 6px;
+	width: 20px;
+	overflow: hidden;
+}
+
+.ui-autocomplete {
+	max-height: 160px;
+	overflow-y: auto;
+	overflow-x: hidden;
+}
+
+.ui-autocomplete .ui-menu-item {
+	white-space: nowrap;
+}
+
+* html .ui-autocomplete {
+	height: 160px;
+}
+
+.calendarmain span.spacer {
+	padding-left: 3em;
+}
+
+#agendaoptions {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	height: auto;
+	z-index: 10;
+	padding: 4px 5px;
+	border: 1px solid #c3c3c3;
+	border-top-color: #ddd;
+	border-bottom-color: #bbb;
+	border-radius: 0 0 4px 4px;
+	background: #eaeaea;
+}
+
+#agendaoptions label {
+	text-shadow: 1px 1px #fff;
+	padding-right: 0.5em;
+}
+
+#calendar-kolabform {
+	position: relative;
+	margin: 0 -8px;
+	min-width: 660px;
+	min-height: 400px;
+}
+
+#calendar-kolabform table td.title {
+	font-weight: bold;
+	white-space: nowrap;
+	color: #666;
+	padding-right: 10px;
+}
+
+#resource-selection {
+	position: absolute;
+	top: 0;
+	left: 8px;
+	right: 0;
+	bottom: 0;
+}
+
+#resource-selection .scroller {
+	top: 34px;
+}
+
+#resource-dialog-left {
+	position: absolute;
+	top: 10px;
+	left: 0;
+	width: 380px;
+	bottom: 10px;
+}
+
+#resource-dialog-right {
+	position: absolute;
+	top: 10px;
+	left: 392px;
+	right: 8px;
+	bottom: 10px;
+}
+
+#resource-info {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	height: 48%;
+}
+
+#resource-info table {
+	margin: 8px;
+	width: 97%;
+}
+
+#resource-info thead td {
+	background: none;
+	font-weight: bold;
+	font-size: 14px;
+}
+
+#resource-availability {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	height: 49%;
+}
+
+#resource-freebusy-calendar {
+	position: absolute;
+	top: 33px;
+	left: -1px;
+	right: -1px;
+	bottom: -1px;
+}
+
+#resource-freebusy-calendar .fc-content {
+	top: 0;
+}
+
+#resource-freebusy-calendar .fc-content .fc-event-bg {
+	background: 0;
+}
+
+#resource-freebusy-calendar .fc-event.status-busy,
+#resource-freebusy-calendar .status-busy .fc-event-skin {
+	border-color: #e26569;
+	background-color: #e26569;
+}
+
+#resource-freebusy-calendar .fc-event.status-tentative,
+#resource-freebusy-calendar .status-tentative .fc-event-skin {
+	border-color: #8383fc;
+	background: #8383fc;
+}
+
+#resource-freebusy-calendar .fc-event.status-outofoffice,
+#resource-freebusy-calendar .status-outofoffice .fc-event-skin {
+	border-color: #fbaa68;
+	background: #fbaa68;
+}
+
+#resourcequicksearch {
+	padding: 4px;
+	background: #c7e3ef;
+}
+
+#resourcesearchbox {
+	width: 100%;
+	height: 26px;
+	-moz-box-sizing: border-box;
+	     box-sizing: border-box;
+}
+
+#resourcequicksearch .iconbutton.searchoptions {
+	position: absolute;
+	top: 5px;
+	left: 6px;
+	width: 16px;
+}
+
+.searchbox .iconbutton.reset {
+	position: absolute;
+	top: 4px;
+	right: 1px;
+}
+
+
+
+/* fullcalendar style overrides */
+
+.rcube-fc-content {
+	overflow: hidden;
+	border: 0;
+	border-radius: 4px;
+}
+
+.calendarmain .fc-content {
+	position: absolute !important;
+	top: 40px;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background: #fff;
+}
+
+.calendarmain.quickview-active .fc-content {
+	background-image: url('images/focusview.png');
+	background-position: center;
+	background-repeat: no-repeat;
+}
+
+#fish-eye-view .fc-content {
+	top: 2px;
+	bottom: 2px;
+}
+
+#quickview-calendar {
+	padding: 8px;
+	overflow: hidden;
+}
+
+.calendarmain .fc-button,
+.calendarmain .fc-button.fc-state-default,
+.calendarmain .fc-button.fc-state-hover {
+	background-color: #f5f5f5;
+}
+
+.calendarmain #calendar .fc-button,
+.calendarmain #calendar .fc-button.fc-state-default,
+.calendarmain #calendar .fc-button.fc-state-hover {
+	margin: -2px 0 0 0;
+	height: 24px;
+	line-height: 24px;
+	color: #333;
+	border: 1px solid #ababab;
+	background: #f1f1f1;
+	text-decoration: none;
+	text-shadow: none;
+}
+
+.calendarmain #calendar .fc-button.fc-state-disabled {
+	color: #666;
+}
+
+.calendarmain .fc-button.fc-state-active,
+.calendarmain .fc-button.fc-state-down,
+.calendarmain #calendar .fc-button.fc-state-active,
+.calendarmain #calendar .fc-button.fc-state-down {
+	color: #333;
+	background: #f1f1f1;
+	box-shadow: none;
+}
+
+.calendarmain #calendar .fc-header .fc-button {
+	margin-left: -1px;
+	margin-right: 0;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button {
+	display: inline-block;
+	margin: 0;
+	text-align: center;
+	font-size: 10px;
+	color: #555;
+	min-width: 50px;
+	max-width: 75px;
+	height: 13px;
+	line-height: 1em;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	margin: -7px 0 0 0;
+	padding: 28px 2px 0 2px;
+	text-shadow: 0px 1px 1px #EEE;
+	border: 0;
+	background: url(images/toolbar.png) center 100px no-repeat;
+	box-shadow: none;
+	-o-box-shadow: none;
+	-webkit-box-shadow: none;
+	-moz-box-shadow: none;
+	outline: none;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button:focus {
+	color: #fff;
+	text-shadow: 0px 1px 1px #666;
+	background-color: rgba(30,150,192, 0.5);
+	border-radius: 3px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button.fc-state-active {
+	font-weight: bold;
+	color: #222;
+	text-shadow: none;
+	background-color: transparent;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-agendaDay {
+	background-position: center -120px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-agendaDay.fc-state-active {
+	background-position: center -160px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-agendaWeek {
+	background-position: center -200px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-agendaWeek.fc-state-active {
+	background-position: center -240px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-month {
+	background-position: center -280px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-month.fc-state-active {
+	background-position: center -320px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-table {
+	background-position: center -360px;
+}
+
+.calendarmain #calendar .fc-header-left .fc-button-table.fc-state-active {
+	background-position: center -400px;
+}
+
+.calendarmain #calendar .fc-header-right {
+	padding-right: 252px;
+	padding-top: 4px;
+}
+
+.calendarmain #calendar .fc-header-title {
+	padding-top: 5px;
+}
+
+.fc-event {
+	font-size: 1em !important;
+}
+
+.fc-event-hori.fc-type-freebusy,
+.fc-event-vert.fc-type-freebusy {
+	opacity: 0.60;
+/*
+	color: #fff !important;
+	background: rgba(80,80,80,0.85) !important;
+	background: -moz-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.9) 100%) !important;
+	background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(80,80,80,0.85)), color-stop(100%,rgba(48,48,48,0.9))) !important;
+	background: -webkit-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important;
+	background: -o-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important;
+	background: -ms-linear-gradient(top, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important;
+	background: linear-gradient(to bottom, rgba(80,80,80,0.85) 0%, rgba(48,48,48,0.85) 100%) !important;
+	border-color: #444 !important;
+	cursor: default !important;
+*/
+	-moz-box-shadow: inset 0px 1px 0 0px #888;
+	-webkit-box-shadow: inset 0px 1px 0 0px #888;
+	-o-box-shadow: inset 0px 1px 0 0px #888;
+	box-shadow: inset 0px 1px 0 0px #888;
+}
+
+.fc-event-row.fc-type-freebusy td {
+	color: #999;
+}
+
+.fc-event-hori.fc-type-freebusy .fc-event-skin,
+.fc-event-hori.fc-type-freebusy .fc-event-inner,
+.fc-event-vert.fc-type-freebusy .fc-event-skin,
+.fc-event-vert.fc-type-freebusy .fc-event-inner {
+/*
+	background-color: transparent !important;
+	border-color: #444 !important;
+	color: #fff !important;
+	text-shadow: 0 1px 1px #000;
+*/
+}
+
+.fc-event-hori.fc-type-freebusy .fc-event-title,
+.fc-event-vert.fc-type-freebusy .fc-event-title {
+	position: absolute;
+	top: -5000px;
+}
+
+.fc-event-vert.fc-invitation-needs-action,
+.fc-event-hori.fc-invitation-needs-action {
+	border: 1px dashed #5757c7 !important;
+}
+
+.fc-event-vert.fc-invitation-tentative,
+.fc-event-hori.fc-invitation-tentative {
+	border: 1px dashed #eb8900 !important;
+}
+
+.fc-event-vert.fc-invitation-declined,
+.fc-event-hori.fc-invitation-declined {
+	border: 1px dashed #c00 !important;
+}
+
+.fc-event-vert.fc-event-ns-other.fc-invitation-declined,
+.fc-event-hori.fc-event-ns-other.fc-invitation-declined {
+	opacity: 0.7;
+}
+
+.fc-event-ns-other.fc-invitation-declined .fc-event-title {
+	text-decoration: line-through;
+}
+
+.fc-event-vert.fc-invitation-tentative .fc-event-head,
+.fc-event-vert.fc-invitation-declined .fc-event-head,
+.fc-event-vert.fc-invitation-needs-action .fc-event-head {
+/*	background-color: transparent !important; */
+}
+
+.fc-event-vert.fc-invitation-tentative .fc-event-bg {
+	background: url(data:image/gif;base64,R0lGODlhCAAIAPABAOuJAP///yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
+}
+
+.fc-event-vert.fc-invitation-needs-action .fc-event-bg {
+	background: url(data:image/gif;base64,R0lGODlhCAAIAPABAFdXx////yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
+}
+
+.fc-event-vert.fc-invitation-declined .fc-event-bg {
+	background: url(data:image/gif;base64,R0lGODlhCAAIAPABAMwAAP///yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
+}
+
+.fc-view-table tr.fc-invitation-tentative td,
+.fc-view-table tr.fc-invitation-declined td,
+.fc-view-table tr.fc-invitation-needs-action td {
+	color: #888;
+}
+
+.fc-view-table tr.fc-invitation-tentative td.fc-event-title,
+.fc-view-table tr.fc-invitation-declined td.fc-event-title,
+.fc-view-table tr.fc-invitation-needs-action td.fc-event-title {
+	font-weight: normal;
+}
+
+#quickview-calendar .fc-view-table tr.fc-invitation-tentative td,
+#quickview-calendar .fc-view-table tr.fc-invitation-declined td,
+#quickview-calendar .fc-view-table tr.fc-invitation-needs-action td {
+	color: #333;
+}
+
+.calendarmain .fc-event:focus {
+	outline: 1px solid rgba(71,135,177, 0.4);
+	-webkit-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
+	   -moz-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
+	     -o-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
+	        box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
+}
+.fc-event-title {
+	font-weight: bold;
+}
+
+.cal-event-status-cancelled .fc-event-title {
+	text-decoration: line-through;
+}
+
+.fc-event-hori .fc-event-title {
+	font-weight: normal;
+	white-space: nowrap;
+}
+
+.fc-event-hori .fc-event-time {
+	white-space: nowrap;
+	font-weight: normal !important;
+	font-size: 10px;
+	padding-right: 0.6em;
+}
+
+.fc-grid .fc-event-time {
+	font-weight: normal !important;
+	padding-right: 0.3em;
+}
+
+.calendarmain .fc-event-vert .fc-event-inner {
+	z-index: 0;
+}
+
+.fc-event-cateories {
+	font-style:italic; 
+}
+
+div.fc-event-location {
+	font-size: 90%;
+}
+
+.fc-more-link {
+	color: #999;
+	padding-top: 1px;
+	cursor: pointer;
+}
+
+.fc-agenda-slots td div {
+	height: 22px;
+}
+
+.fc-sat, .fc-sun {
+	background-color: rgba(198,198,198, 0.08);
+}
+
+.calendarmain .fc-state-highlight {
+	background-color: rgba(233,198,14, 0.12);
+}
+
+.fc-widget-header,
+.fc-widget-content {
+	border-color: #bbd3da !important;
+}
+
+.fc-widget-header .fc-agenda-divider-inner {
+	background: #cad2d9 !important;
+}
+
+.fc-widget-header {
+	background-color: #d6eaf3;
+	color: #004458;
+	text-shadow: 0px 1px 1px #fff;
+}
+
+.fc-view thead th.fc-widget-header {
+	padding: 8px 0;
+	color: #69939e;
+}
+
+.fc-day-number {
+	color: #578da5;
+}
+
+.fc-icon-alarms,
+.fc-icon-sensitive,
+.fc-icon-recurring {
+	display: inline-block;
+	width: 11px;
+	height: 11px;
+	background: url(images/eventicons.png) 0 0 no-repeat;
+	margin-left: 3px;
+	line-height: 10px;
+}
+
+.fc-icon-alarms {
+	background-position: 0 -13px;
+}
+
+.fc-icon-sensitive {
+	background-position: 0 -25px;
+}
+
+.fc-list-section .fc-event {
+	cursor: pointer;
+}
+
+.calendarmain .fc-view-table td.fc-list-header {
+	color: #004458;
+	font-size: 12px;
+}
+
+.calendarmain .fc-view-table tr.fc-event td {
+	border-color: #bbd3da;
+	padding: 6px 8px;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.calendarmain .fc-view-table tr.fc-event td.fc-event-handle {
+	padding: 6px 0 2px 7px;
+	width: 12px;
+}
+
+.calendarmain .fc-view-table .fc-event-handle .fc-event-skin {
+	margin: 0;
+	padding: 0;
+	display: inline-block;
+	width: 10px;
+	height: 10px;
+	font-size: 6px;
+	border-radius: 8px;
+}
+
+.calendarmain .fc-view-table .fc-event-handle .fc-event-inner {
+	display: inline-block;
+	width: 10px;
+	height: 10px;
+	padding: 0;
+	margin: -1px;
+	font-size: 10px;
+	border-radius: 8px;
+	border: 1px solid rgba(0, 0, 0, 0.4);
+}
+
+.calendarmain .fc-view-table col.fc-event-location {
+	width: 25%;
+}
+
+.fc-view-table table.fc-list-smart {
+/*	table-layout: auto; */
+}
+
+.fc-listappend {
+	text-align: center;
+	margin: 1em 0;
+}
+
+.fc-listappend .message {
+	padding: 0.5em;
+	margin-bottom: 0.5em;
+	font-size: 150%;
+	color: #999;
+}
+
+.fc-listappend .formlinks a {
+	font-size: 12px;
+	padding: 0 0.3em;
+}
+
+.fc-event-temp {
+	opacity: 0.4;
+	filter: alpha(opacity=40); /* IE8 */
+}
+
+/* Settings section */
+
+fieldset #calendarcategories div {
+	margin-bottom: 0.3em;
+}
+
+/* Invitation UI in mail */
+
+.messagelist tbody .attachment span.ical {
+	display: inline-block;
+	vertical-align: middle;
+	height: 18px;
+	width: 20px;
+	padding: 0;
+	background: url(images/ical-attachment.png) 2px 1px no-repeat;
+}
+
+ul.toolbarmenu li a.calendarlink span.calendar,
+#attachmentmenu li a.calendarlink span.calendar {
+	background-position: 0px -2197px;
+}
+
+div.calendar-invitebox {
+	min-height: 20px;
+	margin: 5px 8px;
+	padding: 3px 6px 6px 34px;
+	border: 1px solid #ffdf0e;
+	background: url(images/calendar.png) 6px 5px no-repeat #fef893;
+}
+
+div.calendar-invitebox td.ititle {
+	font-weight: bold;
+	padding-right: 0.5em;
+}
+
+div.calendar-invitebox td {
+	padding: 2px;
+}
+
+div.calendar-invitebox td.label {
+	color: #666;
+	padding-right: 1em;
+}
+
+div.calendar-invitebox td.sensitivity {
+	color: #d31400;
+	font-weight: bold;
+}
+
+div.calendar-invitebox td.recurrence-id {
+	text-transform: uppercase;
+	font-style: italic;
+}
+
+div.calendar-invitebox td em {
+	font-weight: bold;
+}
+
+div.calendar-invitebox td.date.modified {
+	font-weight: bold;
+	color: red;
+}
+
+#event-rsvp .rsvp-buttons,
+div.calendar-invitebox .itip-buttons div {
+	margin-top: 0.5em;
+}
+
+#event-rsvp input.button,
+div.calendar-invitebox input.button {
+	font-weight: bold;
+	margin-right: 0.5em;
+}
+
+div.calendar-invitebox input.button.preview {
+	margin-left: 1em;
+	margin-right: 0;
+}
+
+div.calendar-invitebox .folder-select {
+	font-weight: 10px;
+	margin-left: 1em;
+	white-space: nowrap;
+}
+
+div.calendar-invitebox .rsvp-status {
+	padding-left: 2px;
+}
+
+div.calendar-invitebox .rsvp-status.loading {
+	color: #666;
+	padding: 1px 0 2px 24px;
+	background: url(images/loading_blue.gif) top left no-repeat;
+}
+
+div.calendar-invitebox .rsvp-status.hint {
+	color: #666;
+	text-shadow: none;
+	font-style: italic;
+}
+
+#event-partstat .changersvp,
+div.calendar-invitebox .rsvp-status.declined,
+div.calendar-invitebox .rsvp-status.tentative,
+div.calendar-invitebox .rsvp-status.accepted,
+div.calendar-invitebox .rsvp-status.delegated,
+div.calendar-invitebox .rsvp-status.needs-action  {
+	padding: 0 0 1px 22px;
+	background: url(images/attendee-status.png) 2px -20px no-repeat;
+}
+
+#event-partstat .changersvp.declined,
+div.calendar-invitebox .rsvp-status.declined {
+	background-position: 2px -40px;
+}
+
+#event-partstat .changersvp.tentative,
+div.calendar-invitebox .rsvp-status.tentative {
+	background-position: 2px -60px;
+}
+
+#event-partstat .changersvp.delegated,
+div.calendar-invitebox .rsvp-status.delegated {
+	background-position: 2px -180px;
+}
+
+#event-partstat .changersvp.needs-action,
+div.calendar-invitebox .rsvp-status.needs-action {
+	background-position: 2px 0;
+}
+
+div.calendar-invitebox .calendar-agenda-preview {
+	display: none;
+	border-top: 1px solid #dfdfdf;
+	margin-top: 1em;
+	padding-top: 0.6em;
+}
+
+div.calendar-invitebox .calendar-agenda-preview h3.preview-title {
+	margin: 0 0 0.5em 0;
+	font-size: 12px;
+	color: #333;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row {
+	color: #777;
+	padding: 2px 0;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row.current {
+	color: #333;
+	font-weight: bold;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-row.no-event {
+	font-style: italic;
+}
+
+div.calendar-invitebox .calendar-agenda-preview .event-date {
+	display: inline-block;
+	min-width: 8em;
+	margin-right: 1em;
+	white-space: nowrap;
+}
+
+
+/* iTIP attend reply page */
+
+.calendaritipattend .centerbox {
+	width: 40em;
+	min-height: 7em;
+	margin: 80px auto 0 auto;
+	padding: 10px 10px 10px 90px;
+	background: url(images/invitation.png) 10px 10px no-repeat #fff;
+}
+
+.calendaritipattend #message {
+	width: 46em;
+	margin: 0 auto;
+	padding: 10px;
+}
+
+.calendaritipattend .calendar-invitebox {
+	background: none;
+	padding-left: 0;
+	border: 0;
+	margin: 0 0 2em 0;
+}
+
+.calendaritipattend .calendar-invitebox .rsvp-status {
+	margin-top: 2.5em;
+	font-size: 110%;
+	font-weight: bold;
+}
+
+.calendaritipattend .calendar-invitebox td.title,
+.calendaritipattend .calendar-invitebox td.ititle {
+	font-size: 120%;
+}
+
+.calendaritipattend .itip-reply-controls .noreply-toggle,
+.calendaritipattend .itip-reply-controls #noreply-event-rsvp {
+	display: none;
+}
+
+.calendaritipattend .itip-reply-controls a.reply-comment-toggle {
+	margin-left: 2px;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/fullcalendar.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,711 @@
+/*!
+ * FullCalendar v1.6.4-rcube-1.0 Stylesheet
+ * Docs & License: http://arshaw.com/fullcalendar/
+ * (c) 2013 Adam Shaw, 2014 Kolab Systems AG
+ */
+
+
+.fc {
+	direction: ltr;
+	text-align: left;
+	}
+	
+.fc table {
+	border-collapse: collapse;
+	border-spacing: 0;
+	}
+	
+html .fc,
+.fc table {
+	font-size: 1em;
+	}
+	
+.fc td,
+.fc th {
+	padding: 0;
+	vertical-align: top;
+	}
+
+
+
+/* Header
+------------------------------------------------------------------------*/
+
+.fc-header td {
+	white-space: nowrap;
+	}
+
+.fc-header-left {
+	width: 25%;
+	text-align: left;
+	}
+	
+.fc-header-center {
+	text-align: center;
+	}
+	
+.fc-header-right {
+	width: 25%;
+	text-align: right;
+	}
+	
+.fc-header-title {
+	display: inline-block;
+	vertical-align: top;
+	}
+	
+.fc-header-title h2 {
+	margin-top: 0;
+	white-space: nowrap;
+	}
+	
+.fc .fc-header-space {
+	padding-left: 10px;
+	}
+	
+.fc-header .fc-button {
+	margin-bottom: 1em;
+	vertical-align: top;
+	}
+	
+/* buttons edges butting together */
+
+.fc-header .fc-button {
+	margin-right: -1px;
+	}
+	
+.fc-header .fc-corner-right,  /* non-theme */
+.fc-header .ui-corner-right { /* theme */
+	margin-right: 0; /* back to normal */
+	}
+	
+/* button layering (for border precedence) */
+	
+.fc-header .fc-state-hover,
+.fc-header .ui-state-hover {
+	z-index: 2;
+	}
+	
+.fc-header .fc-state-down {
+	z-index: 3;
+	}
+
+.fc-header .fc-state-active,
+.fc-header .ui-state-active {
+	z-index: 4;
+	}
+	
+	
+	
+/* Content
+------------------------------------------------------------------------*/
+	
+.fc-content {
+	clear: both;
+	zoom: 1; /* for IE7, gives accurate coordinates for [un]freezeContentHeight */
+	}
+	
+.fc-view {
+	width: 100%;
+	overflow: hidden;
+	}
+	
+	
+
+/* Cell Styles
+------------------------------------------------------------------------*/
+
+.fc-widget-header,    /* <th>, usually */
+.fc-widget-content {  /* <td>, usually */
+	border: 1px solid #ddd;
+	}
+	
+.fc-state-highlight { /* <td> today cell */ /* TODO: add .fc-today to <th> */
+	background: #fcf8e3;
+	}
+	
+.fc-cell-overlay { /* semi-transparent rectangle while dragging */
+	background: #bce8f1;
+	opacity: .3;
+	filter: alpha(opacity=30); /* for IE */
+	}
+	
+
+
+/* Buttons
+------------------------------------------------------------------------*/
+
+.fc-button {
+	position: relative;
+	display: inline-block;
+	padding: 0 .6em;
+	overflow: hidden;
+	height: 1.9em;
+	line-height: 1.9em;
+	white-space: nowrap;
+	cursor: pointer;
+	}
+	
+.fc-state-default { /* non-theme */
+	border: 1px solid;
+	}
+
+.fc-state-default.fc-corner-left { /* non-theme */
+	border-top-left-radius: 4px;
+	border-bottom-left-radius: 4px;
+	}
+
+.fc-state-default.fc-corner-right { /* non-theme */
+	border-top-right-radius: 4px;
+	border-bottom-right-radius: 4px;
+	}
+
+/*
+	Our default prev/next buttons use HTML entities like &lsaquo; &rsaquo; &laquo; &raquo;
+	and we'll try to make them look good cross-browser.
+*/
+
+.fc-text-arrow {
+	margin: 0 .1em;
+	font-size: 2em;
+	font-family: "Courier New", Courier, monospace;
+	vertical-align: baseline; /* for IE7 */
+	}
+
+.fc-button-prev .fc-text-arrow,
+.fc-button-next .fc-text-arrow { /* for &lsaquo; &rsaquo; */
+	font-weight: bold;
+	}
+	
+/* icon (for jquery ui) */
+	
+.fc-button .fc-icon-wrap {
+	position: relative;
+	float: left;
+	top: 50%;
+	}
+	
+.fc-button .ui-icon {
+	position: relative;
+	float: left;
+	margin-top: -50%;
+	*margin-top: 0;
+	*top: -50%;
+	}
+	
+/*
+  button states
+  borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/)
+*/
+
+.fc-state-default {
+	background-color: #f5f5f5;
+	background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
+	background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
+	background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
+	background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
+	background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
+	background-repeat: repeat-x;
+	border-color: #e6e6e6 #e6e6e6 #bfbfbf;
+	border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+	color: #333;
+	text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+	}
+
+.fc-state-hover,
+.fc-state-down,
+.fc-state-active,
+.fc-state-disabled {
+	color: #333333;
+	background-color: #e6e6e6;
+	}
+
+.fc-state-hover {
+	color: #333333;
+	text-decoration: none;
+	background-position: 0 -15px;
+	-webkit-transition: background-position 0.1s linear;
+	   -moz-transition: background-position 0.1s linear;
+	     -o-transition: background-position 0.1s linear;
+	        transition: background-position 0.1s linear;
+	}
+
+.fc-state-down,
+.fc-state-active {
+	background-color: #cccccc;
+	background-image: none;
+	outline: 0;
+	box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
+	}
+
+.fc-state-disabled {
+	cursor: default;
+	background-image: none;
+	opacity: 0.65;
+	filter: alpha(opacity=65);
+	box-shadow: none;
+	}
+
+	
+
+/* Global Event Styles
+------------------------------------------------------------------------*/
+
+.fc-event-container > * {
+	z-index: 8;
+	}
+
+.fc-event-container > .ui-draggable-dragging,
+.fc-event-container > .ui-resizable-resizing {
+	z-index: 9;
+	}
+	 
+.fc-event {
+	border: 1px solid #3a87ad; /* default BORDER color */
+	background-color: #3a87ad; /* default BACKGROUND color */
+	color: #fff;               /* default TEXT color */
+	font-size: .85em;
+	cursor: default;
+	}
+
+.fc-event:focus {
+	outline: 2px solid ActiveBorder;
+	}
+
+a.fc-event {
+	text-decoration: none;
+	}
+	
+a.fc-event,
+.fc-event-draggable {
+	cursor: pointer;
+	}
+	
+.fc-rtl .fc-event {
+	text-align: right;
+	}
+
+.fc-event-inner {
+	width: 100%;
+	height: 100%;
+	overflow: hidden;
+	}
+	
+.fc-event-time,
+.fc-event-title {
+	padding: 0 1px;
+	}
+	
+.fc .ui-resizable-handle {
+	display: block;
+	position: absolute;
+	z-index: 99999;
+	overflow: hidden; /* hacky spaces (IE6/7) */
+	font-size: 300%;  /* */
+	line-height: 50%; /* */
+	}
+	
+	
+	
+/* Horizontal Events
+------------------------------------------------------------------------*/
+
+.fc-event-hori {
+	border-width: 1px 0;
+	margin-bottom: 1px;
+	}
+
+.fc-ltr .fc-event-hori.fc-event-start,
+.fc-rtl .fc-event-hori.fc-event-end {
+	border-left-width: 1px;
+	border-top-left-radius: 3px;
+	border-bottom-left-radius: 3px;
+	}
+
+.fc-ltr .fc-event-hori.fc-event-end,
+.fc-rtl .fc-event-hori.fc-event-start {
+	border-right-width: 1px;
+	border-top-right-radius: 3px;
+	border-bottom-right-radius: 3px;
+	}
+	
+/* resizable */
+	
+.fc-event-hori .ui-resizable-e {
+	top: 0           !important; /* importants override pre jquery ui 1.7 styles */
+	right: -3px      !important;
+	width: 7px       !important;
+	height: 100%     !important;
+	cursor: e-resize;
+	}
+	
+.fc-event-hori .ui-resizable-w {
+	top: 0           !important;
+	left: -3px       !important;
+	width: 7px       !important;
+	height: 100%     !important;
+	cursor: w-resize;
+	}
+	
+.fc-event-hori .ui-resizable-handle {
+	_padding-bottom: 14px; /* IE6 had 0 height */
+	}
+	
+	
+	
+/* Reusable Separate-border Table
+------------------------------------------------------------*/
+
+table.fc-border-separate {
+	border-collapse: separate;
+	}
+	
+.fc-border-separate th,
+.fc-border-separate td {
+	border-width: 1px 0 0 1px;
+	}
+	
+.fc-border-separate th.fc-last,
+.fc-border-separate td.fc-last {
+	border-right-width: 1px;
+	}
+	
+.fc-border-separate tr.fc-last th,
+.fc-border-separate tr.fc-last td {
+	border-bottom-width: 1px;
+	}
+	
+.fc-border-separate tbody tr.fc-first td,
+.fc-border-separate tbody tr.fc-first th {
+	border-top-width: 0;
+	}
+	
+	
+
+/* Month View, Basic Week View, Basic Day View
+------------------------------------------------------------------------*/
+
+.fc-grid th {
+	text-align: center;
+	}
+
+.fc .fc-week-number {
+	width: 22px;
+	text-align: center;
+	}
+
+.fc .fc-week-number div {
+	padding: 0 2px;
+	}
+	
+.fc-grid .fc-day-number {
+	float: right;
+	padding: 0 2px;
+	}
+	
+.fc-grid .fc-other-month .fc-day-number {
+	opacity: 0.3;
+	filter: alpha(opacity=30); /* for IE */
+	/* opacity with small font can sometimes look too faded
+	   might want to set the 'color' property instead
+	   making day-numbers bold also fixes the problem */
+	}
+	
+.fc-grid .fc-day-content {
+	clear: both;
+	padding: 2px 2px 1px; /* distance between events and day edges */
+	}
+	
+/* event styles */
+	
+.fc-grid .fc-event-time {
+	font-weight: bold;
+	}
+	
+/* right-to-left */
+	
+.fc-rtl .fc-grid .fc-day-number {
+	float: left;
+	}
+	
+.fc-rtl .fc-grid .fc-event-time {
+	float: right;
+	}
+	
+.fc-more-link {
+	font-size: 0.85em;
+	white-space: nowrap;
+	text-decoration: none;
+	cursor: pointer;
+	padding: 1px;
+}
+
+/* Agenda Week View, Agenda Day View
+------------------------------------------------------------------------*/
+
+.fc-agenda table {
+	border-collapse: separate;
+	}
+	
+.fc-agenda-days th {
+	text-align: center;
+	}
+	
+.fc-agenda .fc-agenda-axis {
+	width: 50px;
+	padding: 0 4px;
+	vertical-align: middle;
+	text-align: right;
+	white-space: nowrap;
+	font-weight: normal;
+	}
+
+.fc-agenda .fc-week-number {
+	font-weight: bold;
+	}
+	
+.fc-agenda .fc-day-content {
+	padding: 2px 2px 1px;
+	}
+	
+/* make axis border take precedence */
+	
+.fc-agenda-days .fc-agenda-axis {
+	border-right-width: 1px;
+	}
+	
+.fc-agenda-days .fc-col0 {
+	border-left-width: 0;
+	}
+	
+/* all-day area */
+	
+.fc-agenda-allday th {
+	border-width: 0 1px;
+	}
+	
+.fc-agenda-allday .fc-day-content {
+	min-height: 34px; /* TODO: doesnt work well in quirksmode */
+	_height: 34px;
+	}
+	
+/* divider (between all-day and slots) */
+	
+.fc-agenda-divider-inner {
+	height: 2px;
+	overflow: hidden;
+	}
+	
+.fc-widget-header .fc-agenda-divider-inner {
+	background: #eee;
+	}
+	
+/* slot rows */
+	
+.fc-agenda-slots th {
+	border-width: 1px 1px 0;
+	}
+	
+.fc-agenda-slots td {
+	border-width: 1px 0 0;
+	background: none;
+	}
+	
+.fc-agenda-slots td div {
+	height: 20px;
+	}
+	
+.fc-agenda-slots tr.fc-slot0 th,
+.fc-agenda-slots tr.fc-slot0 td {
+	border-top-width: 0;
+	}
+
+.fc-agenda-slots tr.fc-minor th,
+.fc-agenda-slots tr.fc-minor td {
+	border-top-style: dotted;
+	}
+	
+.fc-agenda-slots tr.fc-minor th.ui-widget-header {
+	*border-top-style: solid; /* doesn't work with background in IE6/7 */
+	}
+	
+
+
+/* Vertical Events
+------------------------------------------------------------------------*/
+
+.fc-event-vert {
+	border-width: 0 1px;
+	}
+
+.fc-event-vert .fc-event-head,
+.fc-event-vert .fc-event-content {
+	position: relative;
+	z-index: 2;
+	width: 100%;
+	overflow: hidden;
+	}
+
+.fc-event-vert.fc-event-start {
+	border-top-width: 1px;
+	border-top-left-radius: 3px;
+	border-top-right-radius: 3px;
+	}
+
+.fc-event-vert.fc-event-end {
+	border-bottom-width: 1px;
+	border-bottom-left-radius: 3px;
+	border-bottom-right-radius: 3px;
+	}
+	
+.fc-event-vert .fc-event-time {
+	white-space: nowrap;
+	font-size: 10px;
+	}
+
+.fc-event-vert .fc-event-inner {
+	position: relative;
+	z-index: 2;
+	}
+	
+.fc-event-vert .fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay  */
+	position: absolute;
+	z-index: 1;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background: #fff;
+	opacity: .25;
+	filter: alpha(opacity=25);
+	}
+	
+.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */
+.fc-select-helper .fc-event-bg {
+	display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work */
+	}
+	
+/* resizable */
+	
+.fc-event-vert .ui-resizable-s {
+	bottom: 0        !important; /* importants override pre jquery ui 1.7 styles */
+	width: 100%      !important;
+	height: 8px      !important;
+	overflow: hidden !important;
+	line-height: 8px !important;
+	font-size: 11px  !important;
+	font-family: monospace;
+	text-align: center;
+	cursor: s-resize;
+	}
+	
+.fc-agenda .ui-resizable-resizing { /* TODO: better selector */
+	_overflow: hidden;
+	}
+	
+.fc-timeline {
+	position: absolute;
+	width: 100%;
+	left: 0;
+	margin: 0;
+	padding: 0;
+	border: none;
+	border-top: 2px solid #3ec400;
+	z-index: 999;
+}
+
+/* List view  (by bruederli@kolabsys.com)
+------------------------------------------------------------------------*/
+
+.fc-view-list,
+.fc-view-table {
+	border: 1px solid #ccc;
+	width: auto;
+}
+
+.fc-view-list .fc-list-header,
+.fc-view-table td.fc-list-header {
+	border-width: 0;
+	border-bottom-width: 1px;
+	padding: 3px 5px;
+}
+
+.fc-view-table .fc-first td.fc-list-header {
+	border-top-width: 0;
+}
+
+.fc-list-section {
+	padding: 4px 2px;
+	border-width: 0;
+	border-bottom-width: 1px;
+}
+
+.fc-view-list .fc-last {
+	border-bottom-width: 0;
+}
+
+.fc-list-section .fc-event {
+	position: relative;
+	margin: 1px 2px 3px 2px;
+}
+
+.fc-view-table tr.fc-event {
+	background: inherit;
+	color: inherit;
+}
+
+.fc-view-table tr.fc-event td {
+	padding: 2px;
+	border-bottom: 1px solid #ccc;
+}
+
+.fc-view-table tr.fc-event td.fc-event-handle {
+	padding: 3px 8px 3px 3px;
+}
+
+.fc-view-table .fc-event-handle .fc-event-skin {
+	border-radius: 2px;
+	-moz-border-radius: 2px;
+}
+
+.fc-view-table .fc-event-handle .fc-event-inner {
+	display: block;
+	width: 8px;
+	height: 10px;
+	border-radius: 2px;
+	-moz-border-radius: 2px;
+}
+
+.fc-view-table table {
+	table-layout: fixed;
+	width: 100%;
+}
+
+.fc-view-table col.fc-event-handle {
+	width: 18px;
+}
+
+.fc-event-handle .fc-event-inner {
+	border-color: inherit;
+	background-color: inherit;
+}
+
+.fc-view-table col.fc-event-date {
+	width: 7em;
+}
+
+.fc-view-table .fc-list-day col.fc-event-date {
+	width: 1px;
+}
+
+.fc-view-table col.fc-event-time {
+	width: 9em;
+}
+
+.fc-view-table td.fc-event-date,
+.fc-view-table td.fc-event-time {
+	white-space: nowrap;
+	padding-right: 1em;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/iehacks.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,77 @@
+/* CSS hacks for IE 7 */
+
+#calendarsidebar,
+#calendarsidebartoggle {
+	height: expression((parseInt(this.parentNode.offsetHeight)-37)+'px');
+}
+
+#calendar {
+	width: expression((parseInt(this.parentNode.offsetWidth)-parseInt(document.getElementById('calendarsidebartoggle').offsetWidth)-parseInt(document.getElementById('calendarsidebartoggle').offsetLeft)-4)+'px');
+	height: expression((parseInt(this.parentNode.offsetHeight)-30)+'px');
+}
+
+#calendars {
+	height: expression((parseInt(this.parentNode.offsetHeight)-240)+'px');
+}
+
+#agendaoptions {
+	width: expression((parseInt(this.parentNode.offsetWidth)-12)+'px');
+}
+
+#calendartoolbar a.buttonPas {
+	filter: alpha(opacity=35);
+}
+
+#datepicker a.ui-priority-secondary {
+	filter: alpha(opacity=40);
+}
+
+.calendarmain .fc-day-content {
+	cursor: default;
+}
+
+.calendarmain .fc-view-table col.fc-event-date {
+	width: 8em;
+}
+
+.calendarmain .fc-view-table col.fc-event-time {
+	width: 9em;
+}
+
+.calendarmain .fc-header-title h2 {
+	font-size: 16px;
+}
+
+.calendarmain .fc-header-left {
+	width: 248px;
+}
+
+.calendarmain .fc-header-center {
+	width: auto;
+}
+
+.calendarmain .fc-header-right {
+	width: 144px;
+	white-space: nowrap;
+}
+
+.calendarmain .fc-event-temp .fc-event-bg {
+	display: none; /* nested opacity filters while dragging don't work */
+}
+
+#schedule-event-time {
+	filter: alpha(opacity=40);
+}
+
+#eventfreebusy .schedule-buttons,
+#edit-attendees-form #edit-attendee-schedule {
+	right: 0.6em;
+}
+
+#schedule-freebusy-times tr.times td.allday {
+	width: expression(Math.max(60, parseInt(this.offsetWidth))+'px');
+}
+
+.ui-dialog .ui-dialog-titlebar {
+	width: expression((parseInt(this.parentNode.offsetWidth)-26)+'px');
+}
Binary file plugins/calendar/skins/larry/images/attendee-status.gif has changed
Binary file plugins/calendar/skins/larry/images/attendee-status.png has changed
Binary file plugins/calendar/skins/larry/images/autocomplete.png has changed
Binary file plugins/calendar/skins/larry/images/badge.png has changed
Binary file plugins/calendar/skins/larry/images/badge_cancelled.png has changed
Binary file plugins/calendar/skins/larry/images/badge_confidential.png has changed
Binary file plugins/calendar/skins/larry/images/badge_private.png has changed
Binary file plugins/calendar/skins/larry/images/calendar.png has changed
Binary file plugins/calendar/skins/larry/images/calendars.png has changed
Binary file plugins/calendar/skins/larry/images/eventicons.png has changed
Binary file plugins/calendar/skins/larry/images/focusview.png has changed
Binary file plugins/calendar/skins/larry/images/freebusy-colors.png has changed
Binary file plugins/calendar/skins/larry/images/ical-attachment.png has changed
Binary file plugins/calendar/skins/larry/images/invitation.png has changed
Binary file plugins/calendar/skins/larry/images/loading_blue.gif has changed
Binary file plugins/calendar/skins/larry/images/minicolors-all.png has changed
Binary file plugins/calendar/skins/larry/images/minicolors-handles.gif has changed
Binary file plugins/calendar/skins/larry/images/sendinvitation.png has changed
Binary file plugins/calendar/skins/larry/images/toggle.gif has changed
Binary file plugins/calendar/skins/larry/images/toolbar.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/jquery.miniColors.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,106 @@
+.miniColors-trigger {
+	height: 22px;
+	width: 22px;
+	background: url('images/minicolors-all.png') -170px 0 no-repeat;
+	vertical-align: middle;
+	margin: 0 .25em;
+	display: inline-block;
+	outline: none;
+}
+
+.miniColors-selector {
+	position: absolute;
+	width: 175px;
+	height: 150px;
+	background: #FFF;
+	border: solid 1px #BBB;
+	-moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	-webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+	border-radius: 5px;
+	padding: 5px;
+	z-index: 999999;
+}
+
+.miniColors-selector.black {
+	background: #000;
+	border-color: #000;
+}
+
+.miniColors-colors {
+	position: absolute;
+	top: 5px;
+	left: 5px;
+	width: 150px;
+	height: 150px;
+	background: url('images/minicolors-all.png') top left no-repeat;
+	cursor: crosshair;
+}
+
+.miniColors-hues {
+	position: absolute;
+	top: 5px;
+	left: 160px;
+	width: 20px;
+	height: 150px;
+	background: url('images/minicolors-all.png') -150px 0 no-repeat;
+	cursor: crosshair;
+}
+
+.miniColors-colorPicker {
+	position: absolute;
+	width: 11px;
+	height: 11px;
+	background: url('images/minicolors-all.png') -170px -28px no-repeat;
+}
+
+.miniColors-huePicker {
+	position: absolute;
+	left: -3px;
+	width: 26px;
+	height: 3px;
+	background: url('images/minicolors-all.png') -170px -24px no-repeat;
+	overflow: hidden;
+}
+
+.miniColors-presets {
+	position: absolute;
+	left: 185px;
+	top: 5px;
+	width: 60px;
+}
+
+.miniColors-colorPreset {
+	float: left;
+	width: 18px;
+	height: 15px;
+	margin: 2px;
+	border: 1px solid #333;
+	cursor: pointer;
+}
+
+.miniColors-colorPreset-active {
+	border: 2px dotted #666;
+	margin: 1px;
+}
+
+/* Hacks for IE6/7 */
+
+* html .miniColors-colors {
+	background-image: none;
+	filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='plugins/calendar/skins/classic/images/minicolors-all.png', sizingMethod='crop');
+}
+
+* html .miniColors-colorPicker {
+	background: url('images/minicolors-handles.gif') 0 -28px no-repeat;
+}
+
+* html .miniColors-huePicker {
+	background: url('images/minicolors-handles.gif') 0 -24px no-repeat;
+}
+
+* html .miniColors-trigger {
+	background: url('images/minicolors-handles.gif') 0 0 no-repeat;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/print.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,229 @@
+/*** Printing styles for Calendar plugin ***/
+
+body {
+	margin: 0 0 1em 0;
+	color: #000;
+	background: #fff;
+}
+
+body, td, th, div, p, h3, select, input, textarea {
+	font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
+	font-size: 8pt;
+}
+
+#calendar {
+	position: relative;
+	top: 0;
+	left: 0;
+	height: auto;
+	margin: 5em auto 0 auto;
+	overflow: visible;
+}
+
+#calendar .fc-header-right {
+	padding-right: 0;
+}
+
+#printconfig {
+	position: fixed;
+	top: 0;
+	left: 0;
+	width: 100%;
+	z-index: 10000;
+	padding: 0.5em;
+	background: #ebebeb;
+	border-bottom: 1px solid #999;
+	box-shadow: 0 3px 4px #ccc;
+	-moz-box-shadow: 0 3px 4px #ccc;
+	-webkit-box-shadow: 0 3px 4px #ccc;
+}
+
+#printconfig .prop {
+	padding-right: 2em;
+}
+
+#message {
+	position: absolute;
+	top: 5.5em;
+	left: 1em;
+}
+
+#message div.loading {
+	color: #666;
+	font-style: italic;
+}
+
+#calendarlist {
+	list-style: none;
+	margin: 2em 0;
+	padding-left: 1em;
+}
+
+#calendarlist ul {
+	float: left;
+	list-style: none;
+	padding-left: 0;
+}
+
+#calendarlist li {
+	float: left;
+	padding-left: 0;
+	padding-right: 0;
+	margin-left: 0;
+	font-weight: bold;
+}
+
+#calendarlist li div {
+	float: left;
+	padding-right: 3em;
+	padding-bottom: 1em;
+}
+
+#calendarlist input,
+#calendarlist .handle {
+	display: none;
+}
+
+#calendarlist li.x-invitations div {
+	color: #999;
+	font-style: italic;
+}
+
+.calwidth {
+	width: 700px;
+	margin: 0 auto;
+}
+
+.rightalign {
+	float: right;
+	padding-top: 0.3em;
+}
+
+@media print {
+	.noprint,
+	.fc-header-right span {
+		display: none;
+	}
+	
+	#calendar {
+		margin-top: 0;
+	}
+}
+
+/* fullcalendar style overrides */
+
+.fc-view {
+	overflow: visible;
+}
+
+.fc-event-skin,
+.fc-event-inner .fc-event-skin {
+	color: black;
+	background-color: #fff !important;
+}
+
+.fc-event-title {
+	font-weight: bold;
+}
+
+.fc-event-hori .fc-event-title {
+	font-weight: normal;
+	white-space: nowrap;
+}
+
+.fc-event-hori .fc-event-time {
+	white-space: nowrap;
+	font-weight: normal !important;
+	font-size: 10px;
+	padding-right: 0.6em;
+}
+
+.fc-grid .fc-event-time {
+	font-weight: normal !important;
+	padding-right: 0.3em;
+}
+
+.fc-event-cateories {
+	font-style: italic;
+}
+
+.fc-event-location {
+	font-size: 90%;
+}
+
+.fc-agenda-slots td div {
+	height: 1.4em;
+}
+
+.fc-widget-header,
+.fc-mon, .fc-tue, .fc-wed, .fc-thu, .fc-fri {
+	background-color: #fff;
+}
+
+.fc-widget-header, .fc-widget-content {
+	border-color: #ccc;
+}
+
+.fc-icon-alarms,
+.fc-icon-recurring {
+	display: inline-block;
+	width: 11px;
+	height: 11px;
+	background: url('images/eventicons.gif') 0 0 no-repeat;
+	margin-left: 3px;
+	line-height: 10px;
+}
+
+.fc-icon-alarms {
+	background-position: 0 -13px;
+}
+
+.fc-view-list, .fc-view-table {
+	border: 0;
+}
+
+.fc-view-list div.fc-list-header,
+.fc-view-table td.fc-list-header {
+	padding: 0.3em;
+	background: #fff;
+	font-weight: bold;
+	font-size: 1.2em;
+	color: #333;
+	border-color: #333;
+	border-style: solid;
+	border-width: 1px 0;
+	filter: none;
+}
+
+.fc-list-section .fc-event {
+	cursor: auto;
+}
+
+.fc-view-table tr.fc-event td,
+.fc-view-table tr.fc-event td.fc-event-handle {
+	border-color: #999;
+	padding-top: 0.5em;
+	padding-bottom: 0.5em;
+}
+
+.fc-view-table tr.fc-last td {
+	border: 0;
+}
+
+.fc-view-table tr.fc-event .fc-event-description {
+	padding-left: 2em;
+	padding-top: 0em;
+}
+
+.fc-event-vert .fc-event-description {
+	font-size: 90%;
+	font-style: italic;
+}
+
+.fc-view-month .fc-event-hori .fc-event-inner {
+	background: #fff !important;
+}
+
+.fc-view-table col.fc-event-location {
+	width: 20%;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/print.iehacks.css	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,25 @@
+/* CSS hacks for IE 7 */
+
+#calendar {
+	top: 5em;
+}
+
+.calwidth {
+	width: 172mm;
+}
+
+.fc-header-title h2 {
+	font-size: 16px;
+}
+
+#calendarlist li {
+	float: none;
+	padding: 0;
+	margin-left: 1em;
+}
+
+@media print {
+	#calendar {
+		top: 0;
+	}
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/attachment.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,64 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+</head>
+<body class="extwin calendar attachmentwin">
+
+<div id="header">
+<div id="topline" role="banner" aria-labelledby="aria-label-topnav">
+	<div class="topleft">
+		<roundcube:container name="topline-left" id="topline-left" />
+	</div>
+	<roundcube:container name="topline-center" id="topline-center" />
+	<div class="topright">
+		<roundcube:container name="topline-right" id="topline-right" />
+		<roundcube:button name="close" type="link" label="close" class="closelink" onclick="self.close()" />
+	</div>
+</div>
+</div>
+
+<div id="mainscreen">
+
+<h1 class="voice"><roundcube:label name="attachment" />: <roundcube:var name="env:filename" /></h1>
+
+<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
+<div id="attachmenttoolbar" class="toolbar fullwidth" role="toolbar" aria-labelledby="aria-label-toolbar">
+	<roundcube:button command="download-attachment" type="link" class="button download disabled" classAct="button download" classSel="button download pressed" label="download" title="download" />
+	<roundcube:button command="print-attachment" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="print" />
+	<roundcube:container name="toolbar" id="messagetoolbar" />
+</div>
+
+<div id="mainscreencontent">
+
+	<div id="partheader" class="uibox listbox" role="contentinfo" aria-labelledby="aria-label-contentinfo">
+		<h2 class="boxtitle" id="aria-label-contentinfo"><roundcube:label name="properties" /></h2>
+		<div class="scroller">
+			<roundcube:object name="plugin.attachmentcontrols" class="listing" />
+		</div>
+	</div>
+
+	<div id="attachmentcontainer" class="uibox" role="main" aria-labelledby="aria-label-messagepart">
+		<h2 id="aria-label-messagepart" class="voice"><roundcube:label name="arialabelattachmentpreview" /></h2>
+		<div class="iframebox">
+		<roundcube:object name="plugin.attachmentframe" id="attachmentframe" frameborder="0" title="arialabelattachmentpreview" />
+		</div>
+	</div>
+
+</div>
+
+</div>
+
+<script type="text/javascript">
+
+$(document).ready(function() {
+	if (window.rcube_splitter) {
+		new rcube_splitter({ id:'mailpartsplitterv', p1:'#partheader', p2:'#attachmentcontainer',
+			orientation:'v', relative:true, start:226, min:150, size:12}).init();
+	}
+});
+
+</script>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/calendar.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,531 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="/this/iehacks.css" /><![endif]-->
+</head>
+<roundcube:if condition="env:extwin" /><body class="calendarmain extwin"><roundcube:else /><body class="calendarmain"><roundcube:endif />
+
+<roundcube:include file="/includes/header.html" />
+
+<h1 class="voice"><roundcube:label name="calendar.calendar" /></h1>
+
+<div id="mainscreen">
+	<div id="calendarsidebar">
+		<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
+		<div id="calendartoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
+			<roundcube:button command="addevent" type="link" class="button addevent disabled" classAct="button addevent" classSel="button addevent pressed" label="calendar.new_event" title="calendar.new_event" />
+			<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="calendar.print" title="calendar.printtitle" />
+			<roundcube:button command="events-import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="calendar.importevents" />
+			<roundcube:button command="export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="calendar.export" title="calendar.exporttitle" />
+			<roundcube:container name="toolbar" id="calendartoolbar" />
+		</div>
+
+		<h2 id="aria-label-minical" class="voice"><roundcube:label name="calendar.arialabelminical" /></h2>
+		<div id="datepicker" class="uibox" role="presentation"></div>
+
+		<div id="calendars" class="uibox listbox" style="visibility:hidden" role="navigation" aria-labelledby="aria-label-calendarlist">
+			<h2 class="boxtitle" id="aria-label-calendarlist"><roundcube:label name="calendar.calendars" />
+				<a href="#calendars" class="iconbutton search" title="<roundcube:label name='calendar.findcalendars' />" tabindex="0"><roundcube:label name='calendar.findcalendars' /></a>
+			</h2>
+			<div class="listsearchbox">
+				<div class="searchbox" role="search" aria-labelledby="aria-label-calsearchform" aria-controls="calendarslist">
+					<h3 id="aria-label-calsearchform" class="voice"><roundcube:label name="calendar.arialabelcalsearchform" /></h3>
+					<label for="calendarlistsearch" class="voice"><roundcube:label name="calendar.searchterms" /></label>
+					<input type="text" name="q" id="calendarlistsearch" placeholder="<roundcube:label name='calendar.findcalendars' />" />
+					<a class="iconbutton searchicon"></a>
+					<roundcube:button type="link" command="reset-listsearch" id="calendarlistsearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
+				</div>
+			</div>
+			<div class="scroller withfooter">
+				<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
+			</div>
+			<div class="boxfooter">
+				<roundcube:button command="calendar-create" type="link" title="calendar.createcalendar" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="calendaroptionslink" id="calendaroptionsmenulink" type="link" title="moreactions" class="listbutton groupactions" onclick="return UI.toggle_popup('calendaroptionsmenu', event, { above:true })" innerClass="inner" label="calendar.calendaractions" aria-haspopup="true" aria-expanded="false" aria-owns="calendaroptionsmenu-menu" />
+			</div>
+		</div>
+	</div>
+
+	<div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform">
+		<h2 id="aria-label-searchform" class="voice"><roundcube:label name="calendar.arialabelsearchform" /></h2>
+		<label for="quicksearchbox" class="voice"><roundcube:label name="calendar.arialabelquicksearchbox" /></label>
+		<roundcube:object name="plugin.searchform" id="quicksearchbox" />
+		<a id="searchmenulink" class="iconbutton searchoptions" tabindex="-1"> </a>
+		<roundcube:button type="link" command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
+	</div>
+
+	<h2 id="aria-label-calendarview" class="voice"><roundcube:label name="calendar.arialabelcalendarview" /></h2>
+	<div id="calendar" role="main" aria-labelledby="aria-label-calendarview">
+		<roundcube:object name="plugin.angenda_options" class="boxfooter" id="agendaoptions" />
+	</div>
+</div>
+
+<div id="timezonedisplay"><roundcube:var name="env:timezone" /></div>
+
+<roundcube:object name="message" id="messagestack" />
+
+<div id="calendaroptionsmenu" class="popupmenu" aria-hidden="true">
+	<h3 id="aria-label-calendaroptions" class="voice"><roundcube:label name="calendar.calendaractions" /></h3>
+	<ul id="calendaroptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-calendaroptions">
+		<li role="menuitem"><roundcube:button type="link" command="calendar-edit" label="calendar.edit" classAct="active" /></li>
+		<li role="menuitem"><roundcube:button type="link" command="calendar-delete" label="delete" classAct="active" /></li>
+		<roundcube:if condition="env:calendar_driver == 'kolab'" />
+		<li role="menuitem"><roundcube:button type="link" command="calendar-remove" label="calendar.removelist" classAct="active" /></li>
+		<roundcube:endif />
+		<li role="menuitem"><roundcube:button type="link" command="calendar-showurl" label="calendar.showurl" classAct="active" /></li>
+		<roundcube:if condition="env:calendar_driver == 'kolab'" />
+		<li role="menuitem"><roundcube:button type="link" command="folders" task="settings" label="managefolders" classAct="active" /></li>
+		<roundcube:endif />
+	</ul>
+</div>
+
+<div id="eventshow" class="uidialog eventdialog" aria-hidden="true">
+	<h1 id="event-title">Event Title</h1>
+	<div id="event-status-badge"><span></span></div>
+	<div class="event-section" id="event-location">Location</div>
+	<div class="event-section" id="event-date">From-To</div>
+	<div class="event-section" id="event-description">
+		<div class="event-text"></div>
+	</div>
+	<div class="event-section event-attendees" id="event-attendees">
+		<div class="event-text"></div>
+	</div>
+	<div class="spacer">&nbsp;</div>
+	<div class="event-line" id="event-url">
+		<label><roundcube:label name="calendar.url" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-repeat">
+		<label><roundcube:label name="calendar.repeat" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-alarm">
+		<label><roundcube:label name="calendar.alarms" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-partstat">
+		<label><roundcube:label name="calendar.mystatus" /></label>
+		<span class="changersvp" role="button" tabindex="0" title="<roundcube:label name='calendar.changepartstat' />">
+			<span class="event-text"></span>
+			<a class="iconbutton edit"><roundcube:label name='calendar.changepartstat' /></a>
+		</span>
+	</div>
+	<div class="event-line" id="event-calendar">
+		<label><roundcube:label name="calendar.calendar" /></label>
+		<span class="event-text">Default</span>
+	</div>
+	<div class="event-line" id="event-category">
+		<label><roundcube:label name="calendar.category" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-status">
+		<label><roundcube:label name="calendar.status" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-free-busy">
+		<label><roundcube:label name="calendar.freebusy" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-priority">
+		<label><roundcube:label name="calendar.priority" /></label>
+		<span class="event-text"></span>
+	</div>
+	<!--
+	<div class="event-line" id="event-sensitivity">
+		<label><roundcube:label name="calendar.sensitivity" /></label>
+		<span class="event-text"></span>
+	</div>
+	-->
+	<div class="event-line" id="event-created-changed">
+		<label><roundcube:label name="calendar.created" /></label>
+		<span class="event-text event-created"></span>
+		<label><roundcube:label name="calendar.changed" /></label>
+		<span class="event-text event-changed"></span>
+	</div>
+	<div class="event-line" id="event-rsvp-comment">
+		<label><roundcube:label name="calendar.rsvpcomment" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-section" id="event-links">
+		<label><roundcube:label name="calendar.links" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-section" id="event-attachments">
+		<div class="event-text"></div>
+	</div>
+
+	<roundcube:object name="plugin.event_rsvp_buttons" id="event-rsvp" class="event-dialog-message" style="display:none" />
+</div>
+
+<div id="eventoptionsmenu" class="popupmenu" aria-hidden="true">
+	<h3 id="aria-label-eventoptions" class="voice"><roundcube:label name="calendar.eventoptions" /></h3>
+	<ul id="eventoptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-eventoptions">
+		<li role="menuitem"><roundcube:button type="link" command="event-download" label="download" classAct="active" /></li>
+		<li role="menuitem"><roundcube:button type="link" command="event-sendbymail" label="send" classAct="active" /></li>
+		<li role="menuitem"><roundcube:button type="link" command="event-copy" label="copy" classAct="active" /></li>
+		<roundcube:if condition="env:calendar_driver == 'kolab' && config:kolab_bonnie_api" />
+		<li role="menuitem"><roundcube:button type="link" command="event-history" label="calendar.eventhistory" classAct="active" /></li>
+		<roundcube:endif />
+	</ul>
+</div>
+
+<div id="eventdiff" class="uidialog eventdialog" aria-hidden="true">
+	<h1 class="event-title">Event Title</h1>
+	<h1 class="event-title-new event-text-new"></h1>
+	<div class="event-section event-date"></div>
+	<div class="event-section event-location">
+		<h5 class="label"><roundcube:label name="calendar.location" /></h5>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+	<div class="event-section event-description">
+		<h5 class="label"><roundcube:label name="calendar.description" /></h5>
+		<div class="event-text-diff" style="white-space:pre-wrap"></div>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+	<div class="event-section event-url">
+		<h5 class="label"><roundcube:label name="calendar.url" /></h5>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+	<div class="event-section event-recurrence">
+		<h5 class="label"><roundcube:label name="calendar.repeat" /></h5>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+	<div class="event-section event-alarms">
+		<h5 class="label"><roundcube:label name="calendar.alarms" /><span class="index"></span></h5>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+	<div class="event-line event-start">
+		<label><roundcube:label name="calendar.start" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-end">
+		<label><roundcube:label name="calendar.end" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-attendees">
+		<label><roundcube:label name="calendar.tabattendees" /><span class="index"></span></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-calendar">
+		<label><roundcube:label name="calendar.calendar" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-categories">
+		<label><roundcube:label name="calendar.category" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-status">
+		<label><roundcube:label name="calendar.status" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-free_busy">
+		<label><roundcube:label name="calendar.freebusy" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-priority">
+		<label><roundcube:label name="calendar.priority" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-line event-sensitivity">
+		<label><roundcube:label name="calendar.sensitivity" /></label>
+		<span class="event-text-old"></span> &#8674;
+		<span class="event-text-new"></span>
+	</div>
+	<div class="event-section event-attachments">
+		<label><roundcube:label name="attachments" /><span class="index"></span></label>
+		<div class="event-text-old"></div>
+		<div class="event-text-new"></div>
+	</div>
+</div>
+
+<roundcube:include file="/templates/eventedit.html" />
+
+<div id="eventresourcesdialog" class="uidialog" aria-hidden="true">
+	<div id="resource-dialog-left">
+		<div id="resource-selection" class="uibox listbox" role="navigation" aria-labelledby="aria-label-resourceselection">
+			<h2 class="voice" id="aria-label-resourceselection"><roundcube:label name="calendar.arialabelresourceselection" /></h2>
+			<div id="resourcequicksearch">
+				<div class="searchbox" role="search" aria-labelledby="aria-label-resourcesearchform" aria-controls="resources-list">
+					<h3 id="aria-label-resourcesearchform" class="voice"><roundcube:label name="calendar.arialabelresourcesearchform" /></h3>
+					<label for="resourcesearchbox" class="voice"><roundcube:label name="calendar.searchterms" /></label>
+					<roundcube:object name="plugin.resources_searchform" id="resourcesearchbox" />
+					<a id="resourcesearchmenulink" class="iconbutton searchoptions"> </a>
+					<roundcube:button type="link" command="reset-resource-search" id="resourcesearchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
+				</div>
+			</div>
+			<div class="scroller">
+				<roundcube:object name="plugin.resources_list" id="resources-list" class="listing treelist" />
+			</div>
+		</div>
+	</div>
+
+	<div id="resource-dialog-right">
+		<div id="resource-info" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourcedetails">
+			<h2 class="boxtitle" id="aria-label-resourcedetails"><roundcube:label name="calendar.resourcedetails" /></h2>
+			<div class="scroller">
+				<roundcube:object name="plugin.resource_info" id="resource-details" class="propform" aria-live="polite" aria-relevant="text" aria-atomic="true" />
+			</div>
+		</div>
+
+		<div id="resource-availability" class="uibox contentbox" role="region" aria-labelledby="aria-label-resourceavailability">
+			<h2 class="boxtitle" id="aria-label-resourceavailability"><roundcube:label name="calendar.resourceavailability" /></h2>
+			<roundcube:object name="plugin.resource_calendar" id="resource-freebusy-calendar" />
+			<div class="boxpagenav">
+				<roundcube:button name="resource-cal-prev" id="resource-calendar-prev" type="link" class="icon prevpage" title="calendar.prevslot" label="calendar.prevweek" />
+				<roundcube:button name="resource-cal-next" id="resource-calendar-next" type="link" class="icon nextpage" title="calendar.nextslot" label="calendar.nextweek" />
+			</div>
+		</div>
+	</div>
+</div>
+
+<div id="eventfreebusy" class="uidialog" aria-hidden="true">
+	<roundcube:object name="plugin.attendees_freebusy_table" id="attendees-freebusy-table" cellpadding="0" />
+
+	<div class="schedule-options">
+		&nbsp;
+		<div class="schedule-buttons">
+			<button id="shedule-freebusy-prev" title="<roundcube:label name='previouspage' />">&#9668;</button><button id="shedule-freebusy-next" title="<roundcube:label name='nextpage' />">&#9658;</button>
+		</div>
+	</div>
+
+	<div style="float:left; width:28em">
+		<div class="form-section">
+			<label for="schedule-startdate"><roundcube:label name="calendar.start" /></label>
+			<input type="text" name="startdate" size="11" id="schedule-startdate" disabled="true" /> &nbsp;
+			<input type="text" name="starttime" size="6" id="schedule-starttime" disabled="true" />
+		</div>
+		<div class="form-section">
+			<label for="schedule-enddate"><roundcube:label name="calendar.end" /></label>
+			<input type="text" name="enddate" size="11" id="schedule-enddate" disabled="true" /> &nbsp;
+			<input type="text" name="endtime" size="6" id="schedule-endtime" disabled="true" />
+		</div>
+	</div>
+	<div style="float:left">
+		<div class="schedule-find-buttons">
+			<button id="shedule-find-prev">&#9668; <roundcube:label name="calendar.prevslot" /></button>
+			<button id="shedule-find-next"><roundcube:label name="calendar.nextslot" /> &#9658;</button>
+		</div>
+		<div class="schedule-options">
+			<label><input type="checkbox" id="schedule-freebusy-workinghours" value="1" /><roundcube:label name="calendar.onlyworkinghours" /></label>
+		</div>
+	</div>
+	<br style="clear:both;" />
+
+	<roundcube:include file="/templates/freebusylegend.html" />
+	<div class="attendees-list">
+		<span class="attendee organizer"><roundcube:label name="calendar.roleorganizer" /></span>
+		<span class="attendee req-participant"><roundcube:label name="calendar.rolerequired" /></span>
+		<span class="attendee opt-participant"><roundcube:label name="calendar.roleoptional" /></span>
+		<span class="attendee non-participant"><roundcube:label name="calendar.rolenonparticipant" /></span>
+		<span class="attendee chair"><roundcube:label name="calendar.rolechair" /></span>
+	</div>
+</div>
+
+<div id="eventhistory" class="uidialog" aria-hidden="true">
+    <roundcube:object name="plugin.object_changelog_table" id="event-changelog-table" class="records-table changelog-table" />
+    <div class="compare-button"><input type="button" class="button" value="↳ <roundcube:label name='libkolab.compare' />" /></div>
+</div>
+
+<div id="calendarform" class="uidialog" aria-hidden="true">
+	<roundcube:label name="loading" />
+</div>
+
+<div id="eventsimport" class="uidialog">
+	<roundcube:object name="plugin.events_import_form" id="events-import-form" uploadFieldSize="30" />
+</div>
+
+<div id="eventsexport" class="uidialog">
+	<roundcube:object name="plugin.events_export_form" id="events-export-form" />
+</div>
+
+<div id="calendarurlbox" class="uidialog">
+	<p><roundcube:label name="calendar.showurldescription" /></p>
+	<textarea id="calfeedurl" rows="2" readonly="readonly"></textarea>
+	<div id="calendarcaldavurl" style="display:none">
+		<p><roundcube:label name="calendar.caldavurldescription" html="yes" /></p>
+		<textarea id="caldavurl" rows="2" readonly="readonly"></textarea>
+	</div>
+</div>
+
+<roundcube:object name="plugin.calendar_css" />
+
+<script type="text/javascript">
+
+// UI startup
+var UI = new rcube_mail_ui();
+
+$(document).ready(function(e){
+	new calendarview_splitter({ id:'calsidebarsplitter', p1:'#calendarsidebar', p2:'#calendar',
+		orientation:'v', relative:true, start:280, min:260, size:12, offset:0 });
+
+	new rcube_splitter({ id:'calresourceviewsplitter', p1:'#resource-dialog-left', p2:'#resource-dialog-right',
+		orientation:'v', relative:true, start:380, min:220, size:10, offset:-3 }).init();
+
+	// animation to unfold list search box
+	$('#calendars .boxtitle a.search').click(function(e){
+		var title = $('#calendars .boxtitle'),
+			box = $('#calendars .listsearchbox'),
+			dir = box.is(':visible') ? -1 : 1;
+
+		if (!rcube_event.is_keyboard(e))
+			$(this).blur();
+
+		box.slideToggle({
+			duration: 160,
+			progress: function(animation, progress) {
+				if (dir < 0) progress = 1 - progress;
+				$('#calendars .scroller').css('top', (title.outerHeight() + 34 * progress) + 'px');
+			},
+			complete: function() {
+				box.toggleClass('expanded');
+				if (box.is(':visible')) {
+					box.find('input[type=text]').focus();
+				}
+				else {
+					$('#calendarlistsearch-reset').click();
+				}
+				// TODO: save state in localStorage
+			}
+		});
+
+		return false;
+	});
+
+});
+
+
+/**
+ * Extended rcube_splitter class that entirely collapses the calendar sidebar
+ */
+function calendarview_splitter(p)
+{
+	this.collapsed = false;
+	this.dragging = false;
+	this.threshold = 80;
+	this.lastpos = -1;
+	this._lastpos = -1;
+	this._min = p.min;
+
+	var me = this;
+	p.callback = function(e){
+		if (me.lastpos != me._lastpos) {
+			me.dragging = true;
+			setTimeout(function(){ me.dragging = false; }, 50);
+			me._lastpos = me.lastpos;
+		}
+	};
+
+	// extend base class
+	p.min = 20;
+	rcube_splitter.call(this, p);
+
+	// @override
+	this.resize = function()
+	{
+		if (this.pos < this.threshold) {
+			if (!this.collapsed)
+				this.collapse();
+		}
+		else if (this.pos < this._min && this.pos > this._min / 2) {
+			if (this.collapsed)
+				this.expand();
+		}
+		else if (this.pos >= this._min) {
+			this.p1.css('width', Math.floor(this.pos - this.p1pos.left - this.halfsize) + 'px');
+			this.p2.css('left', Math.ceil(this.pos + this.halfsize) + 'px');
+			this.handle.css('left', Math.round(this.pos - this.halfsize + this.offset + 3)+'px');
+			if (bw.ie) {
+				var new_width = parseInt(this.parent.outerWidth(), 10) - parseInt(this.p2.css('left'), 10) ;
+				this.p2.css('width', (new_width > 0 ? new_width : 0) + 'px');
+			}
+
+			this.p2.resize();
+			this.p1.resize();
+			this.lastpos = this.pos;
+
+			if (this._lastpos == -1)
+				this._lastpos = this.pos;
+
+			// also resize iframe covers
+			if (this.drag_active) {
+				$('iframe').each(function(i, elem) {
+					var pos = $(this).offset();
+					$('#iframe-splitter-fix-'+i).css({ top: pos.top+'px', left: pos.left+'px', width:elem.offsetWidth+'px', height: elem.offsetHeight+'px' });
+				});
+			}
+
+			if (typeof this.render == 'function')
+				this.render(this);
+		}
+	}
+
+	this.collapse = function(animated)
+	{
+		var me = this, time = 250;
+		if (animated) {
+			this.p1.animate({ left:'0px' }, time, function(){ $(this).hide(); });
+			this.p2.animate({ left:this.p.size + 'px' }, time, function(){ $(this).resize(); });
+			this.handle.animate({ left:'3px'}, time, function(){ $(this).addClass('sidebarclosed') });
+		}
+		else {
+			this.p1.css('left', 0).hide();
+			this.p2.css('left', this.p.size + 'px');
+			this.handle.css('left', '3px').addClass('sidebarclosed');
+			this.p2.resize();
+		}
+
+		// stop dragging
+		if (this.drag_active) {
+			this.drag_active = false;
+			$(document).unbind('.'+this.id);
+			$('div.iframe-splitter-fix').remove();
+		}
+
+		this.pos = 10;
+		this.collapsed = true;
+		this.set_cookie();
+	}
+
+	this.expand = function()
+	{
+		var me = this, time = 250;
+		this.handle.removeClass('sidebarclosed');
+		this.pos = this.lastpos > 0 ? this.lastpos : this._min;
+		this.p1pos.left = 10;
+		this.p1.show().animate({ left:'10px', width:(this.pos - this.p1pos.left - this.halfsize) + 'px' }, time);
+		this.p2.animate({ left:(this.pos + this.halfsize) + 'px' }, time, function(){ me.resize(); });
+		this.handle.animate({ left:(this.pos - this.halfsize + this.offset + 3) + 'px' }, time);
+
+		this.collapsed = false;
+		this.set_cookie();
+	}
+
+	this.init();
+
+	var me = this;
+	this.handle.bind('click', function(e){
+		if (!me.collapsed && !me.dragging)
+			me.collapse(true);
+		else if (!me.dragging)
+			me.expand();
+	});
+}
+
+</script>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/dialog.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,87 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="/this/iehacks.css" /><![endif]-->
+</head>
+<body class="calendarmain dialog">
+
+<div id="mainscreen">
+	<div id="calendarsidebar">
+		<h2 id="aria-label-minical" class="voice"><roundcube:label name="calendar.arialabelminical" /></h2>
+		<div id="datepicker" class="uibox" role="presentation"></div>
+
+		<div id="calendars" class="uibox listbox" style="visibility:hidden" role="navigation" aria-labelledby="aria-label-calendarlist">
+			<h2 class="boxtitle" id="aria-label-calendarlist"><roundcube:label name="calendar.calendars" />
+				<a href="#calendars" class="iconbutton search" title="<roundcube:label name='calendar.findcalendars' />" tabindex="0"><roundcube:label name='calendar.findcalendars' /></a>
+			</h2>
+			<div class="listsearchbox">
+				<div class="searchbox" role="search" aria-labelledby="aria-label-calsearchform" aria-controls="calendarslist">
+					<h3 id="aria-label-calsearchform" class="voice"><roundcube:label name="calendar.arialabelcalsearchform" /></h3>
+					<label for="calendarlistsearch" class="voice"><roundcube:label name="calendar.searchterms" /></label>
+					<input type="text" name="q" id="calendarlistsearch" placeholder="<roundcube:label name='calendar.findcalendars' />" />
+					<a class="iconbutton searchicon"></a>
+					<roundcube:button command="reset-listsearch" id="calendarlistsearch-reset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
+				</div>
+			</div>
+			<div class="scroller">
+				<roundcube:object name="plugin.calendar_list" id="calendarslist" class="treelist listing" />
+			</div>
+		</div>
+	</div>
+
+	<h2 id="aria-label-calendarview" class="voice"><roundcube:label name="calendar.arialabelcalendarview" /></h2>
+	<div id="calendar" role="main" aria-labelledby="aria-label-calendarview">
+		<roundcube:object name="plugin.angenda_options" class="boxfooter" id="agendaoptions" />
+	</div>
+</div>
+
+<div id="timezonedisplay"><roundcube:var name="env:timezone" /></div>
+
+<roundcube:include file="/templates/eventshow.html" />
+<roundcube:include file="/templates/eventedit.html" />
+
+<roundcube:object name="plugin.calendar_css" />
+
+<script type="text/javascript">
+
+// UI startup
+var UI = new rcube_mail_ui();
+
+$(document).ready(function(e) {
+	UI.init();
+
+	// animation to unfold list search box
+	$('#calendars .boxtitle a.search').click(function(e){
+		var title = $('#calendars .boxtitle'),
+			box = $('#calendars .listsearchbox'),
+			dir = box.is(':visible') ? -1 : 1;
+
+		box.slideToggle({
+			duration: 160,
+			progress: function(animation, progress) {
+				if (dir < 0) progress = 1 - progress;
+				$('#calendars .scroller').css('top', (title.outerHeight() + 34 * progress) + 'px');
+			},
+			complete: function() {
+				box.toggleClass('expanded');
+				if (box.is(':visible')) {
+					box.find('input[type=text]').focus();
+				}
+				else {
+					$('#calendarlistsearch-reset').click();
+				}
+				// TODO: save state in localStorage
+			}
+		});
+
+		return false;
+	});
+
+});
+
+</script>
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/eventedit.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,133 @@
+<div id="eventedit" class="uidialog uidialog-tabbed" aria-hidden="true">
+	<form id="eventtabs" action="#" method="post" enctype="multipart/form-data">
+		<ul>
+			<li><a href="#event-panel-summary"><roundcube:label name="calendar.tabsummary" /></a></li><li id="edit-tab-recurrence"><a href="#event-panel-recurrence"><roundcube:label name="calendar.tabrecurrence" /></a></li><li id="edit-tab-attendees"><a href="#event-panel-attendees"><roundcube:label name="calendar.tabattendees" /></a></li><li id="edit-tab-resources"><a href="#event-panel-resources"><roundcube:label name="calendar.tabresources" /></a></li><li id="edit-tab-attachments"><a href="#event-panel-attachments"><roundcube:label name="calendar.tabattachments" /></a></li>
+		</ul>
+		<!-- basic info -->
+		<div id="event-panel-summary">
+			<div class="event-section">
+				<label for="edit-title"><roundcube:label name="calendar.title" /></label>
+				<br />
+				<input type="text" class="text" name="title" id="edit-title" size="40" required="true" />
+			</div>
+			<div class="event-section">
+				<label for="edit-location"><roundcube:label name="calendar.location" /></label>
+				<br />
+				<input type="text" class="text" name="location" id="edit-location" size="40" />
+			</div>
+			<div class="event-section">
+				<label for="edit-description"><roundcube:label name="calendar.description" /></label>
+				<br />
+				<textarea name="description" id="edit-description" class="text" rows="5" cols="40"></textarea>
+			</div>
+			<div class="event-section">
+				<label for="edit-url"><roundcube:label name="calendar.url" /></label>
+				<br />
+				<input type="text" class="text" name="vurl" id="edit-url" size="40" />
+			</div>
+			<div class="event-section">
+				<label style="float:right;padding-right:0.5em"><input type="checkbox" name="allday" id="edit-allday" value="1" /><roundcube:label name="calendar.all-day" /></label>
+				<label for="edit-startdate"><roundcube:label name="calendar.start" /></label>
+				<input type="text" name="startdate" size="11" id="edit-startdate" required="true" /> &nbsp;
+				<input type="text" name="starttime" size="6" id="edit-starttime" aria-label="<roundcube:label name='calendar.starttime' />" />
+			</div>
+			<div class="event-section">
+				<label for="edit-enddate"><roundcube:label name="calendar.end" /></label>
+				<input type="text" name="enddate" size="11" id="edit-enddate" required="true" /> &nbsp;
+				<input type="text" name="endtime" size="6" id="edit-endtime" aria-label="<roundcube:label name='calendar.endtime' />" />
+			</div>
+			<div class="event-section" id="edit-alarms">
+				<div class="edit-alarm-item first">
+					<label for="edit-alarm-item"><roundcube:label name="calendar.alarms" /></label>
+					<roundcube:object name="plugin.alarm_select" id="edit-alarm-item" />
+					<span class="edit-alarm-buttons">
+						<a href="#add" class="iconbutton add add-alarm"><roundcube:label name="libcalendaring.addalarm" /></a>
+						<a href="#delete" class="iconbutton remove delete-alarm"><roundcube:label name="libcalendaring.removealarm" /></a>
+					</span>
+				</div>
+			</div>
+			<div class="event-section" id="calendar-select">
+				<label for="edit-calendar"><roundcube:label name="calendar.calendar" /></label>
+				<roundcube:object name="plugin.calendar_select" id="edit-calendar" />
+			</div>
+			<div class="event-section">
+				<label for="edit-categories"><roundcube:label name="calendar.category" /></label>
+				<roundcube:object name="plugin.category_select" id="edit-categories" />
+			</div>
+			<div class="event-section">
+				<label for="edit-event-status"><roundcube:label name="calendar.status" /></label>
+				<roundcube:object name="plugin.status_select" id="edit-event-status" />
+			</div>
+			<div class="event-section">
+				<label for="edit-free-busy"><roundcube:label name="calendar.freebusy" /></label>
+				<roundcube:object name="plugin.freebusy_select" id="edit-free-busy" />
+			</div>
+			<div class="event-section">
+				<label for="edit-priority"><roundcube:label name="calendar.priority" /></label>
+				<roundcube:object name="plugin.priority_select" id="edit-priority" />
+			</div>
+			<div class="event-section">
+				<label for="edit-sensitivity"><roundcube:label name="calendar.sensitivity" /></label>
+				<roundcube:object name="plugin.sensitivity_select" id="edit-sensitivity" />
+			</div>
+			<div class="event-section" id="edit-event-links">
+				<label><roundcube:label name="calendar.links" /></label>
+				<div class="event-text"></div>
+				<br style="clear:left">
+			</div>
+		</div>
+		<!-- recurrence settings -->
+		<div id="event-panel-recurrence">
+			<div class="event-section border-after">
+				<roundcube:object name="plugin.recurrence_form" part="frequency" />
+			</div>
+			<div class="recurrence-form border-after" id="recurrence-form-daily">
+				<roundcube:object name="plugin.recurrence_form" part="daily" class="event-section" />
+			</div>
+			<div class="recurrence-form border-after" id="recurrence-form-weekly">
+				<roundcube:object name="plugin.recurrence_form" part="weekly" class="event-section" />
+			</div>
+			<div class="recurrence-form border-after" id="recurrence-form-monthly">
+				<roundcube:object name="plugin.recurrence_form" part="monthly" class="event-section" />
+			</div>
+			<div class="recurrence-form border-after" id="recurrence-form-yearly">
+				<roundcube:object name="plugin.recurrence_form" part="yearly" class="event-section" />
+			</div>
+			<div class="recurrence-form" id="recurrence-form-until">
+				<roundcube:object name="plugin.recurrence_form" part="until" class="event-section" />
+			</div>
+			<div class="recurrence-form" id="recurrence-form-rdate">
+				<roundcube:object name="plugin.recurrence_form" part="rdate" class="event-section" />
+			</div>
+		</div>
+		<!-- attendees list -->
+		<div id="event-panel-attendees">
+			<h3 id="aria-label-attendeestable" class="voice"><roundcube:label name="calendar.arialabeleventattendees" /></h3>
+			<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" aria-labelledby="aria-label-attendeestable" />
+			<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
+			<roundcube:include file="/templates/freebusylegend.html" />
+		</div>
+		<!-- resources list -->
+		<div id="event-panel-resources">
+			<h3 id="aria-label-resourcestable" class="voice"><roundcube:label name="calendar.arialabeleventresources" /></h3>
+			<roundcube:object name="plugin.attendees_list" id="edit-resources-table" class="records-table edit-attendees-table" coltitle="resource" aria-labelledby="aria-label-resourcestable" />
+			<roundcube:object name="plugin.resources_form" id="edit-resources-form" />
+			<roundcube:include file="/templates/freebusylegend.html" />
+		</div>
+		<!-- attachments list (with upload form) -->
+		<div id="event-panel-attachments">
+			<div id="edit-attachments">
+				<roundcube:object name="plugin.attachments_list" id="attachmentlist" class="attachmentslist" />
+			</div>
+			<div id="edit-attachments-form" role="region" aria-labelledby="aria-label-attachmentuploadform">
+				<h3 id="aria-label-attachmentuploadform" class="voice"><roundcube:label name="arialabelattachmentuploadform" /></h3>
+				<roundcube:object name="plugin.attachments_form" id="calendar-attachment-form" attachmentFieldSize="30" />
+			</div>
+			<roundcube:object name="plugin.filedroparea" id="event-panel-attachments" />
+		</div>
+	</form>
+
+	<roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="event-dialog-message" style="display:none" />
+	<roundcube:object name="plugin.edit_recurring_warning" class="event-dialog-message edit-recurring-warning" style="display:none" />
+	<div id="edit-localchanges-warning" class="event-dialog-message" style="display:none"><roundcube:label name="calendar.localchangeswarning" /></div>
+</div>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/eventshow.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,80 @@
+<div id="eventshow" class="uidialog eventdialog" aria-hidden="true">
+	<h1 id="event-title">Event Title</h1>
+	<div class="event-section" id="event-location">Location</div>
+	<div class="event-section" id="event-date">From-To</div>
+	<div class="event-section" id="event-description">
+		<h5 class="label"><roundcube:label name="calendar.description" /></h5>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-section" id="event-url">
+		<h5 class="label"><roundcube:label name="calendar.url" /></h5>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-section" id="event-repeat">
+		<h5 class="label"><roundcube:label name="calendar.repeat" /></h5>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-section" id="event-alarm">
+		<h5 class="label"><roundcube:label name="calendar.alarms" /></h5>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-section event-attendees" id="event-attendees">
+		<h5 class="label"><roundcube:label name="calendar.tabattendees" /></h5>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-line" id="event-partstat">
+		<label><roundcube:label name="calendar.mystatus" /></label>
+		<span class="changersvp" role="button" tabindex="0" title="<roundcube:label name='calendar.changepartstat' />">
+			<span class="event-text"></span>
+			<a class="iconbutton edit"><roundcube:label name='calendar.changepartstat' /></a>
+		</span>
+	</div>
+	<div class="event-line" id="event-calendar">
+		<label><roundcube:label name="calendar.calendar" /></label>
+		<span class="event-text">Default</span>
+	</div>
+	<div class="event-line" id="event-category">
+		<label><roundcube:label name="calendar.category" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-status">
+		<label><roundcube:label name="calendar.status" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-free-busy">
+		<label><roundcube:label name="calendar.freebusy" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-priority">
+		<label><roundcube:label name="calendar.priority" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-line" id="event-sensitivity">
+		<label><roundcube:label name="calendar.sensitivity" /></label>
+		<span class="event-text"></span>
+	</div>
+	<div class="event-section" id="event-attachments">
+		<label><roundcube:label name="attachments" /></label>
+		<div class="event-text"></div>
+	</div>
+	<div class="event-line" id="event-created-changed">
+		<label><roundcube:label name="calendar.created" /></label>
+		<span class="event-text event-created"></span>
+		<label><roundcube:label name="calendar.changed" /></label>
+		<span class="event-text event-changed"></span>
+	</div>
+	<div class="event-line" id="event-rsvp-comment">
+		<label><roundcube:label name="calendar.rsvpcomment" /></label>
+		<span class="event-text"></span>
+	</div>
+
+	<roundcube:object name="plugin.event_rsvp_buttons" id="event-rsvp" class="event-dialog-message" style="display:none" />
+</div>
+
+<div id="eventoptionsmenu" class="popupmenu" aria-hidden="true">
+	<h3 id="aria-label-eventoptions" class="voice"><roundcube:label name="calendar.eventoptions" /></h3>
+	<ul id="eventoptionsmenu-menu" class="toolbarmenu" role="menu" aria-labelledby="aria-label-eventoptions">
+		<li role="menuitem"><roundcube:button command="event-download" label="download" classAct="active" /></li>
+		<li role="menuitem"><roundcube:button command="event-sendbymail" label="send" classAct="active" /></li>
+	</ul>
+</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/freebusylegend.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,7 @@
+	<div id="edit-attendees-legend" class="availability">
+		<span class="legend"><img class="availabilityicon free" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availfree" /></span>
+		<span class="legend"><img class="availabilityicon busy" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availbusy" /></span>
+		<span class="legend"><img class="availabilityicon tentative" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availtentative" /></span>
+		<!--<span class="legend"><img class="availabilityicon out-of-office" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availoutofoffice" /></span>-->
+		<span class="legend"><img class="availabilityicon unknown" src="program/resources/blank.gif" /> <roundcube:label name="calendar.availunknown" /></span>
+	</div>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/itipattend.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,29 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+</head>
+<body class="extwin calendaritipattend">
+
+<div id="header">
+<div id="topnav">
+	<roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
+</div>
+</div>
+
+<div id="mainscreen">
+
+<div class="centerbox uibox">
+	<roundcube:object name="plugin.event_inviteform" />
+	<roundcube:object name="plugin.event_invitebox" class="calendar-invitebox" />
+	<roundcube:object name="plugin.event_rsvp_buttons" type="submit" iname="rsvp" id="event-rsvp" delegate="false" />
+	</form>
+</div>
+
+<roundcube:object name="message" id="message" />
+
+</div>
+
+</body>
+</html>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/kolabacl.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,26 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<style type="text/css" media="screen">
+
+body.aclform {
+	background: #efefef;
+	margin: 0;
+}
+
+body.aclform .hint {
+	margin: 1em;
+}
+
+</style>
+</head>
+<body class="iframe aclform">
+
+<roundcube:object name="folderacl" />
+
+<roundcube:include file="/includes/footer.html" />
+
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/kolabform.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,9 @@
+<div id="calendar-kolabform" class="propform tabbed">
+	<roundcube:object name="calendarform" />
+</div>
+<style type="text/css">
+#calendarpropform { min-width:680px; margin-top:-12px; }
+</style>
+<script type="text/javascript">
+UI.init_tabs('#calendar-kolabform');
+</script>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/calendar/skins/larry/templates/print.html	Sat Jan 13 08:56:12 2018 -0500
@@ -0,0 +1,29 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+</head>
+<body class="calendarprint">
+
+<div id="printconfig" class="noprint">
+	<div class="calwidth">
+		<a href="#close" onclick="window.close()" class="rightalign"><roundcube:label name="close" /></a>
+		<span class="prop"><input type="button" id="printme" value="<roundcube:label name='print' />" onclick="window.print()"></span>
+		<span class="prop"><label><input type="checkbox" id="propdescription" checked="checked" /> <roundcube:label name="calendar.printdescriptions" /></label></span>
+	</div>
+</div>
+
+<roundcube:object name="message" id="message" class="noprint" />
+
+<div id="calendar" class="calwidth"></div>
+
+<div class="calwidth">
+	<roundcube:object name="plugin.calendar_list" activeonly="true" id="calendarlist" />
+	<br style="clear:both">
+</div>
+
+<roundcube:object name="plugin.calendar_css" printmode="true" />
+
+<!--[if lte IE 7]><link rel="stylesheet" type="text/css" href="plugins/calendar/skins/classic/print.iehacks.css" /><![endif]-->
+</body>
+</html>
\ No newline at end of file