--- /dev/null
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+\f
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser 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 Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+\f
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
--- /dev/null
+<?php
+/**
+ * The Kolab implementation of free/busy.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php,v 1.14 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * @package Kolab_FreeBusy
+ */
+
+/** PEAR for raising errors */
+require_once 'PEAR.php';
+
+/** View classes for the result */
+require_once 'Horde/Kolab/FreeBusy/View.php';
+
+/** A class that handles access restrictions */
+require_once 'Horde/Kolab/FreeBusy/Access.php';
+
+/**
+ * How to use this class
+ *
+ * require_once 'config.php';
+ *
+ * $fb = new Kolab_Freebusy();
+ *
+ * $fb->trigger();
+ *
+ * OR
+ *
+ * $fb->fetch();
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php,v 1.14 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @since Horde 3.2
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @author Thomas Arendsen Hein <thomas@intevation.de>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy {
+
+ /**
+ * Parameters provided to this class.
+ *
+ * @var array
+ */
+ var $_params;
+
+ /**
+ * Link to the cache.
+ *
+ * @var Horde_Kolab_FreeBusy_Cache
+ */
+ var $_cache;
+
+ /**
+ * Setup the cache.
+ */
+ function _initCache()
+ {
+ global $conf;
+
+ /* Load the cache class now */
+ require_once 'Horde/Kolab/FreeBusy/Cache.php';
+
+ /* Where is the cache data stored? */
+ if (!empty($conf['fb']['cache_dir'])) {
+ $cache_dir = $conf['fb']['cache_dir'];
+ } else {
+ if (class_exists('Horde')) {
+ $cache_dir = Horde::getTempDir();
+ } else {
+ $cache_dir = '/tmp';
+ }
+ }
+
+ $this->_cache = new Horde_Kolab_FreeBusy_Cache($cache_dir);
+ }
+
+ /**
+ * Trigger regeneration of free/busy data in a calender.
+ */
+ function &trigger()
+ {
+ global $conf;
+
+ /* Get the folder name */
+ $req_folder = Horde_Util::getFormData('folder', '');
+
+ Horde::logMessage(sprintf("Starting generation of partial free/busy data for folder %s",
+ $req_folder), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Validate folder access */
+ $access = new Horde_Kolab_FreeBusy_Access();
+ $result = $access->parseFolder($req_folder);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+ 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ Horde::logMessage(sprintf("Partial free/busy data of owner %s on server %s requested by user %s.",
+ $access->owner, $access->freebusyserver, $access->user),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Get the cache request variables */
+ $req_cache = Horde_Util::getFormData('cache', false);
+ $req_extended = Horde_Util::getFormData('extended', false);
+
+ /* Try to fetch the data if it is stored on a remote server */
+ $result = $access->fetchRemote(true, $req_extended);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
+ 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ $this->_initCache();
+
+ if (!$req_cache) {
+ /* User wants to regenerate the cache */
+
+ /* Here we really need an authenticated IMAP user */
+ $result = $access->authenticated();
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED,
+ 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ if (empty($access->owner)) {
+ $message = sprintf(_("No such account %s!"),
+ htmlentities($access->req_owner));
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+ 'error' => PEAR::raiseError($message));
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ /* Update the cache */
+ $result = $this->_cache->store($access);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+ 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+ }
+
+ /* Load the cache data */
+ $vfb = $this->_cache->loadPartial($access, $req_extended);
+ if (is_a($vfb, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND,
+ 'error' => $vfb);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ Horde::logMessage("Delivering partial free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Generate the renderer */
+ $data = array('fb' => $vfb, 'name' => $access->owner . '.ifb');
+ $view = new Horde_Kolab_FreeBusy_View_vfb($data);
+
+ /* Finish up */
+ Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ return $view;
+ }
+
+ /**
+ * Fetch the free/busy data for a user.
+ */
+ function &fetch()
+ {
+ global $conf;
+
+ /* Get the user requsted */
+ $req_owner = Horde_Util::getFormData('uid');
+
+ Horde::logMessage(sprintf("Starting generation of free/busy data for user %s",
+ $req_owner), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Validate folder access */
+ $access = new Horde_Kolab_FreeBusy_Access();
+ $result = $access->parseOwner($req_owner);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ Horde::logMessage(sprintf("Free/busy data of owner %s on server %s requested by user %s.",
+ $access->owner, $access->freebusyserver, $access->user),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $req_extended = Horde_Util::getFormData('extended', false);
+
+ /* Try to fetch the data if it is stored on a remote server */
+ $result = $access->fetchRemote(false, $req_extended);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_UNAUTHORIZED, 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ $this->_initCache();
+
+ $result = $this->_cache->load($access, $req_extended);
+ if (is_a($result, 'PEAR_Error')) {
+ $error = array('type' => FREEBUSY_ERROR_NOTFOUND, 'error' => $result);
+ $view = new Horde_Kolab_FreeBusy_View_error($error);
+ return $view;
+ }
+
+ Horde::logMessage("Delivering complete free/busy data.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ /* Generate the renderer */
+ $data = array('fb' => $result, 'name' => $access->owner . '.vfb');
+ $view = new Horde_Kolab_FreeBusy_View_vfb($data);
+
+ /* Finish up */
+ Horde::logMessage("Free/busy generation complete.", __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ return $view;
+ }
+
+ /**
+ * Regenerate the free/busy cache.
+ */
+ function ®enerate($reporter)
+ {
+ $access = new Horde_Kolab_FreeBusy_Access();
+ $result = $access->authenticated();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result->getMessage();
+ }
+
+ /* Load the required Kolab libraries */
+ require_once "Horde/Kolab/Storage/List.php";
+
+ $list = &Kolab_List::singleton();
+ $calendars = $list->getByType('event');
+ if (is_a($calendars, 'PEAR_Error')) {
+ return $calendars->getMessage();
+ }
+
+ $this->_initCache();
+
+ $lines = array();
+
+ foreach ($calendars as $calendar) {
+ /**
+ * We are using imap folders for our calendar list but
+ * the library expects us to follow the trigger format
+ * used by pfb.php
+ */
+ $req_domain = explode('@', $calendar->name);
+ if (isset($req_domain[1])) {
+ $domain = $req_domain[1];
+ } else {
+ $domain = null;
+ }
+ $req_folder = explode('/', $req_domain[0]);
+ if ($req_folder[0] == 'user') {
+ unset($req_folder[0]);
+ $owner = $req_folder[1];
+ unset($req_folder[1]);
+ } else if ($req_folder[0] == 'INBOX') {
+ $owner = $access->user;
+ unset($req_folder[0]);
+ }
+
+ $trigger = $owner . ($domain ? '@' . $domain : '') . '/' . join('/', $req_folder);
+ $trigger = Horde_String::convertCharset($trigger, 'UTF7-IMAP', 'UTF-8');
+
+ /* Validate folder access */
+ $result = $access->parseFolder($trigger);
+ if (is_a($result, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+
+ /* Hack for allowing manager access */
+ if ($access->user == 'manager') {
+ $imapc = &Horde_Kolab_IMAP::singleton($GLOBALS['conf']['kolab']['imap']['server'],
+ $GLOBALS['conf']['kolab']['imap']['port']);
+ $result = $imapc->connect($access->user, Horde_Auth::getCredential('password'));
+ if (is_a($result, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+ $acl = $imapc->getACL($calendar->name);
+ if (is_a($acl, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+ $oldacl = '';
+ if (isset($acl['manager'])) {
+ $oldacl = $acl['manager'];
+ }
+ $result = $imapc->setACL($calendar->name, 'manager', 'lrs');
+ if (is_a($result, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+ }
+
+ /* Update the cache */
+ $result = $this->_cache->store($access);
+ if (is_a($result, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+
+ /* Revert the acl */
+ if ($access->user == 'manager' && $oldacl) {
+ $result = $imapc->setACL($calendar->name, 'manager', $oldacl);
+ if (is_a($result, 'PEAR_Error')) {
+ $reporter->failure($calendar->name, $result->getMessage());
+ continue;
+ }
+ }
+
+ $reporter->success($calendar->name);
+
+ }
+ return $lines;
+ }
+}
+
+
--- /dev/null
+<?php
+/**
+ * The Horde_Kolab_FreeBusy_Access:: class provides functionality to check
+ * free/busy access rights for the specified folder.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Access.php,v 1.23 2009/07/08 18:39:07 slusarz Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Access {
+
+ /**
+ * The user calling the script.
+ *
+ * @var string
+ */
+ var $user;
+
+ /**
+ * Did the above combination authenticate?
+ *
+ * @var string
+ */
+ var $_authenticated = false;
+
+ /**
+ * The object representing the user calling the script.
+ *
+ * @var string
+ */
+ var $user_object;
+
+ /**
+ * The requested owner.
+ *
+ * @var string
+ */
+ var $owner;
+
+ /**
+ * The object representing the folder owner.
+ *
+ * @var string
+ */
+ var $owner_object;
+
+ /**
+ * The object representing the server configuration.
+ *
+ * @var string
+ */
+ var $server_object;
+
+ /**
+ * The folder we try to access.
+ *
+ * @var string
+ */
+ var $folder;
+
+ /**
+ * The IMAP path of folder we try to access.
+ *
+ * @var string
+ */
+ var $imap_folder;
+
+ /**
+ * The common name (CN) of the owner.
+ *
+ * @var string
+ */
+ var $cn = '';
+
+ /**
+ * The free/busy server for the folder owner.
+ *
+ * @var string
+ */
+ var $freebusyserver;
+
+ /**
+ * Constructor.
+ *
+ * @param array $params Any additional options
+ */
+ function Horde_Kolab_FreeBusy_Access()
+ {
+ $this->_parseUser();
+ }
+
+ /**
+ * Parse the requested folder for the owner of that folder.
+ *
+ * @param string $req_folder The folder requested.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function parseFolder($req_folder = '')
+ {
+ /* Handle the owner/folder name and make sure the owner part is in lower case */
+ $req_folder = Horde_String::convertCharset($req_folder, 'UTF-8', 'UTF7-IMAP');
+ $folder = explode('/', $req_folder);
+ if (count($folder) < 2) {
+ return PEAR::raiseError(sprintf(_("No such folder %s"), $req_folder));
+ }
+
+ $folder[0] = strtolower($folder[0]);
+ $req_folder = implode('/', $folder);
+ $this->owner = $folder[0];
+ unset($folder[0]);
+ $this->folder = join('/', $folder);
+
+ $result = $this->_process();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return true;
+ }
+
+ /**
+ * Parse the owner value.
+ *
+ * @param string $owner The owner that should be processed.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function parseOwner($owner = '')
+ {
+ $this->owner = $owner;
+
+ $result = $this->_process();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return true;
+ }
+
+ /**
+ * Fetch remote free/busy user if the current user is not local or
+ * redirect to the other server if configured this way.
+ *
+ * @param boolean $trigger Have we been called for triggering?
+ * @param boolean $extended Should the extended information been delivered?
+ */
+ function fetchRemote($trigger = false, $extended = false)
+ {
+ global $conf;
+
+ if (!empty($conf['kolab']['freebusy']['server'])) {
+ $server = $conf['kolab']['freebusy']['server'];
+ } else {
+ $server = 'https://localhost/freebusy';
+ }
+ if (!empty($conf['fb']['redirect'])) {
+ $do_redirect = $conf['fb']['redirect'];
+ } else {
+ $do_redirect = false;
+ }
+
+ if ($trigger) {
+ $path = sprintf('/trigger/%s/%s.' . ($extended)?'pxfb':'pfb',
+ urlencode($this->owner), urlencode($this->imap_folder));
+ } else {
+ $path = sprintf('/%s.' . ($extended)?'xfb':'ifb', urlencode($this->owner));
+ }
+
+ /* Check if we are on the right server and redirect if appropriate */
+ if ($this->freebusyserver && $this->freebusyserver != $server) {
+ $redirect = $this->freebusyserver . $path;
+ Horde::logMessage(sprintf("URL %s indicates remote free/busy server since we only offer %s. Redirecting.",
+ $this->freebusyserver, $server), __FILE__,
+ __LINE__, PEAR_LOG_ERR);
+ if ($do_redirect) {
+ header("Location: $redirect");
+ } else {
+ header("X-Redirect-To: $redirect");
+ $redirect = 'https://' . urlencode($this->user) . ':' . urlencode(Horde_Auth::getCredential('password'))
+ . '@' . $this->freebusyserver . $path;
+ if (!@readfile($redirect)) {
+ $message = sprintf(_("Unable to read free/busy information from %s"),
+ 'https://' . urlencode($this->user) . ':XXX'
+ . '@' . $this->freebusyserver . $_SERVER['REQUEST_URI']);
+ return PEAR::raiseError($message);
+ }
+ }
+ exit;
+ }
+ }
+
+ /**
+ * Check if we are in an authenticated situation.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function authenticated()
+ {
+ global $conf;
+
+ if (empty($this->user)) {
+ header('WWW-Authenticate: Basic realm="Kolab Freebusy"');
+ return PEAR::raiseError(_("Please authenticate!"));
+ }
+
+ if (!$this->_authenticated) {
+ return PEAR::raiseError(sprintf(_("Invalid authentication for user %s!"),
+ $this->user));
+ }
+ return true;
+ }
+
+ /**
+ * Parse the current user accessing the page and try to
+ * authenticate the user.
+ */
+ function _parseUser()
+ {
+ global $conf;
+
+ $this->user = Horde_Auth::getAuth();
+
+ if (empty($this->user)) {
+ $this->user = isset($_SERVER['PHP_AUTH_USER'])?$_SERVER['PHP_AUTH_USER']:false;
+ $pass = isset($_SERVER['PHP_AUTH_PW'])?$_SERVER['PHP_AUTH_PW']:false;
+ } else {
+ $this->_authenticated = true;
+ return;
+ }
+
+ // This part allows you to use the PHP scripts with CGI rather than as
+ // an apache module. This will of course slow down things but on the
+ // other hand it allows you to reduce the memory footprint of the
+ // apache server. The default is to use PHP as a module and the CGI
+ // version requires specific Apache configuration.
+ //
+ // The line you need to add to your configuration of the /freebusy
+ // location of your server looks like this:
+ //
+ // RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
+ //
+ // The complete section will probably look like this then:
+ //
+ // <IfModule mod_rewrite.c>
+ // RewriteEngine On
+ // # FreeBusy list handling
+ // RewriteBase /freebusy
+ // RewriteRule .* - [E=REMOTE_USER:%{HTTP:Authorization}]
+ // RewriteRule ^([^/]+)\.ifb freebusy.php?uid=$1 [L]
+ // RewriteRule ^([^/]+)\.vfb freebusy.php?uid=$1 [L]
+ // RewriteRule ^([^/]+)\.xfb freebusy.php?uid=$1&extended=1 [L]
+ // RewriteRule ^trigger/(.+)\.pfb pfb.php?folder=$1&cache=0 [L]
+ // RewriteRule ^(.+)\.pfb pfb.php?folder=$1&cache=1 [L]
+ // RewriteRule ^(.+)\.pxfb pfb.php?folder=$1&cache=1&extended=1 [L]
+ // </IfModule>
+ if (empty($this->user) && isset($_ENV['REDIRECT_REDIRECT_REMOTE_USER'])) {
+ $a = base64_decode(substr($_ENV['REDIRECT_REDIRECT_REMOTE_USER'], 6)) ;
+ if ((strlen($a) != 0) && (strcasecmp($a, ':') == 0)) {
+ list($this->user, $pass) = explode(':', $a, 2);
+ }
+ }
+
+ if (!empty($this->user)) {
+ /* Load the authentication libraries */
+ $auth = Horde_Auth::singleton(isset($conf['auth']['driver'])?$conf['auth']['driver']:'kolab');
+ if (!$this->_authenticated) {
+ $this->_authenticated = $auth->authenticate($this->user, array('password' => $pass), false);
+ }
+ if ($this->_authenticated) {
+ @session_start();
+ $_SESSION['__auth'] = array(
+ 'authenticated' => true,
+ 'userId' => $this->user,
+ 'timestamp' => time(),
+ 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
+ );
+ Horde_Auth::setCredential('password', $pass);
+ }
+ }
+ }
+
+ /**
+ * Process both the user accessing the page as well as the
+ * owner of the requested free/busy information.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function _process()
+ {
+ global $conf;
+
+ require_once 'Horde/Kolab/Server.php';
+
+ if (isset($conf['kolab']['ldap']['phpdn'])) {
+ $params = array(
+ 'uid' => $conf['kolab']['ldap']['phpdn'],
+ 'pass' => $conf['kolab']['ldap']['phppw'],
+ );
+ } else {
+ $params = array(
+ 'user' => Horde_Auth::getAuth(),
+ 'pass' => Horde_Auth::getCredential('password'),
+ );
+ }
+
+ /* Connect to the Kolab user database */
+ $db = &Horde_Kolab_Server::singleton($params);
+ // TODO: Remove once Kolab_Server has been fixed to always return the base dn
+ $db->fetch();
+
+ /* Retrieve the server configuration */
+ try {
+ $server = $db->fetch(sprintf('k=kolab,%s',
+ $db->getBaseUid()),
+ 'Horde_Kolab_Server_Object_Kolab_Server');
+ $this->server_object = $server;
+ } catch (Horde_Kolab_Server_Exception $e) {
+ Horde::logMessage(sprintf("Failed fetching the k=kolab configuration object. Error was: %s",
+ $e->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ $this->server_object = null;
+ }
+
+ /* Fetch the user calling us */
+ $udn = $db->uidForIdOrMail($this->user);
+ if (is_a($udn, 'PEAR_Error')) {
+ return $udn;
+ }
+ if ($udn) {
+ $user = $db->fetch($udn, 'Horde_Kolab_Server_Object_Kolab_User');
+ if (is_a($user, 'PEAR_Error')) {
+ return $user;
+ }
+ $this->user_object = $user;
+ }
+
+ if ($this->user_object && $this->user_object->exists()) {
+ $mail = $this->user_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_MAIL);
+ if (is_a($mail, 'PEAR_Error')) {
+ return $mail;
+ }
+ if ($mail) {
+ $this->user = $mail;
+ }
+ }
+
+ /* Fetch the owner of the free/busy data */
+ $odn = $db->uidForIdOrMailOrAlias($this->owner);
+ if (is_a($odn, 'PEAR_Error')) {
+ return $odn;
+ }
+ if (!$odn) {
+ $idx = strpos($this->user, '@');
+ if($idx !== false) {
+ $domain = substr($this->user, $idx+1);
+ Horde::logMessage(sprintf("Trying to append %s to %s",
+ $domain, $this->owner),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $odn = $odn = $db->uidForIdOrMail($this->owner . '@' . $domain);
+ }
+ }
+
+ if ($odn) {
+ $owner = $db->fetch($odn, 'Horde_Kolab_Server_Object_Kolab_User');
+ if (is_a($owner, 'PEAR_Error')) {
+ return $owner;
+ }
+ $this->owner_object = &$owner;
+ }
+
+ if (!empty($this->owner_object)) {
+ if ($this->owner_object->exists()) {
+ $this->owner = $this->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_MAIL);
+
+ $freebusyserver = $this->owner_object->getServer('freebusy');
+ if (!is_a($freebusyserver, 'PEAR_Error')) {
+ $this->freebusyserver = $freebusyserver;
+ }
+ }
+ } else {
+ return PEAR::raiseError(_("Unable to determine owner of the free/busy data!"));
+ }
+
+ /* Mangle the folder request into an IMAP folder name */
+ $this->imap_folder = $this->_getImapFolder();
+
+ return true;
+ }
+
+ /**
+ * Calculate the correct IMAP folder name to access based on the
+ * combination of user and owner.
+ *
+ * @return string The IMAP folder we should access.
+ */
+ function _getImapFolder()
+ {
+ $userdom = false;
+ $ownerdom = false;
+ if (ereg( '(.*)@(.*)', $this->user, $regs)) {
+ // Regular user
+ $user = $regs[1];
+ $userdom = $regs[2];
+ } else {
+ $user = $this->user;
+ }
+
+ if(ereg( '(.*)@(.*)', $this->owner, $regs)) {
+ // Regular owner
+ $owner = $regs[1];
+ $ownerdom = $regs[2];
+ } else {
+ $owner = $this->owner;
+ }
+
+ $fldrcomp = array();
+ if ($user == $owner) {
+ $fldrcomp[] = 'INBOX';
+ } else {
+ $fldrcomp[] = 'user';
+ $fldrcomp[] = $owner;
+ }
+
+ if (!empty($this->folder)) {
+ $fldrcomp[] = $this->folder;
+ }
+
+ $folder = join('/', $fldrcomp);
+ if ($ownerdom && !$userdom) {
+ $folder .= '@' . $ownerdom;
+ }
+ return $folder;
+ }
+
+}
+
--- /dev/null
+<?php
+/**
+ * Caching for the Kolab free/busy data.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * @package Kolab_FreeBusy
+ */
+
+/** We require the iCalendar library to build the free/busy list */
+require_once 'Horde/iCalendar.php';
+require_once 'Horde/iCalendar/vfreebusy.php';
+
+/**
+ * The Horde_Kolab_FreeBusy_Cache:: class provides functionality to store
+ * prepared free/busy data for quick retrieval.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache {
+
+ /**
+ * The directory that should be used for caching.
+ *
+ * @var string
+ */
+ var $_cache_dir;
+
+ /**
+ * Constructor.
+ *
+ * @param string $cache_dir The cache directory we should use.
+ */
+ function Horde_Kolab_FreeBusy_Cache($cache_dir)
+ {
+ $this->_cache_dir = $cache_dir;
+ }
+
+ /**
+ * Update the cache information for a calendar.
+ *
+ * @param Horde_Kolab_FreeBusy_Access $access The object holding the
+ * relevant access
+ * parameters.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function store($access)
+ {
+ global $conf;
+
+ /* Now we really need the free/busy library */
+ require_once 'Horde/Kolab/FreeBusy/Imap.php';
+
+ $fb = new Horde_Kolab_FreeBusy_Imap();
+
+ $result = $fb->connect($access->imap_folder);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $fbpast = $fbfuture = null;
+ try {
+ if (!empty($access->server_object)) {
+ $result = $access->server_object->get(Horde_Kolab_Server_Object_Kolab_Server::ATTRIBUTE_FBPAST);
+ if (!is_a($result, 'PEAR_Error')) {
+ $fbpast = $result;
+ }
+ }
+ } catch (Horde_Kolab_Server_Exception $e) {
+ Horde::logMessage(sprintf("Failed fetching the k=kolab configuration object. Error was: %s",
+ $e->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_ERR);
+ if (isset($conf['kolab']['freebusy']['past'])) {
+ $fbpast = $conf['kolab']['freebusy']['past'];
+ } else {
+ $fbpast = 10;
+ }
+ }
+
+ if (!empty($access->owner_object)) {
+ $result = $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_FBFUTURE);
+ if (!is_a($result, 'PEAR_Error')) {
+ $fbfuture = $result;
+ }
+ }
+
+ $vCal = $fb->generate(null, null,
+ !empty($fbpast)?$fbpast:0,
+ !empty($fbfuture)?$fbfuture:60,
+ $access->owner,
+ $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN));
+ if (is_a($vCal, 'PEAR_Error')) {
+ $vCal;
+ }
+
+ $fbfilename = $this->_getFilename($access->folder, $access->owner);
+
+ $c_pvcal = new Horde_Kolab_FreeBusy_Cache_File_pvcal($this->_cache_dir, $fbfilename);
+
+ if (!empty($conf['fb']['use_acls'])) {
+ $c_acl = new Horde_Kolab_FreeBusy_Cache_File_acl($this->_cache_dir, $fbfilename);
+ $c_xacl = new Horde_Kolab_FreeBusy_Cache_File_xacl($this->_cache_dir, $fbfilename);
+ }
+
+ /* missing data means delete the cache files */
+ if (empty($vCal)) {
+ Horde::logMessage(sprintf("No events. Purging cache %s.",
+ $fbfilename),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $result = $c_pvcal->purge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (!empty($conf['fb']['use_acls'])) {
+ $result = $c_acl->purge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $result = $c_xacl->purge();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ } else {
+ $result = $c_pvcal->storePVcal($vCal);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $relevance = $fb->getRelevance();
+ if (is_a($relevance, 'PEAR_Error')) {
+ return $relevance;
+ }
+
+ if (!empty($conf['fb']['use_acls'])) {
+ $acl = $fb->getACL();
+ if (is_a($acl, 'PEAR_Error')) {
+ return $acl;
+ }
+
+ /**
+ * Only store the acl information if the current user
+ * has admin rights on the folder and can actually
+ * retrieve the full ACL information.
+ *
+ * A folder that does not have admin rights for a user
+ * will not be considered relvant for that user unless
+ * it has been triggered by the folder owner before.
+ */
+ $append = false;
+ if (isset($acl[$access->user])) {
+ $myacl = $acl[$access->user];
+ if (strpos($myacl, 'a') !== false) {
+ $append = true;
+ }
+ }
+
+ $result = $c_acl->storeACL($acl, $relevance, $append);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $xacl = $fb->getExtendedACL();
+ if (is_a($xacl, 'PEAR_Error')) {
+ return $xacl;
+ }
+
+ $result = $c_xacl->storeXACL($xacl, $acl);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ } else {
+ $acl = null;
+ }
+
+ Horde::logMessage(sprintf("Horde_Kolab_FreeBusy_Cache::store(file=%s, relevance=%s, acl=%s, xacl=%s)",
+ $fbfilename, $relevance, $acl, $xacl),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ }
+ return true;
+ }
+
+ /**
+ * Load partial free/busy data.
+ *
+ * @param Horde_Kolab_FreeBusy_Access $access The object holding the
+ * relevant access
+ * parameters.
+ * @param boolean $extended Should the data hold the extended
+ * free/busy information?
+ *
+ * @return Horde_iCalendar|PEAR_Error The free/busy data of a
+ * single calendar.
+ */
+ function &loadPartial(&$access, $extended)
+ {
+ global $conf;
+
+ $file = $this->_getFilename($access->folder, $access->owner);
+
+ if (!empty($conf['fb']['use_acls'])) {
+ $aclcache = &Horde_Kolab_FreeBusy_Cache_DB_acl::singleton('acl',
+ $this->_cache_dir);
+ if ($extended) {
+ $extended = $this->_allowExtended($file, $access);
+ }
+ }
+
+ $c_pvcal = new Horde_Kolab_FreeBusy_Cache_File_pvcal($this->_cache_dir, $file);
+ $pvCal = $c_pvcal->loadPVcal($extended);
+ if (is_a($pvCal, 'PEAR_Error')) {
+ return $pvCal;
+ }
+ return $pvCal;
+ }
+
+ /**
+ * Load the complete free/busy data of a user.
+ *
+ * @param Horde_Kolab_FreeBusy_Access $access The object holding the
+ * relevant access
+ * parameters.
+ * @param boolean $extended Should the data hold the extended
+ * free/busy information?
+ *
+ * @return Horde_iCalendar|PEAR_Error The free/busy data for a user.
+ */
+ function &load(&$access, $extended)
+ {
+ global $conf;
+
+ /* Which files will we access? */
+ if (!empty($conf['fb']['use_acls'])) {
+ $aclcache = &Horde_Kolab_FreeBusy_Cache_DB_acl::singleton('acl', $this->_cache_dir);
+ $files = $aclcache->get($access->owner);
+ if (is_a($files, 'PEAR_Error')) {
+ return $files;
+ }
+ } else {
+ $file_uid = str_replace("\0", '', str_replace(".", "^", $access->owner));
+ $files = array();
+ $this->findAll_readdir($file_uid, $conf['fb']['cache_dir'].'/'.$file_uid, $files);
+ }
+
+ $owner = $access->owner;
+ if (ereg('(.*)@(.*)', $owner, $regs)) {
+ $owner = $regs[2] . '/' . $regs[1];
+ }
+ $user = $access->user;
+ if (ereg('(.*)@(.*)', $user, $regs)) {
+ $user = $regs[2] . '/' . $regs[1];
+ }
+ $c_file = str_replace("\0", '', str_replace('.', '^', $user . '/' . $owner));
+
+ $c_vcal = new Horde_Kolab_FreeBusy_Cache_File_vcal($this->_cache_dir,
+ $c_file, $extended);
+
+ /* If the current vCal cache did not expire, we can deliver it */
+ if (!$c_vcal->expired($files)) {
+ return $c_vcal->loadVcal();
+ }
+
+ // Create the new iCalendar.
+ $vCal = new Horde_iCalendar();
+ $vCal->setAttribute('PRODID', '-//kolab.org//NONSGML Kolab Server 2//EN');
+ $vCal->setAttribute('METHOD', 'PUBLISH');
+
+ // Create new vFreebusy.
+ $vFb = &Horde_iCalendar::newComponent('vfreebusy', $vCal);
+ $params = array();
+
+ $cn = $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN);
+ if (!empty($cn) || is_a($cn, 'PEAR_Error')) {
+ $params['cn'] = $access->owner_object->get(Horde_Kolab_Server_Object_Kolab_User::ATTRIBUTE_CN);
+ }
+ $vFb->setAttribute('ORGANIZER', 'MAILTO:' . $access->owner, $params);
+
+ $vFb->setAttribute('DTSTAMP', time());
+ if (isset($_SERVER['SERVER_NAME'])) {
+ $host = $_SERVER['SERVER_NAME'];
+ } else {
+ $host = 'localhost';
+ }
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $uri = $_SERVER['REQUEST_URI'];
+ } else {
+ $uri = '/';
+ }
+ $vFb->setAttribute('URL', 'http://' . $host . $uri);
+
+ $mtimes = array();
+ foreach ($files as $file) {
+ if ($extended && !empty($conf['fb']['use_acls'])) {
+ $extended_pvc = $this->_allowExtended($file, $access);
+ } else {
+ $extended_pvc = $extended;
+ }
+ $c_pvcal = new Horde_Kolab_FreeBusy_Cache_File_pvcal($this->_cache_dir, $file);
+ $pvCal = $c_pvcal->loadPVcal($extended_pvc);
+ if (is_a($pvCal, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Ignoring partial free/busy file %s: %s)",
+ $file, $pvCal->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_INFO);
+ continue;
+ }
+ $pvFb = &$pvCal->findComponent('vfreebusy');
+ if( !$pvFb ) {
+ Horde::logMessage(sprintf("Could not find free/busy info in file %s.)",
+ $file), __FILE__, __LINE__, PEAR_LOG_INFO);
+ continue;
+ }
+ if ($ets = $pvFb->getAttributeDefault('DTEND', false) !== false) {
+ // PENDING(steffen): Make value configurable
+ if ($ets < time()) {
+ Horde::logMessage(sprintf("Free/busy info in file %s is too old.)",
+ $file), __FILE__, __LINE__, PEAR_LOG_INFO);
+ $c_pvcal->purge();
+ continue;
+ }
+ }
+ $vFb->merge($pvFb);
+
+ /* Store last modification time */
+ $mtimes[$file] = array($c_pvcal->getFile(), $c_pvcal->getMtime());
+ }
+
+ if (!empty($conf['fb']['remote_servers'])) {
+ $remote_vfb = $this->_fetchRemote($conf['fb']['remote_servers'],
+ $access);
+ if (is_a($remote_vfb, 'PEAR_Error')) {
+ Horde::logMessage(sprintf("Ignoring remote free/busy files: %s)",
+ $remote_vfb->getMessage()),
+ __FILE__, __LINE__, PEAR_LOG_INFO);
+ } else {
+ $vFb->merge($remote_vfb);
+ }
+ }
+
+ if (!(boolean)$vFb->getBusyPeriods()) {
+ /* No busy periods in fb list. We have to add a
+ * dummy one to be standards compliant
+ */
+ $vFb->setAttribute('COMMENT', 'This is a dummy vfreebusy that indicates an empty calendar');
+ $vFb->addBusyPeriod('BUSY', 0,0, null);
+ }
+
+ $vCal->addComponent($vFb);
+
+ $c_vcal->storeVcal($vCal, $mtimes);
+
+ return $vCal;
+ }
+
+ /**
+ * Is extended access to the given file allowed?
+ *
+ * @param string $file Name of the cache file.
+ * @param Horde_Kolab_FreeBusy_Access $access The object holding the
+ * relevant access
+ * parameters.
+ *
+ * @return boolean|PEAR_Error True if extended access is allowed.
+ */
+ function _allowExtended($file, &$access)
+ {
+ if (!isset($access->user_object)) {
+ Horde::logMessage(sprintf("Extended attributes on folder %s disallowed for unknown user.",
+ $access->folder, $access->user), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return false;
+ }
+
+ $xaclcache = &Horde_Kolab_FreeBusy_Cache_DB_xacl::singleton('xacl', $this->_cache_dir);
+
+ /* Check if the calling user has access to the extended information of
+ * the folder we are about to integrate into the free/busy data.
+ */
+ $groups = $access->user_object->getGroupAddresses();
+ if (is_a($groups, 'PEAR_Error')) {
+ return $groups;
+ }
+
+ $groups[] = $access->user;
+ foreach ($groups as $id) {
+ if ($xaclcache->has($file, $id)) {
+ return true;
+ }
+ }
+ Horde::logMessage(sprintf("Extended attributes on folder %s disallowed for user %s.",
+ $access->folder, $access->user), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ return false;
+ }
+
+ /**
+ * Get a cache file name depending on the owner of the free/busy
+ * data.
+ *
+ * @param string $folder Name of the calendar folder.
+ * @param string $owner Owner of the calendar folder.
+ *
+ * @return string Name of the correspoding cache file.
+ */
+ function _getFilename($folder, $owner)
+ {
+ if (ereg('(.*)@(.*)', $owner, $regs)) {
+ $owner = $regs[2] . '/' . $regs[1];
+ }
+
+ return str_replace("\0", '', str_replace('.', '^', $owner . '/' . $folder));
+ }
+
+ /**
+ * Retrieve external free/busy data.
+ *
+ * @param array $servers The remote servers to query
+ * @param Horde_Kolab_FreeBusy_Access $access The object holding the
+ * relevant access
+ * parameters.
+ *
+ * @return Horde_iCalender The remote free/busy information.
+ */
+ function &_fetchRemote($servers, $access)
+ {
+ $vFb = null;
+
+ foreach ($servers as $server) {
+
+ $url = 'https://' . urlencode($access->user) . ':' . urlencode($access->pass)
+ . '@' . $server . $_SERVER['REQUEST_URI'];
+ $remote = @file_get_contents($url);
+ if (!$remote) {
+ $message = sprintf("Unable to read free/busy information from %s",
+ 'https://' . urlencode($access->user) . ':XXX'
+ . '@' . $server . $_SERVER['REQUEST_URI']);
+ Horde::logMessage($message, __FILE__, __LINE__, PEAR_LOG_INFO);
+ }
+
+ $rvCal = new Horde_iCalendar();
+ $result = $rvCal->parsevCalendar($remote);
+
+ if (is_a($result, 'PEAR_Error')) {
+ $message = sprintf("Unable to parse free/busy information from %s: %s",
+ 'https://' . urlencode($access->user) . ':XXX'
+ . '@' . $server . $_SERVER['REQUEST_URI'],
+ $result->getMessage());
+ Horde::logMessage($message, __FILE__, __LINE__, PEAR_LOG_INFO);
+ }
+
+ $rvFb = &$rvCal->findComponent('vfreebusy');
+ if (!$pvFb) {
+ $message = sprintf("Unable to find free/busy information in data from %s.",
+ 'https://' . urlencode($access->user) . ':XXX'
+ . '@' . $server . $_SERVER['REQUEST_URI']);
+ Horde::logMessage($message, __FILE__, __LINE__, PEAR_LOG_INFO);
+ }
+ if ($ets = $rvFb->getAttributeDefault('DTEND', false) !== false) {
+ // PENDING(steffen): Make value configurable
+ if ($ets < time()) {
+ $message = sprintf("free/busy information from %s is too old.",
+ 'https://' . urlencode($access->user) . ':XXX'
+ . '@' . $server . $_SERVER['REQUEST_URI']);
+ Horde::logMessage($message, __FILE__, __LINE__, PEAR_LOG_INFO);
+ }
+ }
+ if (!empty($vFb)) {
+ $vFb->merge($rvFb);
+ } else {
+ $vFb = $rvFb;
+ }
+ }
+ return $vFb;
+ }
+
+ function findAll_readdir($uid, $dirname, &$lst) {
+ if ($dir = @opendir($dirname)) {
+ while (($file = readdir($dir)) !== false) {
+ if ($file == "." || $file == "..")
+ continue;
+
+ $full_path = $dirname."/".$file;
+
+ if (is_file($full_path) && preg_match("/(.*)\.x?pvc$/", $file, $matches))
+ $lst[] = $uid."/".$matches[1];
+ else if(is_dir($full_path))
+ $this->findAll_readdir($uid."/".$file, $full_path, $lst);
+ }
+ closedir($dir);
+ }
+ }
+};
+
+/**
+ * A berkeley db based cache for free/busy data.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_DB {
+
+ /**
+ * The directory that should be used for caching.
+ *
+ * @var string
+ */
+ var $_cache_dir;
+
+ /**
+ * The resource handle into the database.
+ *
+ * @var resource
+ */
+ var $_db = false;
+
+ /**
+ * The format of the database.
+ *
+ * @var string
+ */
+ var $_dbformat;
+
+ /**
+ * The type of this cache.
+ *
+ * @var string
+ */
+ var $_type = '';
+
+ /**
+ * The directory that should be used for caching.
+ *
+ * @var string
+ */
+ function Horde_Kolab_FreeBusy_Cache_DB($cache_dir) {
+ global $conf;
+
+ $this->_cache_dir = $cache_dir;
+
+ if (!empty($conf['fb']['dbformat'])) {
+ $this->_dbformat = $conf['fb']['dbformat'];
+ } else {
+ $this->_dbformat = 'db4';
+ }
+
+ /* make sure that a database really exists before accessing it */
+ if (!file_exists($this->_cache_dir . '/' . $this->_type . 'cache.db')) {
+ $result = $this->_open();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ $this->_close();
+ }
+
+ }
+
+ /**
+ * Open the database.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function _open()
+ {
+ if ($this->_db !== false) {
+ return true;
+ }
+
+ $dbfile = $this->_cache_dir . '/' . $this->_type . 'cache.db';
+ $this->_db = dba_open($dbfile, 'cd', $this->_dbformat);
+ if ($this->_db === false) {
+ return PEAR::raiseError(sprintf("Unable to open freebusy cache db %s", $dbfile));
+ }
+ return true;
+ }
+
+ /**
+ * Close the database.
+ */
+ function _close()
+ {
+ if ($this->_db !== false) {
+ dba_close($this->_db);
+ }
+ $this->_db = false;
+ }
+
+ /**
+ * Set a cache file as irrelevant for a user.
+ *
+ * @param string $filename The cache file to remove.
+ * @param string $uid The user ID.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function _remove($filename, $uid)
+ {
+ $result = $this->_open();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (dba_exists($uid, $this->_db)) {
+ $lst = dba_fetch($uid, $this->_db);
+ $lst = explode(',', $lst);
+ $lst = array_diff($lst, array($filename));
+ $result = dba_replace($uid, join(',', $lst), $this->_db);
+ if ($result === false) {
+ $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
+ }
+ }
+ $this->_close();
+
+ return $result;
+ }
+
+ /**
+ * Set a cache file as relevant for a user.
+ *
+ * @param string $filename The cache file to add.
+ * @param string $uid The user ID.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function _add($filename, $uid)
+ {
+ if (empty($filename)) {
+ return true;
+ }
+
+ $result = $this->_open();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ if (dba_exists($uid, $this->_db)) {
+ $lst = dba_fetch($uid, $this->_db);
+ $lst = explode(',', $lst);
+ $lst[] = $filename;
+ $result = dba_replace($uid, join(',', array_keys(array_flip($lst))), $this->_db);
+ if ($result === false) {
+ $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
+ }
+ } else {
+ $result = dba_insert($uid, $filename, $this->_db);
+ if ($result === false) {
+ $result = PEAR::raiseError(sprintf("Unable to set db value for uid %s", $uid));
+ }
+ }
+ $this->_close();
+
+ return $result;
+ }
+
+ /**
+ * Is the cache file relevant for the user?
+ *
+ * @param string $filename The cache file.
+ * @param string $uid The user ID.
+ *
+ * @return boolean|PEAR_Error True if the cache file is relevant.
+ */
+ function has($filename, $uid)
+ {
+ $result = $this->_open();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = false;
+ if (dba_exists($uid, $this->_db)) {
+ $lst = dba_fetch($uid, $this->_db);
+ $lst = explode(',', $lst);
+ $result = in_array($filename, $lst);
+ }
+ $this->_close();
+
+ return $result;
+ }
+
+ /**
+ * Get the full list of relevant cache files for a uid.
+ *
+ * @param string $uid The user ID.
+ *
+ * @return array|PEAR_Error The list of cache files.
+ */
+ function get($uid)
+ {
+ $result = $this->_open();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = array();
+ if (dba_exists($uid, $this->_db)) {
+ $lst = dba_fetch($uid, $this->_db);
+ $lst = explode(',', $lst);
+ $result = array_filter($lst, array($this, '_notEmpty'));
+ }
+ $this->_close();
+
+ return $result;
+ }
+
+ /**
+ * Check if the value is set.
+ *
+ * @param mixed $value The value to check.
+ *
+ * @return boolean True if the value is set.
+ */
+ function _notEmpty($value)
+ {
+ return !empty($value);
+ }
+
+ /**
+ * Attempts to return a reference to a concrete FreeBusyACLCache
+ * instance. It will only create a new instance if no
+ * FreeBusyACLCache instance currently exists.
+ *
+ * This method must be invoked as:
+ * <code>$var = &FreeBusyACLCache::singleton($cache_dir);</code>
+ *
+ * @static
+ *
+ * @param string $type The type of the cache.
+ * @param string $cache_dir The directory for storing the cache.
+ *
+ * @return FreeBusyACLCache The concrete FreeBusyACLCache
+ * reference, or false on an error.
+ */
+ function &singleton($type, $cache_dir)
+ {
+ static $cachedb = array();
+
+ $signature = $type . $cache_dir;
+
+ if (empty($cachedb[$signature])) {
+ $class = 'Horde_Kolab_FreeBusy_Cache_DB_' . $type;
+ $cachedb[$signature] = new $class($cache_dir);
+ }
+
+ return $cachedb[$signature];
+ }
+}
+
+/**
+ * A berkeley db based cache for free/busy data that holds relevant
+ * cache files based on folder ACLs.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_DB_acl extends Horde_Kolab_FreeBusy_Cache_DB {
+
+ /**
+ * The type of this cache.
+ *
+ * @var string
+ */
+ var $_type = 'acl';
+
+ /**
+ * Store permissions on a calender folder.
+ *
+ * @param string $filename The cache file representing the calendar folder.
+ * @param array $acl The new ACL.
+ * @param array $oldacl The old ACL.
+ * @param mixed $perm False if all permissions should be revoked, a
+ * single character specifying allowed access
+ * otherwise.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function store($filename, $acl, $oldacl, $perm)
+ {
+ /* We remove the filename from all users listed in the old ACL first */
+ foreach ($oldacl as $user => $ac) {
+ $result = $this->_remove($filename, $user);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ /* Now add the filename for all users with the correct permissions */
+ if ($perm !== false ) {
+ foreach ($acl as $user => $ac) {
+ if (strpos($ac, $perm) !== false) {
+ if (!empty($user)) {
+ $result = $this->_add($filename, $user);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+}
+
+/**
+ * A berkeley db based cache for free/busy data that holds relevant
+ * cache files based on extended folder ACLs.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_DB_xacl extends Horde_Kolab_FreeBusy_Cache_DB {
+
+ /**
+ * The type of this cache.
+ *
+ * @var string
+ */
+ var $_type = 'xacl';
+
+ /**
+ * Store permissions on a calender folder.
+ *
+ * @param string $filename The cache file representing the calendar folder.
+ * @param array $xacl The new extended ACL.
+ * @param array $oldxacl The old extended ACL.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function store($filename, $xacl, $oldxacl)
+ {
+ $xacl = explode(' ', $xacl);
+ $oldxacl = explode(' ', $oldxacl);
+ $both = array_intersect($xacl, $oldxacl);
+
+ /* Removed access rights */
+ foreach (array_diff($oldxacl, $both) as $uid) {
+ if (!empty($uid)) {
+ $result = $this->_remove($filename, $uid);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+
+ /* Added access rights */
+ foreach (array_diff($xacl, $both) as $uid) {
+ if (!empty($uid)) {
+ $result = $this->_add($filename, $uid);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ }
+
+ return true;
+ }
+}
+
+/**
+ * A representation of a cache file.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_File {
+
+ /**
+ * The suffix of this cache file.
+ *
+ * @var string
+ */
+ var $_suffix = '';
+
+ /**
+ * Name of the cache file.
+ *
+ * @var string
+ */
+ var $_filename;
+
+ /**
+ * Full path to the cache file.
+ *
+ * @var string
+ */
+ var $_file;
+
+ /**
+ * Cache file version.
+ *
+ * @var int
+ */
+ var $_version = 1;
+
+ /**
+ * Construct the Horde_Kolab_FreeBusy_Cache_File instance.
+ *
+ * @param string $cache_dir The path to the cache direcory.
+ * @param string $filename The file name of the cache file.
+ * @param string $suffix The suffix of the cache file name.
+ */
+ function Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, $suffix = null)
+ {
+ if (!empty($suffix)) {
+ $this->_suffix = $suffix;
+ }
+
+ $this->_cache_dir = $cache_dir;
+ $this->_filename = $filename;
+ $this->_file = $this->_cache_dir . '/' . $this->_filename . '.' . $this->_suffix;
+ }
+
+ /**
+ * Get the full path to the cache file.
+ *
+ * @return string The full path to the file.
+ */
+ function getFile()
+ {
+ return $this->_file;
+ }
+
+ /**
+ * Clean the cache file contents.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function purge()
+ {
+ if (file_exists($this->_file)) {
+ $result = @unlink($this->_file);
+ if (!$result) {
+ return PEAR::raiseError(sprintf("Failed removing file %s",
+ $this->_file));
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Store data in the cache file.
+ *
+ * @param mixed $data A reference to the data object.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function store(&$data)
+ {
+ /* Create directories if missing */
+ $fbdirname = dirname($this->_file);
+ if (!is_dir($fbdirname)) {
+ $result = $this->_makeTree($fbdirname);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+
+ /* Store the cache data */
+ $fh = fopen($this->_file, 'w');
+ if (!$fh) {
+ return PEAR::raiseError(sprintf("Failed creating cache file %s!",
+ $this->_file));
+ }
+ fwrite($fh, serialize(array('version' => $this->_version,
+ 'data' => $data)));
+ fclose($fh);
+ return true;
+ }
+
+ /**
+ * Load data from the cache file.
+ *
+ * @return mixed|PEAR_Error The data retrieved from the cache file.
+ */
+ function &load()
+ {
+ $file = @file_get_contents($this->_file);
+ if ($file === false) {
+ return PEAR::raiseError(sprintf("%s failed reading cache file %s!",
+ get_class($this), $this->_file));
+ }
+ $cache = @unserialize($file);
+ if ($cache === false) {
+ return PEAR::raiseError(sprintf("%s failed to unserialize cache data from file %s!",
+ get_class($this), $this->_file));
+ }
+ if (!isset($cache['version'])) {
+ return PEAR::raiseError(sprintf("Cache file %s lacks version data!",
+ $this->_file));
+ }
+ $this->_version = $cache['version'];
+ if (!isset($cache['data'])) {
+ return PEAR::raiseError(sprintf("Cache file %s lacks data!",
+ $this->_file));
+ }
+ if ($cache['version'] != $this->_version) {
+ return PEAR::raiseError(sprintf("Cache file %s has version %s while %s is required!",
+ $this->_file, $cache['version'], $this->_version));
+ }
+ return $cache['data'];
+ }
+
+ /**
+ * Generate a tree of directories.
+ *
+ * @param string $dirname The path to a directory that should exist.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function _maketree($dirname)
+ {
+ $base = substr($dirname, 0, strrpos($dirname, '/'));
+ $base = str_replace(".", "^", $base);
+ if (!empty($base) && !is_dir($base)) {
+ $result = $this->_maketree($base);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ }
+ if (!file_exists($dirname)) {
+ $result = @mkdir($dirname, 0755);
+ if (!$result) {
+ return PEAR::raiseError(sprintf("Error creating directory %s", $dirname));
+ }
+ }
+ return true;
+ }
+}
+
+/**
+ * A cache file for partial free/busy information.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_File_pvcal extends Horde_Kolab_FreeBusy_Cache_File {
+
+ /**
+ * The suffix of this cache file.
+ *
+ * @var string
+ */
+ var $_suffix = 'pvc';
+
+ /**
+ * Store partial free/busy infomation in the cache file.
+ *
+ * @param Horde_iCalendar $pvcal A reference to the data object.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function storePVcal(&$pvcal)
+ {
+ return $this->store($pvcal);
+ }
+
+ /**
+ * Load partial free/busy data from the cache file.
+ *
+ * @param boolean $extended Should the extended information be retrieved?
+ *
+ * @return Horde_iCalendar|PEAR_Error The data retrieved from the cache file.
+ */
+ function &loadPVcal($extended)
+ {
+ $pvcal = $this->load();
+ if (is_a($pvcal, 'PEAR_Error')) {
+ return $pvcal;
+ }
+ if (!$extended) {
+ $components = &$pvcal->getComponents();
+ foreach ($components as $component) {
+ if ($component->getType() == 'vFreebusy') {
+ $component->_extraParams = array();
+ }
+ }
+ }
+ return $pvcal;
+ }
+
+ /**
+ * Return the last modification date of the cache file.
+ *
+ * @return int The last modification date.
+ */
+ function getMtime()
+ {
+ return filemtime($this->_file);
+ }
+}
+
+/**
+ * A cache file for complete free/busy information.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_File_vcal extends Horde_Kolab_FreeBusy_Cache_File {
+
+ /**
+ * The suffix of this cache file.
+ *
+ * @var string
+ */
+ var $_suffix = 'vc';
+
+ /**
+ * Cache file version.
+ *
+ * @var int
+ */
+ var $_version = 2;
+
+ /**
+ * Cached data.
+ *
+ * @var array
+ */
+ var $_data;
+
+ /**
+ * Construct the Horde_Kolab_FreeBusy_Cache_File_vcal instance.
+ *
+ * @param string $cache_dir The path to the cache direcory.
+ * @param string $filename The file name of the cache file.
+ * @param boolean $extended Does the cache hold extended data?
+ */
+ function Horde_Kolab_FreeBusy_Cache_File_vcal($cache_dir, $filename, $extended)
+ {
+ $extension = empty($extended) ? 'vc' : 'xvc';
+ parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, $extension);
+ }
+
+ /**
+ * Store free/busy infomation in the cache file.
+ *
+ * @param Horde_iCalendar $vcal A reference to the data object.
+ * @param array $mtimes A list of modification times for the
+ * partial free/busy cache times.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function storeVcal(&$vcal, &$mtimes)
+ {
+ $data = array('vcal' => $vcal,
+ 'mtimes' => $mtimes);
+ return $this->store($data);
+ }
+
+ /**
+ * Load the free/busy information from the cache.
+ *
+ * @return Horde_iCalendar|PEAR_Error The retrieved free/busy information.
+ */
+ function &loadVcal()
+ {
+ if ($this->_data) {
+ return $this->_data;
+ }
+
+ $result = $this->load();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $this->_data = $result['vcal'];
+
+ return $this->_data;
+ }
+
+ /**
+ * Check if the cached free/busy expired.
+ *
+ * @param array $files A list of partial free/busy cache files.
+ *
+ * @return boolean|PEAR_Error True if the cache expired.
+ */
+ function expired($files)
+ {
+ $result = $this->load();
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ /* Check the cache version */
+ if ($this->_version < 2) {
+ return true;
+ }
+
+ $this->_data = $result['vcal'];
+
+ /* Files changed? */
+ $keys = array_keys($result['mtimes']);
+ $changes = array_diff($keys, $files);
+ if (count($keys) != count($files) || !empty($changes)) {
+ return true;
+ }
+
+ /* Check the file mtimes */
+ foreach ($files as $file) {
+ if (filemtime($result['mtimes'][$file][0]) != $result['mtimes'][$file][1]) {
+ return true;
+ }
+ }
+
+ /* Older than three days? */
+ $components = $this->_data->getComponents();
+ foreach ($components as $component) {
+ if ($component->getType() == 'vFreebusy') {
+ $attr = $component->getAttribute('DTSTAMP');
+ if (!empty($attr) && !is_a($attr, 'PEAR_Error')) {
+ //Should be configurable
+ if (time() - (int)$attr > 259200) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+}
+
+/**
+ * A cache file for ACLs. This serves as a buffer between the DB based
+ * ACL storage and is required to hold the old ACL list for updates to
+ * the DB based cache.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_File_acl extends Horde_Kolab_FreeBusy_Cache_File {
+
+ /**
+ * The suffix of this cache file.
+ *
+ * @var string
+ */
+ var $_suffix = 'acl';
+
+ /**
+ * Link to the ACL stored in a data base.
+ *
+ * @var Horde_Kolab_FreeBusy_Cache_DB
+ */
+ var $_acls;
+
+ /**
+ * Construct the Horde_Kolab_FreeBusy_Cache_File_acl instance.
+ *
+ * @param string $cache_dir The path to the cache direcory.
+ * @param string $filename The file name of the cache file.
+ */
+ function Horde_Kolab_FreeBusy_Cache_File_acl($cache_dir, $filename)
+ {
+ $this->_acls = &Horde_Kolab_FreeBusy_Cache_DB::singleton('acl', $cache_dir);
+ parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, 'acl');
+ }
+
+ /**
+ * Clean the cache file contents.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function purge()
+ {
+ $oldacl = $this->load();
+ if (is_a($oldacl, 'PEAR_Error')) {
+ $oldacl = array();
+ }
+
+ $result = $this->_acls->store($this->_filename, array(), $oldacl, false);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return parent::purge();
+ }
+
+ /**
+ * Store a new ACL.
+ *
+ * @param array $acl The new ACL.
+ * @param string $relevance Folder relevance.
+ * @param string $append Should old entries be purged?
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function storeACL(&$acl, $relevance, $append = false)
+ {
+ if (!$append) {
+ $oldacl = $this->load();
+ if (is_a($oldacl, 'PEAR_Error')) {
+ $oldacl = array();
+ }
+ $acl = array_merge($oldacl, $acl);
+ } else {
+ $oldacl = array();
+ }
+
+ /* Handle relevance */
+ switch ($relevance) {
+ case 'readers':
+ $perm = 'r';
+ break;
+ case 'nobody':
+ $perm = false;
+ break;
+ case 'admins':
+ default:
+ $perm = 'a';
+ }
+
+ $result = $this->_acls->store($this->_filename, $acl, $oldacl, $perm);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->store($acl);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return true;
+ }
+}
+
+/**
+ * A cache file for extended ACLs. This serves as a buffer between the
+ * DB based ACL storage and is required to hold the old ACL list for
+ * updates to the DB based cache.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php,v 1.27 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Cache_File_xacl extends Horde_Kolab_FreeBusy_Cache_File {
+
+ /**
+ * The suffix of this cache file.
+ *
+ * @var string
+ */
+ var $_suffix = 'xacl';
+
+ /**
+ * Link to the ACL stored in a data base.
+ *
+ * @var Horde_Kolab_FreeBusy_Cache_DB
+ */
+ var $_xacls;
+
+ /**
+ * Construct the Horde_Kolab_FreeBusy_Cache_File_xacl instance.
+ *
+ * @param string $cache_dir The path to the cache direcory.
+ * @param string $filename The file name of the cache file.
+ */
+ function Horde_Kolab_FreeBusy_Cache_File_xacl($cache_dir, $filename)
+ {
+ $this->_xacls = &Horde_Kolab_FreeBusy_Cache_DB::singleton('xacl', $cache_dir);
+ parent::Horde_Kolab_FreeBusy_Cache_File($cache_dir, $filename, 'xacl');
+ }
+
+ /**
+ * Clean the cache file contents.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function purge()
+ {
+ $oldxacl = $this->load();
+ if (is_a($oldxacl, 'PEAR_Error')) {
+ $oldxacl = '';
+ }
+
+ $result = $this->_xacls->store($this->_filename, '', $oldxacl);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ return parent::purge();
+ }
+
+ /**
+ * Store a new extended ACL.
+ *
+ * @param array $xacl The new extended ACL.
+ * @param array $acl General ACL for the folder.
+ *
+ * @return boolean|PEAR_Error True if successful.
+ */
+ function storeXACL(&$xacl, &$acl)
+ {
+ $oldxacl = $this->load();
+ if (is_a($oldxacl, 'PEAR_Error')) {
+ $oldxacl = '';
+ }
+
+ /* Users with read access to the folder may also access the extended information */
+ foreach ($acl as $user => $ac) {
+ if (strpos($ac, 'r') !== false) {
+ if (!empty($user)) {
+ $xacl .= ' ' . $user;
+ }
+ }
+ }
+
+ $result = $this->_xacls->store($this->_filename, $xacl, $oldxacl);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+
+ $result = $this->store($xacl);
+ if (is_a($result, 'PEAR_Error')) {
+ return $result;
+ }
+ return true;
+ }
+}
--- /dev/null
+<?php
+/**
+ * IMAP access for Kolab free/busy.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php,v 1.10 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * @package Kolab_FreeBusy
+ */
+
+/** We need the Kolab Stores library for connecting to the IMAP storage. */
+require_once 'Horde/Kolab/Storage/List.php';
+
+/** We need the Date library for event handling. */
+require_once 'Horde/Date.php';
+
+/** We need the Recurrence library for recurrence handling. */
+require_once 'Horde/Date/Recurrence.php';
+
+/** Event status - Taken from Kronolith*/
+define('KRONOLITH_STATUS_NONE', 0);
+define('KRONOLITH_STATUS_TENTATIVE', 1);
+define('KRONOLITH_STATUS_CONFIRMED', 2);
+define('KRONOLITH_STATUS_CANCELLED', 3);
+define('KRONOLITH_STATUS_FREE', 4);
+
+/**
+ * The Horde_Kolab_Freebusy class provides a library for quickly
+ * generating free/busy information from the Kolab IMAP data.
+ *
+ * This class is a merged result from the Kolab free/busy package and
+ * the Horde::Kronolith free/busy driver.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php,v 1.10 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_Imap {
+
+ /**
+ * Our list of Kolab server IMAP folders.
+ *
+ * @var Kolab_List
+ */
+ var $_kolab = null;
+
+ /**
+ * The folder we are generating free/busy information for.
+ *
+ * @var Kolab_Folder
+ */
+ var $_folder;
+
+ /**
+ * The link to the folder data.
+ *
+ * @var Kolab_Data
+ */
+ var $_data;
+
+ /**
+ * Is this store relevant only for users or admins?
+ *
+ * @var string
+ */
+ var $_relevance;
+
+ /**
+ * Store ACLs.
+ *
+ * @var string
+ */
+ var $_acl;
+
+ /**
+ * Store extended attributes ACL.
+ *
+ * @var string
+ */
+ var $_xacl;
+
+ /**
+ * Initialize the free/busy IMAP handler.
+ */
+ function Horde_Kolab_FreeBusy_Imap()
+ {
+ $this->_kolab = &Kolab_List::singleton();
+ }
+
+ /**
+ * Connect to IMAP.
+ *
+ * This function has been derived from the synchronize() function
+ * in the Kolab driver for Kronolith.
+ *
+ * @param string $folder The folder to generate free/busy data for.
+ */
+ function connect($folder)
+ {
+ // Connect to the Kolab backend
+ $this->_folder = $this->_kolab->getFolder($folder);
+ if (is_a($this->_folder, 'PEAR_Error')) {
+ return $this->_folder;
+ }
+
+ $this->_data = $this->_folder->getData();
+ if (is_a($this->_data, 'PEAR_Error')) {
+ return $this->_data;
+ }
+ if (!$this->_folder->exists()) {
+ return PEAR::raiseError(sprintf(_("Folder %s does not exist!"), $folder));
+ }
+ $type = $this->_folder->getType();
+ if (is_a($type, 'PEAR_Error')) {
+ return $type;
+ }
+ if ($type != 'event') {
+ return PEAR::raiseError(sprintf(_("Folder %s has type \"%s\" not \"event\"!"),
+ $folder, $type));
+ }
+ }
+
+ /**
+ * Lists all events in the time range, optionally restricting
+ * results to only events with alarms.
+ *
+ * Taken from the Kolab driver for Kronolith.
+ *
+ * @param Horde_Date $startInterval Start of range date object.
+ * @param Horde_Date $endInterval End of range data object.
+ *
+ * @return array Events in the given time range.
+ */
+ function listEvents($startDate = null, $endDate = null)
+ {
+ $objects = $this->_data->getObjects();
+ if (is_a($objects, 'PEAR_Error')) {
+ return $objects;
+ }
+
+ if (is_null($startDate)) {
+ $startDate = new Horde_Date(array('mday' => 1, 'month' => 1, 'year' => 0000));
+ }
+ if (is_null($endDate)) {
+ $endDate = new Horde_Date(array('mday' => 31, 'month' => 12, 'year' => 9999));
+ }
+ $startts = $startDate->timestamp();
+ $endts = $endDate->timestamp();
+
+ $result = array();
+
+ foreach($objects as $object) {
+ /* check if event period intersects with given period */
+ if (!(($object['start-date'] > $endts) ||
+ ($object['end-date'] < $startts))) {
+ $event = new Kolab_Event($object);
+ $result[] = $event;
+ continue;
+ }
+
+ /* do recurrence expansion if not keeping anyway */
+ if (isset($object['recurrence'])) {
+ $event = new Kolab_Event($object);
+ $next = $event->recurrence->nextRecurrence($startDate);
+ while ($next !== false &&
+ $event->recurrence->hasException($next->year, $next->month, $next->mday)) {
+ $next->mday++;
+ $next = $event->recurrence->nextRecurrence($next);
+ }
+
+ if ($next !== false) {
+ $duration = $next->timestamp() - $event->start->timestamp();
+ $next_end = new Horde_Date($event->end->timestamp() + $duration);
+
+ if ((!(($endDate->compareDateTime($next) < 0) ||
+ ($startDate->compareDateTime($next_end) > 0)))) {
+ $result[] = $event;
+ }
+
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Fetch the relevance of this calendar folder.
+ *
+ * @return string|PEAR_Error Relevance of this folder.
+ */
+ function getRelevance() {
+
+ /* cached? */
+ if (isset($this->_relevance)) {
+ return $this->_relevance;
+ }
+
+ $annotation = $this->_folder->getKolabAttribute('incidences-for');
+ if (is_a($annotation, 'PEAR_Error')) {
+ return $annotation;
+ }
+
+ if (empty($annotation)) {
+ Horde::logMessage(sprintf('No relevance value found for %s', $this->_folder->name),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_relevance = 'admins';
+ } else {
+ Horde::logMessage(sprintf('Relevance for %s is %s', $this->_folder->name, $annotation),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_relevance = $annotation;
+ }
+ return $this->_relevance;
+ }
+
+ /**
+ * Fetch the ACL of this calendar folder.
+ *
+ * @return array|PEAR_Error IMAP ACL of this folder.
+ */
+ function getACL() {
+
+ /* cached? */
+ if (isset($this->_acl)) {
+ return $this->_acl;
+ }
+
+ $perm = $this->_folder->getPermission();
+ if (is_a($perm, 'PEAR_Error')) {
+ return $perm;
+ }
+
+ $acl = &$perm->acl;
+ if (empty($acl)) {
+ Horde::logMessage(sprintf('No ACL found for %s', $this->_folder->name),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_acl = array();
+ } else {
+ Horde::logMessage(sprintf('ACL for %s is %s',
+ $this->_folder->name, serialize($acl)),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_acl = $acl;
+ }
+ return $this->_acl;
+ }
+
+ /**
+ * Fetch the extended ACL of this calendar folder.
+ *
+ * @return array|PEAR_Error Extended ACL of this folder.
+ */
+ function getExtendedACL() {
+
+ /* cached? */
+ if (isset($this->_xacl)) {
+ return $this->_xacl;
+ }
+
+ $annotation = $this->_folder->getXfbaccess();
+ if (is_a($annotation, 'PEAR_Error')) {
+ return $annotation;
+ }
+
+ if (empty($annotation)) {
+ Horde::logMessage(sprintf('No extended ACL value found for %s', $this->_folder->name),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_xacl = '';
+ } else {
+ $annotation = join(' ', $annotation);
+ Horde::logMessage(sprintf('Extended ACL for %s is %s', $this->_folder->name, $annotation),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+ $this->_xacl = $annotation;
+ }
+ return $this->_xacl;
+ }
+
+ /**
+ * Generates the free/busy text for $calendar. Cache it for at least an
+ * hour, as well.
+ *
+ * @param integer $startstamp The start of the time period to retrieve.
+ * @param integer $endstamp The end of the time period to retrieve.
+ * @param integer $fbpast The number of days that free/busy should
+ * be calculated for the past
+ * @param integer $fbfuture The number of days that free/busy should
+ * be calculated for the future
+ * @param string $user Set organizer to this user.
+ * @param string $cn Set the common name of this user.
+ *
+ * @return Horde_iCalendar The iCal object or a PEAR error.
+ */
+ function &generate($startstamp = null, $endstamp = null,
+ $fbpast = 0, $fbfuture = 60,
+ $user = null, $cn = null)
+ {
+ /* Get the iCalendar library at this point */
+ require_once 'Horde/iCalendar.php';
+
+ /* Default the start date to today. */
+ if (is_null($startstamp)) {
+ $month = date('n');
+ $year = date('Y');
+ $day = date('j');
+
+ $startstamp = mktime(0, 0, 0, $month, $day - $fbpast, $year);
+ }
+
+ /* Default the end date to the start date + freebusy_days. */
+ if (is_null($endstamp) || $endstamp < $startstamp) {
+ $month = date('n');
+ $year = date('Y');
+ $day = date('j');
+
+ $endstamp = mktime(0, 0, 0,
+ $month,
+ $day + $fbfuture,
+ $year);
+ }
+
+ Horde::logMessage(sprintf('Creating free/busy information from %s to %s',
+ $startstamp, $endstamp), __FILE__, __LINE__,
+ PEAR_LOG_DEBUG);
+
+ /* Fetch events. */
+ $startDate = new Horde_Date($startstamp);
+ $endDate = new Horde_Date($endstamp);
+ $events = $this->listEvents($startDate, $endDate);
+ if (is_a($events, 'PEAR_Error')) {
+ return $events;
+ }
+
+ /* Create the new iCalendar. */
+ $vCal = new Horde_iCalendar();
+ $vCal->setAttribute('PRODID', '-//kolab.org//NONSGML Kolab Server 2//EN');
+ $vCal->setAttribute('METHOD', 'PUBLISH');
+
+ /* Create new vFreebusy. */
+ $vFb = &Horde_iCalendar::newComponent('vfreebusy', $vCal);
+ $params = array();
+ if ($cn) {
+ $params['cn'] = $cn;
+ }
+ $vFb->setAttribute('ORGANIZER', 'MAILTO:' . $user, $params);
+
+ $vFb->setAttribute('DTSTAMP', $_SERVER['REQUEST_TIME']);
+ $vFb->setAttribute('DTSTART', $startstamp);
+ $vFb->setAttribute('DTEND', $endstamp);
+ // URL is not required, so out it goes...
+ //$vFb->setAttribute('URL', Horde::applicationUrl('fb.php?u=' . $share->get('owner'), true, -1));
+
+ /* Add all the busy periods. */
+ foreach ($events as $event) {
+ if ($event->hasStatus(KRONOLITH_STATUS_FREE)) {
+ continue;
+ }
+
+ $duration = $event->end->timestamp() - $event->start->timestamp();
+ $extra = array('X-UID' => base64_encode($event->eventID),
+ 'X-SUMMARY' => base64_encode($event->private ? '' : $event->title),
+ 'X-LOCATION' => base64_encode($event->private ? '' : $event->location));
+
+ /* Make sure that we're using the current date for recurring
+ * events. */
+ if ($event->recurs()) {
+ $startThisDay = mktime($event->start->hour,
+ $event->start->min,
+ $event->start->sec,
+ date('n', $day),
+ date('j', $day),
+ date('Y', $day));
+ } else {
+ $startThisDay = $event->start->timestamp($extra);
+ }
+ if (!$event->recurs()) {
+ $vFb->addBusyPeriod('BUSY', $startThisDay, null, $duration, $extra);
+ } else {
+ $next = $event->recurrence->nextRecurrence($startDate);
+ while ($next) {
+ if ($endDate->compareDateTime($next) < 0) {
+ break;
+ }
+ if (!$event->recurrence->hasException($next->year, $next->month, $next->mday)) {
+ $vFb->addBusyPeriod('BUSY', $next->timestamp(), null, $duration, $extra);
+ }
+ $next->mday++;
+ $next = $event->recurrence->nextRecurrence($next);
+ }
+ }
+ }
+
+ /* Remove the overlaps. */
+ $vFb->simplify();
+ $vCal->addComponent($vFb);
+
+ return $vCal;
+ }
+}
+
+/**
+ * A reduced event representation derived from the Kronolith event
+ * representation.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php,v 1.10 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @package Kolab_FreeBusy
+ */
+class Kolab_Event {
+
+ /**
+ * The driver unique identifier for this event.
+ *
+ * @var string
+ */
+ var $eventID = null;
+
+ /**
+ * The start time of the event.
+ *
+ * @var Horde_Date
+ */
+ var $start;
+
+ /**
+ * The end time of the event.
+ *
+ * @var Horde_Date
+ */
+ var $end;
+
+ /**
+ * The title of this event.
+ *
+ * @var string
+ */
+ var $title = '';
+
+ /**
+ * The location this event occurs at.
+ *
+ * @var string
+ */
+ var $location = '';
+
+ /**
+ * Whether the event is private.
+ *
+ * @var boolean
+ */
+ var $private = false;
+
+ function Kolab_Event($event)
+ {
+ $this->eventID = $event['uid'];
+
+ $this->start = new Horde_Date($event['start-date']);
+ $this->end = new Horde_Date($event['end-date']);
+
+ if (isset($event['summary'])) {
+ $this->title = $event['summary'];
+ }
+
+ if (isset($event['location'])) {
+ $this->location = $event['location'];
+ }
+
+ if ($event['sensitivity'] == 'private' || $event['sensitivity'] == 'confidential') {
+ $this->private = true;
+ }
+
+ if (isset($event['show-time-as'])) {
+ switch ($event['show-time-as']) {
+ case 'free':
+ $this->status = KRONOLITH_STATUS_FREE;
+ break;
+
+ case 'tentative':
+ $this->status = KRONOLITH_STATUS_TENTATIVE;
+ break;
+
+ case 'busy':
+ case 'outofoffice':
+ default:
+ $this->status = KRONOLITH_STATUS_CONFIRMED;
+ }
+ } else {
+ $this->status = KRONOLITH_STATUS_CONFIRMED;
+ }
+
+ // Recurrence
+ if (isset($event['recurrence'])) {
+ $this->recurrence = new Horde_Date_Recurrence($this->start);
+ $this->recurrence->fromHash($event['recurrence']);
+ }
+
+ }
+
+ /**
+ * Returns whether this event is a recurring event.
+ *
+ * @return boolean True if this is a recurring event.
+ */
+ function recurs()
+ {
+ return isset($this->recurrence) &&
+ !$this->recurrence->hasRecurType(Horde_Date_Recurrence::RECUR_NONE);
+ }
+
+ /**
+ * Sets the global UID for this event.
+ *
+ * @param string $uid The global UID for this event.
+ */
+ function setUID($uid)
+ {
+ $this->_uid = $uid;
+ }
+
+ /**
+ * Checks whether the events status is the same as the specified value.
+ *
+ * @param integer $status The status value to check against.
+ *
+ * @return boolean True if the events status is the same as $status.
+ */
+ function hasStatus($status)
+ {
+ return ($status == $this->status);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * A view for regenerating the Kolab Free/Busy cache.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Report.php,v 1.2 2009/04/02 20:12:16 wrobel Exp $
+ *
+ * Copyright 2009 Klarälvdalens Datakonsult AB
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @package Kolab_FreeBusy
+ */
+
+class Horde_Kolab_FreeBusy_Report {
+
+ var $_break = '<br/>';
+
+ var $_errors = array();
+
+ function Horde_Kolab_FreeBusy_Report()
+ {
+ if (PHP_SAPI == 'cli') {
+ $this->_break = "\n";
+
+ /* Display errors if we are working on the command line */
+ ini_set('display_errors', 1);
+
+ /** Don't report notices */
+ error_reporting(E_ALL & ~E_NOTICE);
+ }
+ }
+
+ function start()
+ {
+ echo _("Starting to regenerate the free/busy cache...");
+ $this->linebreak(2);
+ }
+
+ function success($calendar)
+ {
+ echo sprintf(_("Successfully regenerated calendar \"%s\"!"),
+ $calendar);
+ $this->linebreak(1);
+ }
+
+ function failure($calendar, $error)
+ {
+ $this->_errors[] = sprintf(_("Failed regenerating calendar %s: %s"),
+ $calendar, $error->getMessage());
+ }
+
+ function stop()
+ {
+ if (!empty($this->_errors)) {
+ $this->linebreak(1);
+ echo _("Errors:");
+ $this->linebreak(1);
+ foreach ($this->_errors as $error) {
+ echo $error;
+ }
+ return false;
+ } else {
+ $this->linebreak(1);
+ echo _("Successfully regenerated all calendar caches!");
+ $this->linebreak(1);
+ return true;
+ }
+ }
+
+ function linebreak($count = 1)
+ {
+ for ($i = 0; $i < $count; $i++) {
+ echo $this->_break;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Provides simple views.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php,v 1.6 2008/12/12 15:00:07 wrobel Exp $
+ *
+ * @package Kolab_FreeBusy
+ */
+
+/**
+ * The Horde_Kolab_FreeBusy_View:: class renders data.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php,v 1.6 2008/12/12 15:00:07 wrobel Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_View {
+
+ /**
+ * The data that should get rendered.
+ *
+ * @var array
+ */
+ var $_data;
+
+ /**
+ * Constructor.
+ *
+ * @param array $data The data to display
+ */
+ function Horde_Kolab_FreeBusy_View(&$data)
+ {
+ $this->_data = $data;
+ }
+
+ /**
+ * Render the data.
+ */
+ function render()
+ {
+ echo 'Not implemented!';
+ }
+}
+
+/**
+ * The Horde_Kolab_FreeBusy_View_vfb:: class renders free/busy data.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php,v 1.6 2008/12/12 15:00:07 wrobel Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_View_vfb extends Horde_Kolab_FreeBusy_View {
+
+ /**
+ * The free/busy data that should be displayed.
+ *
+ * @var Horde_iCalendar
+ */
+ var $_vfb;
+
+ /**
+ * Current timestamp.
+ *
+ * @var int
+ */
+ var $_ts;
+
+ /**
+ * Constructor.
+ *
+ * @param Horde_iCalendar $vfb The free/busy data to display.
+ */
+ function Horde_Kolab_FreeBusy_View_vfb(&$data)
+ {
+
+ $data['vfb'] = $data['fb']->exportvCalendar();
+
+ $ts = time();
+
+ $components = &$data['fb']->getComponents();
+ foreach ($components as $component) {
+ if ($component->getType() == 'vFreebusy') {
+ $attr = $component->getAttribute('DTSTAMP');
+ if (!empty($attr) && !is_a($attr, 'PEAR_Error')) {
+ $ts = $attr;
+ break;
+ }
+ }
+ }
+
+ $data['ts'] = $ts;
+
+ Horde_Kolab_FreeBusy_View::Horde_Kolab_FreeBusy_View($data);
+ }
+
+ /**
+ * Display the free/busy information.
+ *
+ * @param string $content File name of the offered file.
+ */
+ function render()
+ {
+ global $conf;
+
+ if (!empty($conf['fb']['send_content_type'])) {
+ $send_content_type = $conf['fb']['send_content_type'];
+ } else {
+ $send_content_type = false;
+ }
+
+ if (!empty($conf['fb']['send_content_length'])) {
+ $send_content_length = $conf['fb']['send_content_length'];
+ } else {
+ $send_content_length = false;
+ }
+
+ if (!empty($conf['fb']['send_content_disposition'])) {
+ $send_content_disposition = $conf['fb']['send_content_disposition'];
+ } else {
+ $send_content_disposition = false;
+ }
+
+ /* Ensure that the data doesn't get cached along the way */
+ header('Cache-Control: no-store, no-cache, must-revalidate');
+ header('Cache-Control: post-check=0, pre-check=0', false);
+ header('Pragma: no-cache');
+ header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
+ header('Last-Modified: ' . gmdate("D, d M Y H:i:s", $this->_data['ts']) . ' GMT');
+ header('Pragma: public');
+ header('Content-Transfer-Encoding: none');
+ if ($send_content_type) {
+ header('Content-Type: text/calendar');
+ }
+ if ($send_content_length) {
+ header('Content-Length: ' . strlen($this->_data['vfb']));
+ }
+ if ($send_content_disposition) {
+ header('Content-Disposition: attachment; filename="' . $this->_data['name'] . '"');
+ }
+
+ echo $this->_data['vfb'];
+
+ exit(0);
+ }
+}
+
+/** Error types */
+define('FREEBUSY_ERROR_NOTFOUND', 0);
+define('FREEBUSY_ERROR_UNAUTHORIZED', 1);
+define('FREEBUSY_ERROR_SERVER', 2);
+
+/**
+ * The Horde_Kolab_FreeBusy_View_error:: class provides error pages for the
+ * Kolab free/busy system.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php,v 1.6 2008/12/12 15:00:07 wrobel Exp $
+ *
+ * Copyright 2004-2008 Klarälvdalens Datakonsult AB
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+ *
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_View_error extends Horde_Kolab_FreeBusy_View {
+
+ /**
+ * Display the error information.
+ */
+ function render()
+ {
+ switch ($this->_data['type']) {
+ case FREEBUSY_ERROR_NOTFOUND:
+ $this->notFound($this->_data['error']);
+ exit(1);
+ case FREEBUSY_ERROR_UNAUTHORIZED:
+ $this->unauthorized($this->_data['error']);
+ exit(1);
+ case FREEBUSY_ERROR_SERVER:
+ $this->serverError($this->_data['error']);
+ exit(1);
+ }
+ }
+
+ /**
+ * Deliver a "Not Found" page
+ *
+ * @param PEAR_Error $error The error.
+ */
+ function notFound($error)
+ {
+ $headers = array('HTTP/1.0 404 Not Found');
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $url = htmlentities($_SERVER['REQUEST_URI']);
+ } else {
+ $url = '/';
+ }
+ $message = sprintf(_("The requested URL %s was not found on this server."), $url);
+
+ $this->_errorPage($error, $headers, _("404 Not Found"), _("Not found"), $message);
+ }
+
+ /**
+ * Deliver a "Unauthorized" page
+ *
+ * @param PEAR_Error $error The error.
+ */
+ function unauthorized($error)
+ {
+ global $conf;
+
+ if (!empty($conf['kolab']['imap']['maildomain'])) {
+ $email_domain = $conf['kolab']['imap']['maildomain'];
+ } else {
+ $email_domain = 'localhost';
+ }
+
+ $headers = array('WWW-Authenticate: Basic realm="freebusy-' . $email_domain . '"',
+ 'HTTP/1.0 401 Unauthorized');
+
+ $this->_errorPage($error, $headers, _("401 Unauthorized"), _("Unauthorized"),
+ _("You are not authorized to access the requested URL."));
+ }
+
+ /**
+ * Deliver a "Server Error" page
+ *
+ * @param PEAR_Error $error The error.
+ */
+ function serverError($error)
+ {
+ $headers = array('HTTP/1.0 500 Server Error');
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $url = htmlentities($_SERVER['REQUEST_URI']);
+ } else {
+ $url = '/';
+ }
+ $this->_errorPage($error, $headers, _("500 Server Error"), _("Error"),
+ htmlentities($$url));
+ }
+
+ /**
+ * Deliver an error page
+ *
+ * @param PEAR_Error $error The error.
+ * @param array $headers The HTTP headers to deliver with the response
+ * @param string $title The page title
+ * @param string $headline The headline of the page
+ * @param string $body The message to display on the page
+ */
+ function _errorPage($error, $headers, $title, $headline, $body)
+ {
+ global $conf;
+
+ /* Print the headers */
+ foreach ($headers as $line) {
+ header($line);
+ }
+
+ /* Print the page */
+ echo "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n";
+ echo "<html><head><title>" . $title . "</title></head>\n";
+ echo "<body>\n";
+ echo "<h1>" . $headline . "</h1>\n";
+ echo "<p>" . $body . "</p>\n";
+ if (!empty($error)) {
+ echo "<hr><pre>" . $error->getMessage() . "</pre>\n";
+ Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR);
+ }
+ echo "<hr>\n";
+ echo isset($_SERVER['SERVER_SIGNATURE'])?$_SERVER['SERVER_SIGNATURE']:'' . "\n";
+ echo "</body></html>\n";
+ }
+}
+
--- /dev/null
+<?php
+/**
+ * Base for PHPUnit scenarios.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/Test/FreeBusy.php,v 1.5 2009/04/25 19:39:38 wrobel Exp $
+ *
+ * PHP version 5
+ *
+ * @category Kolab
+ * @package Kolab_Test
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+
+/**
+ * We need the unit test framework
+ */
+require_once 'Horde/Kolab/Test/Storage.php';
+
+/**
+ * Base for PHPUnit scenarios.
+ *
+ * $Horde: framework/Kolab_FreeBusy/lib/Horde/Kolab/Test/FreeBusy.php,v 1.5 2009/04/25 19:39:38 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_Test
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Storage
+ */
+class Horde_Kolab_Test_FreeBusy extends Horde_Kolab_Test_Storage
+{
+
+ /**
+ * Prepare the configuration.
+ *
+ * @return NULL
+ */
+ public function prepareConfiguration()
+ {
+ $fh = fopen(HORDE_BASE . '/config/conf.php', 'w');
+ $data = <<<EOD
+\$conf['use_ssl'] = 2;
+\$conf['server']['name'] = \$_SERVER['SERVER_NAME'];
+\$conf['server']['port'] = \$_SERVER['SERVER_PORT'];
+\$conf['debug_level'] = E_ALL;
+\$conf['umask'] = 077;
+\$conf['compress_pages'] = true;
+\$conf['menu']['always'] = false;
+\$conf['portal']['fixed_blocks'] = array();
+\$conf['imsp']['enabled'] = false;
+
+/** Additional config variables required for a clean Horde setup */
+\$conf['session']['use_only_cookies'] = false;
+\$conf['session']['timeout'] = 0;
+\$conf['cookie']['path'] = '/';
+\$conf['cookie']['domain'] = \$_SERVER['SERVER_NAME'];
+\$conf['use_ssl'] = false;
+\$conf['session']['cache_limiter'] = 'nocache';
+\$conf['session']['name'] = 'Horde';
+\$conf['log']['enabled'] = false;
+\$conf['prefs']['driver'] = 'session';
+\$conf['auth']['driver'] = 'kolab';
+\$conf['share']['driver'] = 'kolab';
+\$conf['debug_level'] = E_ALL;
+
+/** Make the share driver happy */
+\$conf['kolab']['enabled'] = true;
+
+/** Ensure we still use the LDAP test driver */
+\$conf['kolab']['server']['driver'] = 'test';
+
+/** Ensure that we do not trigger on folder update */
+\$conf['kolab']['no_triggering'] = true;
+
+/** Storage location for the free/busy system */
+\$conf['fb']['cache_dir'] = '/tmp';
+\$conf['kolab']['freebusy']['server'] = 'https://fb.example.org/freebusy';
+
+/** Setup the virtual file system for Kolab */
+\$conf['vfs']['params']['all_folders'] = true;
+\$conf['vfs']['type'] = 'kolab';
+
+\$conf['kolab']['ldap']['phpdn'] = null;
+\$conf['fb']['use_acls'] = true;
+EOD;
+ fwrite($fh, "<?php\n" . $data);
+ fclose($fh);
+ }
+
+ /**
+ * Tear down testing
+ */
+ public function tearDown()
+ {
+ if (file_exists('/tmp/aclcache.db')) {
+ unlink('/tmp/aclcache.db');
+ }
+ if (file_exists('/tmp/xaclcache.db')) {
+ unlink('/tmp/xaclcache.db');
+ }
+ if (file_exists('/tmp/example^org')) {
+ $this->unlinkDir('/tmp/example^org');
+ }
+ }
+
+ function unlinkDir($dir)
+ {
+ if(!$dh = @opendir($dir)) {
+ return;
+ }
+ while (false !== ($obj = readdir($dh))) {
+ if($obj == '.' || $obj == '..') {
+ continue;
+ }
+ if (!@unlink($dir . '/' . $obj)) {
+ $this->unlinkDir($dir . '/' . $obj);
+ }
+ }
+ closedir($dh);
+ @rmdir($dir);
+
+ return;
+ }
+
+ /**
+ * Handle a "given" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runGiven(&$world, $action, $arguments)
+ {
+ switch($action) {
+ default:
+ return parent::runGiven($world, $action, $arguments);
+ }
+ }
+
+ /**
+ * Handle a "when" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runWhen(&$world, $action, $arguments)
+ {
+ switch($action) {
+ case 'adding an event to a folder':
+ $world['result']['add_event'][] = $this->addEvent($arguments[0],
+ $arguments[1]);
+ break;
+ case 'triggering the folder':
+ include_once 'Horde/Kolab/FreeBusy.php';
+
+ $_GET['folder'] = $arguments[0];
+ $_GET['extended'] = '1';
+
+ $fb = &new Horde_Kolab_FreeBusy();
+
+ $world['result']['trigger'] = $fb->trigger();
+
+ break;
+ case 'fetching the free/busy information for':
+ include_once 'Horde/Kolab/FreeBusy.php';
+
+ $_GET['uid'] = $arguments[0];
+ $_GET['extended'] = '1';
+
+ $fb = &new Horde_Kolab_FreeBusy();
+
+ $world['result']['fetch'] = $fb->fetch();
+
+ break;
+ default:
+ return parent::runWhen($world, $action, $arguments);
+ }
+ }
+
+ /**
+ * Handle a "then" step.
+ *
+ * @param array &$world Joined "world" of variables.
+ * @param string $action The description of the step.
+ * @param array $arguments Additional arguments to the step.
+ *
+ * @return mixed The outcome of the step.
+ */
+ public function runThen(&$world, $action, $arguments)
+ {
+ switch($action) {
+ case 'the fetch result should contain a free/busy time with summary':
+ $this->assertTrue($this->freeBusyContainsSummary($world['result']['fetch']->_data['fb']->findComponent('vfreebusy'),
+ $arguments[0]));
+ break;
+ case 'the fetch result should not contain a free/busy time with summary':
+ $this->assertFalse($this->freeBusyContainsSummary($world['result']['fetch']->_data['fb']->findComponent('vfreebusy'),
+ $arguments[0]));
+ break;
+ default:
+ return parent::runThen($world, $action, $arguments);
+ }
+ }
+
+ public function freeBusyContainsSummary($vfb, $summary)
+ {
+ $params = $vfb->getExtraParams();
+ $present = false;
+ foreach ($params as $event) {
+ if (isset($event['X-SUMMARY'])
+ && base64_decode($event['X-SUMMARY']) == $summary) {
+ $present = true;
+ }
+ }
+ return $present;
+ }
+
+ /**
+ * Add an event.
+ *
+ * @return NULL
+ */
+ public function addEvent($event, $folder)
+ {
+ include_once 'Horde/Kolab/Storage.php';
+
+ $folder = Kolab_Storage::getShare($folder, 'event');
+ $this->assertNoError($folder);
+ $data = Kolab_Storage::getData($folder, 'event', 1);
+ $this->assertNoError($data);
+ /* Add the event */
+ $result = $data->save($event);
+ $this->assertNoError($result);
+ return $result;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Kolab_FreeBusy</name>
+ <channel>pear.horde.org</channel>
+ <summary>A package for providing free/busy information.</summary>
+ <description>This package provides free/busy information for the
+ users of a Kolab server. A Kolab client changing calendar data in an
+ IMAP folder is required to call the triggering script provided within
+ this package via HTTP. This will refresh the cache maintained by this
+ package with partial free/busy data. This partial data sets are
+ finally combined to the complete free/busy information once a client
+ requests this data for a particular user.
+ </description>
+ <lead>
+ <name>Gunnar Wrobel</name>
+ <user>wrobel</user>
+ <email>p@rdus.de</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Thomas Jarosch</name>
+ <user>jarosch</user>
+ <email>thomas.jarosch@intra2net.com</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <lead>
+ <name>Jan Schneider</name>
+ <user>jan</user>
+ <email>jan@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2009-04-25</date>
+ <version>
+ <release>0.1.5</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+ <notes>
+ * Adapted the package to the changes in Kolab_Server.
+ * kolab/issue3537 (Allowing xfb access to groups does not work)
+ * Improved handling of dba access.
+ * Extended support for more complex scenario testing.
+ </notes>
+ <contents>
+ <dir name="/">
+ <file name="COPYING" role="doc" />
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Kolab">
+ <file name="FreeBusy.php" role="php" />
+ <dir name="FreeBusy">
+ <file name="Access.php" role="php" />
+ <file name="Cache.php" role="php" />
+ <file name="Imap.php" role="php" />
+ <file name="Report.php" role="php" />
+ <file name="View.php" role="php" />
+ </dir> <!-- /lib/Horde/Kolab/FreeBusy -->
+ <dir name="Test">
+ <file name="FreeBusy.php" role="php" />
+ </dir> <!-- /lib/Horde/Kolab/Test -->
+ </dir> <!-- /lib/Horde/Kolab -->
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib -->
+ <dir name="test">
+ <dir name="Horde">
+ <dir name="Kolab">
+ <dir name="FreeBusy">
+ <file name="AllTests.php" role="test" />
+ <file name="FreeBusyTest.php" role="test" />
+ <file name="FreeBusyScenarioTest.php" role="test" />
+ </dir> <!-- /test/Horde/Kolab/FreeBusy -->
+ </dir> <!-- /test/Horde/Kolab -->
+ </dir> <!-- /test/Horde -->
+ </dir> <!-- /test -->
+ <dir name="www">
+ <dir name="Horde">
+ <dir name="Kolab">
+ <dir name="FreeBusy">
+ <file name="config.php" role="www" />
+ <file name="freebusy.php" role="www" />
+ <file name="pfb.php" role="www" />
+ <file name="regenerate.php" role="www" />
+ </dir> <!-- /www/Horde/Kolab/FreeBusy -->
+ </dir> <!-- /www/Horde/Kolab -->
+ </dir> <!-- /www/Horde -->
+ </dir> <!-- /www -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.0.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.7.0</min>
+ </pearinstaller>
+ <package>
+ <name>Kolab_Format</name>
+ <channel>pear.horde.org</channel>
+ <min>1.0.1</min>
+ </package>
+ <package>
+ <name>Kolab_Server</name>
+ <channel>pear.horde.org</channel>
+ <min>0.5.0</min>
+ </package>
+ <package>
+ <name>Kolab_Storage</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ <package>
+ <name>Horde_iCalendar</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ <package>
+ <name>Horde_Date</name>
+ <channel>pear.horde.org</channel>
+ </package>
+ </required>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Kolab/FreeBusy.php" as="Horde/Kolab/FreeBusy.php" />
+ <install name="lib/Horde/Kolab/FreeBusy/Access.php" as="Horde/Kolab/FreeBusy/Access.php" />
+ <install name="lib/Horde/Kolab/FreeBusy/Cache.php" as="Horde/Kolab/FreeBusy/Cache.php" />
+ <install name="lib/Horde/Kolab/FreeBusy/Imap.php" as="Horde/Kolab/FreeBusy/Imap.php" />
+ <install name="lib/Horde/Kolab/FreeBusy/Report.php" as="Horde/Kolab/FreeBusy/Report.php" />
+ <install name="lib/Horde/Kolab/FreeBusy/View.php" as="Horde/Kolab/FreeBusy/View.php" />
+ <install name="lib/Horde/Kolab/Test/FreeBusy.php" as="Horde/Kolab/Test/FreeBusy.php" />
+ <install name="test/Horde/Kolab/FreeBusy/AllTests.php" as="Horde/Kolab/FreeBusy/AllTests.php" />
+ <install name="test/Horde/Kolab/FreeBusy/FreeBusyTest.php" as="Horde/Kolab/FreeBusy/FreeBusyTest.php" />
+ <install name="test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php" as="Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php" />
+ <install name="www/Horde/Kolab/FreeBusy/config.php" as="config.php" />
+ <install name="www/Horde/Kolab/FreeBusy/freebusy.php" as="freebusy.php" />
+ <install name="www/Horde/Kolab/FreeBusy/pfb.php" as="pfb.php" />
+ <install name="www/Horde/Kolab/FreeBusy/regenerate.php" as="regenerate.php" />
+ </filelist>
+ </phprelease>
+ <changelog>
+ <release>
+ <date>2009-04-02</date>
+ <version>
+ <release>0.1.4</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+ <notes>
+ * kolab/issue3450 (freebusy/user@example.com.ifb returns extended
+ free/busy information)
+ * kolab/issue3527 (no immediately printing of folders during
+ freebusy cache regeneration)
+ </notes>
+ </release>
+ <release>
+ <date>2009-03-06</date>
+ <version>
+ <release>0.1.3</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+ <notes>
+ * SERVER_NAME undefined in freebusy/regenerate.php (kolab/issue3446,
+ https://www.intevation.de/roundup/kolab/issue3446)
+ * freebusy cache regeneration on the command line looks confusing
+ (kolab/issue3415, https://www.intevation.de/roundup/kolab/issue3415)
+ * kolabFreeBusyPast is not used (kolab/issue3438,
+ https://www.intevation.de/roundup/kolab/issue3438)
+ * Fixed manager access in the regeneration script
+ kolab/issue3313 (free/busy regeneration as manager broken in 2.2.1-beta1)
+ </notes>
+ </release>
+ <release>
+ <date>2008-12-12</date>
+ <version>
+ <release>0.1.2</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html">LGPL</license>
+ <notes>
+ * Fixed copyright information.
+ </notes>
+ </release>
+ <release>
+ <date>2008-12-05</date>
+ <version>
+ <release>0.1.1</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>
+ * Fixed handling of extended free/busy information.
+ * Fixed storing acl values when the folder is triggered by a foreign user
+ kolab/issue3208 (Free/Busy list is always empty)
+ * Fixed identification of the corresponding free/busy server.
+ * Implemented extended free/busy access concept.
+ * kolab/issue3256 (resmgr responses should reflect server revision in PRODID)
+ </notes>
+ </release>
+ <release>
+ <date>2008-10-29</date>
+ <version>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
+ </version>
+ <stability>
+ <release>alpha</release>
+ <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>
+ * Initial release.
+ </notes>
+ </release>
+ </changelog>
+</package>
--- /dev/null
+<?php
+/**
+ * All tests for the Kolab_FreeBusy:: package.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/AllTests.php,v 1.2 2009/01/06 17:49:24 jan Exp $
+ *
+ * @package Kolab_FreeBusy
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_Kolab_FreeBusy_AllTests::main');
+}
+
+require_once 'PHPUnit/Framework/TestSuite.php';
+require_once 'PHPUnit/TextUI/TestRunner.php';
+
+/**
+ * Combine the tests for this package.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/AllTests.php,v 1.2 2009/01/06 17:49:24 jan Exp $
+ *
+ * Copyright 2007-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @package Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_AllTests {
+
+ public static function main()
+ {
+ PHPUnit_TextUI_TestRunner::run(self::suite());
+ }
+
+ public static function suite()
+ {
+ $suite = new PHPUnit_Framework_TestSuite('Horde Framework - Horde_Kolab_FreeBusy');
+
+ $basedir = dirname(__FILE__);
+ $baseregexp = preg_quote($basedir . DIRECTORY_SEPARATOR, '/');
+
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($basedir)) as $file) {
+ if ($file->isFile() && preg_match('/Test.php$/', $file->getFilename())) {
+ $pathname = $file->getPathname();
+ require $pathname;
+
+ $class = str_replace(DIRECTORY_SEPARATOR, '_',
+ preg_replace("/^$baseregexp(.*)\.php/", '\\1', $pathname));
+ $suite->addTestSuite('Horde_Kolab_FreeBusy_' . $class);
+ }
+ }
+
+ return $suite;
+ }
+
+}
+
+if (PHPUnit_MAIN_METHOD == 'Horde_Kolab_FreeBusy_AllTests::main') {
+ Horde_Kolab_FreeBusy_AllTests::main();
+}
--- /dev/null
+<?php
+/**
+ * Checks for the Kolab Free/Busy system.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php,v 1.4 2009/04/25 19:30:35 wrobel Exp $
+ *
+ * PHP version 5
+ *
+ * @category Horde
+ * @package Share
+ * @subpackage UnitTests
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Share
+ */
+
+/**
+ * We need the base class
+ */
+require_once 'Horde/Kolab/Test/FreeBusy.php';
+
+/**
+ * Checks for the Kolab Free/Busy system.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php,v 1.4 2009/04/25 19:30:35 wrobel Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_Server
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_Server
+ */
+class Horde_Kolab_FreeBusy_FreeBusyScenarioTest extends Horde_Kolab_Test_FreeBusy
+{
+ /**
+ * Test triggering a calendar folder.
+ *
+ * @scenario
+ *
+ * @return NULL
+ */
+ public function triggering()
+ {
+ $this->given('a populated Kolab setup')
+ ->when('logging in as a user with a password', 'wrobel', 'none')
+ ->and('create a Kolab default calendar with name', 'Calendar')
+ ->and('triggering the folder', 'wrobel@example.org/Calendar')
+ ->then('the login was successful')
+ ->and('the creation of the folder was successful')
+ ->and('the result should be an object of type', 'Horde_Kolab_FreeBusy_View_vfb');
+ }
+
+ /**
+ * Test fetching free/busy data.
+ *
+ * @scenario
+ *
+ * @return NULL
+ */
+ public function fetching()
+ {
+ $now = time();
+ $event = array(
+ 'uid' => 1,
+ 'summary' => 'hello',
+ 'start-date' => $now,
+ 'end-date' => $now + 120,
+ );
+
+ $this->given('a populated Kolab setup')
+ ->when('logging in as a user with a password', 'wrobel', 'none')
+ ->and('create a Kolab default calendar with name', 'Calendar')
+ ->and('adding an event to a folder', $event, 'INBOX/Calendar')
+ ->and('triggering the folder', 'wrobel@example.org/Calendar')
+ ->and('fetching the free/busy information for', 'wrobel@example.org')
+ ->then('the login was successful')
+ ->and('the creation of the folder was successful')
+ ->and('the fetch result should contain a free/busy time with summary', 'hello');
+ }
+
+ /**
+ * Test fetching free/busy data as a foreign user should not contain
+ * extended information.
+ *
+ * @scenario
+ *
+ * @return NULL
+ */
+ public function fetchingAsForeignUser()
+ {
+ $now = time();
+ $event = array(
+ 'uid' => 1,
+ 'summary' => 'hello',
+ 'start-date' => $now,
+ 'end-date' => $now + 120,
+ );
+
+ $this->given('a populated Kolab setup')
+ ->when('logging in as a user with a password', 'wrobel', 'none')
+ ->and('create a Kolab default calendar with name', 'Calendar')
+ ->and('adding an event to a folder', $event, 'INBOX/Calendar')
+ ->and('triggering the folder', 'wrobel@example.org/Calendar')
+ ->and('logging in as a user with a password', 'test', 'test')
+ ->and('fetching the free/busy information for', 'wrobel@example.org')
+ ->then('the login was successful')
+ ->and('the creation of the folder was successful')
+ ->and('the fetch result should not contain a free/busy time with summary', 'hello');
+ }
+
+ /**
+ * Test fetching free/busy data as a foreign user in group with read access
+ * should contain extended information.
+ *
+ * @scenario
+ *
+ * @return NULL
+ */
+ public function fetchingAsForeignUserInSameGroup()
+ {
+ $now = time();
+ $event = array(
+ 'uid' => 1,
+ 'summary' => 'hello',
+ 'start-date' => $now,
+ 'end-date' => $now + 120,
+ );
+
+ $this->given('a populated Kolab setup')
+ ->when('logging in as a user with a password', 'wrobel', 'none')
+ ->and('create a Kolab default calendar with name', 'Calendar')
+ ->and('allow a group full access to a folder', 'group@example.org', 'INBOX/Calendar')
+ ->and('adding an event to a folder', $event, 'INBOX/Calendar')
+ ->and('triggering the folder', 'wrobel@example.org/Calendar')
+ ->and('logging in as a user with a password', 'test', 'test')
+ ->and('fetching the free/busy information for', 'wrobel@example.org')
+ ->then('the login was successful')
+ ->and('the creation of the folder was successful')
+ ->and('the fetch result should contain a free/busy time with summary', 'hello');
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Test the Kolab free/busy system.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyTest.php,v 1.13 2009/06/09 23:23:38 slusarz Exp $
+ *
+ * @category Kolab
+ * @package Kolab_FreeBusy
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+
+/**
+ * We need the base class
+ */
+require_once 'Horde/Kolab/Test/Storage.php';
+
+require_once 'Horde/Kolab/FreeBusy.php';
+
+/**
+ * Test the Kolab free/busy system.
+ *
+ * $Horde: framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyTest.php,v 1.13 2009/06/09 23:23:38 slusarz Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @category Kolab
+ * @package Kolab_FreeBusy
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=Kolab_FreeBusy
+ */
+class Horde_Kolab_FreeBusy_FreeBusyTest extends Horde_Kolab_Test_FreeBusy
+{
+
+ /**
+ * Test setup.
+ */
+ public function setUp()
+ {
+ $world = $this->prepareBasicSetup();
+
+ global $conf;
+ $conf['kolab']['ldap']['phpdn'] = null;
+ $conf['fb']['cache_dir'] = '/tmp';
+ $conf['kolab']['freebusy']['server'] = 'https://fb.example.org/freebusy';
+ $conf['fb']['use_acls'] = true;
+
+ $this->assertTrue($world['auth']->authenticate('wrobel@example.org',
+ array('password' => 'none')));
+
+ $folder = $world['storage']->getNewFolder();
+ $folder->setName('Calendar');
+ $this->assertNoError($folder->save(array('type' => 'event',
+ 'default' => true)));
+
+ $this->server = $world['server'];
+ $this->auth = $world['auth'];
+ }
+
+ /**
+ * Add an event.
+ *
+ * @return NULL
+ */
+ public function _addEvent($start)
+ {
+ include_once 'Horde/Kolab/Storage.php';
+
+ $folder = Kolab_Storage::getShare('INBOX/Calendar', 'event');
+ $data = Kolab_Storage::getData($folder, 'event', 1);
+ $object = array(
+ 'uid' => 1,
+ 'summary' => 'test',
+ 'start-date' => $start,
+ 'end-date' => $start + 120,
+ );
+
+ /* Add the event */
+ $result = $data->save($object);
+ if (is_a($result, 'PEAR_Error')) {
+ $this->assertEquals('', $result->getMessage());
+ }
+ }
+
+ /**
+ * Test getting free/busy information.
+ *
+ * @return NULL
+ */
+ public function testFetch()
+ {
+ $start = time();
+
+ $this->_addEvent($start);
+
+ $_GET['folder'] = 'wrobel@example.org/Calendar';
+
+ $fb = &new Horde_Kolab_FreeBusy();
+
+ /** Trigger the free/busy cache update */
+ $view = $fb->trigger();
+ $this->assertTrue(is_a($view, 'Horde_Kolab_FreeBusy_View_vfb'));
+
+ $vcal = $view->_data['fb'];
+
+ $vfb = $vcal->findComponent('VFREEBUSY');
+ $p = $vfb->getBusyPeriods();
+
+ $this->assertTrue($p[$start] == $start + 120);
+ }
+
+ /**
+ * Test triggering.
+ *
+ * @return NULL
+ */
+ public function testTrigger()
+ {
+ $_GET['folder'] = 'wrobel@example.org/Calendar';
+ $_GET['extended'] = '1';
+
+ $req_folder = Horde_Util::getFormData('folder', '');
+ $access = &new Horde_Kolab_FreeBusy_Access();
+ $result = $access->parseFolder($req_folder);
+ $this->assertEquals('wrobel@example.org', $access->owner);
+
+ $result = $this->server->uidForIdOrMailOrAlias($access->owner);
+ $this->assertEquals('cn=Gunnar Wrobel,dc=example,dc=org', $result);
+
+ $result = $this->server->fetch($result, 'Horde_Kolab_Server_Object_Kolab_User');
+ $this->assertNoError($result);
+
+ $fb = &new Horde_Kolab_FreeBusy();
+ $view = $fb->trigger();
+ $this->assertEquals('Horde_Kolab_FreeBusy_View_vfb', get_class($view));
+
+ /** Test triggering an invalid folder */
+ $_GET['folder'] = '';
+
+ $fb = &new Horde_Kolab_FreeBusy();
+
+ /** Trigger the free/busy cache update */
+ $view = $fb->trigger();
+ $this->assertTrue(is_a($view, 'Horde_Kolab_FreeBusy_View_error'));
+ $this->assertEquals('No such folder ', $view->_data['error']->getMessage());
+ }
+
+ /**
+ * Test triggering the folder of another user.
+ *
+ * @return NULL
+ */
+ public function testForeignTrigger()
+ {
+ $start = time();
+
+ $this->_addEvent($start);
+
+ $this->assertTrue($this->auth->authenticate('test@example.org',
+ array('password' => 'test')));
+
+ $_GET['folder'] = 'wrobel@example.org/Calendar';
+ $_GET['extended'] = '1';
+
+ $fb = &new Horde_Kolab_FreeBusy();
+ $view = $fb->trigger();
+ $this->assertEquals('Horde_Kolab_FreeBusy_View_vfb', get_class($view));
+
+ $vcal = $view->_data['fb'];
+
+ $vfb = $vcal->findComponent('VFREEBUSY');
+ $p = $vfb->getBusyPeriods();
+
+ $this->assertTrue($p[$start] == $start + 120);
+ }
+}
--- /dev/null
+<?php
+/**
+ * This file provides configuration settings for both the freebusy.php
+ * and the pfb.php scripts.
+ *
+ * $Horde: framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/config.php,v 1.4 2009/01/06 17:49:24 jan Exp $
+ *
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Thomas Arendsen Hein <thomas@intevation.de>
+ * @package Kolab_FreeBusy
+ */
+
+$conf = array();
+
+/* Horde::Log configuration */
+$conf['log']['enabled'] = true;
+$conf['log']['priority'] = PEAR_LOG_DEBUG; // Leave this on DEBUG for now. We just restructured the package...
+$conf['log']['type'] = 'file';
+$conf['log']['name'] = '/kolab/var/kolab-freebusy/log/freebusy.log';
+$conf['log']['ident'] = 'Kolab Free/Busy';
+$conf['log']['params']['append'] = true;
+
+/* PHP error logging */
+ini_set('error_log', '/kolab/var/kolab-freebusy/log/php-error.log');
+
+/* Horde::Kolab::LDAP configuration */
+$conf['kolab']['ldap']['server'] = 'example.com';
+$conf['kolab']['ldap']['basedn'] = 'dc=example,dc=com';
+$conf['kolab']['ldap']['phpdn'] = 'cn=nobody,cn=internal,dc=example,dc=com';
+$conf['kolab']['ldap']['phppw'] = 'xyz';
+
+/* Horde::Kolab::IMAP configuration */
+$conf['kolab']['imap']['server'] = 'example.com';
+$conf['kolab']['imap']['port'] = 143;
+$conf['kolab']['imap']['protocol'] = 'notls/readonly';
+
+/* Horde::Auth configuration */
+$conf['auth']['params']['login_block'] = 0;
+$conf['auth']['checkbrowser'] = false;
+$conf['auth']['checkip'] = false;
+
+/* Kolab::Freebusy configuration */
+
+/* Should we redirect using a Location header, if the user is not local? If this
+ * is false we silently download the file ourselves and output it so that it
+ * looks as though the free/busy information is coming from us.
+ */
+$conf['fb']['redirect'] = false;
+
+/* What is the address of the current server where the calendar data is stored?
+ * This is also used as the LDAP server address where user objects reside.
+ */
+$conf['kolab']['freebusy']['server'] = 'https://example.com/freebusy';
+
+/* What is our default mail domain? This is used if any users do not have
+ * '@domain' specified after their username as part of their email address.
+ */
+$conf['fb']['email_domain'] = 'example.com';
+
+/* Location of the cache files */
+$conf['fb']['cache_dir'] = '/kolab/var/kolab-freebusy/cache';
+
+/* What db type to use for the freebusy caches */
+$conf['fb']['dbformat'] = 'db4';
+
+/* Should we send a Content-Type header, indicating what the mime type of the
+ * resulting VFB file is?
+ */
+$conf['fb']['send_content_type'] = false;
+
+/* Should we send a Content-Length header, indicating how large the resulting
+ * VFB file is?
+ */
+$conf['fb']['send_content_length'] = false;
+
+/* Should we send a Content-Disposition header, indicating what the name of the
+ * resulting VFB file should be?
+ */
+$conf['fb']['send_content_disposition'] = false;
+
+/* Should we use ACLs or does everybody get full rights? DO NOT set
+ * this to false if you don't know what you are doing. Your free/busy
+ * service should not be visible to any outside networks when
+ * disabling the use of ACL settings.
+ */
+$conf['fb']['use_acls'] = true;
+
+/* Are there remote servers on which users have additional (shared)
+ * folders? In that case free/busy information should also be fetched
+ * from these servers.
+ *
+ * Add them like this:
+ *
+ * array('remote1.example.com', 'remote2.example.com')
+ */
+$conf['fb']['remote_servers'] = array();
+
+//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+//
+// If you modify this file, please do not forget to also modify the
+// template in kolabd!
+//
+//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+// DEBUGGING
+// =========
+//
+// Activate this to see the log messages on the screen
+// $conf['log']['type'] = 'display';
+//
+// Activate this to see the php messages on the screen
+// ini_set('display_errors', 1);
+//
+// Both settings will disrupt header delivery (which should not cause a
+// problem).
--- /dev/null
+<?php
+/**
+ * A script for fetching the Kolab Free/Busy information.
+ *
+ * $Horde: framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php,v 1.6 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2009 Klarälvdalens Datakonsult AB
+ *
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Thomas Arendsen Hein <thomas@intevation.de>
+ * @package Kolab_FreeBusy
+ */
+
+/** Load the required free/busy library */
+require_once 'Horde/Kolab/FreeBusy.php';
+
+/** Load the configuration */
+require_once 'config.php';
+
+$fb = new Horde_Kolab_FreeBusy();
+$view = $fb->fetch();
+$view->render();
+
--- /dev/null
+<?php
+/**
+ * A script for triggering an update of the Kolab Free/Busy information.
+ *
+ * This script generates partial free/busy information based on a
+ * single calendar folder on the Kolab groupware server. The partial
+ * information is cached and later assembled for display by the
+ * freebusy.php script.
+ *
+ * $Horde: framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/pfb.php,v 1.6 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2009 Klarälvdalens Datakonsult AB
+ *
+ * @author Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Thomas Arendsen Hein <thomas@intevation.de>
+ * @package Kolab_FreeBusy
+ */
+
+/** Load the required free/busy library */
+require_once 'Horde/Kolab/FreeBusy.php';
+
+/** Load the configuration */
+require_once 'config.php';
+
+$fb = new Horde_Kolab_FreeBusy();
+$view = $fb->trigger();
+$view->render();
--- /dev/null
+<?php
+/**
+ * A script for regenerating the Kolab Free/Busy cache.
+ *
+ * $Horde: framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/regenerate.php,v 1.7 2009/07/14 00:28:33 mrubinsk Exp $
+ *
+ * Copyright 2004-2009 Klarälvdalens Datakonsult AB
+ *
+ * @author Gunnar Wrobel <p@rdus.de>
+ * @author Thomas Arendsen Hein <thomas@intevation.de>
+ * @package Kolab_FreeBusy
+ */
+
+/** Report all errors */
+error_reporting(E_ALL);
+
+/** requires safe_mode to be turned off */
+ini_set('memory_limit', -1);
+
+/**
+ * Load the required free/busy libraries - this also loads Horde:: and
+ * Horde_Util:: as well as the PEAR constants
+ */
+require_once 'Horde/Kolab/FreeBusy.php';
+require_once 'Horde/Kolab/FreeBusy/Report.php';
+
+/** Load the configuration */
+require_once 'config.php';
+
+$conf['kolab']['misc']['allow_special'] = true;
+
+if (empty($_SERVER['SERVER_NAME'])) {
+ $_SERVER['SERVER_NAME'] = $conf['kolab']['imap']['server'];
+}
+
+$fb_reporter = new Horde_Kolab_FreeBusy_Report();
+
+$fb_reporter->start();
+
+$fb = new Horde_Kolab_FreeBusy();
+$result = $fb->regenerate($fb_reporter);
+if (is_a($result, 'PEAR_Error')) {
+ echo $result->getMessage();
+ exit(1);
+}
+$result = $fb_reporter->stop();
+if ($result === false) {
+ exit(1);
+}
+exit(0);