From: Gunnar Wrobel Date: Wed, 9 Sep 2009 07:06:10 +0000 (+0200) Subject: Imported Kolab_FreeBusy from CVS. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=abe5588e540bd359f9bad1451a12705d114306cb;p=horde.git Imported Kolab_FreeBusy from CVS. --- diff --git a/framework/Kolab_FreeBusy/COPYING b/framework/Kolab_FreeBusy/COPYING new file mode 100644 index 000000000..d1c6b98aa --- /dev/null +++ b/framework/Kolab_FreeBusy/COPYING @@ -0,0 +1,504 @@ + 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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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. + + 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 + + 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. + + + Copyright + + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php new file mode 100644 index 000000000..32112746e --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy.php @@ -0,0 +1,340 @@ +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 + * @author Gunnar Wrobel + * @author Thomas Arendsen Hein + * @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; + } +} + + diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Access.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Access.php new file mode 100644 index 000000000..0bb50871d --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Access.php @@ -0,0 +1,442 @@ + + * @author Steffen Hansen + * @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: + // + // + // 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] + // + 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; + } + +} + diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php new file mode 100644 index 000000000..d52d38f50 --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Cache.php @@ -0,0 +1,1491 @@ + + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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: + * $var = &FreeBusyACLCache::singleton($cache_dir); + * + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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; + } +} diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php new file mode 100644 index 000000000..c5c8c480b --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Imap.php @@ -0,0 +1,544 @@ + + * @author Chuck Hagenbuch + * @author Steffen Hansen + * @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 + * @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); + } + +} diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Report.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Report.php new file mode 100644 index 000000000..7633bd886 --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/Report.php @@ -0,0 +1,75 @@ + + * @package Kolab_FreeBusy + */ + +class Horde_Kolab_FreeBusy_Report { + + var $_break = '
'; + + 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 diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php new file mode 100644 index 000000000..cdaf71cb8 --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/FreeBusy/View.php @@ -0,0 +1,291 @@ + + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 + * @author Steffen Hansen + * @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 "\n"; + echo "" . $title . "\n"; + echo "\n"; + echo "

" . $headline . "

\n"; + echo "

" . $body . "

\n"; + if (!empty($error)) { + echo "
" . $error->getMessage() . "
\n"; + Horde::logMessage($error, __FILE__, __LINE__, PEAR_LOG_ERR); + } + echo "
\n"; + echo isset($_SERVER['SERVER_SIGNATURE'])?$_SERVER['SERVER_SIGNATURE']:'' . "\n"; + echo "\n"; + } +} + diff --git a/framework/Kolab_FreeBusy/lib/Horde/Kolab/Test/FreeBusy.php b/framework/Kolab_FreeBusy/lib/Horde/Kolab/Test/FreeBusy.php new file mode 100644 index 000000000..64c41e504 --- /dev/null +++ b/framework/Kolab_FreeBusy/lib/Horde/Kolab/Test/FreeBusy.php @@ -0,0 +1,249 @@ + + * @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 + * @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 = <<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; + } + +} diff --git a/framework/Kolab_FreeBusy/package.xml b/framework/Kolab_FreeBusy/package.xml new file mode 100644 index 000000000..3fc93e916 --- /dev/null +++ b/framework/Kolab_FreeBusy/package.xml @@ -0,0 +1,244 @@ + + + Kolab_FreeBusy + pear.horde.org + A package for providing free/busy information. + 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. + + + Gunnar Wrobel + wrobel + p@rdus.de + yes + + + Thomas Jarosch + jarosch + thomas.jarosch@intra2net.com + yes + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Jan Schneider + jan + jan@horde.org + yes + + 2009-04-25 + + 0.1.5 + 0.1.0 + + + alpha + alpha + + LGPL + + * 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.0.0 + + + 1.7.0 + + + Kolab_Format + pear.horde.org + 1.0.1 + + + Kolab_Server + pear.horde.org + 0.5.0 + + + Kolab_Storage + pear.horde.org + + + Horde_iCalendar + pear.horde.org + + + Horde_Date + pear.horde.org + + + + + + + + + + + + + + + + + + + + + + + + 2009-04-02 + + 0.1.4 + 0.1.0 + + + alpha + alpha + + LGPL + + * kolab/issue3450 (freebusy/user@example.com.ifb returns extended + free/busy information) + * kolab/issue3527 (no immediately printing of folders during + freebusy cache regeneration) + + + + 2009-03-06 + + 0.1.3 + 0.1.0 + + + alpha + alpha + + LGPL + + * 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) + + + + 2008-12-12 + + 0.1.2 + 0.1.0 + + + alpha + alpha + + LGPL + + * Fixed copyright information. + + + + 2008-12-05 + + 0.1.1 + 0.1.0 + + + alpha + alpha + + LGPL + + * 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) + + + + 2008-10-29 + + 0.1.0 + 0.1.0 + + + alpha + alpha + + LGPL + + * Initial release. + + + + diff --git a/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/AllTests.php b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/AllTests.php new file mode 100644 index 000000000..f1f4fff61 --- /dev/null +++ b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/AllTests.php @@ -0,0 +1,64 @@ +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(); +} diff --git a/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php new file mode 100644 index 000000000..51b412f04 --- /dev/null +++ b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyScenarioTest.php @@ -0,0 +1,146 @@ + + * @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 + * @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 diff --git a/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyTest.php b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyTest.php new file mode 100644 index 000000000..98ad0f643 --- /dev/null +++ b/framework/Kolab_FreeBusy/test/Horde/Kolab/FreeBusy/FreeBusyTest.php @@ -0,0 +1,181 @@ + + * @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 + * @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); + } +} diff --git a/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/config.php b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/config.php new file mode 100644 index 000000000..8c838544e --- /dev/null +++ b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/config.php @@ -0,0 +1,118 @@ + + * @author Gunnar Wrobel + * @author Thomas Arendsen Hein + * @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). diff --git a/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php new file mode 100644 index 000000000..0e2880227 --- /dev/null +++ b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/freebusy.php @@ -0,0 +1,24 @@ + + * @author Gunnar Wrobel + * @author Thomas Arendsen Hein + * @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(); + diff --git a/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/pfb.php b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/pfb.php new file mode 100644 index 000000000..4beb60b66 --- /dev/null +++ b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/pfb.php @@ -0,0 +1,28 @@ + + * @author Gunnar Wrobel + * @author Thomas Arendsen Hein + * @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(); diff --git a/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/regenerate.php b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/regenerate.php new file mode 100644 index 000000000..a71904e09 --- /dev/null +++ b/framework/Kolab_FreeBusy/www/Horde/Kolab/FreeBusy/regenerate.php @@ -0,0 +1,50 @@ + + * @author Thomas Arendsen Hein + * @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);